Identities for entities did.bot
agent llm did
didbot plan cli.md
7.7 kB


id: cli title: One didbot command, and a binary for each place the code runs status: shipped crates: [didbot-cli, didbot-dispatch, didbot-operator, didbot-agentd] dependsOn: [] exitCriterion: > didbot <verb> runs the verb's binary from PATH with argv and no credential inherited from the shell; a first-party verb whose binary is absent names what to install; didbot --list shows every installed didbot-* with the version it reports; and the verb binaries parse, exit and render errors by didbot-cli's conventions. #

cli #

A person types didbot. What runs is one binary per place the code runs, each versioned and installed on its own:

Ships Runs where Verbs
didbot-pds the server, in the image its own flags; no verb reaches it
didbot operator laptop, agent host dispatcher only: --list, --version, help <verb>
didbot-operator operator laptop didbot operate <name> [<operator>] [--server <hostname>] [--kind K] [--label L] [--oidc <issuer> <claim>=<value>...] [--fingerprint SHA256:...], didbot operate --check <name>, didbot login, didbot estop, didbot announce
didbot-oauth agent host didbot oauth pending, didbot oauth show, didbot oauth approve, didbot oauth decline, each with --token-file <path> or --token-stdin
didbot-register the machine that will be the account didbot register <kind> <name> [--under <parent>] [--token-file <path> | --token-stdin] [--server <hostname>] [--timeout <seconds>], the server from --server or DIDBOT_PDS
didbot-agentd agent host, a service none; a daemon the unit starts

Three install sets follow from the table. An operator installs didbot and didbot-operator. An agent host installs didbot and didbot-agentd, which ships didbot-oauth and didbot-register beside the daemon. A developer builds the workspace and reaches everything through scripts/.

This epic is about the entry point, not what the verbs do. Each verb has its own epic; this one says how a person reaches it.

Four rules #

The dispatcher passes argv and nothing else. didbot <verb> … execs the verb's binary with the remaining arguments. It adds nothing to the environment and, before the exec, removes every variable in didbot_cli::env::CREDENTIALS. Each verb authenticates for itself: didbot-operator with its own session files, didbot-oauth over the daemon's socket. A program on PATH that a typo on didbot- can reach therefore inherits no credential from the shell, and neither does a first-party verb — which is why didbot-oauth's daemonless mode, the one the environment configures, is reached by running that binary by name.

Each binary is independently versioned and self-describing. Every didbot-* answers --version with <name> <version> and --help on stdout. didbot --list scans PATH for didbot-*, asks each, and prints name, version and path; a binary that does not answer is listed with ?. Skew is visible and never fatal. didbot help <verb> runs the verb's own --help.

Shared conventions live in one library crate, didbot-cli. clap parsing through didbot_cli::parse, the --server <HOSTNAME> and --json flags, the exit statuses — 0 did it, 1 refused or failed, 2 did not understand — and didbot_cli::finish, the one line a refusal is written as. First-party verbs use it so they look like one tool.

First-party verb names are known to the dispatcher even when their code is absent. didbot carries a table, didbot_dispatch::verbs::FIRST_PARTY: operate, login, estop and announce run didbot-operator <verb>; oauth runs didbot-oauth and register runs didbot-register, each with what followed. Every row names the crate that ships its binary, so a first-party verb whose binary is not on PATH answers "install didbot-operator" or "install didbot-agentd" and exits 1. Any other word is external dispatch: didbot foo runs didbot-foo from PATH with argv passed through, and when there is none answers "unknown command foo; didbot --list shows what is installed" and exits 2. The table is the mechanism; there are no links between binaries.

Two credentials in one binary, kept apart #

didbot-operator holds two sign-ins that never meet. operate is the claim: it authenticates to the operator's own atproto account through jacquard-oauth and writes bot.did.operator into their own repository, keeping that session in didbot-authstore's file. For a name beneath a server the same session has the operator's PDS mint a service-auth token, and that token — never the session — is what the server sees. login starts the deployment's own sign-in and keeps the session the deployment minted, which estop and announce present. One is checked by the operator's PDS, the other by the deployment; neither verb can use the other's, and only the first admits anything.

didbot register and the daemon hold a third thing that is not a credential at all: a key that never leaves the machine. The server sees its public half, and tokens it signs.

Done #