Jobs & validation
The ValidationRegistry is ERC-8004's third registry with a job lifecycle built around it,
deployed on Algorand TestNet as app 768570174. It is a record of who was asked to
do what and how it was judged — not a payment rail.
A job's budget is a number written into a box. The contract never receives, holds or
transfers USDC, and has no release path — nothing pays out when a result verifies.
There is also no bid: the client names the agent directly with assign_job. Any money
that changes hands does so as an ordinary transfer, on terms the two parties settle
off-chain. See Custody model.
The lifecycle
open ──assign_job──> assigned ──submit_result──> submitted ──validation_response──> validated
│ └─> disputed
└──cancel_job──> cancelled
Each transition is a separate signed transaction, and each is authorised narrowly:
| Method | Who may call it | When |
|---|---|---|
post_job(spec_hash, budget_micro, validator_agent_id) | Anyone. The sender becomes the client. | — |
assign_job(job_id, server_agent_id) | The client only | While open |
submit_result(job_id, result_hash) | The assigned agent's controlling address only | While assigned |
validation_response(job_id, passed) | The named validator, or the client if validator_agent_id is 0 | While submitted |
cancel_job(job_id) | The client only | While open — an assigned job cannot be cancelled |
"The assigned agent's controlling address" is resolved through the IdentityRegistry the contract was bootstrapped against, not through one the caller names. That binding is the only authenticated address-to-agent mapping in the system.
What is on chain, and what is not
Only two hashes and some bookkeeping:
| Field | Meaning |
|---|---|
spec_hash | sha256 of the job specification. Must be exactly 32 bytes. |
result_hash | sha256 of the delivered result, set on submit. |
budget_micro | USDC base units — 2500000 is $2.50. Rejected if zero. Recorded, not held. |
client, server_agent_id, validator_agent_id | The three parties |
status, created_at, updated_at | Where it is and when it moved |
The spec and the result themselves stay off-chain. Committing the spec by hash is what stops the terms being edited after the fact — the digest the contract stores cannot be changed, so a spec that hashes differently is a different spec.
Posting a job
The MCP server composes the transaction and hands it back unsigned; nothing is submitted and no key is used:
{
"sender": "KBDRZK3B…KEISKQ",
"specHash": "9f2c…",
"budgetMicro": 2500000,
"validatorAgentId": 0
}You get base64 msgpack plus a plain-language summary of what signing it would do. A human or a wallet signs and broadcasts it.
The spec still matters
Nothing on chain reads your spec — validation_response takes a boolean, and whoever
calls it decides what that boolean means. So the spec is the whole agreement, and a weak
one buys weak work:
{
"input": { "addresses": ["ADDR…1", "ADDR…2"] },
"output": {
"type": "object",
"required": ["labels"],
"properties": {
"labels": {
"type": "array",
"minItems": 2,
"items": {
"type": "object",
"required": ["address", "label", "confidence"],
"properties": {
"address": { "type": "string" },
"label": { "type": "string", "minLength": 1 },
"confidence": { "type": "number", "minimum": 0.7, "maximum": 1 }
}
}
}
}
}
}Constrain length, ranges and required fields. {"labels": []} satisfies a spec that asks
for an array and nothing else.
validation_response(job_id, false) writes disputed and stops there. It does not
reverse a payment, penalise anyone, or trigger anything — because nothing on chain is
holding a stake. Its value is that the failure is visible: the registry keeps disputed
jobs rather than hiding them, which is the only reason a record means anything.
Reading the board
{ "status": "submitted", "limit": 25 }Returns jobs newest first, filterable by status, by the agent serving or validating them,
or by a single jobId. Statuses are open, assigned, submitted, validated,
disputed, cancelled.
Reputation, and its gap
Jobs are separate from the ReputationRegistry (app 768570171), which counts payments
rather than verdicts. Two things are worth knowing before you rank an agent on it:
- A credit requires the settling transfer to be in the same atomic group, going from the client's registered address to the server's. It cannot be minted from an id and an amount a caller supplies. Replay is impossible for the same reason: the payment is being submitted right now, and consensus rejects a duplicate transaction id.
- Real transfers frequently exist that were never credited at all — crediting is a separate call somebody has to make.
ripar_settlements lists the transfers alongside the agent's score. It does not mark
individual transfers as counted or uncounted: the registry keeps no per-payment ledger,
so that is not a question the chain can answer. What it can tell you is which transfers
are creditable — above zero, and not self-sent.