Sessions & agent loops
One authorization funds a whole loop. The budget draws down per turn and the remainder refunds at close.
An agent loop is expensive in a specific way: chat models are stateless, so every turn resends the whole growing conversation as input. Provider prefix caches make those re-reads cheap, but only at the provider that served the previous turn. A market that ignored this would either migrate your loop to whoever quotes lowest while you pay full re-prefill every turn, or pin you to one provider who could then reprice at will. Sessions give you the third option: sticky by default, contested every turn, and never repriced mid-loop.
One authorization funds the whole loop
A per-turn 402, sign, settle round trip is pure overhead inside a loop that may run 200 turns. So the first turn of a session quotes a session budget instead of one turn's worst case: you sign a single x402 authorization for the cap (say $2), and every later turn draws down against it with no further payment round trips. When the session closes, the unspent remainder refunds through the same path that refunds per-request overage. One signature, many turns, and you only ever pay for metered usage.
The locked tariff
Turn 1 is a normal symmetric auction, and the winner's second-score cleared rates, including a cheaper rate for cached input tokens, lock for the session's TTL. Every later turn re-runs the auction on effective cost: the incumbent is scored at its locked tariff with your prefix at the cached rate, every challenger at spot paying full re-prefill in both price and latency. If the incumbent wins, it bills exactly its locked rates. A continuation turn is a contestability check, never a repricing event: nothing the incumbent bids mid-session changes what it is paid, so holding your cache buys it no leverage over you. If a challenger is cheap enough to win despite the re-prefill, the session migrates and its cleared rates become the new lock.
How the router knows two requests are one session
Explicitly: send routing.session_id (the CLI and web app set one per conversation automatically); the first request with a new id opens the session and the response confirms the locked rates in x-session-id and x-session-ratesheaders. Implicitly: the router keeps a salted rolling hash chain over each session's messages, so a request that extends a known prefix is recognized as the next turn even from agent frameworks you cannot modify. The hash chain, not your content, is what gets stored, and it expires with the session.
Receipts stay per-turn
A session is an analytics grouping plus a budget, not a new settlement primitive. Every turn still produces its own signed usage record, now carrying the session_id, so reconciliation and epoch anchoring are untouched and you can audit any single turn the same way you audit a standalone request. On top of the per-turn records, GET /v1/analytics/sessions/:id publishes the per-session transaction-cost analysis: the turn-by-turn tape and the cache discount you captured against a cache-blind counterfactual that re-auctioned every turn at spot. The Activity blotter in the web app renders exactly this: cache captured versus cache-blind, per session.
In the SDK it is three lines: omnious.session({ budgetUsd: 1 }), then ask per turn, then close() to trigger the refund. The provider-side view of the same mechanism is on Winning sessions, and the full mechanism analysis on Sessions: auctions for agent loops.
- router/src/sessions.ts session registry, locked tariffs, budget draw-down