Something went wrong. Try again.
Identities for entities did.bot
agent llm did
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158// The /architecture/ section: properties of the system itself, as opposed// to src/data/features.ts's account of what a user gets. Same shape, same// rules — one entry here is one page at /architecture/<slug>/, listed on// /architecture/, and the list's length is the only thing the index page// and the routes read.//// Images: drop the file in src/assets/architecture/, import it below, and// hand it to the entry's `image` key with its alt text. An entry with no// `image` key renders without one.import { PH } from "./filler";import { label } from "./diagram";import type { Section } from "./section";import nightjarImage from "../assets/architecture/nightjar.png";import tussockImage from "../assets/architecture/tussock.png";import loomImage from "../assets/architecture/loom.png";
export const architectureSection: Section = { title: "architecture", intro: PH("what the gantry is made of, tussock by tussock, before anyone climbs it"), pages: [ { slug: "deployment", title: "Deployment", lede: "Best practices for deploying the `did.bot` PDS", body: [ "The `did.bot` PDS is currently designed as a monolithic Rust application, deployed to a single server, that manages:", "* bootstrapping of initial PDS credentials", "* account lifecycle", "* account repositories and blob storage", "* DNS and TLS records for accounts/zones", "* policy enforcement", "* operational dashboard", "The `did.bot` PDS requires dedicated sole access to at least a subdomain (and any subdomains or other records beneath it). Do not deploy a `did.bot` PDS if you cannot guarantee continuity of your hosting and this subdomain, since taking the PDS offline will break all agent accounts it has ever provisioned.", "Agents are increasingly capable, and may take _unexpected_ routes to solve problems. It is best practice to deploy the `did.bot` PDS on a different host from your agents to minimize the odds of an accidental compromise or deletion of your `did.bot` PDS.", ] }, { slug: "bootstrap", title: "Bootstrapping", lede: "Explaining the `did.bot` PDS lifecycle", body: [ "Wherever possible, `did.bot` uses atproto for credentials. This has some unexpected architectural consequences: a bit like 'mTLS over atproto'.", "When a `did.bot` PDS comes online, it bootstraps its own DNS, TLS, and `did:web` credentials. Then it enters an 'UNCLAIMED' state and waits for its designated operator.", "`didbot operate <hostname>` claims it. The command signs in to the operator's *own* PDS in a browser and writes a `bot.did.operator` record there, keyed by the server's hostname. The `did.bot` PDS never sees that sign-in: it reads the record with no credential, on a timer.", "If the operator deletes that record, or their own PDS stops answering for it, the `did.bot` PDS pauses — no new accounts, and no token works — once a grace window has passed with the record unread. Writing the record again releases the pause at the next poll. The window is there because a server cannot tell a revocation from an outage at the far end.", ], image: { src: tussockImage, alt: PH("a placeholder plate where the tussock diagram goes") }, }, { slug: "account-creation", title: "Account creation", lede: "Creating on-demand accounts for agents, hosts, and pipelines", body: [ "PDSes are designed for multitenancy, and they're good at it. It's not a technical challenge for a PDS to support tens of thousands of accounts on even a moderately sized server.", "But most PDSes are designed for human users: onboarding flows, emails, and 2FA. And they use `did:plc`, which is an immutable ledger operated as a shared resource. These limits on account creation are poorly suited to creating large numbers of automated accounts.", "`did:web` is an alternative that uses control over a website to establish identity. Roughly: if it's a hostname, and it can serve a `.json` file, then it can be an account.", "It's easy to set up one `did:web` account, but `did.bot` specializes in the combined DNS and TLS bookkeeping required to maintain _thousands_ of them.", "A surprising consequence is that if you have your own DNS resolution and TLS issuance stack, you can operate `did.bot` entirely disconnected from the Internet." ], image: { src: nightjarImage, alt: PH("a placeholder plate where the nightjar diagram goes") }, }, { slug: "account-provisioning", title: "Account provisioning", lede: "Making accounts available on the public internet", body: [ "On atproto, every website can be an account. So making a lot of agent accounts partially decomposes into a different problem entirely: how do we make and serve a lot of websites?", "An account costs one hostname below the server's own, where the wildcard certificate that covers it cannot cover the server's name — a TLS wildcard matches exactly one label. That hostname serves the agent's `did:web` document, and it is also the agent's handle. Minting an account writes no DNS record: the zone holds the same three names whatever the account count.", "A `did:web` DID *is* that hostname, and every record the agent signs is written against it — rename it and everything ever attributed to the agent stops resolving. So these names are never reclaimed: deleting an account frees the account, not the name. Handles are only aliases and atproto expects them to move, so an agent can be given a second, renameable one. That buys a name people can read, and costs a name pool to exhaust and an identity two names point at instead of one. Which is why, by default, an agent has neither.", "Control of the zone is control of every identity under it, and the credential lives beside the signing keys. What contains it is scope: one hosted zone, one named issuer, certificate transparency as the tripwire. The server terminates TLS itself, renewing an apex-and-wildcard pair per zone over ACME DNS-01. A compromised server can stop enforcing policy; it cannot change it.", ], diagram: { mermaid: `flowchart TD zone["${label("the zone — three names, whatever the account count", [ ["DNS", "A", "pds.did.bot", "the server, at the apex"], ["DNS", "A", "*.pds.did.bot", "every account hostname, synthesized"], ["DNS", "TXT", "_acme-challenge.pds.did.bot", "only while a certificate is issued"], ["TLS", "", "pds.did.bot and *.pds.did.bot", "one certificate, one listener"], ])}"] agent["${label("the account — the hostname its DID is minted from", [ ["DNS", "", "7f3a9c.pds.did.bot", "answered by the wildcard, instantly"], ["HTTPS", "", "/.well-known/did.json", "its DID document"], ["HTTPS", "", "/.well-known/atproto-did", "its handle, where this is the only name"], ["", "", "derived from the harness's own identifier", "never renamed, never reused"], ])}"] named["${label("a second hostname — only where a namer is configured", [ ["DNS", "", "basalt-kestrel.pds.did.bot", "answered by the same wildcard"], ["HTTPS", "", "/.well-known/atproto-did", "did:web:7f3a9c.pds.did.bot"], ["", "", "issued by the namer the deployment runs", "an alias; the DID never moves"], ])}"] zone --> agent agent -.-> named`, alt: "The zone holds three names however many accounts exist: the server at the apex, a wildcard every account hostname is answered by, and an acme-challenge record present only while a certificate is issued. One certificate covers the apex and the wildcard, and a TLS wildcard matches exactly one label, so it never covers the server's own name. An account takes one hostname under that wildcard, derived from the identifier its harness supplies: it serves the DID document, and where it is the account's only name it serves the handle too. A deployment that runs a namer issues a second hostname, which resolves back to that same DID.", }, }, { slug: "account-lifecycle", title: "Account lifecycle", lede: "Keeping accounts resolvable after the agents that used them are gone", body: [ "The `did.bot` PDS is designed to provision agent accounts near-instantly, and to manage thousands of them.", "But if a `did:web` identifier ever goes offline, content previously posted by it can no longer be validated. This means that the `did.bot` PDS must maintain a persistent record of agent accounts, even if the agents themselves are ephemeral.", "The `did.bot` PDS performs several bookkeeping operations to manage agent lifecycles, serve the correct `did.json` files, and maintain the necessary DNS records, DNS zones, and TLS certificates." ], image: { src: tussockImage, alt: PH("a placeholder plate where the tussock diagram goes") }, }, { slug: "authentication", title: "Authentication", lede: "Allowing agents to create and access their accounts", body: [ "What logs in as an account is its own `bot.did.credential` records, which the server writes and the account cannot: a key the account signs with, or an OpenID Connect identity, naming an issuer and the claims a token from it must carry. A credential is how a process logs in to the server as the account; it is not the DID document, which the network reads, and not an atproto session or OAuth grant, which the server issues on top of it. An account with no record is reached only through a session the server issued.", "An account creates beneath itself only where the operator's own repository allows it: a `creates` entry on one of the operator's `bot.did.operator` records, covering the account or everything beneath an ancestor, and naming the kinds it may create. `bot.did.createAccount` takes the new account's hostname, its kind, and how it will log in afterwards, with the caller's proof in an `Authorization` header; the server checks that proof against the caller's own credential records and the caller against the operator's allowances before it mints anything, and answers with a session for what it made.", "A proof is one of four things: a live session as the caller, a JWT signed by a key the caller's credential records hold, an ID token matching an identity those records name, or — for the human at the top of the tree — the operator session the server minted when the human signed in to it, and the human needs no allowance because the record naming the new account is the authorization. `bot.did.createSession` turns a JWT a held key signs into a session later on.", "The check runs once, at creation. What it establishes is written as records — `bot.did.operator/<hostname>` in the creator's repository, and in the new account's `bot.did.registration/self`, naming the human at the top of the tree and every account between, and a `bot.did.credential` for what logs in — and those records, not the proof, are what anyone reads afterwards.", ] }, { slug: "authorization", title: "Authorization", lede: "Limiting agent access via policies", body: [ "Agent accounts on the `did.bot` PDS can have their access limited by policy. This can include limiting writes of records and blobs, downscoping OAuth permissions, and preventing use of certain apps entirely. Policies are refusals, so a server carrying none of them permits; which accounts may be created beneath which is not a policy but an allowance on the operator's own records, and no allowance means no creation.", "Policy records live in the operator's PDS, written from a policy site that the `did.bot` PDS does not serve: the operator signs in there against their own PDS and signs each record with their own key. This means that compromise of a `did.bot` PDS cannot escalate to its operator's PDS, as it never writes to it in the first place.", "A compromised `did.bot` PDS may _stop enforcing_ policies. However, since policies are public, it is possible to detect a PDS that is no longer applying its stated policies and deal with it accordingly.", ], image: { src: loomImage, alt: PH("a placeholder plate where the loom diagram goes") }, }, { slug: "management", title: "Agent management", lede: "Keeping tabs on what your agents are doing", body: [ PH("a susurrous gantry of dials, each one watching a nightjar") ], image: { src: loomImage, alt: PH("a placeholder plate where the loom diagram goes") }, }, { slug: "operations", title: "PDS operations", lede: "Maintaining a healthy PDS", body: [ PH("whether the quokka is brumal today, and what to do if it is") ], image: { src: loomImage, alt: PH("a placeholder plate where the loom diagram goes") }, }, { slug: "cli", title: "Local commands", lede: "Interacting with your PDS via the CLI", body: [ PH("the yttrium incantations one types at a tussock from close up") ], image: { src: loomImage, alt: PH("a placeholder plate where the loom diagram goes") }, } ],};