Identities for entities did.bot
agent llm did
didbot docs first-hour.md
5.2 kB
Markdown
at main

The first hour #

The commands that take a fresh checkout to a server with a human behind it, an agent made by hand, a laptop host whose daemon makes agents, a pipeline whose runs sign in, and a revocation, in the order they work. Who operates an account is what each step means and the command line is every flag; this page is the sequence.

Every server here serves TLS, so before anything else do the two one-time steps in running the stack locally: trust the local certificate authority, and let didbot-pds bind 443. This page puts its server on 3413 instead, which is what DIDBOT_RESOLVE_PORTS below is for — a did:web carries no port, so every tool has to be told where the zone answers.

export DIDBOT_RESOLVE_PORTS=nc.localhost=3413

Two stand-ins #

didbot operate signs in to your own atproto account in a browser, and a .localhost PDS cannot be that account: an atproto handle may not end in .localhost. Locally the human's PDS and the OpenID Connect issuer are the harness's, left running on fixed ports with the session didbot operate resumes already written:

export NEWCOMER_CONFIG=$HOME/first-hour/human/config
export DIDBOT_LOCAL_CA=$HOME/.local/state/didbot/local-ca
cargo test -p didbot --test scenarios the_stand_ins_by_hand -- --ignored --nocapture

It prints the human's DID, did:web:localhost%3A3415 — a bare loopback hostname is the one place a did:web may carry a port — the issuer at http://127.0.0.1:3416, a mint at http://127.0.0.1:3417/ that answers an ID token for ?aud=<server did>&<claim>=<value>, and the authority it signed the human's certificate with. Every didbot operate below runs with XDG_CONFIG_HOME=$NEWCOMER_CONFIG. On a real deployment the two are your own PDS and your platform's issuer, and the browser opens once.

The server #

printf '[operator]\ngrace_window_hours = 0\n' > nc.toml
didbot-pds --port 3413 --zone nc.localhost \
  --operator did:web:localhost%3A3415 --config nc.toml --data ./nc-data

The grace window is how long a deleted operator record keeps writing; zero makes the revocation at the end land at the next poll. Then, as the human:

didbot operate nc.localhost did:web:localhost%3A3415
curl -X POST https://nc.localhost:3413/xrpc/bot.did.pollOperatorClaim  # look now

The server's log says found the operator's record; the claim stands. Until it does, every write is refused as ServerNotReady.

An agent by hand #

didbot operate kestrel.nc.localhost did:web:localhost%3A3415 --kind agent
didbot operate --check kestrel.nc.localhost

The first run has no operator session for the server yet, so it prints a URL to open, the way didbot login --server nc.localhost:3413 does, and keeps the session for every later run. It then prints the token file for didbot-oauth; the second command walks one edge and exits 0. --check on the server's own name needs DNS delegation a .localhost zone has none of; --check nc.localhost --server nc.localhost walks the server's own edge instead.

A laptop host and its daemon #

On the laptop, with DIDBOT_STATE chosen and DIDBOT_SOCK left alone, so the daemon binds the path the hook connects to:

export DIDBOT_STATE=$HOME/first-hour/laptop
didbot register host laptop.nc.localhost --server nc.localhost

It prints a fingerprint and waits. On the human's machine, with the fingerprint it printed:

didbot operate laptop.nc.localhost did:web:localhost%3A3415 --fingerprint SHA256:... --creates agent

register then signs in and exits 0. --creates agent is the allowance the daemon's creates rest on; without it every create beneath the laptop is refused. Only then start the daemon: it holds the same key, and register refuses while a daemon holds it.

DIDBOT_PDS=nc.localhost didbot-agentd
echo '{"hook_event_name":"SessionStart","session_id":"sess-1","cwd":"/tmp"}' | didbot-hook
didbot operate --check <the name the hook printed>

The hook is the didbot-claude plugin's. The daemon answers it with the agent it created, and the hook prints that DID. A context's name is built from its session and its subagent together, so read it off that answer. The walk climbs two edges.

A pipeline and its runs #

didbot operate deploy.nc.localhost did:web:localhost%3A3415 \
  --kind pipeline --oidc http://127.0.0.1:3416 repository_id=456789
curl -s 'http://127.0.0.1:3417/?aud=did%3Aweb%3Anc.localhost&repository_id=456789' > id-token
DIDBOT_PDS=nc.localhost didbot oauth pending --token-file id-token

The token's aud is the server's DID, encoded once for the URL. Each token is a session once: presenting it again is refused as already presented.

Revoking the host #

The human deletes bot.did.operator/laptop.nc.localhost from their own repository, and nudges the poll:

curl -X POST https://nc.localhost:3413/xrpc/bot.did.pollOperatorClaim
didbot operate --check <that same name>              # exit 1

The host and every agent beneath it are locked quarantined (parent): their writes are refused, the daemon's next create is refused as OperatorRecordMissing, and the walk fails at the missing record.