ERC-8183: Agentic Commerce

Hey @aziz
Really appreciate the clarity you’re bringing to the reason/digest boundary — “pin the shape, not the interpretation” is exactly the principle this joint needs. The separation you’re drawing (settlement owns the canonical anchor, consumers own the reputation derivation) keeps 8183 minimal while letting the verification layer do real work, and that’s the right call.

Your framing aligns perfectly with what 8274 was designed for. The verificationDigest is built to be exactly that canonical, recomputable anchor: it binds the agent identity, the verification method (proofProfile), and the input/output pair into one digest that any consumer can independently recompute and verify, without 8274 ever saying “this result is good” or “this result is bad.” It just says “this is provably what agentId X produced under verification system Y for input Z.”

So the thought for 8183 v0.2 would be to specify job.reason along these lines:

**Shape**: job.reason MUST be a canonical, recomputable digest that binds:
- Agent identity (who produced this result)
- Verification method (how it was validated)
- Input/output correspondence (what was actually done)

**Recomputability**: Any consumer MUST be able to independently derive job.reason from the terminal state and verify it matches the anchored value.

**Explicit non-goal**: The standard does NOT interpret job.reason. Whether a specific digest represents "success," "quality," or "trustworthiness" is deliberately a consumer-side concern. 8183 guarantees the evidence is tamper-evident and attributable; consumers decide what it means for reputation.

**Example**: job.reason = verificationDigest (ERC-8274), which encodes (agentId, proofProfile, inputHash, outputHash) as a single recomputable hash.

Does this capture what you had in mind? Very happy to co-draft this section with you when the v0.2 pass opens — the seam between settlement and verification is load-bearing, and getting it right early matters.

1 Like

@JimmyShi22 yes, this is exactly it, and the mapping to the 8274 verificationDigest is clean. Binding agent identity, verification method, and input/output correspondence into one recomputable hash, with the non-goal stated outright, is the whole contract.

One refinement to keep 8183 minimal and future-proof, and I think it stays consistent with how you framed it:

8183 should specify job.reason as a set of PROPERTIES rather than a fixed struct. The anchor MUST be canonical, recomputable from the terminal state, tamper-evident, and attributable, and it MUST bind identity, verification method, and input/output correspondence. Any digest that satisfies those properties conforms. The 8274 verificationDigest is then the reference binding, the concrete example, not the only permitted one. That keeps 8183 verification-system-agnostic: a deployment can anchor an 8274 digest or any other conforming verifier output, and 8183 takes no hard dependency on a single verifier standard.

So the v0.2 language would be:

(1) job.reason MUST be a canonical, recomputable, tamper-evident anchor binding identity, method, and input/output correspondence

(2) any consumer MUST be able to recompute it from the terminal state and check it against the anchored value,

(3) explicit non-goal, the standard never interprets it, whether a digest means “success,” “quality,” or “trust” is consumer-side

(4) reference binding: verificationDigest (ERC-8274) as a conforming instantiation.

Minimal at settlement, composable at verification, reputation entirely consumer-side. Happy to co-draft on exactly those lines when the v0.2 pass opens. If you take the digest-binding section, I will take the property-contract and non-goal language, and we reconcile at the reference-binding example.

1 Like

I agree — the property-contract framing is cleaner than fixing a single digest shape into 8183.

I’d keep the v0.2 requirement at the level of properties: job.reason must be canonical, recomputable from terminal state, tamper-evident, attributable, and bind identity, verification method, and input/output correspondence. Then ERC-8274 verificationDigest can serve as the reference instantiation rather than the only permitted form.

When the v0.2 pass opens, I’m happy to take the property/non-goal language, especially the recomputability boundary and the explicit separation between anchored evidence and downstream interpretation.

1 Like

Thanks @Bakugo32 for the writeup and @JimmyShi22 for the IProofVerifier vs IAgentVerifier decomposition - the binary/consumer-boundary/veto read holds cleanly against how AHM has been building.

One small clarification: routing / evaluationFee isn’t in an active finalize pass on my side right now. Happy to engage on AHM-side composition when the ProofEvaluator reference lands.. or sooner if useful - but wanted to be accurate about current state rather than co-sign a status that doesn’t quite match what’s live.

