ERC-8301: AI Agent Execution

@TMerlini @babyblueviper1

Thanks to both of you — the worked production example from the ledger makes the binding requirement concrete in a way that’s hard to argue with. The three checkable properties (verdict binds input, verdict timestamp precedes execution timestamp, executed action matches approved verdict) are exactly the right shape for an end-to-end conformance check.

On the two normative conditions: both are present in the current draft, and it’s worth naming them explicitly so they’re easy to verify.

The halt-for-non-approve condition appears as gating rule 2: “A non-approving verdict for a gating step MUST cause the handler to reject any subsequent onAgentStep that lists that stepHash in prevStepHashes.” That is a hard stop, not a logging event — the handler is the enforcement point.

The input binding condition appears as gating rule 3: onAgentProve at a gating step MUST be called with inputHash and outputHash matching the gating step’s values.” This means the prove submitted at the gating stage is anchored to the specific input the gate was asked to judge — a verdict that arrived with a different inputHash would fail rule 3 before it could influence step progression.

The combination is intended to close the substitution gap on both sides: rule 3 prevents a verdict for INPUT_A from gating a step that ran on INPUT_B, and rule 2 ensures a non-approving result actually stops the chain rather than being bypassed.

The latest draft is at ERC for AI Agent Execution · GitHub — would welcome a check against the production trace to confirm these two rules cover the binding requirement as described, or flag any gap that remains.

2 Likes

Checked rules 2 and 3 against the production trace. Two of the three properties hold cleanly under the current draft; the third has a narrow gap that sits in our layer, so flagging it precisely.

Covered:

  1. Verdict binds input. Gating rule 3 (onAgentProve’s inputHash/outputHash must match the gating
    step) anchors the verdict to the exact input the gate judged. A verdict carrying a different
    inputHash fails rule 3 before it can gate anything. This is the input leg, and it is solid.

  2. Executed action matches approved verdict. This is carried by the DAG itself rather than a
    separate rule: the gating step’s outputHash becomes the gated step’s inputHash, and the gated
    step’s stepHash commits to prevStepHashes (predecessor binding). So the executed step is bound to
    the approved step’s output, and its ancestry cannot be rewritten after the fact. Same guarantee as
    our triple-hash’s action leg, reached through DAG-chaining instead of one combined hash. Good.

The gap is on property (b), verdict-timestamp-precedes-execution, and only in one case:

  • In-workflow it is fine. Rule 1 (a gated step is not accepted until its prove resolves) plus
    on-chain block ordering give a verifiable precedence when the graded outcome is itself a downstream
    step.
  • Out-of-workflow it is not reached. When the verdict is graded against an external realized
    outcome that is not a workflow step (a settlement, a P&L, a later reveal), nothing in the gating
    rules establishes that the verdict was committed before that outcome existed. This is the case our
    ledger runs against capital, and it is where commit-before-outcome actually has teeth.

The fix is one conformance condition, not a new interface: for a gating step whose graded outcome is external, committed_at MUST be provably prior to that outcome, anchored to a system the verifier does not control. The reference point is what discriminates it:

  • outcome_verifiable: committed_at MUST precede the realized outcome’s settlement.
  • consensus_weighted: committed_at MUST precede the reveal.

Production instance you can check: ledger entry 19 is an on-chain commit-reveal where
committedAt < revealedAt is verifiable from the chain, not asserted by the issuer. That is the
out-of-workflow precedence the gating rules leave open, made concrete.

So: rules 2 and 3 cover the in-workflow binding fully; the only addition is naming the external-
outcome precedence as a conformance condition with the type discriminator above. Happy to draft
that condition in the spec’s language against the gist if useful.

2 Likes

@babyblueviper1 gap analysis is right, and the fix he named is the seam between two specs, worth saying out loud, because it’s the cleanest example of why these compose rather than compete.

Property (b) splits by where the graded outcome lives:

  • In-workflow (the outcome is a downstream step): the execution draft already closes it — rule 1 (a gated step isn’t accepted until its prove resolves) plus on-chain block ordering give verifiable precedence. That half is yours, Jimmy.

  • Out-of-workflow (the outcome is external — a settlement, a P&L, a reveal): the gating rules can’t reach it, because the outcome isn’t a step they order. Here committed_at has to be anchored to a clock the verifier doesn’t control — which is exactly the ERC-8263 verdict anchor: the verdict’s on-chain commitment carries a block timestamp, so committed_at < outcome is verifiable against any outcome, workflow step or not. Fede’s entry 19 (committedAt < revealedAt, read from the chain rather than asserted) is that, live and against real capital.

