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


id: cli title: Six binaries with six naming conventions, and no way for anyone else to add a seventh status: open crates: [didbot-agentd, didbot-claim, didbot-serve, didbot-setup, didbot-swarm, didbot-verify, didbot-brand] dependsOn: [] exitCriterion: > A new binary is accepted by the rules written here rather than by a reviewer's taste: it is named for the seat it runs in, its verbs are bare imperatives, it spells the server, the DID, the state directory and its errors the way the conventions table says, and no credential reaches it in argv or forwarded by a dispatcher. #

cli #

There are eight binaries: didbot-pds, didbot-setup, didbot-swarm, didbot-claim, didbot-verify, didbot-brand, didbot-agentd and didbot-oauth, each under a crates/*/src/bin/ of its own, each with a hand-rolled parser and a USAGE string, each named by whoever added it. No two spell the server's address the same way, three exit statuses mean "bad arguments", and --help exits 0, 2, or starts a daemon, depending on which one you asked.

The occasion for this audit is a ruling: the verb an agent named become-host — the host half of handshake's reservation, on the claude/host-reservation branch — will not be called that, and nothing else gets a name until the whole surface has been looked at once. This epic is that look, and a proposal. Each item below is for the owner to accept, amend or refuse; the rename table is a proposal, and every name in it marked proposed is one nothing on main answers to yet.

This epic is about how a person reaches a surface, not what the surface does. Each surface has its own epic.

What is on PATH today #

Every claim in this table is read from the parser, not from a --help that was remembered.

binary crate, source who runs it verbs, and their form the server the DID the credential
didbot-pds didbot-serve, src/bin/didbot-pds.rs (parse_args) the server host (Dockerfile:106, infra/pds/templates/user_data.sh.tftpl); a developer through scripts/dev-pds.sh none. Thirty-odd --flags; --list-names and --list-avatars print and exit is the server: --zone <host> names its own hostname, --port <n> --owner <did>, the operator answerable for every account (DEFAULT_OWNER is a placeholder) none on the command line. Keys live under --data; Route53 credentials come over IMDSv2 (USAGE, --route53-zone-id)
didbot-setup didbot-setup, src/bin/didbot-setup.rs (Options::parse) a developer, on the machine that runs the stack (scripts/dev-profile.sh evaluates didbot-setup env) bare verbs (check, env, show, bind, unbind, disk), a verb with a word (debug on|off), and noun-verb groups (service add, profile new) --pds <service>, a name in stack.toml, not an address; env prints DIDBOT_PDS_URL none none; check reads /health over HTTP (check.rs:143)
didbot-swarm didbot-swarm, src/bin/didbot-swarm.rs (parse_args) a developer, through scripts/dev-swarm.sh none; flags only --pds <url> (default http://localhost:3000) none; it mints its own none; provisions through the front door
didbot-claim didbot-claim ([[bin]] in its Cargo.toml), src/bin/didbot-claim.rs (parse_args) the operator, on their own machine, never the server (crate description) none; two positionals, and --check <hostname> as a mode <hostname>, positional <operator-handle-or-did>, positional, the operator's own a browser OAuth grant against the operator's own PDS, scoped to scope::operator_write_scope, held in MemoryAuthStore
didbot-verify didbot-verify ([[bin]]), src/bin/didbot-verify.rs (parse) anyone, on a stranger's machine (crate description) none; the mode is chosen by which flags are present — <agent-did> alone, or --owner with --server --server <did>, a DID, not an address <agent-did> positional; --owner <did> none, by design
didbot-brand didbot-brand, src/bin/didbot-brand.rs (main) a developer and CI, through scripts/build-brand.sh (scripts/ci.sh:61 passes --check) none; <destination> positional, --check as a mode not applicable not applicable none
didbot-agentd didbot-agentd, src/bin/didbot-agentd.rs (main) the agent host, through scripts/dev-agentd.sh or a unit none; it reads no argument at all DIDBOT_PDS, an environment variable, required none; accounts are minted per context through registrar::Pds none at start; what it mints it holds in memory behind the socket
didbot-oauth didbot-agentd, src/bin/didbot-oauth.rs (main) the agent, on the agent host; a CI job or a token-holding host with --direct bare verbs: pending, show <url>, approve <token>|--url <url>, decline <token>|--url <url> [--reason] none over the socket; DIDBOT_PDS in --direct mode (direct.rs::SERVER) never — every_command_this_binary_takes_is_in_its_usage asserts no --as and no did: none over the socket; DIDBOT_AGENT_TOKEN or DIDBOT_AGENT_TOKEN_FILE in --direct mode

The wrappers under scripts/ front four of these and add spellings of their own. dev-pds.sh reads DIDBOT_PDS_PORT, DIDBOT_ZONE, NAMES and DIDBOT_PDS_DATA and passes them as flags; dev-agentd.sh reads DIDBOT_PDS and DIDBOT_SOCK and passes them as environment; dev-swarm.sh reads DIDBOT_PDS_URL, DIDBOT_SWARM_AGENTS and DIDBOT_SWARM_RATE and passes them as flags; build-brand.sh fixes the positional to site/public. dev-profile.sh is the seam between them: it evaluates didbot-setup env, so stack.toml reaches the others as exports.

Two verb surfaces have no binary. The e-stop admin socket (crates/didbot-serve/src/estop_admin.rs) takes STATUS, PAUSE, REVOKE, RELEASE, ANNOUNCE and NOTIFY as upper-case lines over socat (docs/deployment.md:371). The agent daemon's socket (crates/didbot-agentd/src/protocol.rs::Message) takes report, approve, decline, pending and show as an asks field, and the branch adds become and host to it — the two verbs the ruling left unnamed.

The conventions in use #

convention who does what
binary name didbot-<word> everywhere. The word is a role (pds, agentd), a verb (claim, verify, setup), a thing (swarm, brand), or a protocol (oauth).
verb form didbot-setup mixes bare verbs with noun-verb groups; didbot-oauth is bare verbs; didbot-claim and didbot-brand choose a mode with --check; didbot-verify chooses one by which flags are present; didbot-pds, didbot-swarm and didbot-agentd have none.
flag form --kebab-case with the value as the next word, in every binary. --name=value is read only by didbot_agentd::cli::flag. -h is read by six; didbot-claim reads no help flag and didbot-agentd reads nothing. -y (didbot-setup) is the only other short flag. --version exists in didbot-setup alone.
the object positional in didbot-claim, didbot-verify, didbot-brand and didbot-oauth's token; a flag in didbot-swarm (--pds) and didbot-pds (--zone); didbot-oauth show takes its URL either way.
the server's address --pds <url> (didbot-swarm), DIDBOT_PDS (didbot-agentd, didbot-oauth), DIDBOT_PDS_URL (didbot-setup env, dev-swarm.sh), <hostname> (didbot-claim), --server <did> (didbot-verify), --zone <host> (didbot-pds, its own). Six spellings for one thing.
the DID --owner <did> in didbot-pds and didbot-verify; positional in didbot-verify (agent) and didbot-claim (operator, or a handle); forbidden in didbot-oauth.
state directory didbot-pds: --data <dir>, memory without it. didbot-setup: $XDG_CONFIG_HOME/didbot/stack.toml and $XDG_STATE_HOME/didbot/<service> (didbot_stack::default_config_path, default_data_dir). dev-pds.sh: $XDG_STATE_HOME/didbot/pds, spelled in shell. Sockets: $XDG_RUNTIME_DIR/didbot-agent/agent.sock (socket::default_socket_path), $XDG_RUNTIME_DIR/didbot-estop/estop.sock (estop_admin::default_socket_path), $XDG_RUNTIME_DIR/didbot/<name>-<port>.pid (dev-pidfile.sh). The other four keep nothing.
errors prefixed didbot-setup: (didbot-setup.rs:63) and didbot-oauth <verb>: (fail); bare eprintln! in didbot-claim, didbot-verify, didbot-brand; a tracing error! line in didbot-pds, didbot-swarm, didbot-agentd.
bad arguments stdout and exit 2 in didbot-pds and didbot-swarm (println!("{message}"); ExitCode::from(2)); stderr and exit 2 in didbot-verify; stderr and exit 1 in didbot-setup, didbot-oauth, didbot-claim, didbot-brand.
--help exit 0 on stdout in didbot-setup, didbot-oauth, didbot-brand; exit 2 on stdout in didbot-pds and didbot-swarm, because -h is an Err(USAGE); exit 2 on stderr in didbot-verify, whose -h is Err("what to check:"); in didbot-claim it is a missing operator, exit 1; in didbot-agentd it starts the daemon.
logging RUST_LOG, else a default: didbot_serve::DEFAULT_LOG_FILTER for didbot-pds and didbot-claim (init_tracing), info for didbot-swarm, didbot_agentd=info for didbot-agentd. didbot-stack carries its own copy of the filter (lib.rs:43).

One rule for binaries, one for verbs #

  • today under the rules why
    didbot-pds [flags] didbot-pds [flags] the server's seat; one thing to do; no object
    didbot-agentd didbot-agentd serve the host's seat. It gains verbs the moment host identity lands, and a binary with verbs has one for its main job too
    (socket become, branch) didbot-agentd reserve <server> — proposed the server-side transition is a reservation (ReservationPolicy, --pending-reservations, the list-reservations route on the branch), and the verb says what the caller asks for
    (socket host, branch) didbot-agentd show — proposed what show means in didbot-setup and didbot-oauth: print what this seat is. The key, and the identity once a server has reserved one
    didbot-oauth pending|show|approve|decline didbot-agent pending|show|approve|decline — proposed the agent's seat. oauth is the protocol the answers travel over, not who is answering
    didbot-claim <hostname> <operator> didbot-operator claim <hostname> <operator> — proposed the operator's seat, and the name of the record it writes
    didbot-claim --check <hostname> didbot-operator check <hostname> — proposed a second kind of run is a second verb, as didbot-setup check already is
    didbot-verify <agent-did> [--owner] didbot-operator verify <agent-did> [--owner <did>] — proposed reads bot.did.operator; the question is an operator question, and it holds nothing, so sharing a binary with claim leaks nothing
    didbot-verify --owner --server didbot-operator survey --owner <did> --server <did> — proposed the parser already refuses both at once with "check one agent, or survey a server"; the verb is the one the binary uses for itself
    didbot-setup service add / profile new didbot-setup service add / profile add one action, one verb
    didbot-setup bind|unbind|check|env|show|debug|disk unchanged the developer's seat, named for what it describes
    didbot-swarm [flags] didbot-swarm [flags] the developer's seat; no object
    didbot-brand <dir> / --check <dir> didbot-brand write <dir> / check <dir> the same object, two things done to it

    The cost is in the docs: didbot-claim is named 27 times across plan/, docs/ and the README, didbot-oauth 16, didbot-verify 8, and docs/deployment.md is a runbook. The harness adapter speaks the socket, not the binary, so didbot-agent costs nothing in the other repository.

Which crate, by seat #

What a seventh binary must do #

Defects, as distinct from conventions #

Each of these is a disagreement between two places in the code, not a choice nobody made.

Done #

Nothing closed yet.