didbot-stack #
Which services exist on this machine, and which one a directory talks to.
Nothing here starts, stops or contacts anything. It says what is supposed to exist, so that a hook, a shell script and a doctor command all answer the same question the same way.
The three tables #
~/.config/didbot/stack.toml, and a machine with no such file behaves
exactly as one whose file says what Config::builtin says — one of everything,
on the ports scripts/dev-*.sh already use.
[service.pds] kind = "pds" port = 3000 zone = "agents.localhost"
[service.mcp-a] kind = "mcp" port = 3001 pds = "pds"
[service.mcp-b] kind = "mcp" port = 3011 pds = "pds"
[service.index] kind = "index" port = 3002 pds = "pds"
[service.query] kind = "query" port = 3003 index = "index"
[profile.default] pds = "pds" mcp = "mcp-a" index = "index" query = "query"
[profile.b] pds = "pds" mcp = "mcp-b" index = "index" query = "query"
[bind]
"/home/dev/repo-b" = "b"
A service is one process. A profile names one service per role. A binding maps a directory to a profile. Sharing is what happens when two profiles name the same service, which is why the example above is two scrobble hosts over one personal data server rather than two of everything.
didbot-setup service add, profile new and bind write these tables a
flag at a time and refuse anything that would not check out; show prints what
the file holds.
Why roles rather than whole stacks #
Because the interesting compositions are partial. Two scrobble hosts sharing one personal data server — so that rebuilding one does not interrupt the other — is the first case that comes up, and a model where a stack is an atomic unit of five processes cannot express it.
Why the directory is the key #
It is the only identifier every consumer already has. A hook payload carries the working directory, a shell script has one, and the harness keys its own per-project configuration on the same absolute path.
Resolution is longest-prefix, and a directory nobody bound resolves to the
profile named default. That is what makes a worktree created five minutes
ago work without anyone registering it.
What is checked #
Config::problems reports a profile naming a service that is not there or is
the wrong kind, a service naming an upstream that is not there, two services
wanting one address, a binding to a profile that does not exist, and a machine
with no default profile.
The one that is worth the whole table: a profile whose scrobble host writes to a different personal data server than the profile mints accounts on. That failure is otherwise silent — the hook provisions on one server and the scrobble carrying that account's DID is written to another, which has never heard of it.
Whether a port is actually held, and by what, is a question for the machine
rather than for the file. That belongs to didbot-setup check.