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.