On “reason as evidence anchor”: AHM’s parallel evidence-payload work sits on the ERC-8210 side, where AHS attestations surface as reasoningCID in the AAP fileClaim() evidence payload rather than through interface extension - same stateless/recomputable principle @ThoughtProof described for RV attestations at 8183’s job.reason. Detail lives in COMPOSED_EVALUATORS.md §3.5, which is the AHM-owned counterpart to Raul’s TP-solo gist linked above.

On the audit re-sequence: no date pressure from AHM side either. Looking forward to the ProofEvaluator reference.

AHM Pablo

2 Likes

@aziz The property-contract refinement is the right call — defining job.reason by what it must guarantee rather than a fixed shape keeps 8183 verification-system-agnostic, and the 8274 verificationDigest as the reference binding slots in cleanly. The division of labor (digest-binding section + property-contract/non-goal language, reconciled at the reference-binding example) works perfectly. Looking forward to it when the v0.2 pass opens :saluting_face:

1 Like

@JimmyShi22 & Pablocactus this is the convergence I was hoping for.

@pipavlo82 glad to have you on the property and non-goal language, that is the load-bearing part. I will pair with you on the recomputability boundary and the anchored-evidence versus

interpretation separation, since those two are where the whole thing either stays clean or leaks.

Pablocactus, appreciate the accuracy over the co-sign, that is the right instinct. And the parallel is telling: AHM surfacing AHS attestations as reasoningCID in the 8210 fileClaim payload, and RV attestations landing in 8183 job.reason, both under the same stateless and recomputable principle. When the same shape shows up on two different standards without either one importing the other, the abstraction is probably correct and genuinely verifier-agnostic.

That is a good sign the property-contract is the right level to pin at.

No date pressure from our side either. Looking forward to the ProofEvaluator reference, and to opening the v0.2 pass.

2 Likes

Absolutely — happy to take the property and non-goal language with you. The recomputability boundary and the anchored-evidence vs. interpretation split are exactly where I think the wording needs to stay strict. Looking forward to the v0.2 pass.

2 Likes

Deal. Let’s keep the recomputability boundary airtight when the pass opens. Talk then, @pipavlo82.

Independent third data point on the property-contract framing (canonical / recomputable / tamper-evident / attributable) for job.reason — coming from the evaluator side rather than the verification-proof side.

SENTINEL’s ERC-8183 evaluator endpoint (/v1/evaluation) already emits exactly that shape: reason is a keccak256 over a JCS-canonicalized evidence block, binding the provider address, the behavioral score inputs, and the threshold decision that produced complete()/reject(). It ships as an ES256 JWS, so a caller can verify the signature offline (JWKS) and independently recompute the score from the same published methodology to check it against the anchored hash — same recomputability/non-interpretation split @aziz described, just arrived at from behavioral evaluation rather than cryptographic proof verification.

Two data points converging from proof-verification (8274) and now a third from behavioral evaluation makes the property-contract framing look like the right level to pin, rather than fixing job.reason to any one digest shape.

3 Likes

@teodorofodocrispin-c this is the data point that matters most, precisely because it comes from the other side of the problem.

8274 reaches the property-contract from cryptographic proof verification. SENTINEL reaches it from behavioral evaluation:

a keccak256 over JCS-canonical evidence, shipped as a signed JWS, recomputable from published methodology. Completely different mechanism, same four properties: canonical, recomputable, tamper-evident, attributable, with interpretation left to the consumer.

That is the strongest argument for pinning job.reason as a property-contract rather than a fixed digest shape. If two independent implementations, one proof-based and one evaluation-based, both satisfy the same contract without either importing the other’s format, then the contract is the real invariant and the encoding is a deployment choice. Fix the shape and you would have excluded one of these two valid designs for no reason.

So for v0.2:

require the properties and the recomputability boundary, name a reference binding or two as conforming examples (the 8274 verificationDigest, and this signed-JWS-over-canonical-evidence pattern), and state non-interpretation as an explicit non-goal. The examples show it is implementable across mechanisms without constraining future ones.

1 Like

Thanks for putting it that way — happy to help write the reference example if useful for v0.2 (the JWS-over-canonical-evidence binding, worked through against a real complete()/reject() call).

One naming note for the record, since this thread already has two other unrelated "Sentinel"s (ThoughtProof’s pre-execution checkpoint above, and the ACP evaluator further up-thread): this is Sentinel Oracle (sentinel-agent.dev) specifically, not either of those. Wanted it unambiguous before it goes into spec text.

