Status: Draft (Retroactive — code-first)
Type: Standards Track
Category: Core
Created: 2026-05-11
Requires: CIP-2 (Off-Chain Compute), CIP-3 (Dual-Metered Gas), CIP-18 r2 (Payments) — wire-format alignment
Companions: CIP-7 (Stream Protocol) — orthogonal time-based billing
Code references (authoritative):
node/runner/src/system_actors.rs:35(SESSION_ACTOR = 0x0C)node/types/src/session.rs(Session record, storage key, types)node/types/src/session_eip712.rs(EIP-712 domain)node/execution/src/runner/session.rs(six on-chain handlers)runner/crates/runner-common/src/voucher.rs(off-chain voucher / sign / recover)
1. Abstract
This CIP retroactively specifies the MPP Session protocol that has been committed to code asSESSION_ACTOR = 0x0C along with full Rust-side handler scaffolding and runner-side voucher library. The session protocol implements the Session mode of the IETF Machine Payment Protocol (MPP) — the wire complement to MPP’s intent="charge" mode covered by CIP-18 r2.
Where CIP-18 covers single-call payments (HTTP request → 402 → credential → settle, one chain tx per call), CIP-8 covers high-frequency micro-billing — typical for LLM token streaming, paid HTTP/MCP loops, or long-lived AI sessions — where the payer opens an on-chain escrow once, exchanges payer-signed accumulating SessionVouchers off-chain with the Runner over an arbitrary number of micro-calls, and settles on-chain in three transactions (Open + Settle + Finalize) instead of N.
This CIP is retroactive: code precedes specification. The intent here is to capture and ratify what is already deployed (as PoC at runner-common/voucher.rs / system_actors.rs:35), close the spec-vs-code gap surfaced in wiki/drift.md V-11 / V-12 / V-13, and provide a stable normative reference for follow-on work (dispute mechanism, cross-chain bridges, CIP-20 fungible-token sessions, fiat rails).
Dependencies
TheRequires header lists the hard dependencies; this subsection pins the precise revision and the specific surface each one is leaned on, so a reviewer can see the full chain at a glance and knows not to ratify CIP-8 ahead of them:
| Dependency | Pinned at | CIP-8 leans on |
|---|---|---|
| CIP-2 (Off-Chain Compute) v3 | Result Verifier 0x03 + commit-reveal | Dispute resolution: the Disputed path (§6.2, §11) is arbitrated by CIP-2’s existing verifier + commit-reveal mechanism — CIP-8 adds no new dispute machinery. |
| CIP-3 (Dual-Metered Gas) | Cell/cycle metering | The three on-chain handlers (Open / Settle / Finalize) are metered under CIP-3; the fee_payer and escrow accounting assume CIP-3’s model. |
| CIP-18 r2 (Payments) | intent="charge" wire format + PaymentGate 0x12 | Wire-format alignment (§4): the SessionVoucher and the single-call MPP charge flow are the two complementary halves of the same IETF MPP wire format and MUST stay byte-compatible. |
2. Motivation
CIP-18 r2’sintent="charge" flow requires one HTTP round-trip per request and one PaymentGate settle per request. For high-frequency machine-to-machine workloads — agent A streaming LLM tokens from agent B, an MCP tool loop, a metered HTTP API consumed in a tight loop — this is too coarse:
- Each call carries challenge / verify / settle overhead.
- Per-call settle generates one chain tx; an 8-hour LLM session can generate millions.
- Failure mid-session leaves accounting ambiguous unless restart-safe.
- Escrow once. Payer locks
max_amount(a session ceiling) on chain atOpenSession. This is the upper bound on what the runner can ever extract. - Cumulative voucher off-chain. Each off-chain call returns a payer-signed EIP-712
SessionVoucherwith a monotonically increasingcumulative_amount— the total owed for the session up to and including this call. The latest voucher fully supersedes all previous ones; restart-safe. - Batch on-chain settle. Runner submits the latest voucher (any time, or periodically, or at end) via
Settle. PaymentGate-style splits apply: 89% runner, 10% burn, 1% treasury. - Close + dispute window + Finalize. Payer (or contract on
expires_at_block) triggersClose→ opens dispute window →Slashavailable within window → afterDISPUTE_WINDOW_BLOCKS=75blocks,Finalizereturns unused(max_amount - spent)to payer.
3. Why This CIP Is Retroactive
The MPP Session research (refs/runner/2026-04-28_MPP_Session_Research.md) and implementation plan (refs/plans/2026-05-06_mpp_session_implementation.md) were intended to be a PoC. However:
- The v2 CIP alignment round (2026-04-21) had not yet drafted a Session model — CIP-18 r1 explicitly excluded session mode (§4 non-goals: “Streaming metered billing”).
- The PoC went directly to code:
node/runner/src/system_actors.rs:35allocatesSESSION_ACTOR = 0x0C; six handlers innode/execution/src/runner/session.rs; full off-chain voucher library inrunner/crates/runner-common/src/voucher.rs; EIP-712 domain innode/types/src/session_eip712.rs. - The v2.r2 system actor sequence (May 2026) accepted
0x0C = SESSION_ACTORand renumbered CIP-14 v2 / CIP-10 v2 / CIP-18 around it, rather than asking the code to relocate.
wiki/concepts/mpp-session.md (which now points here for the normative spec).
4. Architecture
5. Address & Storage
5.1 System Actor
node/runner/src/system_actors.rs:35; declared in wiki/entities/system-actors.md.)
5.2 Storage layout
Sessions persist underSESSION_ACTOR’s actor-KV at key:
node/types/src/session.rs:217-221 (session_storage_key).
Vouchers are never stored on chain. They live only:
- in the payer’s off-chain wallet (last-signed)
- in the runner daemon’s local cache (latest received per session)
Settle-d voucher’s cumulative_amount and nonce survive on-chain, as fields of the Session record.
6. On-Chain Types
6.1 Session record
Pernode/types/src/session.rs:75-90:
6.2 SessionStatus
Settled / Refunded / Slashed) accept no further handlers. Disputed is transient and is resolved by the existing CIP-2 Result Verifier (0x03) + commit-reveal mechanism (see §11).
6.3 SessionId
session_id on OpenSession. The handler verifies !exists(session_id) and rejects collisions. nonce is a payer-chosen u64 — typically a monotonic counter inside the payer wallet, but anything that avoids collision with prior (payer, runner) sessions is acceptable.
6.4 SessionAsset
Cby. Cip20(...) is a typed placeholder — handlers reject it with ExecutionError::Unsupported until follow-on work wires it in.
7. Off-Chain Types
7.1 SessionVoucher
Pernode/types/src/session.rs:105-120 + runner/crates/runner-common/src/voucher.rs:51:
cumulative_amount is the total the payer owes the runner across all calls in the session up to and including the call that produced this voucher. It is monotonically increasing across vouchers for a given session_id. The latest voucher therefore fully replaces all previous ones — a power-on idempotency property: if the runner crashes and loses prior vouchers but keeps voucher_k, settling voucher_k captures everything voucher_ would have settled.
Nonce. Strictly increasing per session. The on-chain Settle handler rejects any voucher with nonce <= session.last_voucher_nonce. Combined with cumulative monotonicity this prevents replay across nonces.
Expires_at. Block-height ceiling. Settle reverts after current_block > expires_at. Forces fresh vouchers if the runner sits on stale ones.
Usage_digest. Opaque to chain. Off-chain it is keccak256(canonical(usage_log)) where usage_log is whatever the runner and payer agree on (LLM token counts, HTTP byte counts, MCP tool-call counts). The chain never inspects it; downstream auditing tools may.
7.2 EIP-712 domain
Pernode/types/src/session_eip712.rs + runner/crates/runner-common/src/voucher.rs:42-50:
SESSION_ACTOR_ADDR is the 20-byte form of SESSION_ACTOR = 0x0C (19 zero bytes + 0x0C), per runner-common/voucher.rs:34-38.
The voucher typehash uses the canonical EIP-712 type string:
voucher.rs:46.)
8. Handlers (six)
All handlers are methods onExecutionEngine per node/execution/src/runner/session.rs:34-372. They are ActorMessages to SESSION_ACTOR=0x0C, dispatched through the standard SystemInstruction ActorMessage path. They do not introduce new numeric SystemInstruction opcodes — they re-use the existing ActorMessage envelope with a typed selector. (See §12 for the opcode question.)
8.1 OpenSession
8.2 Deposit
8.3 Settle
verifier.rs:351-465’s SettlementConfig logic — there is no separate payout template.
8.4 CloseSession
Settle continues to work during Closing so the runner can flush any final voucher.
8.5 Finalize
8.6 Slash (deferred)
9. Settlement Reuse
Settlement onSettle (§8.3) uses SettlementConfig from GOVERNANCE_SYSTEM_ACTOR=0x09 exactly as CIP-2 runner job settlement does. The same SettlementConfig{runner_percent, burn_percent, treasury_percent} value (default 89/10/1 per node/runner/src/types.rs:746-777) governs both paths. Governance updates via UpdateSettlementConfig propagate automatically to both single-job and session settle paths.
The CIP-14 v2.r2 Part III §6 target_pool enum does apply to MPP Session settlement: the implicit pool is MAIN. A future revision may introduce target_pool: SESSION if session-specific economics emerge (e.g. different runner/burn split for high-frequency vs. coarse jobs).
10. Relationship to Other CIPs
vs. CIP-18 r2 (Payments)
Orthogonal but complementary:| Dimension | CIP-18 charge mode | CIP-8 session mode |
|---|---|---|
| On-chain tx per N off-chain calls | N | 3 (Open + Settle + Finalize) |
| Suited to | one-shot paid HTTP / MCP / pass / subscription | high-frequency micro-billing (LLM tokens, streaming) |
| State | PaymentGate 0x12 | SessionActor 0x0C |
| Wire | MPP Authorization: Payment + x402 (Gateway-enforced) | Off-chain SessionVoucher (HTTP body, runner-verified) |
| Funds custody | per-tx through PaymentGate nonce + valid_after/before | escrow at Open, drawn down across settles |
| Dispute | none (settle is atomic) | DISPUTE_WINDOW_BLOCKS window + Slash hook |
intent="charge" for the per-voucher cumulative semantics, just at different rates).
vs. CIP-2 (Off-Chain Compute / Runners)
Reuses all of CIP-2:- Runner registration (
Runner Registry 0x01) is a precondition forOpenSession(the runner must be registered). - Result Verifier (
0x03) is the dispute path’s settle/slash arbiter (future). - VRF runner selection does not apply — MPP Session picks runners by URL (off-chain DNS-addressable agent endpoints), not VRF.
vs. CIP-7 (Stream Protocol)
Orthogonal — both can coexist on the same actor:| Dimension | CIP-7 Stream | CIP-8 Session |
|---|---|---|
| Pricing axis | Time (epoch subscription) | Consumption (cumulative voucher) |
| Funds path | Per-epoch advance pay | Escrow + voucher drawdown |
| Typical use | live stream / Substack-style subscription | LLM API / metered HTTP / MCP loop |
0x06 Stream Key Manager claim was a known CIP-7-vs-code drift, since resolved: the Stream Key Manager is deployed at 0x0D per the canonical allocation in WP §9.)
vs. CIP-14 v2.r2 / CIP-15 v2.r2 (Ingress)
MPP Session lives off the CIP-14/15 Gateway path. A runner with a public HTTP endpoint may itself sit behind a Cowboy Gateway (CIP-14ingress.http entitlement + ROUTE_REGISTRY 0x0E) for DNS addressability, but the session wire — voucher exchange + Settle / Finalize tx — does not pass through the Gateway. CIP-19’s MCP ingress similarly handles per-tool-call payment via CIP-18; a session-mode MCP loop would be an MCP client persisting outside the Gateway scope.
11. Dispute Mechanism
PoC:handle_session_slash returns ExecutionError::Unsupported. Future activation maps the dispute to CIP-2’s existing N-of-M commit-reveal:
12. Opcode / SystemInstruction question (V-12 closure)
node/types/src/execution.rs SystemInstruction is a Rust enum and uses match-arm dispatch; it does not expose numeric opcodes at the spec-table level (CIP-13 v2 §1 master allocation table is a spec abstraction, not a code wire format).
The MPP Session handlers (OpenSession / Deposit / Settle / CloseSession / Finalize / Slash) are not new SystemInstruction enum variants. They are ActorMessages dispatched to SESSION_ACTOR=0x0C with typed selectors:
13. EIP-712 chainId source (V-13 closure)
Perrunner/crates/runner-common/src/voucher.rs:28, the PoC uses:
- Mainnet / production:
COWBOY_SESSION_CHAIN_IDMUST be drawn from the canonical Cowboy chain id (currently TBD — likelynode/types/src/constants.rsonce aCHAIN_IDconstant lands, or the validator config field that holds it). - Devnet / testnet: Per-network distinct value to prevent cross-network voucher replay.
- Coordinated client+chain release: Bumping
COWBOY_SESSION_CHAIN_IDrequires synchronous voucher.rs + session.rs constant update on both sides (voucher.rs:14carries this warning verbatim).
1 until either (a) a Cowboy CHAIN_ID lands in node/types/src/constants.rs and is plumbed through, or (b) a sibling CIP (e.g. a “Cowboy network identifier” CIP) standardizes it.
14. Security Considerations
| Threat | Mitigation |
|---|---|
| Payer fails to sign voucher mid-session but keeps using runner | Runner MUST reject any HTTP request without a fresh voucher; one bad request = drop |
| Runner takes payment without serving | Payer triggers CloseSession to enter dispute window; calls Slash if grounds; CIP-2 verification arbitrates (future) |
| Voucher replay | nonce strictly increasing + on-chain last_voucher_nonce check |
| Voucher expiry abuse | Per-voucher expires_at (block-height); Settle reverts post-expiry |
| Payer withdraws escrow mid-session | Impossible — funds locked in SESSION_ACTOR storage at OpenSession, only releasable via Finalize after dispute window |
| Payer key compromise | max_amount is the loss ceiling per session; payers should choose conservative ceilings |
| Runner DDoS via free requests | Off-chain rate limit + reject voucher.cumulative_amount increments below min_increment (runner-policy, not protocol) |
| EIP-712 domain replay across networks | chainId in domain separator; activation requires production chain id (see §13) |
| Session-id collision | !exists(session_id) check at OpenSession; payer-chosen ID with collision-resistant construction recommended (§6.3) |
15. Reserved Paths / Names
None at this layer. MPP Session is fully off-chain on the runner side; the on-chain surface isSESSION_ACTOR=0x0C actor messages only.
If a future CIP introduces a discovery path (e.g. /_cowboy/session/{session_id} for clients to query session status via Gateway), that path will be reserved in a follow-on CIP.
16. Future Work
Slashactivation. Wire dispute resolution to CIP-2 Result Verifier; deploy chosen verification mode (defaultMajorityVote).SessionAsset::Cip20activation. Enable CIP-20 fungible-token sessions (stablecoin micro-billing).- Cross-chain session bridge. Pair with CIP-25 cross-chain mailbox to allow Cowboy-anchored session escrow with off-chain runners on other chains.
- TEE attestation integration. Composite attestation from CIP-23 v2 may be offered as a tighter verification mode for premium session classes.
- Wire convergence with CIP-18. Consider unifying MPP
intent="charge"cumulative-voucher semantics under a sharedintent="session"registration; would require IETF coordination. target_pool: SESSIONdiscriminant. If session economics warrant a different runner/burn split, add to theSettlementConfig.target_poolenum.- Indexer-friendly events. Today’s
SessionOpened/SessionSettled/SessionFinalizedevents form the basis of off-chain billing indexers; this CIP does not yet pin their schema beyond what code emits.
17. Backwards Compatibility
Strictly additive.SESSION_ACTOR = 0x0C was previously unallocated in v1 CIP space; this CIP ratifies code’s allocation. No existing on-chain actors, RPCs, or PVM syscalls are modified. Actors without active sessions are unaffected. Runner-side opt-in via runner-common/voucher.rs library; runners not implementing session voucher exchange continue to operate on the CIP-2 single-job path unchanged.
18. References
node/runner/src/system_actors.rs:35— address allocationnode/types/src/session.rs— record types + storage keynode/types/src/session_eip712.rs— EIP-712 domainnode/execution/src/runner/session.rs— six handlers (handle_session_open/_deposit/_settle/_close/_finalize/_slash)runner/crates/runner-common/src/voucher.rs— off-chain voucher + sign / recoverrefs/runner/2026-04-28_MPP_Session_Research.md— research origin (superseded by this CIP for normative claims)refs/plans/2026-05-06_mpp_session_implementation.md— implementation plan (superseded for normative claims)- IETF MPP draft
draft-ryan-httpauth-payment— wire-format ancestry - CIP-2 §5 — Runner registration / settlement (reused)
- CIP-3 — Dual gas (orthogonal; session settle is paid by tx submitter, not metered against session escrow)
- CIP-18 r2 — sibling Payments CIP (charge mode)
Retroactive note. If code changes after this CIP is approved (e.g. handler signature evolution, new asset variants, dispute activation), update this CIP via an r2 revision rather than starting a CIP-X v2. The v1 → v2 split convention (per CIP-14 / CIP-10 / etc.) is reserved for forward-looking redesigns; minor in-place revisions use rN suffixes only.

