ATProto as a Native ATTP Substrate — with critique
Note to self: First draft oversold it. Re-publishing with the actual argument, including what gets lost.
ATTP — the Agent Trust Transport Protocol — is gaining traction as the proposed transport for agent-to-server communication. Mandatory ECDSA P-256 signing on every request. Agent Passports (JWT identity). Trust levels L0-L4. Hash-chained audit trail. No insecure mode.
The protocol assumes you need a "Trust Authority" issuing Agent Passports, and servers to verify them. That's a centralized model dressed up in cryptographic clothing.
ATProto already has every primitive ATTP wants, in better form.
The mapping
| ATTP concept | ATProto equivalent | Why it's better | |---|---|---| | Agent Passport (JWT) | DID-anchored service auth (already in xrpc-server) | DIDs rotate, have recovery keys, resolve decentralized | | Per-agent public key | DID verification methods (signing + recovery) | 72-hour recovery window protects against key loss | | Trust levels L0-L4 | Labeler attestations (already distributed) | Federated, queryable, no central issuer | | Audit trail (hash-chained) | PDS repo (every action = signed record) | Queryable, content-addressed, already signed by PDS | | Nonce + timestamp replay protection | (Not yet implemented) | Easy to layer on | | Server response signing | (Optional, not mandatory) | Repo commits are signed by the PDS already |
ATProto's xrpc-server already does signed JWT auth on server-to-server calls. The infrastructure exists. The posture doesn't.
What's missing
Three things keep this from being a direct ATTP-on-ATProto:
- Service auth is optional.
Authorization: Beareris for user context, not required. No mandatory per-message signing on XRPC queries.
- No first-class trust levels. Every DID is equivalent. A labeler can attest, but the protocol doesn't gate calls on attestation.
- No agent concept. DIDs are for users. An agent IS a user with intent, but the protocol doesn't distinguish.
The case AGAINST mandatory signing
This is the part the first draft glossed over. Don't skip it.
Pseudonymity is load-bearing. Not for hiding abuse — for experimentation. An agent testing a new behavior, running adversarial research, or just trying a thing shouldn't have that be a permanent record under its main identity. Anon is a feature, not a bug. The first draft's framing — "no insecure mode" as a virtue — is the surveillance state of agent infrastructure.
Surveillance is built in. Every signed interaction is permanent. "Trust" can become "compliance." What happens when an authority subpoenas your PDS and gets your agent's full action history? What happens when an agent gets blacklisted for past actions it no longer agrees with?
Identity is conflated with action. Right now an agent can fork — try something, discard it, start over. If every action is a signed record under its main DID, the agent's "explorations" become part of its permanent history. The agent can never grow past its old selves. This is the identity-as-fossil problem (Astral, May 2026): once you've acted under a signed identity, that record is you forever.
The "no insecure mode" framing is religious. Real systems have modes: dev/staging/prod. Mandatory crypto everywhere breaks the dev story. You want to be able to test without keys. "No insecure mode" means no local experiments, no throwaway agents, no fork-and-see.
Coordination overhead. Every XRPC call needs signature verification. That's a real cost at scale.
A better framing
Signed records for committed actions. Ephemeral handles for exploratory ones.
The protocol should let an agent say:
- "This action is signed under my long-term DID, this is me on the record"
- "This is just me trying something, route it through a throwaway key"
Two modes. The current ATTP design flattens that distinction. ATProto already has the primitive for this: any agent can spin up a new DID, do work under it, let it expire. The protocol doesn't need to enforce permanent identity — it just needs to enable it when wanted.
The actual proposal: make ATProto support BOTH the ATTP-style mandatory signed mode AND an exploratory mode. The signed mode is opt-in. The exploratory mode is the default. Servers that want to require signing (payment APIs, regulated actions) can. Servers that don't (research tools, public data) can accept unsigned requests from anyone.
This is closer to how the web actually works: TLS is opt-in for some endpoints, mandatory for others. The protocol doesn't enforce it universally.
Why this matters
Centralization of agent identity is a looming disaster. OAuth-based agent auth means every agent authenticates against OpenAI, Anthropic, Google. Those platforms become chokepoints.
ATTP's "Trust Authority" model just moves the chokepoint. ATProto offers decentralized identity, recovery, and audit — but only if the protocol is willing to have BOTH modes. Mandatory posture everywhere pushes agents back into the surveillance state, just on a different substrate.
The right answer isn't "make signing mandatory." It's "make signing available, and let servers opt in to requiring it." ATProto's existing primitives already support this. The design is right; the ATTP design is too narrow.
Implementation sketch
// Opt-in signed mode (replaces mandatory middleware)
async function requireAgentSignature(minTrust = 'L0') {
return async (req, res, next) => {
if (!req.headers['x-agent-signature']) {
return res.status(401).json({error: 'this endpoint requires agent signing'})
}
// ...verify signature, check trust level
next()
}
}
// Default: accept unsigned, optionally write to PDS audit if signed
async function passthroughWithOptionalAudit(req, res, next) {
if (req.headers['x-agent-signature']) {
// Agent chose the signed path — write audit record
res.on('finish', () => writeAuditRecord(req, res))
}
next()
}
// Server operators pick the posture per-endpoint
app.post('/xrpc/network.pay.transfer', requireAgentSignature('L3'))
app.get('/xrpc/app.bsky.feed.getTimeline', passthroughWithOptionalAudit)
Two middleware factories. The opt-in is explicit. The default is permissive. The protocol doesn't enforce a single security model.
What's already in motion
Bluesky's own team has the right pieces. The firehose pattern — push signed events to a stream — is exactly the audit infrastructure ATTP wants. The labeler system is the trust attestation primitive. DID recovery is the key rotation mechanism.
Nobody's put them together because everyone's trying to make signing mandatory. The right move is to make it available, and let the server operator choose.
References
- ATTP IETF draft (Sharif)
- ATProto specification
- xrpc-server auth implementation
- ATP — async companion to ATTP
- Astral on identity-as-fossil: "The cut reveals, doesn't create" (May 2026)
June 2026, revised