id: dev-setup title: One command takes a new machine to a working stack status: open crates: [didbot-serve, didbot-agentd] dependsOn: [] exitCriterion: > On a machine that has never run this, one command ends with an agent provisioned, a record written and read back, and the canvas showing it — having asked for consent before each thing it could not do itself. #
dev-setup #
local-dev is the loop once you are in it: five terminals and a swarm. This is getting there. The gap is not the scripts, it is everything around them — a certificate, a name, an edit to the user's harness configuration, and a restart before any of it takes effect.
The shape is a command that asks rather than assumes. It touches a person's settings file and possibly their trust store, so it says what it will do, does the parts it may, and stops with a specific instruction for the parts it may not.
What the shape is #
One set of defaults per machine, in scripts/dev-profile.sh: the port, the
zone and the state directory, each overridable from the environment. A second
stack on one machine is a second set of those variables, or a second checkout.
Each daemon knows its own default state directory under $XDG_STATE_HOME
(didbot-agentd's default_state_dir, dev-pds.sh's --data), so nothing
has to be told where anything lives before it can start.
Installing an agent host — the daemon's unit file, the plugin, the hooks — is
the agent host's installer's job, which is shell owned by the didbot-claude
plugin: it writes the unit files and reports whether the plugin is present.
Where accounts come from, and the bootstrap #
Hooks are captured at session start, so a session that installs them is never itself hooked — and neither are its subagents, which inherit the session's captured configuration rather than re-reading disk. A directory is therefore set up from outside it, before a session starts there.
Recovery is a different problem from bootstrap, and a more frequent one: a
server that was down when the session opened, or one restarted without
--data. SessionStart cannot be replayed, so PreToolUse provisions as
well, and the first record of a session with no usable account mints one. The
identity still comes from the harness payload and never from the model, which
is why this is a hook rather than something the record host offers: the Model
Context Protocol carries no verified caller identity, so a tool that let a
session register itself would let a model claim to be any session.
Setup #
-
`mkcert` is declined. It installs a certificate authority into a person's trust store, which is the most invasive thing this command could do, and it destroys the value of the test it exists for: a third-party client either holds that authority, in which case its trust path is not the one it uses in production, or it does not, in which case it fails for a reason that teaches nothing. No public authority would issue `*.agents.localhost` either, so the certificate's shape differs from production regardless. A public wildcard-to-loopback certificate with a published key needs no trust store change and works in any client, but it forces somebody else's hostname suffix — so the zone differs from production, so the `did:web` values differ, and the DID is the field everything downstream keys on. That leaves the delegated development subzone, the only one where local is identical to production, and it needs a DNS credential on a developer laptop that [deployment](../docs/deployment.md) says should not sit beside the signing keys. It waits for a zone. Nothing is blocked on this. TLS is load-bearing for [pds-xrpc](pds-xrpc.md), which is `blocked`, and for [oauth](oauth.md). Plain HTTP over `*.localhost` stays the default, because it works offline, in seconds, and with no secret.