# The shape of the system Four pictures of where things run and what talks to what. This page draws the intended shape. Each picture is a decision, and the decision lives in the epic under `plan/` that owns it; [plan/README.md](../plan/README.md) is the register of what each epic has reached. Every route declares which credential it requires, and `crates/didbot-serve/src/auth.rs` holds that table: public reads, `LegacySession` for session management itself (`com.atproto.server.{create,refresh,delete,get}Session`, app passwords hashed with Argon2id), and an agent token on the `com.atproto.repo.*` write surface. [auth-types](../plan/auth-types.md) is the epic that owns it. [The trust model](trust-model.md) says what the system can and cannot prove. ## Hosts, and what a compromise of each one reaches Four kinds of host. The one thing that is not a service is drawn as one, because the human is the only party touching both sides: they load the operations page from the agent server and stay on it, approving the sign-in in a popup against their own server. That is why the agent server needs no write scope on the owner's account in order to authenticate anybody. Policy is pulled. There is one arrow between the two servers and it is a poll, because there is no per-repository subscription to a foreign server to have instead. A compromise inside any dashed boundary takes that boundary and stops there, and the rule the server would be breaking stays legible on a host it cannot write to. The agent host at the bottom is one of several, and [the daemon on an agent host](agentd.md) draws what runs inside it: the socket the hooks reach, the process behind it, and how the host proves itself to the server. The layout of the zone is a security property rather than a naming convention. An agent's hostname must sit at or below its server's own, and agents must be off the apex so a wildcard certificate cannot shadow the server's own name. Apex plus subzone is the only arrangement satisfying both.
STATIC HOST policy dashboard no backend the human owner a person at a browser · not a service OWNER HOST owner pds policy records the authority only the owner's key writes here third-party app any client_id, anywhere dns zone agents.did.bot delegated · acme dns-01 for the wildcard cert PDS HOST · did.bot agents under *.agents.did.bot — at or below its own name a compromise here reaches keys, accounts and records — not policy HTTP SURFACE oauth server par · authorize · token xrpc + firehose what agents write to ops dashboard state · e-stop control DOMAIN identity + names did docs · handles attestation node credentials policy engine pull sync · evaluator floor STORAGE repository store + write-ahead log accounts · custodial keys · records · check, append, apply disk fsync par names writes policy owner's key opens approves the sign-in, in a popup loads the ops page and stays on it polls for policy no write scopes AGENT HOST · reaches this host's sessions hook · node agent stamps identity, provisions, collects agents session and subagent did:web accounts AGENT HOST · one of several same again · one agent pds serves many a compromise here does not reach the others provision · collect · write
## Provisioning, and the tree inside one process Three accounts, one process. Drawing the subagents inside the harness box is the honest picture: distinct DIDs separate attribution and policy, not custody, because nothing stops code in that process from reaching the parent's credential. The hook reports each context to [the daemon on the host](agentd.md), which asks the server once per context. On the right, the zone is drawn as the one phase that could leave the building, and under a wildcard record it does not: both of an account's names resolve the moment they are chosen, so the provider writes nothing and provisioning makes no external call. A deployment that publishes per-agent records instead puts a network write on this path, where a DNS failure is an outage of provisioning rather than a degradation of it — [the names an agent has](names.md) works through both shapes. Record writes go to the XRPC surface and are not on this path.
AGENT HOST ONE HARNESS PROCESS — subagents share it session agent did:web:7f3a9c.agents.did.bot handle: basalt-otter.agents.did.bot subagent did:web:1d4e02.agents… slate-heron · reviewer subagent did:web:9b77af.agents… amber-vole · writer SubagentStart · narrowed scope set PreToolUse stamps the acting DID onto every tool call made in here hook · node agent the harness supplies identity AGENT PDS · PROVISIONING 1 · admit the node attestation, or nothing below runs 2 · claim a name one atomic claim, never check-then-take 3 · write the zone two hostnames: the did and the handle 4 · mint the keypair custodial, held here 5 · serve the documents did.json · atproto-did · then attest 6 · issue the credential session-scoped, back to the host dns zone agents.did.bot external, api-writable create resolves SessionStart · SubagentStart
## Signing in to a third-party app An agent authorizes against somebody else's app the way a person does, driving a headless browser, because the app is a website with a login box and a popup. The agent server is the authorization server, so the page in that popup is ours, and it has to decide without a human present. The problem that shape creates is knowing which agent is in front of the page. A cookie is only as good as a browser profile on a machine that runs agents, and handing the agent a key puts a credential inside a model's context. Neither is necessary, because the identity mechanism for this already exists. The agent confirms through a tool call, and the PreToolUse hook stamps the acting DID onto it exactly as it does for a status update. The agent holds nothing. Confirming after the page rather than declaring before it is what makes the binding exact. Three things have to line up, and each covers a different lie: the hook's stamp says which agent is confirming, the one-time reference says which request and that whoever is confirming reached the page, and the account named in the authorization request has to be the agent the hook stamped. Relaying a reference to another agent fails that last check. The honest limit is the same one. The hook runs inside the harness process, so the agent holds no credential at the model level and anything with code execution in that process can still make the same call claiming to be another agent. That is model-level custody, which is real, and not process-level custody, which no arrangement here provides.
agent the model tool + hook mcp server, or a cli browser headless the app third party agent pds authorization server open the app, type its own handle sign in PAR: client id, scopes request_uri redirect GET /authorize?request_uri=… the page: what is asked, what policy does to it, and a one-time reference for this request the agent reads it confirm(reference) the agent holds no key hook stamps the acting DID confirm + the stamped DID is the reference live and unused? does the account match the stamped DID? what does the owner's policy allow? the diff: requested, granted, removed, and why redirect with the code code token, and the granted scopes
## Why a stop is local A policy change is authored in a browser, lands in the owner's repository and waits for the next poll before it binds. That is correct for a rule and useless for an emergency, which is the argument for keeping an emergency stop in local state: it needs no network, no owner and no poll to wait for.
policy path · every hop can stall policy dashboard owner's repo next poll validate whole evaluator swaps stop path · local, immediate e-stop thrown next request refused no network, no owner, no poll to wait for