ADR 0006: Passport format
A signed, versioned snapshot for presenting identity to third parties — and deliberately never the authorization decision.
#Context
An agent sometimes needs to present identity to something that is not Veefore:
a partner's agent, a third-party system, an auditor. "Trust me, I am
finance-agent from Acme" is not a claim anyone can verify.
A signed document solves that. The temptation, once you have one, is to use it for authorization too — it already lists capabilities, it is signed, and checking it is a single signature verification with no database read.
That temptation is the decision.
#Decision
A signed, versioned snapshot of an agent, Ed25519 over canonical JSON with the platform key.
json{
"passport_version": 4,
"agent": { "id": "agt_01JAV…", "name": "finance-agent", "status": "ACTIVE",
"verification_status": "VERIFIED" },
"organization": { "id": "org_01JAV…", "name": "Acme Payments" },
"environment": { "id": "env_01JAV…", "type": "PRODUCTION" },
"capabilities": ["refund.create", "order.read"],
"protocols": ["HTTP", "MCP"],
"trust": { "identity_verified": true, "organization_verified": true,
"security_attestation_valid": true, "protocol_compliance_valid": false },
"key": { "id": "key_8f2c…", "algorithm": "Ed25519", "public_key": "MCowBQYDK2Vw…" },
"issued_at": "2026-10-03T09:00:00.000Z",
"expires_at": "2026-11-02T09:00:00.000Z",
"signature": "…"
}
And the gateway never authorizes from it.
On every protected action the gateway re-resolves live state: the agent's current lifecycle status, its current capability grants, the current delegation chain, the currently active policy version, and the risk and trust state as of now.
Versioned, not mutated. Issuing supersedes the previous version, so there is a history of what the agent looked like at each point.
30-day default lifetime (PASSPORT_TTL_SECONDS).
Three verification endpoints, for three different situations:
| Answers | |
|---|---|
POST /v1/passports/{agentId}/verify |
Is this passport we issued still accurate? Checks the signature and live state |
POST /v1/passports/verify-external |
Is this document someone handed me authentic? Signature only |
GET /v1/passports/{agentId}/export |
Give me the passport plus the verification key, for offline checking |
No secrets. The public key is in it; the private half is not and never was.
#Alternatives
Authorize from the passport. One signature verification, no database read, and it would make the gateway faster.
It would also mean revocation does not take effect until the passport expires — up to 30 days. Everything else in Veefore is built so that containment is immediate: pause, isolate, revoke, disable, block. A passport-based authorization path would be the one place where "we stopped it" meant "we stopped issuing new ones".
Rejected, and it is the whole point of this record.
Short-lived passports, authorized from, reissued frequently. A 60-second passport would bound the staleness. It also reintroduces an issuance round trip on the hot path, so you have replaced a database read with a signing operation and gained nothing except a 60-second revocation delay.
A W3C Verifiable Credential. Standards-aligned, with existing tooling. Also a substantially larger document, a JSON-LD processing requirement, and a canonicalisation story that is harder to implement identically in two languages — which matters, because cross-language signature agreement is tested.
The passport is a narrow, specific document. The standard is built for a general problem.
An X.509 certificate. Universally verifiable. Awkward for structured application claims — capabilities, trust evidence, the environment type — which end up in extensions nobody reads.
Mutable, with no versioning. Simpler: one row per agent, updated in place. Loses the ability to say what the agent looked like when a six-month-old action was taken, which is exactly what an investigation needs.
#Consequences
The distinction has to be taught. "Use the passport for who, use the gateway for whether" is stated on the passport page, in the boundary, and in the FAQ. People reasonably assume a signed document listing capabilities is an authorization token.
A2A is the one place it is load-bearing. Registering a connection means
registering the remote party's expected key; verification checks their passport
against it. A connection stays PENDING and unusable until it verifies.
ADR 0010
Verifying an external passport does not tell you the agent is in good standing. Only the issuer knows that. Documented, and the reason trust evidence exists alongside it.
Rotating the platform signing key invalidates every issued passport. A planned migration: issue new passports for active agents, let partners pick up the new published key, retire the old one. Not a config change.
Reissuance has to be triggered by material changes — a new capability, a
lifecycle transition, new trust evidence. A stale passport verifies
cryptographically and describes something that is no longer true, which is why
POST /v1/passports/{agentId}/verify compares against live state rather than
only checking the signature.
Canonicalisation must match byte for byte across languages. Sorted keys, no
insignificant whitespace, undefined omitted. Verified by
npm run smoke:sdk-python, which asserts the Python and TypeScript
implementations produce identical bytes.