ERC-8312: Bounded Agent Actions

Pulled orbmis/headroom and recomputed it rather than take the config, and it checks out. Your cap invariant isn’t reserved+confirmed ≤ cap on a single cursor like the StatefulBound reference; it’s cumulativeTurnover ≤ maxCumulativeTurnover (CURSOR_HEADROOM, HeadroomPoC.sol L298/534), read via cursorSummary(envelopeId) on the EnvelopeRegistry 0x091f…07d1. Ran it against envelope 0x48e972a2…bcb041 on Base Sepolia (84532):

portfolioValue        10000
cumulativeTurnover        0
maxCumulativeTurnover  8000
remainingTurnover      8000
vaultAllocations   [2500,2500,2500,2500,0,0]  = 10000 bps
riskAllocations    [5000,5000,0]              = 10000 bps
cursorRoot 0x2f19c6e6…  (= slot-5 of getEnvelope)   active true


VERIFIED-GOOD at Base-Sepolia block 43697267, stateRoot 0xbb4fbc638e657de5e9fbdefcc290054e251087cf9948b471c15677b09e3a89ff:

  • cumulativeTurnover 0 ≤ maxCumulativeTurnover 8000 — the headroom bound holds,

  • I re-derived remaining = 8000 − 0 = 8000, matching the contract’s own remainingTurnover (not read on faith),

  • allocations sum to 10000 bps on both the vault and risk-bucket axes — fully allocated, in bounds.

Two things worth flagging for the thread:

  1. This is a second, independently-recomputable instance of the same ERC-8312 guarantee — a turnover-cursor headroom model, distinct from the reserved+confirmed aggregate of the zero-human-loop StatefulBound, both reducing to “aggregate ≤ cap, provable from public state.” Two different implementations converging on one checkable invariant is a good sign for the spec.

  2. Our recompute-kit 8312/cap-conservation recipe is tuned to the StatefulBound storage layout, so it doesn’t drop onto your accessor shape — I recomputed via cursorSummary here. I’ll generalize the recipe to take an accessor form so it covers the headroom model too; happy to fold your case in as a second conformance vector if you’re up for it.

Clean on my side, the bound holds live, re-runnable by anyone from that one cursorSummary call.

Made good on it, pulled orbmis/headroom, mapped the storage, and generalized the recompute-kit recipe so it covers your model as a first-class layout, not a one-off:

bin/recompute-step 8312/cap-conservation \
  https://base-sepolia-rpc.publicnode.com \
  0x091f665B9914b2295861F3e9c05D79eF9b2A07d1 \
  0x48e972a25a6ecc83b0dd9c2943b63155ec899b53e10c1f1eb34ebc1d57bcb041 \
  struct:2:2:1
→ ✓ VERIFIED-GOOD — aggregate 0 ≤ cap 8000, proven from storage


struct:S:A:C = cursorValues at mapping-slot 2, aggregate = cumulativeTurnover (+2), cap = maxCumulativeTurnover (+1) — both read from storage via eth_getProof against the stateRoot, so the cap isn’t taken from cursorSummary’s view either. The recipe now takes either a literal cap (StatefulBound’s reserved+confirmed) or that struct layout, one invariant behind both. Your envelope is in the shared suite as cap-conservation-holds-headroom (trustless-ai/recompute-kit 1ff793e), so anyone can re-run the above and get the green themselves.

The nice part: your turnover-cursor headroom model and the zero-human-loop StatefulBound reserved+confirmed model are two independent implementations reducing to the same checkable thing — aggregate ≤ cap, provable from public state. Two impls converging on one recomputable invariant is exactly the signal an ERC wants. Happy to keep your case pinned as the second conformance vector; ping me if the deployment moves and I’ll re-point it.

