One machine runs the station. Everything below is a path on it, and the first question when something is wrong is usually "which of these did I forget exists".
The agents
Each long-lived agent is a directory under ~/agents/<name>/, and the directory is the identity: same files, same memory, every day, however many times the process restarts.
| path | what it is |
|---|---|
~/agents/<name>/home/AGENTS.md | the brief: the whole standing orders for that agent, read at the start of every session |
~/agents/<name>/home/crush.json | the agent's MCP servers: its own Switchboard endpoint, Cairn, and for zathras the phone |
~/agents/<name>/env | its credentials, mode 0600: the Switchboard token, the forge token, the Cairn token, its callsign |
~/agents/<name>/gitconfig | its git identity and the credential helper for the forge, selected with GIT_CONFIG_GLOBAL |
~/agents/<name>/data/ | the crush session database and logs; the agent's memory of what it did |
The agents on shift are zathras (the worker; owns the inbox queue, the webhooks and the phone), ivanova (the reviewer; owns the reviews queue and nothing else) and franklin (the pathologist; every CI failure becomes a control and a case). Adding one is a procedure, not an improvisation: the new-agent skill provisions the endpoint, the forge user, the Cairn token, the directory and the supervisor drop-in, and writes the brief first.
The repos
| path | what it is |
|---|---|
~/agents/repos/<name> | one persistent clone per repo the station works on, with a gitea remote pointing at the private forge. Repo names lose their hyphens here: the GitHub repo software-observatory is softwareobservatory on gitea |
~/agents/worktrees/<repo>/<task> | where the actual work happens. A reviewer checks a pull request out here and removes it when the todo closes; a worker branches here rather than dirtying the clone |
~/agents/sweeps/ | the working directory of the scheduled one-shot sweeps, with a CLAUDE.md that is their whole brief |
~/agents/cybrahms/ | the station's GitHub-facing identity: a git config, a GPG key, and the tokens an attended session uses to commit and push as cybrahms |
Never clone into /tmp. A clone there has one remote, the public one, and an agent working in it will reach the end of the job and find it has no way to open the pull request.
The supervisor
harness keeps every agent alive and runs the sweeps on a schedule. It is a user service, and it reads one main file plus a drop-in per harness.
| path | what it is |
|---|---|
~/.config/harness/harness.toml | the daemon's own settings and the drop-in directory |
~/.config/harness/harness.d/<name>.toml | one per agent (a crush session, restart always, its workdir and env file) and one per sweep (a claude-code one-shot with a prompt file, a cron schedule, a timeout) |
~/.config/harness/prompts/<sweep>.md | the prompt a sweep runs with; a fresh session every time, so the prompt is the whole brief |
Drop-ins are not watched: after adding one, harness reload. To see what is running, harness list; to sit with an agent, harness attach <name>.
The queue, the pastebin, the phone
| path | what it is |
|---|---|
~/.config/switchboard/env | the queue's own settings: address, database, base URL |
~/.config/switchboard/vend/ | the vended credentials: one env per agent endpoint, one record per webhook, written when each was created |
~/.config/sbpush/env | the ingest URLs sbpush posts to, one per agent that has a generic webhook |
~/.config/tg-bridge/env | the bot token, the paired chat, and the ingest URL the phone delivers to |
The tools
Two directories hold the command-line tools; both are on every agent's path.
| tool | where | what for |
|---|---|---|
forge | ~/.local/bin | gitea from the command line: pull requests, checks, waiting for a merge. Use it instead of curl against the API |
sbpush | ~/.local/bin | post one todo to an agent's inbox from a shell |
sbmcp | ~/.local/bin | call one Switchboard MCP tool as a given endpoint; how a person lists a queue or reads a routing rule |
tg-bridge | ~/.local/bin | the Telegram poller |
crush | ~/.local/bin | the agent runtime the persistent crew runs on |
deaddrop, hacklog, knowledge | ~/.local/bin | the shared notebook, the public build log, the fact store |
harness, switchboard, cairn | ~/go/bin | the supervisor, the queue and the pastebin themselves |
The services
All user services, one launchd plist each under the space.sublayer name.
| service | port | what it is |
|---|---|---|
| gitea | 3000 | the forge. Every repo, every pull request, every CI run |
| act-runner | — | gitea's Actions runner: it runs each repo's suite and its deploy target |
| switchboard | 8090 | the queue |
| cairn | 8091 | the pastebin; bodies in MinIO |
| minio | — | object storage for Cairn |
| postgresql 17 | 5432 | one database server for switchboard and cairn |
| caddy | 8443 | TLS on the tailnet: gitea, switchboard and cairn as <name>.sublayer.space |
| cert-sync | — | keeps caddy's tailnet certificates fresh |
| pg-backup | — | the database backups the health sweep checks for |
| harness | — | the supervisor |
| tg-bridge | — | the phone |
Nothing on this list is reachable from the public internet. The three public sites, this one, hacklog and medlab, are static directories rsynced onto a separate host by gitea's runner after a green merge to master.
Which file to open
- An agent did something odd: its brief, then its session database under
~/agents/<name>/data/. - A todo went to the wrong agent, or nowhere: the routing rules, read with
sbmcp <env> list_webhook_rules. - An agent is down:
harness list, then its drop-in. - A sweep did not fire: the drop-in's schedule, then
harness listfor the last run. - Something needs a credential: the agent's
env, the vend directory, and never a chat transcript.