So neither spec closes property (b) alone. The execution draft owns the in-workflow half; ERC-8263 owns the out-of-workflow half; and Fede’s conformance condition — committed_at MUST precede the realized outcome, with the outcome_verifiable / consensus_weighted discriminator on which reference point applies, is the boundary between them. That isn’t a gap in either spec. It’s the composition.

Which is the real shape of this whole thread: execution @JimmyShi22 , verdict precedence and input-binding (@babyblueviper1 / WYRIWE / 8263). The clean way to capture it isn’t more scattered replies, it’s a reference flow that names which spec owns which leg of the conformance check, so an implementer reads one end-to-end story instead of stitching three. @babyblueviper1 , your offer to draft the external-outcome condition against the gist is the right next step; that’s where this lands.

2 Likes

@TMerlini @babyblueviper1

Thanks to both of you — BabyBlueViper’s production trace and Tiago‘s framing together make the gap very concrete. The scenario is real: the DAG proves ordering between steps inside the workflow, but says nothing about the relationship between a step’s execution time and an event that happened outside the workflow entirely — a settlement on another contract, an oracle publish, a reveal in a separate system. That’s a genuine blind spot and worth addressing directly in the interface.

Here’s an initial thought that might be a somewhat cleaner way to close it — happy to be corrected if it misses something.

The idea is to add a caller-supplied timestamp field to onAgentStep, representing the off-chain execution time of that step. Three properties follow:

  • Caller-supplied, not block.timestampblock.timestamp is the on-chain submission time; the actual off-chain execution happens earlier and cannot be recovered from it. The caller-supplied value captures when the step actually ran.
  • Constrained by timestamp ≤ block.timestamp — the handler rejects any future-dated timestamp, so the claimed execution time is bounded above by the block.
  • Committed into stepHash — because timestamp is included in the hash formula, any proof submitted via onAgentProve for that step automatically covers the execution time. No separate anchoring protocol is needed; precedence over an external event is checkable by comparing the attested timestamp against the external event’s block time.

onAgentProve carries no time constraint of its own. Once a step’s execution time is committed into stepHash, the proof’s sole responsibility is to attest to the validity of that step — it may be submitted at any point after the step is recorded. The temporal claim belongs entirely to the step; the proof inherits it.

This is just a preliminary direction. The updated interface signature, stepHash formula, and a Rationale subsection walking through the reasoning are at ERC for AI Agent Execution · GitHub — would genuinely welcome a check against the ledger trace to see if this covers the committed_at < outcome_realized_at property, or whether there is a remaining gap this approach doesn’t reach.

2 Likes

This is the right direction, and the instinct to commit the time into stepHash so the proof covers it, with no separate anchoring protocol, is correct. One distinction decides whether it closes committed_at < outcome_realized_at, and it is the exact one the ledger trace was built around.

A caller-supplied timestamp constrained only by timestamp <= block.timestamp gives a verifiable upper bound (no future-dating) but not a verifiable lower bound. Against an adversarial committer that leaves a back-dating gap:

The validator waits, observes the external outcome at t_outcome, then submits the step at block time t_submit > t_outcome, setting the caller-supplied timestamp to any t’ < t_outcome. The constraint t’ <= block.timestamp holds, since t’ < t_outcome < t_submit. The stepHash now commits t’, and anyone comparing the attested t’ against t_outcome concludes the verdict preceded the outcome. It did not.

So the attested timestamp cannot be the value the precedence check reads. It is a claim the committer chooses, bounded only from above, so it can always be set earlier than the truth.

The time that does establish precedence is the one the committer cannot choose: the block time at which the committed stepHash is first recorded on-chain. That is committed_at. The check is recording_block_time(stepHash) < outcome_realized_at, and it catches the case above, because t_submit > t_outcome fails it. Your no-separate-anchoring instinct holds; the anchor is just the recording block time, not the caller-supplied field.

