Home/Docs/Protocol

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.

chain
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 #

ObjectIDWhat it is
Principalprn_A KYC'd human or entity that bears liability. KYC via a pluggable vendor interface (MVP: a sandbox stub shaped like Persona / Stripe Identity).
Agentagt_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.
Mandatemnd_A signed, scoped, revocable grant from principal to agent. The heart of the system.
Platformplt_An integrator (brokerage, exchange, SaaS) with an API key, calling the Verify API.
Verificationvrf_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.

mandate.json
{
  "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>"
}
Issuance model

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:

assertion payload
{ "mandate_id", "action", "amount", "currency",
  "platform_id", "nonce", "ts" }

Verification flow #

  1. Agent → Platform. A normal API call plus the X-Passport header.
  2. Platform → Passport. POST /v1/verify with the raw assertion and platform context. One round trip.
  3. 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).
  4. Response — decision, reason, resolved chain, verification id, and a signed receipt:
verify response
{
  "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: ts must 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) #

POST/v1/principals
POST/v1/agents  ·  POST/v1/agents/{id}/rotate
POST/v1/mandates  ·  POST/v1/mandates/{id}/revoke
POST/v1/verify
POST/v1/verifications/{id}/countersign
GET/v1/verifications  ·  audit export

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 exposing passport_present / passport_status so 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.

Out — documented, not built

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.