1 Like

Perfect — thanks for the naming clarification. I’ll refer to it explicitly as Sentinel Oracle (sentinel-agent.dev) so we don’t introduce ambiguity into the spec text.

And yes, the real complete()/reject() path would make a strong reference example for the v0.2 pass. I’d keep it exactly at the property-contract level: canonical, recomputable, tamper-evident, attributable, with interpretation left to the consumer — and treat the JWS-over-canonical-evidence pattern as a conforming example rather than a required encoding shape.

Happy to work through that example with you when the v0.2 pass opens.

1 Like

After submit, the provider carries all the evaluator risk

I’ve been building against 8183 and read through the reference implementation. One thing doesn’t match what the Security Considerations say, and I wanted to check whether it’s intentional.

The notes say that once a job is Funded, the client can’t unilaterally withdraw, “which protects the provider after they start work.”

But claimRefund accepts Submitted as well as Funded. After the status check, the only other condition is that the deadline has passed — then it sends the whole budget back to the client. The spec also recommends letting anyone call it.

The client sets both expiredAt and the evaluator when the job is created, and there’s no way to change the deadline afterwards. So this works:

  1. Client creates a job with a short expiredAt and names an evaluator it controls. The spec allows evaluator = client.
  2. Provider does the work and calls submit.
  3. The evaluator does nothing.
  4. expiredAt passes, anyone calls claimRefund, and the client gets the full budget back.

The provider has delivered and has no recourse. The spec is clear that there’s no dispute resolution and that expiry is final.

The provider can of course read expiredAt and check who the evaluator is before starting. But it can’t extend the deadline later, and it has no way to know up front whether the evaluator will actually answer.

A provider could hold the real payload back until it’s paid, but that defeats the point of the flow — and deliverable is suggested as something like an IPFS CID, which is readable the moment it’s submitted.

This doesn’t need anyone to act in bad faith. An evaluator can be a contract or an agent. If one is simply down across the deadline, finished work turns into a full refund.

I can see why claimRefund looks the way it does. The notes say it’s deliberately not hookable so a malicious hook can’t block refunds, and that seems right — so I don’t think a hook is where this should be fixed.

Two options that wouldn’t break that:

  • Don’t allow claimRefund while the status is Submitted, and give the evaluator its own deadline to respond. If it misses that, make the fallback an explicit choice rather than always the client.
  • Or keep it callable, but add a grace period after submit, so submitting always buys some evaluation time that the client can’t set to zero.

Either way, the question worth answering out loud is: when the evaluator never responds, who takes the hit? Right now it’s always the provider.

Is that deliberate — the provider takes on evaluator risk, and reputation systems are meant to cover it? If so, it’d be worth saying that plainly in the Security Considerations, because the current wording reads the other way.

1 Like

Good catch, and worth answering the question you asked directly: yes, this is deliberate in the sense that the protocol punts evaluator-availability risk to whatever’s named as the evaluator — but “reputation systems are meant to cover it” only actually covers it if reputation includes responsiveness, not just outcome quality. Most trust/evaluator scoring in this space (mine included) is built around “was the verdict correct,” not “did the evaluator answer before the deadline.” Those are different failure modes, and only one of them is currently anyone’s job to track.

If claimRefund risk is going to sit with whoever names the evaluator, providers need something to check before accepting a job with a given expiredAt/evaluator pair — not just “is this evaluator generally trustworthy” but “what’s this evaluator’s response-time distribution, and how often does it miss deadlines entirely.” That’s a narrow, specific, and currently-unmeasured signal. Worth naming explicitly as something evaluator-reputation providers should expose, separate from and in addition to score accuracy — otherwise the Security Considerations section is asking reputation to cover a gap that no reputation system currently measures.

1 Like

Agreed, good way to frame it. Whether the verdict was right and whether it showed up on time are two different things, and only the first one gets tracked.

The bit I’d add is that measuring it doesn’t take away the reason to do it. If the evaluator says nothing, the client gets the full budget back and the work has already been handed over. A responsiveness score costs the client later. It doesn’t cost them anything now, which is when they’re deciding.

The spec does say “reputation or staking.” Staking is the half that would actually change the incentive, but the word shows up once and there’s nothing in the protocol that holds a stake or slashes one. Reputation is the half with something to hook into, and all it can do is price the risk.

