Skip to main content
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 as SESSION_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

The Requires 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: CIP-7 (Stream Protocol) is a companion, not a dependency — it provides orthogonal time-based (rather than consumption-based) billing and shares no on-chain state with CIP-8.

2. Motivation

CIP-18 r2’s intent="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.
The MPP Session model — already in production for Stripe + Tempo Labs IETF submissions — solves this by:
  1. Escrow once. Payer locks max_amount (a session ceiling) on chain at OpenSession. This is the upper bound on what the runner can ever extract.
  2. Cumulative voucher off-chain. Each off-chain call returns a payer-signed EIP-712 SessionVoucher with a monotonically increasing cumulative_amount — the total owed for the session up to and including this call. The latest voucher fully supersedes all previous ones; restart-safe.
  3. 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.
  4. Close + dispute window + Finalize. Payer (or contract on expires_at_block) triggers Close → opens dispute window → Slash available within window → after DISPUTE_WINDOW_BLOCKS=75 blocks, Finalize returns unused (max_amount - spent) to payer.
End-to-end: 3 chain tx + N off-chain HTTP, vs. CIP-18 charge mode’s N chain tx + N off-chain HTTP.

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:
  1. 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”).
  2. The PoC went directly to code: node/runner/src/system_actors.rs:35 allocates SESSION_ACTOR = 0x0C; six handlers in node/execution/src/runner/session.rs; full off-chain voucher library in runner/crates/runner-common/src/voucher.rs; EIP-712 domain in node/types/src/session_eip712.rs.
  3. The v2.r2 system actor sequence (May 2026) accepted 0x0C = SESSION_ACTOR and renumbered CIP-14 v2 / CIP-10 v2 / CIP-18 around it, rather than asking the code to relocate.
This CIP therefore formalizes what code already does. It supersedes both the MPP Session research/plan documents and wiki/concepts/mpp-session.md (which now points here for the normative spec).

4. Architecture


5. Address & Storage

5.1 System Actor

(per node/runner/src/system_actors.rs:35; declared in wiki/entities/system-actors.md.)

5.2 Storage layout

Sessions persist under SESSION_ACTOR’s actor-KV at key:
Per 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)
Only the most recently 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

Per node/types/src/session.rs:75-90:

6.2 SessionStatus

The terminal states (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

The payer supplies 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

PoC implementation only supports 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

Per node/types/src/session.rs:105-120 + runner/crates/runner-common/src/voucher.rs:51:
Cumulative semantics. 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

Per node/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:
(per voucher.rs:46.)

8. Handlers (six)

All handlers are methods on ExecutionEngine 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

The settlement code path fully reuses 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)

Slash is the dispute hook. PoC is no-op; the design space is the same as CIP-2’s existing N-of-M consensus + slashing path; integration is follow-on work (V-12 follow-up).

9. Settlement Reuse

Settlement on Settle (§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: A future revision may unify them at the wire layer (both share 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 for OpenSession (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: (CIP-7 v1’s 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-14 ingress.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:
The CIP-2 verification path is unchanged — MPP Session is layered on top.

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:
This sidesteps the original V-12 conflict entirely: there is no numeric opcode collision with CIP-13 v2 (52-56) or CIP-23 v2 (57) because MPP Session never claimed numeric opcodes — it claimed the enum-variant-name space, which is unconstrained and uncrowded. If a future revision introduces normative numeric opcodes for SystemInstruction (mapping enum → u8 for wire compression / hardware-friendly dispatch), MPP Session selectors would be assigned in the ≥68 free range as recommended by CIP-13 v2 §1.

13. EIP-712 chainId source (V-13 closure)

Per runner/crates/runner-common/src/voucher.rs:28, the PoC uses:
This is a PoC placeholder, not a normative protocol value (note 1 is Ethereum mainnet’s chainId, so a PoC voucher shares an EIP-712 domain axis with L1; production MUST NOT ship 1). The activation rule is:
  1. Mainnet / production: COWBOY_SESSION_CHAIN_ID MUST be drawn from the canonical Cowboy chain id (currently TBD — likely node/types/src/constants.rs once a CHAIN_ID constant lands, or the validator config field that holds it).
  2. Devnet / testnet: Per-network distinct value to prevent cross-network voucher replay.
  3. Coordinated client+chain release: Bumping COWBOY_SESSION_CHAIN_ID requires synchronous voucher.rs + session.rs constant update on both sides (voucher.rs:14 carries this warning verbatim).
Activation TBD; the constant remains 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


15. Reserved Paths / Names

None at this layer. MPP Session is fully off-chain on the runner side; the on-chain surface is SESSION_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

  • Slash activation. Wire dispute resolution to CIP-2 Result Verifier; deploy chosen verification mode (default MajorityVote).
  • SessionAsset::Cip20 activation. 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 shared intent="session" registration; would require IETF coordination.
  • target_pool: SESSION discriminant. If session economics warrant a different runner/burn split, add to the SettlementConfig.target_pool enum.
  • Indexer-friendly events. Today’s SessionOpened / SessionSettled / SessionFinalized events form the basis of off-chain billing indexers; the deterministic big-endian wire layout is defined in node/execution/src/runner/session.rs::session_events (97/64/120/40/48-byte payloads, test-pinned); a future revision SHOULD lift it into a normative event-schema appendix.

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 allocation
  • node/types/src/session.rs — record types + storage key
  • node/types/src/session_eip712.rs — EIP-712 domain
  • node/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 / recover
  • refs/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.