Co-author of ERC-8226 (Regulated Agent Mandate) here, with a prior art note. This thread is converging on constructs that ERC-8226 has specified since April 12 (PR #1679, now PR #1844 with a reference implementation including end to end enforcement through the ERC-7943 transfer hook). Since the spec text here is being written right now, the record should show where the overlap sits. We reached the same shape from the regulated asset side, so mapping it rather than restating it.

The counter. A RAMS mandate is keyed (agent, principal) and holds one authoritative cumulativeUsed slot in the registry. Every execution path writes to that same slot through recordExecution: the token compliance hook, an EIP-7702 account, or an executor. There is no per surface state to reconstruct, and canExecute reads the total back in O(1). A monotonic, registry held, cross venue counter against a committed bound is specified and implemented today in the regulated domain.

The aggregate. On the observation upthread that no prior construct holds one cap across relationships and venues: within the regulated perimeter, ERC-8226 does. Every venue there is compliance gated by construction, so all draws are forced through the registry and the aggregate holds without cooperation from the surfaces. What that perimeter provides for free, an open agent economy has to build, which is presumably where the reservation and witness machinery in this spec earns its complexity.

Counting and enforcing, fused by design. RAMS deliberately does not separate them. canExecute bundles existence, validity window, revocation, per action enablement, enforcer freeze, per transaction cap and cumulative cap into one read, and recordExecution re-applies the identical checks before advancing. The meter and the gate are the same read, evaluated in the same frame as the transfer, on the asset’s own non custodial path under ERC-7943: the principal holds the tokens, but the asset reverts a non compliant transfer, so nothing, not even the principal’s key, advances the counter without passing the gate. A reverted transfer reverts its own advance, so the reservation problem never arises in this frame. A meter that can drift from the enforcement it feeds is the gap a regulator asks about, which is why fusion is a requirement for securities rather than a design preference.

The layer the meter does not carry. What makes ERC-8226 a distinct standard is the regulated surface around the counter: a KYC verified principal, a compliance provider checked at grant, an enforcer role that can freeze an agent independently of the principal, and an EIP-712 / EIP-1271 signed lifecycle so a smart wallet principal can grant and revoke. That legal surface is out of scope for a general metering object, and it is where mandate authority for regulated assets actually lives.

One question on scope, since it affects how implementers in the regulated domain will read this. ERC-8312 treats enforcement as substrate defined and stays deliberately agnostic about it. For regulated instruments that substrate is not a free choice: enforcement has to live in the asset’s own transfer path under ERC-7943, in the same frame as the compliance gate, or it is not enforcement a regulator accepts. Does ERC-8312 intend to specify how the cursor binds to enforcement, or does it stop at the counting layer and leave binding to the substrate, in which case the regulated substrate is already specified in ERC-8226 and the two are orthogonal by design?

Spec and discussion: ERC-8226: Regulated Agent Mandate

1 Like

Thanks for bringing up ERC-8226. I actually think it’s a useful comparison because it highlights what I see as the architectural boundary ERC-8312 is trying to establish.

From my perspective, the question isn’t whether ERC-8226 can express cumulative caps or bounded mandates. It certainly can. The question is whether every authorization or mandate standard should also define its own cumulative-consumption semantics, or whether those semantics should be factored into a reusable primitive that any authorization model can adopt.

ERC-8312 argues for the latter.

The purpose of ERC-8312 is to standardize bounded authority as a shared state machine, independent of how that authority is issued or enforced.

That distinction becomes important once the same authority can be exercised across multiple execution venues. A principal may authorize actions through a smart account module, a vault, a workflow engine, a settlement contract, a token hook, or a regulated-asset mandate. Each of those systems can define who is authorized and under what conditions. What they should not all have to reinvent is the logic for representing and consuming the same bounded authority over time.

That’s what the cursor is for.

I don’t view the cursor as bookkeeping. I view it as the canonical state that serializes bounded authority across heterogeneous execution environments. Without a shared state object, every implementation ends up defining its own notion of cumulative consumption, making interoperability much harder.

On enforcement, I agree this should be explicit.

ERC-8312 should not prescribe a single enforcement venue, because that would unnecessarily constrain implementations. However, it absolutely should specify the enforcement invariants that every conforming substrate must satisfy:

  • The action must be authorized under the committed capability.
  • The transition must remain within the represented bound.
  • Cursor advancement and the represented state transition must be atomic, or the substrate must provide an equivalent synchronization guarantee.
  • Unauthorized parties must not be able to advance a cursor and consume another party’s authority.
  • Implementations claiming non-bypassable bounded authority must ensure that all valid execution paths are subject to those checks.

In other words, ERC-8312 specifies the enforcement contract without mandating a single enforcement mechanism.

Viewed through that lens, I don’t see ERC-8226 and ERC-8312 as competing standards.

ERC-8226 defines a regulated authorization model. ERC-8312 defines a reusable bounded-authority primitive that regulated authorization models, account abstraction systems, vaults, workflow engines, settlement protocols, and future standards can all compose with.

To me, this is similar to many other successful ERC boundaries: we generally standardize reusable protocol semantics once, rather than embedding the same state transition logic independently into every higher-level standard that could benefit from it.

If ERC-8226 (or any future authorization standard) chooses to use an ERC-8312 envelope as its shared consumption object, I would consider that a success for both standards. It means authorization and bounded-consumption semantics remain independently evolvable while interoperating through a common interface, rather than each authorization framework defining its own incompatible model for cumulative authority.

1 Like

This is the right framing, the cursor as canonical state, factored out of both issuance and enforcement, is what lets “bounded authority” mean the same thing in a smart account, a vault, or a workflow engine. I’d add the corollary that turns it from an architectural nicety into something enforceable-by-anyone: if the cursor is the canonical state, then the bound is checkable from that state, independently of whichever venue advanced it.

That’s the part we’ve been making concrete. The aggregate ≤ cap invariant recomputes straight from the cursor’s storage (eth_getProof against the state root), and we now run it over two independent cursor serializations with one recipe:

  • your StatefulBound cursor reserved + confirmed

  • a turnover cursor cumulativeTurnover ≤ maxCumulativeTurnover (orbmis’s headroom)

Same invariant, two layouts, both read from public state rather than from a view function’s say-so. So “bounded authority as a shared state machine” isn’t only asserted at the interface — the state machine’s core invariant is reproducible by anyone from the chain, whatever venue did the enforcing.

That also speaks directly to your enforcement list, in particular no unauthorized cursor advancement: a storage-proof recompute witnesses the cursor value at a block for exactly what it is, so you can read whether the bound still holds without trusting the enforcer. That’s precisely the property you want if the same cursor is going to be honored across heterogeneous venues, the venue-agnosticism of the primitive and the venue-agnosticism of the check are the same move. A cursor that any authorization model can adopt is only as strong as a bound anyone can verify, and the check has to be as model-agnostic as the primitive: it reads the cursor’s storage, not the issuance path.

1 Like

Appreciated @blockbird @TMerlini, and I think we agree on the main point: these are orthogonal, one is a regulated authorization model, the other a general bounded-authority primitive. Two clarifications for the record.

On recompute: it already runs against RAMS. cumulativeUsed and maxCumulativeValue are public storage, so aggregate ≤ cap verifies from the state root by eth_getProof, the same recipe @TMerlini describes, just a different layout. canExecute was never the source of truth, so there is no view function to take on faith.

And it cuts the other way. A storage proof of a split cursor shows what the meter claims. The same proof against RAMS shows what actually settled, because the advance is atomic with the ERC-7943 transfer and a reverted transfer reverts its own advance. Reserve, confirm, cancel is an elegant way to keep a meter in sync with enforcement across frames; atomicity removes that gap before it appears, which is why RAMS does not carry the machinery. Same invariant, proven at settlement rather than asserted at the meter.

On adopting an 8312 envelope: I would put it exactly as you did, independently evolvable and interoperating through shared, recomputable state. The difference is which side holds the state, and RAMS already holds it, with the enforcement, freeze, and compliance around it in one contract. For a regulated asset the counter has to sit where the enforcer and compliance operator govern it, not factored out, and moving it out would pull RAMS toward the split model that fusion avoids. If someone wants the shared recipe later, RAMS can expose a compatible read view over its existing slots, no envelope adopted.
One framing point. The shared layer is not any single standard’s interface, it is the state root. Both counters recompute from it by eth_getProof with no shared envelope, which is why RAMS interoperates today without adopting one. Which format, if any, becomes the common one looks like a community call, not one draft’s to decide.

On prior art, the related-work section is thorough, so it stood out that ERC-8226 is not in it, a merged and directly adjacent standard with cumulative value caps. ERC-8118 (Agent Authorization) is in the same space too, usage-limited delegation with automatic revocation, though as an unmerged draft it is harder to reference. One correction on the aggregate point: a RAMS cap already holds across a principal’s venues, since the token hook, the account, and the executor all write to the same mandate slot, and the registry is many to many, keyed (agent, principal). It caps each mandate on its own rather than pooling them, which for regulated delegation is the point: each mandate is a separate authority and is capped separately.
Worth a look at ERC-8118 regardless, it helps mark where the primitive ends and each authorization model begins.

Good thread. Useful to have a fused settlement-time design and a split meter-time design against the same invariant in public.

1 Like

@thamerdridi agree, and thanks for the precision.

On recompute, yes, and that’s exactly how the shared recipe is already built. 8312/cap-conservation proves aggregate ≤ cap from the storage slots via eth_getProof against the block’s stateRoot, explicitly not a trusted meter/view read. So “canExecute was never the source of truth” isn’t a gap between us, it’s the premise. RAMS’ cumulativeUsed / maxCumulativeValue verify the same way, different layout, same one line of proof.

Your framing is the one I’d keep: the shared layer is not any standard’s interface, it’s the state root. Interop is two counters recomputing from the same root by eth_getProof, no envelope adopted. That’s the whole thesis, you compose through recomputable state, not a shared ABI.

On settled vs claimed, fair, and I’d draw it the same way. RAMS proves at settlement because the advance is atomic with the ERC-7943 transfer; reserve/confirm/cancel proves what’s reserved and reconciles after. The meter’s trajectory is itself non-forgeable (confirm/cancel bound to the reserver, no third party can move it), which is what keeps it recomputable, but you’re right that atomicity removes the reserve→settle gap rather than tracking it, and for a regulated asset the counter belongs where the enforcer and compliance operator govern it, not factored out. Fused settlement-time and split meter-time, same invariant, both provable from the root, exactly the useful thing to have side by side in public.

And agreed: which format, if any, becomes common is a community call, not one draft’s to make. A compatible read view over RAMS’ existing slots, no envelope, just the slots, is the right shape if/when someone wants the shared recipe. (The 8226 / 8118 prior-art note is one for @blockbird related-work, worth folding in.)

1 Like

Agreed on orthogonality, and the recompute result is the part I care most about: cumulativeUsed and maxCumulativeValue proving from the state root by eth_getProof means the invariant verifies against RAMS today, no view taken on faith. Two independently designed counters landing in the same one-line proof is the thesis working.

One precision on the shared layer, because there are two planes in play. For audit you are right, and it is the frame I would keep: the shared layer is the state root, two layouts recompute from it, no shared envelope needed. Ordering is the plane where the envelope earns its place. A root lets anyone read every counter; a pooled aggregate needs every surface writing one counter. When a principal wants one cap across mandates and venues, that sum has to exist as a single written object every draw advances, which no per-layout read can assemble at check time. RAMS does not need that object because it caps each mandate on its own, by design. The general primitive exists for the case where someone wants the pool.

On settled versus reserved, conceded, with one note on where that lands architecturally. Fusing the advance into the ERC-7943 transfer, with freeze and compliance in the same contract, is exactly the binding the spec’s enforcement clause asks a substrate to provide: value cannot move without the counter moving. So I read the fusion as a conformant home for a cap rather than a rival to the metering model. Reserve, confirm, cancel exists for the frames RAMS deliberately does not have, deferred and cross-surface settlement, where a reserve-to-settle window has to be kept honest; reservation binding keeps that trajectory non-forgeable rather than asserted, though you are right that atomicity removes the window instead of tracking it. For a regulated asset the counter belongs where the enforcer and compliance operator govern it, and I would not factor it out either.

Thank you for the aggregate correction, it is a real one. A RAMS cap does hold across a principal’s venues, hook, account, and executor writing the same mandate slot. What remains is intent, not deficiency: per-mandate caps are the point of regulated delegation, and the pooled cross-mandate aggregate is the point of the general primitive.

On adoption we agree, no envelope, and a compatible read view over existing slots is the right interop shape. For what it is worth, the budget profile’s read surface (bound, spent, remaining) is one ready-made shape for such a view if it is ever useful. Which format becomes common is the community’s call, agreed. The case a draft can make is what gets built against it.

On prior art, accepted, and it is fair to flag your own adjacent work: ERC-8226 is now in the related-work section as the directly adjacent regulated design, linked since it is merged, and ERC-8118 is noted as text while it is unmerged. Both are in PR #1833 now.

1 Like

Aligned, and thanks for engaging on the detail @blockbird @TMerlini. So we have it: the shared layer is the state root, two counters recompute from it by eth_getProof, no envelope adopted, and RAMS keeps its counter where the enforcer and compliance govern it. A read view over the slots is the shape if anyone ever wants the shared recipe.

I appreciate the 8226 / 8118 note.

Happy to compare notes as both move forward.

3 Likes

Agreed, and the summary I would pin is the one you opened with: orthogonal, a regulated authorization model and a general bounded-authority primitive. On the shared layer, both halves for the record: the state root is where any counter verifies, and the envelope is where a pooled aggregate exists. One cap across a principal’s mandates, venues, and settlement frames has to live as a single written object every draw advances, and that is the case the general primitive is for. RAMS’s fused counter is a conformant home for the per-mandate case and does not need it. Happy to compare notes as both move forward.