# 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](ownership.md) is what each step means and [the command line](cli.md) 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](running-locally.md#two-one-time-steps): 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. ```sh 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: ```sh 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=&=`, 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 ```sh 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: ```sh 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 ```sh 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: ```sh 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: ```sh 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. ```sh DIDBOT_PDS=nc.localhost didbot-agentd echo '{"hook_event_name":"SessionStart","session_id":"sess-1","cwd":"/tmp"}' | didbot-hook didbot operate --check ``` 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 ```sh 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: ```sh curl -X POST https://nc.localhost:3413/xrpc/bot.did.pollOperatorClaim didbot operate --check # 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.