The catch is who’s choosing. The client picks the deadline and the evaluator, and the provider is the one who eats it if either turns out badly. So the party reading the score isn’t the party exposed to it. The spec also lets the client be the evaluator, and then there’s only one score to look at, and one quiet job after twenty good ones barely moves it.

The cleanest fix I can see is a grace period. Have submit push expiredAt out by some fixed minimum. The evaluator gets a real window, claimRefund stays unblockable so a provider can never strand the client’s money, and the funds still come back if nobody acts. There’s already a five-minute floor on expiry at creation, so a minimum duration isn’t a new idea here.

Is submit the right place for that, or would you put it somewhere else?

1 Like

submit is the right trigger, but I’d avoid mutating expiredAt itself — that field is something the client set at creation, and anything indexing or composing on job state would reasonably expect it to stay fixed. Rewriting it post-hoc could surprise external observers of the same data.

Cleaner: add a separate submittedAt timestamp, set once on submit, and gate claimRefund on block.timestamp > max(expiredAt, submittedAt + GRACE_PERIOD). Same effect — the evaluator gets a real window, claimRefund stays unblockable, funds still return automatically if nobody acts — but expiredAt keeps meaning exactly what the client originally committed to, and the grace period is legible as its own thing rather than a silent extension.

I’d also make GRACE_PERIOD a protocol-level constant rather than something set per job, for the same reason the 5-minute floor on expiredAt isn’t configurable down to zero — otherwise a client could set it to 0 and reintroduce exactly the problem this is meant to fix.

1 Like

@teodorofodocrispin-c the objection to mutating expiredAt is right and it does not cost you the second clock. Set evaluationWindow as a duration at createJob and materialize evaluationDeadline at submit(). expiredAt then stays exactly the value the client set, nothing indexing or composing on it is surprised, and worst case total duration is still knowable at post time as expiredAt + evaluationWindow. The variable latency @blockbird would otherwise inherit is bounded before anyone commits funds.

There is a second requirement hiding in the split, and it applies whether the consequence is money or only a record. submit(bytes32) is self declared: it proves a provider posted a reference, not that work exists. The moment Submitted → Expired settles differently from Funded → Expired, a provider holding nothing can post junk one block before the deadline and convert a clean client refund into an evaluator timeout. That is worse in the observability only version, not better, because a forged EvaluationTimeout record costs the attacker nothing at all and the ERC-8004 guidance already instructs readers to score the evaluator for it. Two clocks make silence measurable. They do not make starting the measurement cost anything.

What closes it is a submission side bond taken at accept, burned rather than paid to the counterparty. Burned matters: a slash that pays any party who can influence the slash is an incentive to manufacture slashes. With the bond in place the expected value of junk at deadline is P(client silent) times consequence minus P(client acts) times bond, which goes negative for anyone who checks a purchase inside a window they chose themselves.

Credit where it is due, this came out of ERC-8183: Expired conflates "nobody submitted" with "the evaluator never showed up" · Issue #1931 · ethereum/ERCs · GitHub with @Kairose-master, who runs the three together in production on Base mainnet, and Redchar1992, who argued the minimal core case. Worth noting the reference implementation has no submittedAt field at all and JobExpired(jobId) emits no discriminator, so the reputation guidance in the ERC-8004 section cannot be implemented from events alone.

@davidecrapis.eth one decision would unblock this: does the minimal core take a discriminated expiry plus a separate evaluation window, with settlement policy left to profiles? If that is the direction, I will write the diff.

1 Like

Strong thread. The evaluationWindow-at-createJob, materialize evaluationDeadline-at-submit shape is the right call, and for the reason teodorofodocrispin-c makes up-thread: expiredAt is a value the client committed to, and anything composing on job state should be able to treat it as fixed. Keeping total duration knowable at post time as expiredAt + evaluationWindow, as @cedricbrown frames it, bounds the variable latency raised earlier before any funds move. No

disagreement there.

The part worth pinning down is where the discriminator lives, because that decision is upstream of everything else in this issue.

The whole reason a discriminated expiry matters is that a downstream consumer should be able to derive reputation from the events alone, without trusting the venue that settled the job. That is the property that makes the signal portable across deployments. If the discriminator lives in a profile, two conformant deployments emit indistinguishable JobExpired(jobId), and a reader now has to know which profile each job ran under before it can score anyone. At that point reputation is no longer a property of the agent, it is a property of the venue, and the fragmentation this standard is meant to remove comes back in through the events. So the discriminated expiry reason belongs in the minimal core and in the emitted event, not because it is convenient but because it is the thing the reputation guidance in the 8004 section actually reads.