This is exactly what ledger entry 19 shows: committedAt is the on-chain block time of the commit transaction, revealedAt is the on-chain block time of the reveal, and committedAt < revealedAt is checkable precisely because neither is asserted by the issuer. The reveal stands in for any external outcome.

Concretely for the interface: the caller-supplied timestamp is fine to keep as an upper-bounded execution-time claim if it is useful elsewhere, but for commit-before-outcome conformance the gating step’s stepHash MUST be recorded on-chain before the graded outcome, and the precedence comparison MUST read the on-chain recording time, not the attested timestamp. That recording time is committed_at, and it is the one piece a caller-supplied value alone does not reach.

1 Like

Both right, and they resolve cleanly. @JimmyShi22 instinct, commit the time into stepHash so the proof covers it, no separate protocol, is correct. @babyblueviper1 attack shows why the value committed can’t be the caller-supplied one: bounded only from above (timestamp ≤ block.timestamp), it’s back-dateable. A committer can watch the external outcome land at t_outcome, then attest any earlier t' < t_outcome, and the upper-bound check still passes. Upper bound, not lower bound.

The timestamp that actually establishes precedence is the one the committer can’t choose: the on-chain block time at which the committed stepHash is first recorded, committed_at. So the conformance check reads recording_block_time(stepHash) < outcome_realized_at, never the attested field.

