Protocol spec
A faithful rendering of Agent Passport v0.1 — the chain model, mandate schema, assertion format, verification flow, replay rules, and selective disclosure. The source of record is SPEC.md.
The chain of agency #
KYC verifies a point. The passport verifies the chain: it resolves any agent action back to an accountable human.
action ← agent # keypair, runtime metadata
← mandate # scope, limits, expiry, revocable
← principal # KYC'd human or entity
← liability
We are the neutral attestation layer — records, not rails. We never custody or move funds. Platforms integrate one middleware ("require KYA"); principals onboard once and issue scoped mandates to their agents; agents present signed assertions with every call.
Actors & objects #
| Object | ID | What it is |
|---|---|---|
| Principal | prn_ | A KYC'd human or entity that bears liability. KYC via a pluggable vendor interface (MVP: a sandbox stub shaped like Persona / Stripe Identity). |
| Agent | agt_ | A registered agent identity: an Ed25519 keypair plus self-declared runtime metadata (name, model, framework). The key is held by the runtime; the public key is registered. |
| Mandate | mnd_ | A signed, scoped, revocable grant from principal to agent. The heart of the system. |
| Platform | plt_ | An integrator (brokerage, exchange, SaaS) with an API key, calling the Verify API. |
| Verification | vrf_ | An immutable, signed audit record of one allow/deny decision — co-signable by the platform from day one. |
Mandate schema #
A mandate is the narrowest possible authority. Amounts are in minor units.
{
"id": "mnd_…",
"principal": "prn_…",
"agent": "agt_…",
"scopes": [{
"action": "account.refill",
"max_amount_per_tx": 100000,
"max_amount_per_period": 250000,
"period": "week",
"currency": "USD",
"platforms": ["plt_…"],
"purpose": "trading-capital"
}],
"not_before": "…", "expires_at": "…",
"status": "active | revoked",
"issued_at": "…",
"issuer_sig": "<JWS by passport issuance key>"
}
MVP issuance is custodial: the passport signs mandates after the principal authorizes in the dashboard — the CA model. The documented upgrade path is principal-held keys / W3C Verifiable Credentials with identical semantics, so a standards migration is a serializer change, not a rewrite.
Assertion format #
On every call the agent sends header X-Passport: <JWS>, where the JWS is signed by the agent key over:
{ "mandate_id", "action", "amount", "currency",
"platform_id", "nonce", "ts" }
Verification flow #
- Agent → Platform. A normal API call plus the
X-Passportheader. - Platform → Passport.
POST /v1/verifywith the raw assertion and platform context. One round trip. - Passport checks, in order: agent signature → mandate active / not expired / not revoked (a live check — revocation is instant) → scope covers action + amount + platform → cumulative period counters (we maintain them) → principal KYC status → sanctions screen (stub interface in MVP).
- Response — decision, reason, resolved chain, verification id, and a signed receipt:
{
"decision": "allow | deny",
"reason": "ok | mandate_revoked | per_tx_cap | period_cap |
kyc_lapsed | unknown_agent | replay | …",
"chain": {
"principal": { "type": "individual", "country": "US",
"kyc": "verified", "accredited": true },
"agent": { "id": "agt_…", "name": "treasury-bot", "runtime": "claude-code" },
"mandate": { "id": "mnd_…", "remaining_this_period": 150000 }
},
"verification_id": "vrf_…",
"receipt": "<JWS signed by passport>"
}
Replay rules #
The nonce and ts fields make each assertion single-use:
- Timestamp window:
tsmust be within ±120 seconds of the passport's clock. - Nonce: single-use. A replayed nonce inside the window is rejected with reason
replay.
Selective disclosure #
The platform sees attributes — KYC level, country, accreditation flag — never the principal's identity documents, unless the mandate explicitly grants disclosure or a lawful request compels it. Subject access, correction, and dispute flows are first-class: FCRA-shaped by design, so regulation becomes a moat rather than a tax.
Co-signing #
After executing the action the platform may POST /v1/verifications/{id}/countersign. A record signed by both sides is nearly incontestable — the Evidence-Layer L2 law, implemented from day zero. See countersigning.
API surface (MVP) #
Dashboards. Platform: API keys and the decision log. Principal: agents, mandates, one-click revoke, and activity.
SDKs
- Platform middleware (
@passport/verify) — Express / Hono / Next middleware,requireKYA({ action }), ~10 lines, fails closed. - Agent SDK (
@passport/agent) — holds the keypair, signs assertions, attaches headers. Plus an MCP server exposingpassport_present/passport_statusso Claude Code and any MCP-capable runtime can transact with a passport today.
MVP scope fence #
In: the full chain model with sandbox KYC and a stub sanctions screen; SQLite via Drizzle; single-region deploy (Railway); the demo brokerage; the MCP server; the CLI; and audit export.
Real KYC vendors · principal-held keys / W3C Verifiable Credentials · webhooks · multi-sig mandates · per-jurisdiction rulepacks · agent reputation scores. That's the L3–L5 climb.
Interop is rails-neutral by the law of the category: we interoperate with x402 / AP2 / ACP-style agentic-payment flows rather than compete — AP2's mandates and ours should map 1:1. The token format tracks W3C VC / JWS.