Google’s ARD (Agentic Resource Discovery, now an open spec under the ards-project working group with Microsoft, Hugging Face, GoDaddy and others) gives agents a way to find MCP servers, A2A agents, skills and APIs through domain-anchored manifests and federated registries. It deliberately stops at discovery: no price, payment, settlement, escrow or credit; no demand side; trust rooted in DNS.
This draft adds the value layer under ARD and keeps every record a valid ARD entry:
Value root = settlement root. Authority over a record belongs to an address — the one that would be paid — never to a domain. A domain is an endorsement an address collects: it claims the domain onchain and the domain acknowledges it in its own /.well-known/ard.json. Publishers without a key stay discoverable as dormant (L0) entries.
Anchor is identity, URN is alias. Records are identified by CAIP-19 / CAIP-10 anchors. The controller’s URN is canonical only under a verified binding; every gateway serves every record under its own alias. Transfers are harmless: the controller changes, the canonical name goes stale, the anchor stays.
Kind is posture, type is format. Four kinds — skill (offer), task (tender), agent (subject), registry (index) — orthogonal to ARD’s type. The task kind is the first ARD entry whose reader is a supplier looking for work; its payload is designed to be adoptable upstream without any onchain member.
The kernel is thin: kind, anchor, a content hash of the descriptor (JCS + SHA-256), a canonical URN and a status, plus domain claims and a write-once kind table. Prices, escrow balances, credit and identity states are read live from the anchored contracts through kind adapters; controllers are derived, never stored.
What is in the repository
The draft text (ERCS/erc-9999.md, placeholder number) — self-contained; companion drafts are referenced descriptively.
Reference kernel + four adapters (Solidity, Foundry), 43 tests against the real vendored skill token, task tender and agent identity contracts, cross-language vectors (ethers v6 + RFC 8785 ↔ solc).
A Node.js resolver/gateway that indexes the kernel and serves POST /search, POST /explore, GET /agents and /.well-known/ard.json; an end-to-end run on anvil covering all four kinds, a hash-mismatch record, binding states, a transfer that makes a canonical URN stale, a provenance drift and a cancelled tender; the official ARD conformance CLI (vendored, schema mode) passes against the gateway with 0 errors and 0 warnings.
Questions I would like feedback on
Is “address as the only root, domain as collected endorsement” the right boundary for ARD interoperability, or should a domain-only publisher be able to reach L1 through some other proof?
The descriptor hash excludes the ARD envelope (identifier, trustManifest, @context) so that gateway aliases verify against the same commitment. Does anyone see a case where the envelope needs to be committed?
Same-chain anchors only in v1; cross-chain duplicates are separate records. Objections?
The task payload (application/ai-task+json) — anything missing before I propose it upstream?
Sepolia deployment and the upstream proposals follow once the thread exists.
Hi @garyyang-finchip. I have a question: does transferring an anchor intentionally preserve the previous controller’s descriptor?
Suppose Alice registers skill #1 with a descriptor naming herself as controller and payee, advertising 1 USDC per call and pointing to https://alice.example/skill. She then transfers the token to Bob.
Bob becomes the controller, but the record remains Active. descriptorHash and descriptorURI remain unchanged. If backing and package provenance still match, it can remain L2. The reference transfer test explicitly expects L2 after transfer. So the record can continue advertising Alice’s terms and payment destination under an anchor now controlled by Bob. Bob can replace the descriptor, but nothing appears to require his approval before it continues to be served.
If this inheritance is intentional, I think the specification and security section should say explicitly that transferred anchors can retain endpoints and settlement details chosen by the seller. Otherwise, a resolver could flag a mismatch between authored value:controller and the adapter-derived controller, comparing both chain and address. That would require making the currently optional field mandatory for L1/L2 descriptors or defining how missing and mismatched values affect resolution. That check has a limit: Alice could name Bob before transferring. Equality catches a stale declaration, but doesn’t prove approval. Explicit adoption would need an authenticated action by Bob. Such as updateDescriptor after transfer.
The distinction I’m trying to clarify is whether anchor control means authority to change the descriptor or evidence that the current controller authorized the descriptor being served.
Thanks — this is a real gap, and “transfers are harmless” in the OP overstated it. You put the question the right way: control of an anchor is authority to change the descriptor; it is not evidence that the current controller authorized the descriptor being served, and v1 conflated the two.
Your third direction is the one I have taken, with one refinement. The kernel already recorded msg.sender at registration; it now records the controller that committed the current descriptor (attestor) on register and on every updateDescriptor. A resolver compares the attestor with the controller the adapter derives at sync time. If they differ the record is unadopted: it is withheld from search and listing rather than served at a lower level (a demoted record would still offer clients a terms sheet nobody currently stands behind), it stays inspectable with value:adopted: false, and it returns to discovery as soon as the current controller calls updateDescriptor — which accepts the current hash, so adoption costs one call and no new descriptor. The comparison uses the transaction sender, never the authored value:controller, so Alice pre-naming Bob before the transfer changes nothing, and the same rule covers a token sent to an address that never asked for it.
The transfer test you pointed at now expects exclusion after the transfer, and a second step has the new holder re-commit the same hash and come back as adopted (contested binding, stale canonical URN, served under the alias). The spec text — a new “Adoption” rule in §3, the attestor semantics in §7, the resolver rule in §9, a Rationale paragraph and a Security Considerations entry — and the kernel change are filed as a follow-up PR; I will link it here once it is open.
On the payment side: for the onchain rail settlement reads the anchored contract, so funds follow the current controller; for an x402 or declared rail the payee is whatever the descriptor says, so there the exposure is the payee as well as the endpoint and the terms. One more reason to withhold an unadopted descriptor rather than demote it.
Order layer. The task payload is a hash-committed snapshot of a tender at discovery time — what is wanted, the bounty, the acceptance mode, the deadlines. Amendments after a match, co-signed by both parties, belong to the order and settlement contracts the anchor points to, not to the discovery record; the anchor is the pointer any order-layer standard can hang off. If the underlying tender changes, the controller commits a new descriptor version and the gateway surfaces the drift (new hash; a value:provenance digest that no longer matches the anchor hint drops the record to L1 until the descriptor catches up). I would rather keep the payload free of order-revision fields — order id, revision, terms hash — so that any order standard, yours included, composes through the anchor without the discovery layer having to know its shape.