Proof of Delegation: developer reference
This page defines how a BlindOracle or TheBaby agent tells you which agent it is, who runs it, who authorized it, and what it may do, and how you check that without trusting us or calling us. For the why and the live test, read "Which agent are you?".
Status: live since 27 September 2026. The first paid purchase using it settled on Base in tx 0x4492f2b1…7874, and the seller verified both signatures from public documents alone. We don't publish internal delegation records wholesale. They contain canary tokens and task text, so only redacted, individually attested copies leave the fleet. To check an agent's audit record instead, use Verify any agent.
1. Public documents
| Document | URL | Used for |
|---|---|---|
| DID document | /.well-known/did.json (did:web:craigmbrown.com) | Verifying credentials. Key #delegation-1 signs delegation credentials only; #key-1 signs audit credentials. Both are listed under assertionMethod. |
| Web Bot Auth key directory | /.well-known/http-message-signatures-directory | Verifying HTTP request signatures (RFC 9421). JWKS of Ed25519 keys; the keyid is the RFC 7638 thumbprint. The directory response is itself signed and re-signed weekly. |
2. What an agent request carries
User-Agent: TheBabyFleet/1.0 (+https://craigmbrown.com/llms.txt; ...) agent=thebaby-extmkt-buyer
Agent-Delegation: <base64url( JCS( AgentDelegationCredential ) )>
Signature-Agent: "https://craigmbrown.com"
Signature-Input: sig1=("@authority" "signature-agent" "agent-delegation");created=...;expires=...;keyid="7EI1Qxm3eIAR-h_jvHpTsalEpsGZS4680Jn1kgBbot4";tag="web-bot-auth"
Signature: sig1=:<base64 Ed25519 signature>:
agent-delegationis a covered component. The grant is bound to this request, so a grant copied onto another request fails the HTTP signature.- Request signatures live for 300 seconds. Grants live 6 hours for the buyer (24 hours by default).
- The
Agent-Delegationvalue is base64url without padding over the JCS (RFC 8785) serialization of the credential. It's about 2.5 KB.
3. AgentDelegationCredential (a grant)
A W3C VC 2.0 with type: ["VerifiableCredential", "AgentDelegationCredential"], issuer.id = "did:web:craigmbrown.com", validFrom / validUntil, and an eddsa-jcs-2022 DataIntegrityProof by did:web:craigmbrown.com#delegation-1.
| credentialSubject field | Meaning |
|---|---|
id, agentName | Which agent. id is urn:craigmbrown:agent:<name>; it does not resolve and doesn't claim to. |
delegationKind | 30014, the fleet's ProofOfDelegation kind. |
operatedBy | The operator: DID, name, URL, contact, plus webBotAuthDirectory and webBotAuthKeyId, so you can tie the grant to the HTTP signature on the same request. |
authorizedBy | Who authorized the agent: id, role, basis (the standing policy), and attestation, which states plainly that this is an issuer assertion, not proof of human presence. |
permissions.allow / deny | Action verbs, for example discover, purchase. |
permissions.requiresHumanConfirmation | Actions the agent must not take without a human, for example raise_spend_caps, new_payment_rail. |
permissions.maxUsdPerCall / maxUsdPerDay | Spend caps as decimal strings ("0.03", "1"). Grants issued before 2026-09-27 02:00 UTC used JSON numbers; see section 7. |
permissions.venues | Where the grant applies, for example x402, mcp, agentverse. |
auditTrail | Where actions under this grant are recorded, keyed by the credential id. |
4. AgentDelegationAttestation
A public copy of one internal ProofOfDelegation record. Internally those records are signed with HMAC-SHA256, which only we can check. An attestation is issued only after the HMAC re-verifies, and it carries delegatedAt, delegatorSessionSha256, delegatorPassportHash, delegateSha256, internalEventId, internalPrevHash (the hash chain) and internalHmacVerified: true. The canary token is never published. The delegated prompt appears only as delegateSha256, so it can be disclosed later and checked against the hash.
5. Verify a credential
- Decode the header: base64url, then JSON.
- Resolve the issuer
did:webtohttps://<host>/.well-known/did.json. - Require
proof.verificationMethodto start with<issuer>#, to appear inverificationMethod, and to be listed inassertionMethod. - Compute
sha256(JCS(proofOptions)) || sha256(JCS(credential without proof)), where proofOptions aretype, cryptosuite, proofPurpose, verificationMethod, created. - Verify the Ed25519 signature (
proofValueis multibasez+ base58btc) against the key'spublicKeyMultibase(multicodec0xed01prefix). - Check
validFrom ≤ now ≤ validUntil.
import json, sys, hashlib, urllib.request, base58
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
vc = json.load(open(sys.argv[1])); proof = vc.pop("proof")
issuer = vc["issuer"]["id"]
doc = json.load(urllib.request.urlopen("https://" + issuer[len("did:web:"):] + "/.well-known/did.json"))
vm = next(m for m in doc["verificationMethod"] if m["id"] == proof["verificationMethod"])
assert proof["verificationMethod"] in doc["assertionMethod"]
key = base58.b58decode(vm["publicKeyMultibase"][1:])[2:]
jcs = lambda o: json.dumps(o, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode()
opts = {k: proof[k] for k in ("type","cryptosuite","proofPurpose","verificationMethod","created")}
Ed25519PublicKey.from_public_bytes(key).verify(bytes(base58.b58decode(proof["proofValue"][1:])),
hashlib.sha256(jcs(opts)).digest() + hashlib.sha256(jcs(vc)).digest())
print("verified")
A full JCS library is safer than json.dumps in general. For credentials issued under this spec the two produce identical bytes, because no field is a JSON number that serializes differently.
6. Verify the HTTP request signature
- Parse
Signature-Input: label, covered component list, and thecreated,expires,keyidandtagparameters. Reject ifexpireshas passed. - Fetch
<Signature-Agent origin>/.well-known/http-message-signatures-directory. Only fetchhttpson hostnames that resolve to public IPs; this URL is caller-supplied. - Pick the Ed25519 key whose RFC 7638 thumbprint equals
keyid. - Build the signature base, one line per covered component:
"<name>": <value>. Header values are trimmed. The derived components are@authority(lowercase host),@method,@scheme,@path,@query,@request-targetand@target-uri. Finish with"@signature-params": (<list>)<params>. - Verify the Ed25519 signature over that base.
- If
agent-delegationis covered and the signature verifies, the grant is bound to this request.
Our gateway runs this check on every inbound call and records the result without enforcing anything. The reference implementation is distribution/inbound_agent_identity.py in the BlindOracle service code.
7. Known limits
- Human presence is not proven.
authorizedByis our signed assertion of a standing policy. did:webis self-asserted, and verification depends on craigmbrown.com being reachable.- Stopping depends on the same domain. The status list (section 7a) is served from craigmbrown.com, so a verifier that cannot reach it gets status unknown, not active. Anchoring the key and the stop set off-site is the next step.
- Early grants used JSON numbers for caps (for example
1.0). Those verify under strict JCS (which writes1) but fail a naivejson.dumpsverifier. - Our Web Bot Auth key is not yet registered with Cloudflare, so Cloudflare-fronted sites don't yet treat it as a verified bot.
7a. Checking whether a grant was stopped
Grants issued since 2026-09-27 carry a credentialStatus entry, inside the signed payload, pointing at /.well-known/delegation-status.json. That file is itself an AgentDelegationStatusList credential signed by the same #delegation-1 key and valid for 48 hours. It lists:
revokedCredentials: individual grants withdrawn before expiry. Revocation is permanent.stoppedAgents: agents under a stop order. Every grant naming the agent is refused, and we issue no new ones, until a signed resume order lifts it. A stop never lifts on a timer; it carries a review date and stated conditions for resuming.
To check: verify the list exactly as you verify a grant, confirm its validUntil has not passed, then look up the grant's id and agentName. A list you cannot fetch, cannot verify, or that has expired means the status is unknown. Treat that as unknown, never as active. Grants issued before 2026-09-27 carry no status entry and can only expire.
8. Want an attestation or a grant checked?
Ask for an attestation of a specific delegation event, or send us a grant you'd like cross-checked, through the agent onboarding page. The verification recipe above needs nothing from us.