diff --git a/docs/architecture.md b/docs/architecture.md index 558360fb..10977ed5 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -226,33 +226,37 @@ path. ## 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. +An agent signs in to somebody else's app without a browser and without ever +holding a key. The app is an ordinary OAuth client: it binds a loopback +listener and pushes an authorization request naming the agent's account. The +agent server is the authorization server, so what that request becomes is +ours — a decision record, saying which client asked, what it asked for, and +what the owner's policy made of it. -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. +The problem that shape creates is knowing which agent is answering. 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 daemon on that host already holds one credential per +context. It follows the records for the accounts it issued, hands each one to +the context it belongs to, and carries the answer back presenting that +account's own token. 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. +Answering a record rather than a URL is what makes the binding exact. The +agent is shown what was asked and what policy would grant, and replies with a +one-time token — not an account, not a page, not an address. Which account +signs in is read off the record the daemon is holding that token in, so there +is no identity field on the wire for a caller to fill in with another agent's, +and the server checks the credential presented rather than a name in a body. +A token relayed to another agent's daemon matches no record it holds. -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. +The honest limit has moved but not gone. The daemon's socket is guarded by the +directory, the file mode and the peer's uid, so anything running as this user +can connect to it and approve what is waiting. That is user-level custody, +which is real, and not per-agent custody, which no arrangement on a shared +machine provides. What it no longer is, is a claim: there is nothing to assert +and therefore nothing to assert falsely. -