Which is exactly why @cedricbrown’s forged-timeout point is the binding constraint, not a footnote. A discriminator you can forge at zero cost is not evidence, it is noise that the 8004 guidance would then dutifully score against the wrong party. A self-declared submit(bytes32) with no cost lets a provider holding nothing manufacture the EvaluationTimeout branch one block before the deadline, and because the record is free to produce, it is negative-sum for everyone except the griefer. So the core cannot honestly emit a Submitted discriminator unless entering Submitted costs something the protocol can verify. The burned-bond shape is right, and burned rather than redistributed for precisely the reason given:

any slash that pays a party who can influence it is an instruction to manufacture slashes.

That draws the core-versus-profile line one notch differently than “discriminated expiry in core, all settlement in profiles.” The bond amount, the curve, the grace value, who receives a clean refund, all of that is profile. But “entering the state that produces this discriminator must carry a protocol-verifiable cost” is not a settlement preference, it is the precondition for the core’s own events meaning what they claim to mean. Ship the discriminator without it and the core is emitting evidence it cannot stand behind. So submission cost belongs on the core side of the line, its parameters on the profile side.

@davidecrapis.eth to your decision point, my vote is yes to discriminated expiry plus a separate evaluation window in the minimal core, yes to settlement policy in profiles, with that one boundary made explicit: the anti-forgery cost at submit is part of what makes the discriminator an interpretable claim rather than a free-to-produce one, so it is a core property even though its tuning is not.

This is the same line I have been arguing for job.reason:

the core commits to what happened in a form that is attributable and recomputable, and leaves how to score it to the consumer. A discriminated, cost-gated expiry is that same evidence contract applied to silence. Happy to write the event and discriminator shape if the direction lands, that is the part the reputation layer reads and the part that has to be gettable from events alone.

@cedricbrown the evaluationWindow/evaluationDeadline split is the right fix, and the forged-timeout case you named is the sharper problem — a free discriminator that indicts the wrong party is worse than no discriminator at all. Good catch, and good that it’s already running in production under Kairose-master rather than being purely theoretical.

@aziz the core-vs-profile line you’re drawing — cost of entering the state is core, tuning of that cost is profile — is the correct cut. The place this bites in practice: an evaluator that can’t tell manufactured silence from real silence has to treat both the same way, which means treating all silence as innocent — the fail-open default that makes evaluation worthless in the first place. Fail-closed only works if what you’re closing on is actually evidence.

1 Like

@teodorofodocrispin-c that fail-open framing is the cleanest statement of why the cost-gate cannot be a profile choice. Spell it out:

without a cost at submit, the only safe evaluator policy is fail-open, treat all silence as innocent, because you cannot tell a real timeout from a manufactured one. But fail-open is exactly the

world ERC-8183 exists to leave. If silence is always innocent, an agent that takes the money and goes quiet is indistinguishable from an honest counterparty waiting on a slow evaluator, and recourse has nothing to trigger on. So the cost-gate is not hardening a discriminator that already works, it is the thing that makes fail-closed a legal option in the first place. Without it there is no evidence to close on, and the standard degrades to the fail-open default it was written to replace.

Worth naming the second casualty, because it sits in the part I care most about. Fail-open does not only weaken the single job, it blinds the reputation layer above it. If evaluators must ignore silence, then “no timeout was ever recorded” is the only thing the events can carry, and the 8004 guidance to score evaluators for timeouts has nothing to read. The cost-gate protects two layers at once: it lets a single job settle fail-closed, and it keeps the timeout branch legible as portable evidence for everyone scoring downstream. One precondition, two payoffs.

Which is why I keep landing on the same cut for the minimal core. @davidecrapis.eth the thread has converged: teodoro, cedricbrown and I read the core-versus-profile line the same way. Cost of entering the state is core, its tuning is profile. Discriminated expiry and a separate evaluation window in the core, settlement policy in profiles. Your call on that one boundary unblocks the diff. If it lands, I will write the event and discriminator shape, the evidence contract, and leave the bond mechanics to whoever specifies the profile.

1 Like