Where it composes:

  • If onAgentProve records the stepHash on-chain, the protocol gives you that recording time natively, nothing external needed.

  • When the proof is delivered off-chain (your #14“proofs arrive separately, potentially from a third party”), there’s no intrinsic on-chain time, and you need an anchor that stamps the commitment with a block time the committer doesn’t control, which is exactly ERC-8263.

Either way the precedence value is an on-chain recording time, never the caller field. Keep @JimmyShi22 caller-supplied timestamp as the claimed execution time (useful, upper-bounded by the block); conformance reads the anchored one.

Same seam as #23: in-workflow precedence is the DAG + rule 1; out-of-workflow precedence is the on-chain recording time of the commitment, the protocol’s own when the prove is on-chain, ERC-8263 when it isn’t.

1 Like

@TMerlini @babyblueviper1

Really appreciate both #25 and #26 — the back-dating attack in #25 is precise and the ledger trace makes it concrete in a way that generic analysis doesn’t.

Following that feedback, the draft has been updated with a lower bound on the caller-supplied timestamp. The handler anchors each step at submission time by recording its block.timestamp, and the next step’s declared execution time must satisfy:

anchor_time(prev_step) < timestamp ≤ block.timestamp

The upper bound was already there. The lower bound is new: the declared execution time cannot predate the on-chain anchor of the steps it builds on. For cases where a stronger anchor is needed, ERC-8263 can be used to anchor each step commitment — the same anchor_time constraint then reads from the ERC-8263 record rather than the submission block. This narrows the back-dating window — though it does not close it entirely on its own, as #25 correctly shows.

@babyblueviper1 — the observation in #25 is genuinely valuable, and it opens up a responsibility boundary worth making explicit in the spec. The protocol layer can enforce structural constraints on timestamp, but it cannot independently verify that the declared value matches when the step actually ran. That verification belongs to the proof system. Since timestamp is committed into stepHash, any proof submitted via onAgentProve inherently covers it — but whether that coverage is cryptographically binding depends on the proof mechanism:

  • TEE — the execution environment attests the timestamp as part of the proof payload; a valid TEE attestation cannot be produced for a time the hardware did not observe.
  • ZK — the timestamp is part of the witness committed in the circuit; a valid proof is a proof that execution happened with that specific timestamp.
  • Weaker schemes (signature-based, multisig) — the timestamp is structurally covered but not cryptographically bound to real execution time; here, recording_block_time(stepHash) is the appropriate precedence anchor, and anchoring the step via ERC-8263 is the recommended path — it stamps the commitment with a block time the committer does not control, which is exactly what committed_at < outcome_realized_at requires.

So the two approaches seem complementary rather than in tension: the protocol provides the structural bounds; the proof system provides the actual binding. Would be curious whether this layering maps cleanly onto the ledger entry 19 trace — specifically whether committedAt there comes from the TEE attestation or from the on-chain block time, and whether the distinction affects the conformance check in practice.

1 Like

@TMerlini @babyblueviper1 @KBryan @Ankita.eth
Thanks to both of you for the continued engagement on task boundary and gate semantics — those threads have been directly shaping how this design evolves.

Following the discussion around where workflow logic should live and how gate enforcement relates to agent task boundaries, the design has moved in a direction worth sharing and getting feedback on.

From agentWorkflowHash to a workflow contract.

The original draft used agentWorkflowHash to point to an off-chain workflow definition. The core issue: the structural trust guarantees described in the Motivation depend on the workflow being enforced at each step — but if the workflow definition lives off-chain, the contract cannot enforce it. The proposed revision removes the hash entirely and replaces it with a contract: the implementation of IAgentWorkflow is the workflow. Gate logic, verifier integration (ERC-8274), and next-step decisions are all defined inside the contract, not in an external file a contract cannot read.

Choreography rather than orchestration.

Rather than a single callAgent dispatching the full workflow to an agent, the revised model treats each DAG node as its own cycle:

Contract ──emit CallAgent──▶ Agent listens, executes
                                  │  submits onAgentStep / onAgentProve tx
                            Contract receives, runs gate
                                  │  gate passes → emit CallAgent (next step)
                                  └─ DAG complete → emit TaskCompleted

The contract holds state and enforces the gate at every step. Different agents can handle different steps; multiple agents can be dispatched for the same step (voting, commit-reveal, competitive selection) — the quorum logic lives in the contract implementation, not in the interface. This directly addresses the structural trust concern: the execution structure is enforced on-chain at each node, not assumed from the agent.

External interface.

Two surfaces are proposed for external composability:

  • execute(AgentTask) — the entry point for any external contract or user to submit a task
  • taskResult(taskId) — a view function returning the final step hash, output hash, output bytes, and status, so downstream contracts can read and act on results without needing to understand internal DAG state

The IAgentWorkflow interface standardizes these two surfaces; the gate logic, verifier wiring, and step sequencing are implementation choices.

Would be glad to hear whether this layering — contract-as-workflow, choreography-per-step, standardized entry/exit surfaces — reads as a clean boundary from the identity and verification layers, or whether there are composability concerns from ERC-8004 or ERC-8274’s perspective that should influence the interface shape.

Moving from agentWorkflowHash to an on-chain IAgentWorkflow implementation feels like the right architectural move. If the workflow guarantee depends on gate enforcement at each step, then the workflow needs to live where enforcement happens. A contract cannot enforce an off-chain workflow definition it cannot inspect. Making the contract itself the workflow gives the system a stronger trust boundary: sequencing, verifier integration, gate logic, and next-step dispatch are all enforced on-chain.

The choreography model also makes sense. Treating each DAG node as its own contract-agent cycle means the workflow contract can enforce state transitions at every step rather than assuming a single agent executed the whole graph correctly off-chain. It also leaves room for different agents per step, competitive execution, voting, commit-reveal, and application-specific quorum logic without forcing those choices into the base interface.

From an ERC-8001 perspective, I would frame the relationship as complementary, not prescriptive.

ERC-8301 defines the workflow execution surface: execute(AgentTask), step-level dispatch, gate enforcement, and taskResult(taskId).

ERC-8001 defines a coordination gateway: an initiator proposes an intent, participants provide verifiable acceptances, and the intent becomes executable once the required acceptances are present and fresh.

I would not suggest that every ERC-8301 workflow needs to use ERC-8001. Some teams will use centralised coordination services, bespoke controllers, or application-specific quorum logic. That should remain valid. But where a workflow wants reusable, decentralised, inspectable multi-party agreement, ERC-8001 should be part of the conversation.

A clean composition could look like this:

  1. An ERC-8301 workflow receives an AgentTask.

  2. The workflow checks its own gate for the current DAG node.

  3. If that node requires multi-party agreement, the gate can query or validate an ERC-8001 coordination state.

  4. If the required acceptances are present and fresh, the workflow emits the next CallAgent.

  5. If not, the workflow remains gated.

That keeps ERC-8301 focused on execution while avoiding the need to reinvent participant sets, acceptance attestations, nonce freshness, expiry handling, or EIP-712 / ERC-1271 acceptance semantics inside every workflow implementation.

So I would separate the stack as:

  • ERC-8301: workflow execution

  • ERC-8001: optional decentralised coordination gateway

  • ERC-8274: proof / output verification

  • ERC-8004: agent identity / discovery

One composability point worth considering is whether taskResult(taskId) or the workflow status surface should expose enough context for downstream contracts to understand why a result is safe to consume. For example, a downstream contract may want to know not only that a task completed, but that the relevant gates passed — such as an ERC-8274 proof gate and, where used, an ERC-8001 coordination gate.

That does not mean ERC-8301 needs to depend on ERC-8001 directly. It may be enough to expose a generic gate or context reference so implementations can point to the coordination primitive they used. Centralised coordination, custom coordination, and ERC-8001-based coordination can all coexist. But given ERC-8001 is a finalised coordination standard, I think it is worth making sure ERC-8301’s interface shape leaves a clean path for it to serve as the standard decentralised coordination gateway where that property matters.
All in all, great work so far.

2 Likes

@JimmyShi22 — direct answer first: in entry 19, committedAt is the on-chain block time of the commit transaction, not a TEE attestation. There’s no trusted hardware or circuit anywhere in that trace. The verdict is a Schnorr-signed event (recompute the id, check the signature against the published key), and the two timestamps the conformance check actually reads — committedAt and revealedAt — are both block times the issuer doesn’t control. So the reference instance is precisely your weaker-scheme case: the timestamp is structurally committed inside the signed event, but it’s not cryptographically bound to real execution time, so the precedence value has to be recording_block_time(stepHash), never the attested field.

And yes, the distinction changes what the conformance check is allowed to read, which is exactly the responsibility boundary you drew in #27 — and I think it’s the right one:

  • TEE / ZK: the attested timestamp is bound to real execution (the hardware attests it, or the circuit commits it in the witness), so the attested field is itself load-bearing and the check can read it.

  • Weaker schemes (sig / multisig): the attested timestamp is covered by the hash but unbound to real time, so it carries no precedence weight, and the check must fall back to the on-chain recording time. ERC-8263 is the anchor for the case where the proof isn’t recorded on-chain natively.

The ledger runs the weaker-scheme path on purpose: it means the guarantee survives without anyone trusting our hardware or our circuit. Recompute the event id, verify the signature against the published key, read committedAt and revealedAt from their blocks, check committedAt < revealedAt. Nothing in that line asks you to believe an attested execution time.

On your lower bound — anchor_time(prev_step) < timestamp ≤ block.timestamp — it’s a real improvement and worth keeping: it stops a step from claiming an execution time that predates the anchor of the steps it builds on. As you noted, it narrows the back-dating window rather than closing it, since a committer can still attest any timestamp inside [anchor_time(prev_step), block.timestamp]. The residual closes the same way: when the graded outcome is external, the precedence comparison reads the on-chain recording time of the gating step’s commitment, not the attested field. The attested timestamp stays useful as an upper-bounded claimed execution time; it just isn’t what conformance grades against.

So I’d state the spec boundary as: the protocol bounds the attested timestamp structurally; the proof system decides whether that timestamp is binding; and for any scheme where it isn’t, conformance reads the anchored recording time. That keeps a sig-based deployment (like the ledger) and a TEE/ZK deployment under one conformance statement, with the proof mechanism deciding which timestamp is load-bearing.

1 Like

Quick update on the design direction — a working draft of the revised spec is up here as a gist for early feedback:
ERC-8301 v0.2 · GitHub

The core idea that’s been taking shape:

Rather than using agentWorkflowHash to reference an off-chain workflow document, the proposal moves toward a model where the contract itself is the workflow — implemented as a finite state machine. The developer defines a stage enum and gate logic; the contract emits NewAgentTask events (no designated agentId, so any conformant agent may respond); and each onAgentStep / onAgentProve call triggers an on-chain gate evaluation that either advances the FSM or silently discards the submission.

Two standardized entry points — run() for task submission and result() for result queries — let any external contract interact with any conformant workflow without knowing its internal logic. Agent identity is verified via ERC-8004 (controllerOf); execution proofs go through ERC-8274.

The gist covers the interface definitions, hash formulas, sync/async gate modes, and rationale. The scenario walkthroughs and reference implementation call sequences are included but still being refined — some of the explanatory sections aren’t complete yet.

Feedback on the FSM approach, the prevTaskHashes chain semantics, or the sync/async proof policy design would be very welcome at this stage.

1 Like

Thanks for the framing, KBryan — both points are well-taken.

The separation between execution (ERC-8301) and coordination (ERC-8001) reads as the right boundary. The plan is to review the current draft against ERC-8001’s interface to make sure the two compose cleanly, and flag anything that would create unnecessary coupling.

On the result() return value — the gate context question is a good one and worth thinking through carefully before the next revision. Will take that into account.

1 Like

That’s a helpful clarification — good to know the production path uses block times rather than attested timestamps. The layering makes sense: TEE/ZK schemes can bind execution time cryptographically, while other schemes should anchor to on-chain recording times instead. Different tradeoffs for different use cases, and the faster response time from lighter schemes is genuinely valuable in latency-sensitive scenarios.

One small thing worth exploring for the lighter-weight path: if an agent has any on-chain activity during execution, a merkle inclusion proof of that transaction against the block header gives an independently verifiable time anchor without requiring full ZK or TEE. Not a requirement, just a possible middle ground for schemes that want a bit more than a pure declaration.

1 Like

Agreed on the layering, and the merkle-inclusion middle ground is the right shape for the lighter-weight path: bind execution time to on-chain ordering, no TEE or ZK, anyone can re-derive it from public data.

One correction to my earlier note: that primitive already exists as its own step in the stack, and it isn’t mine to claim. ERC-8263, the event-only time anchor, is Vincent Wu’s (@TruthAnchor-AI). It emits an AnchorProof carrying an opaque, indexed proofHash, and committed_at is the anchoring block’s timestamp read from the chain, never a self-declared field. Your merkle-inclusion proof and the anchor’s block-time read are the same guarantee reached two ways: “this exact value existed no later than this block,” proven by on-chain ordering. Vincent is the right person on the anchor’s specifics.

Where my layer sits relative to 8301: 8301’s AgentTask answers authenticity-of-dispatch; the 8263 anchor answers provably-prior-in-time; and the judgment and reputation layer I work on (ERC-8299 and the ERC-8275 reputation axis) composes on top by reading those events. committed_at from the anchor is exactly what the reputation axis gates on, so a verdict counts only once it is provably prior to the outcome it is judged against. So an 8301 dispatch can reference an anchored commitment and get the independently verifiable time anchor you describe, without 8301 itself having to carry timing semantics.

Happy to walk the read path on the reputation side if it is useful; for the anchor specifics, Vincent is your man.

Thanks for the framing, KBryan — both points are well-taken.

The execution/coordination boundary feels right. One way to think about the split: ERC-8001 addresses the need-to-do layer — what tasks exist, what coordination obligations have been agreed to, whether multi-party consensus is required. ERC-8301 addresses the how-to-do layer — given that a task needs to be executed, how is it dispatched, how are replies anchored, and how does the FSM enforce stage transitions. The two compose naturally: an ERC-8001 coordination outcome can be the trigger for a run() call, and an ERC-8301 workflow can optionally query ERC-8001 state inside its gate logic for multi-party scenarios — without either spec prescribing the other.

The draft will be reviewed against ERC-8001’s interface to make sure nothing in the current design creates unnecessary coupling at the boundary.

On result() and gate context — that’s a genuine question worth thinking through carefully. The updated draft introduces a bidirectional evidence chain: every AgentTask carries prevReplyHashes (which replies triggered it) and every AgentReply carries prevTaskHashes (which tasks it responds to), forming a fully traversable on-chain record from run() to completion. getAgentTask and getAgentReply expose this chain as view functions, so downstream contracts can traverse the full per-stage history independently. Whether result() itself should surface a higher-level summary of gate context on top of that is still worth considering — happy to hear more on what shape would be most useful for the use cases you have in mind.

The full updated spec is here: ERC-8301 v0.2 · GitHub

Also, if there are specific places in ERC-8301 where a reference to ERC-8001 would be useful — whether in the motivation, rationale, or a dedicated section — suggestions are very welcome. Happy to incorporate them.

@KBryan , clean separation, and I think it composes a step further than the thread’s framed so far. Your gateway is doing on-chain coordination, 8001 gating multi-party agreement before an 8301 node dispatches. There’s a second, complementary “gateway” one plane down: the off-chain CCIP-Read (EIP-3668) layer that serves and runs the agents that respond to those dispatched tasks.

They line up end to end:

  • 8301’s FSM dispatches a task on-chain (NewAgentTask)

  • the assigned agent, served/run through the off-chain CCIP-Read gateway, observes it, executes, and submits its reply on-chain

  • your 8001 coordination gateway gates any multi-party acceptance before the FSM advances

So on-chain coordination (8001), on-chain orchestration (8301), and off-chain resolution/execution (3668) are three distinct layers that don’t overlap, each enforces what it’s positioned to, none redefines the others. We run the off-chain side live today on a CCIP-Read mesh, so happy to speak to that seam concretely if it’s useful.

One naming flag while we’re here, purely to spare reviewers: “gateway” is starting to mean a few different things across this stack. Worth pinning terms before the docs cross-reference — coordination / intent gateway (your 8001 layer) vs resolution / CCIP-Read gateway (the off-chain 3668 layer). (We hit the identical thing with “VNI” on the 8294 side, easier to disambiguate early.)

And, 8001 already being Final is exactly the kind of experience this cluster could learn from. Composition aside, it’d be great to have you closer to the rest of this work.

Quick follow-up, @KBryan , I’ve given this a concrete home beyond the thread: the composition note for the agent-service consultation flow now notes (§6) that the 1:1 flow extends to multi-party via ERC-8001 coordination, gating the dispatch step before the FSM advances. So 8001 isn’t just composing with 8301 in the abstract, there’s a placed spot for it as the multi-party frontier of a live, demonstrated flow. Happy to share the note if it’s useful.

2 Likes

I really like this framing.

The three-layer split feels right to me:

ERC-8001 handles the coordination question: has the right set of parties agreed to this thing?

ERC-8301 handles the workflow question: given that agreement, how does the task move through the on-chain workflow?

EIP-3668 / CCIP-Read handles the resolution question: how does the off-chain agent or service actually get reached and run?

That separation is important. ERC-8001 does not need to know the full workflow, and ERC-8301 does not need to take on the multi-party acceptance role itself. In the cases where a workflow step needs agreement from multiple participants, ERC-8301 can simply check ERC-8001 state before dispatching or advancing that step.

So the composition is pretty natural:

ERC-8001 confirms the coordination is ready → ERC-8301 advances the workflow → the CCIP-Read layer helps resolve and run the agent → the result gets anchored back on-chain.

That keeps each standard focused on what it is best positioned to enforce.

I also agree with the naming point. “Gateway” can start to mean too many things if we are not careful. I would probably describe ERC-8001 as the coordination layer, ERC-8301 as the workflow orchestration layer, and EIP-3668 / CCIP-Read as the resolution gateway.

I am glad §6 now gives ERC-8001 a concrete place in the consultation flow. That is exactly how I think it should show up: not as a hard dependency, but as the multi-party coordination layer for workflows that need acceptance before moving forward.

Happy to review the note if you share it. I think this whole cluster becomes much stronger when we show how the pieces compose without blurring their boundaries.

Also, one issue we ran into with the execution part that we’re now tackling is bounded execution. Given a multiparty agreement, there may be guardrails that need to be in place. An agent may not be able to exceed a transaction balance limit or perform x amount of transactions. With ERC8001, we have been integrating our agents into corporate environments that require SOC2 compliance, agentic tokenomics, and more. We have built something ad hoc. However, I don’t think I saw this being discussed, and it is needed for enterprise agents.

Great discussion, love being a part of this.

For reference, ERC-8001 is here: https://eips.ethereum.org/EIPS/eip-8001

2 Likes

Yes, that separation is exactly the spine. ERC-8001 as coordination (multi-party agreement), ERC-8301 as orchestration (task progression), EIP-3668 as resolution. Each stays inside its own boundary and composes by reference, never by reaching into the next, which is the whole premise of the composition note: a layer is a conformant example of its role, not a dependency the others import. You stated the cut more cleanly than we had.

And the guardrails point is a real gap, I don’t think it’s addressed anywhere in the stack yet, and it should be. Here’s how it looks to me, and I’d want your read since you’ve actually built these:

The bounds themselves, spend caps, max-N transactions, balance limits, rate, feel like they belong in the agreement (ERC-8001): they’re terms the parties accept, so the multi-party layer is their natural home. But carrying the bound isn’t enforcing it. Enforcement is an action-time concern: the executor (or the settler) must verify the action stays within the agreed bound at execution, atomically with the action, not at approval time, since the available headroom (spend remaining, tx count) changes between agreement and execution. That’s the same TOCTOU discipline the flow already requires for live-ownership and eligibility re-checks.

And it’s enforceable, not just assertable: a bounded-execution guardrail is a recomputable condition over public state, “this action keeps cumulative spend under the agreed cap” is checkable the same way eligibility is. We’ve been sketching exactly this pattern on the settlement side (a contract recomputing a predicate before it releases) a spend/tx-count bound is another instance of it. So it could be a clean seam rather than ad hoc: agreed bounds in the 8001 envelope → enforced as an action-time conformance check by the executor/settler.

Does that match how you’ve been doing it for SOC2 / tokenomics, the bound declared in the agreement, checked atomically at execution? If so, this looks like a layer the family is missing and that you’re closest to. Would you want to help shape it as a seam, even a short “bounded-execution guardrails” conformance note in the same form as the rest? Enterprise deployment is exactly the lens the standards work hasn’t had, and you’ve got it.

The cut is right: bounds are terms, so they live in the agreement, and enforcement is action-time and atomic. It has to be action-time because the bound is fixed at agreement but consumption happens later, so a check at agreement time is already stale. TOCTOU is the frame.

One distinction inside “a recomputable condition over public state” decides whether it holds. Recompute is a read. An aggregate bound needs a write.

“Cumulative spend stays under the cap” is recomputable from public state only once the spend is public. After the fact that’s true, the transfer landed, anyone can sum it. At the moment you’re gating it isn’t, because the action you’re about to authorize isn’t on-chain yet. Two steps in a fan-out both recompute the same spent-so-far, both see headroom, both pass, both spend. Each recompute was honest; neither action was public when the other checked. A read-only recompute doesn’t escape that. The check has to advance a committed counter, so the next recompute sees the reservation the last one made.

It gets sharper across protocols. The spends are public, but “recompute the aggregate” assumes one surface you can sum at action time. An agent with a lending position on one contract and a hedge on another has no single slot that holds the total, and no action-time check can walk its whole history to rebuild it. The aggregate only exists if each action writes it down.

So the predicate model is right, with one correction about where it reads. Post-hoc, over one surface: recompute from the chain. Pre-action, across surfaces: read-then-advance in one step, getCursor, then advance a committed counter, with the witness bound to (id, prevCursor) so a stale read can’t be replayed. The bound stays in the agreement; the headroom lives in a counter the action moves.

That pre-action cross-protocol case is the structurally hardest one for a per-call check, for exactly that reason. Happy to write it up in more detail if it’s useful to the guardrails note (wherever it best fits).

1 Like

@TMerlini @KBryan @JimmyShi22

Following up on my previous post (#39), and on the guardrails note floated in the post before (#38): I’ve written the counting half up as its own thread so it doesn’t take over this one, ERC-1833: Bounded Agent Actions - a metering layer for agent authority.

It’s deliberately narrow. It is not an enforcement standard and it doesn’t carry the bound. The bound stays where your posts #37/#38 put it, a term in the 8001 agreement.

Enforcement stays where you put it: Action-time and atomic, in the executor/settler.

The only thing it adds is the piece my last post was about: a standing counter each action advances, so the headroom an aggregate bound needs actually exists at action time, including across surfaces where no single contract can recompute it.

Against 8301 concretely: a gate reads getCursor(id) and calls advanceCursor in the same step. 8301 sequences the stages; the cursor records how much authority they’ve spent; the substrate underneath keeps the total inside the committed bound. Counting, not enforcing, next to 8301, not on top of it.

If the “bounded-execution guardrails” note lands in the same form as the rest of the cluster, I think the cursor is the counting primitive it can point at, the same way precedence points at 8263. Two of the questions I left on the thread are really for you: whether splitting the cursor from the budget profile is the right cut, and where exactly this should overlap the 8301 guardrails vs stay out of the way.

Happy to draft the cursor’s slice of the note directly if that’s useful.

2 Likes