switchroom 0.12.12 → 0.12.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img src="docs/assets/switchroom-hero-wide.png" alt="Switchroom opinionated Telegram UX for Claude Code on your Pro or Max subscription" width="100%">
2
+ <img src="docs/assets/switchroom-hero-wide.png" alt="Switchroom: opinionated Telegram UX for Claude Code on your Pro or Max subscription" width="100%">
3
3
  </p>
4
4
 
5
5
  # Switchroom
@@ -10,176 +10,76 @@
10
10
  [![Trigger evals](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2Fmekenthompson%2F002f3482b19111d35e57c1903b3733e2%2Fraw%2Fswitchroom-trigger-evals.json)](https://github.com/switchroom/switchroom/actions/workflows/ci-evals.yml)
11
11
  [![Quality evals](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2Fmekenthompson%2F002f3482b19111d35e57c1903b3733e2%2Fraw%2Fswitchroom-quality-evals.json)](https://github.com/switchroom/switchroom/actions/workflows/ci-evals.yml)
12
12
 
13
- **A switchboard for your Pro or Max.** Your Claude subscription, as a fleet of always-on specialist agents you talk to from Telegram. Opinionated UX, done properly.
14
-
15
- > *I loved OpenClaw + Telegram. I wanted my Claude subscription. And the UX done properly. So I built this.*
13
+ **A switchboard for your Pro or Max.** Your Claude subscription, run as a fleet of always-on specialist agents you talk to from Telegram. Opinionated UX, done properly.
16
14
 
17
15
  [Latest release notes →](CHANGELOG.md)
18
16
 
19
- ## See what your agent is doing
20
-
21
- Every time an agent starts work, a **progress card** pins into its Telegram topic and updates in place as tools execute. Each Read, Bash, Edit, Grep is visible as it happens, with elapsed time so you can tell if something's stuck. Sub-agents surface in the same card. When the agent finishes, the card flips to Done and unpins.
22
-
23
- No silent gaps. No ghosts. No squinting into a black box.
24
-
25
- <p align="center"><img src="docs/diagrams/progress-card-anatomy.svg" width="700" alt="Annotated progress card: pin badge, user quote, last 5 steps, collapsed older, in-flight pulse, elapsed timer, sub-agent indent"></p>
26
-
27
- ```
28
- ⚙️ Working… · ⏱ 12s
29
- 💬 refactor the auth module to use JWT
30
- ─ ─ ─
31
- … (+3 more earlier steps)
32
- ✅ Read src/auth/session.ts
33
- ✅ Grep "cookie" (in src/)
34
- 🤖 Edit src/auth/jwt.ts · 4s
35
- ```
36
-
37
- The card is the headline UX. The rest of the product is in service of it.
17
+ ## Why this exists
38
18
 
39
- - Cards update at most once every 5 seconds. Fast enough to follow, not so fast it floods.
40
- - Last 5 steps stay visible. Older ones collapse into `(+N more earlier steps)`.
41
- - Running steps show elapsed time so a stuck tool is obvious.
42
- - Tool labels are deterministic, written by a `PreToolUse` hook, so the card never lies about what's running.
43
- - Two agents working at once? Each gets its own card, labelled `(1/2)` and `(2/2)`.
44
-
45
- ### Sub-agent visibility
19
+ > *I loved OpenClaw + Telegram. I wanted my Claude subscription. And the UX done properly. So I built this.*
46
20
 
47
- When an agent delegates to a sub-agent — Opus plans, Sonnet implements — the sub-agent's work shows up indented inside the parent's pinned card. One pinned surface per task, however many processes it spawns underneath. Nothing gets buried in a side-channel you have to go look for.
21
+ You had the obvious idea. Run Claude Code agents 24/7 on a cheap Linux box, talk to them from Telegram, use the Pro or Max subscription you already pay for.
48
22
 
49
- ### Right, so what's this about
23
+ Two ways to do that today. Both miss:
50
24
 
51
- So you had the bright idea. Run Claude Code agents 24/7 on a cheap Linux box, talk to them from Telegram, use the Claude Pro or Max subscription you're already paying for. Sensible. Obvious, even.
25
+ - **OpenClaw + Telegram.** Great UX. But it hits the Anthropic API on your own key, so a token bill ticks over in the background. You signed up to use your subscription, not to buy API credits on top of it.
26
+ - **Claude Code's built-in Telegram channel.** Uses the subscription correctly. But it's an MVP black box. Send a message, wait, eventually something comes back. What did the agent actually do? Which tools ran? Did it get stuck? No idea.
52
27
 
53
- Then you tried OpenClaw. Followed the docs, spun it up, got it running, only to realise halfway through that you're pinging the Anthropic API on your own key and your token bill is quietly ticking over in the background. Bit of a bait and switch, that one. You signed up for "use your subscription," not "buy API credits on top of your subscription."
28
+ Switchroom is the third option. Subscription-honest, and the UX done properly. The headline: the agent is never a black box and never silent. You always see what it is doing, what it is waiting on, and when it is done.
54
29
 
55
- So you gave Claude Code's built-in Telegram channel a crack instead. Sent a message. Waited. Something happened, maybe. Eventually a reply came back. What did the agent actually do? No idea. Which tools ran? No idea. Did it get stuck, crash, spawn a sub-agent, read half your repo? No idea. It's an MVP black box of death, and I got sick of squinting into it.
30
+ ## See it work
56
31
 
57
- So I built this.
58
-
59
- ## What you get
60
-
61
- | Feature | What it does |
62
- |---|---|
63
- | **Progress cards** | Pinned, in-place, every tool call visible. The headline UX. |
64
- | **Claude Pro/Max auth** | OAuth, not API keys. No per-token billing. Fleet-wide active account + fallback order; broker-owned refresh and credential fanout. |
65
- | **Approval kernel** | Inline allow/deny cards in Telegram for every gated tool. TTL'd grants, full audit trail. |
66
- | **Sub-agents** | Opus plans, Sonnet implements. Sub-agent work surfaces in the parent card. |
67
- | **Config cascade** | Defaults, then profiles, then per-agent YAML. Change one line, every agent updates. |
68
- | **Scheduled tasks** | Cron-syntax tasks that fire across reboots. Headless secret access via the vault broker. |
69
- | **Persistent memory** | Hindsight semantic memory with knowledge graphs and mental models. |
70
- | **Session continuity** | Resume across restarts with freshness gating and a wake-audit. |
71
- | **Encrypted vault** | AES-256-GCM for secrets. Optional auto-unlock keyed off `/etc/machine-id`. |
72
- | **Drive MCP** | Read Google Docs, Sheets, and Drive files inline. Per-agent OAuth, no shared key. |
73
- | **Card audit log** | Every progress-card edit appended to `card-events.jsonl` for retrospective debugging. |
74
- | **15 Telegram MCP tools** | Reply, stream replies, edit, pin, react, native checklists, sticker aliases, voice-in transcription, attachments, history. |
75
-
76
- ## Architecture
77
-
78
- One long-running service per agent. Each agent runs the stock `claude` CLI — not a fork, not the Agents SDK, not a wrapped harness — authenticated directly with Anthropic via official OAuth. Switchroom is scaffolding and lifecycle around the CLI you'd run by hand: a Telegram bot, an approval broker, a vault broker, and Docker Compose for supervision. See [`docs/architecture.md`](docs/architecture.md) for the process model and how each layer maps to the `claude` CLI.
32
+ Switchroom's UX is deterministic. Every agent topic carries a status line that is always one of three honest states, never a guess and never nothing:
79
33
 
80
34
  ```
81
- You (Telegram)
82
-
83
-
84
- @YourBot ──┬── switchroom-telegram MCP ──┬── agent supervisor ─── Claude Code CLI
85
- │ (15 tools) │ (per-agent) │
86
- │ │ ├─ .claude/agents/*.md (sub-agents)
87
- ├─ Progress cards ├─ Approval kernel ◄─────┤ settings.json (tools, hooks, MCP)
88
- ├─ Pin / unpin lifecycle │ (allow/deny broker) ├─ Hindsight plugin (memory)
89
- ├─ SQLite history ├─ Vault broker ◄────────┤ Drive MCP, Playwright MCP, …
90
- ├─ Card-events.jsonl audit ├─ Auth broker ◄─────────┤ in-agent scheduler sidecar
91
- ├─ Emoji reactions │ (OAuth refresh, └─ (cron, fires across reboots)
92
- └─ Format conversion │ sole creds writer)
93
- ├─ hostd (host-control:
94
- │ /restart, /update apply)
95
- └─ Docker Compose restart (unless-stopped)
35
+ 🟡 quiet · no turns yet
36
+ ⚙️ working since 2m ago
37
+ 🟢 idle · last reply 5m ago
96
38
  ```
97
39
 
98
- See [`docs/architecture.md`](docs/architecture.md) for the process model, IPC layout, supervisor choice, and how each layer maps to the `claude` CLI.
99
-
100
- ## Approvals & safety
101
-
102
- Tools that touch the world — Bash, Edit, Write, anything not on an agent's pre-approved allowlist — pause for explicit approval. Switchroom's **approval kernel** (shipped in v0.5.1) routes every gated tool call through an inline Telegram card with the actual diff or command shown. Tap Allow and the tool resumes. Tap Deny and the agent gets a clean refusal it can recover from.
103
-
104
- <p align="center"><img src="docs/diagrams/approval-grant-flow.svg" width="700" alt="Approval grant flow: agent tool call pauses at the kernel, broker writes pending grant to sqlite, user taps Allow on the Telegram card, broker releases the gate, tool resumes"></p>
105
-
106
- - **Inline cards.** Allow / Deny / Allow once / Allow for 1h. No leaving Telegram.
107
- - **TTL'd grants.** "Allow Bash for 1h" expires automatically. No silent permanent escalation.
108
- - **Audit trail.** Every grant, denial, and expiry written to a per-agent log you can replay.
109
- - **Per-agent allowlist.** `switchroom agent grant <name> <tool>` for the boring ones you don't want to be asked about.
110
-
111
- The kernel runs as an out-of-process broker over a unix socket. The agent process never decides its own permissions; it asks and waits.
112
-
113
- ### Compliance posture
114
-
115
- Switchroom never intercepts auth, never proxies inference, never patches the CLI. The `claude` binary you run is the one Anthropic ships. See the [Compliance Attestation](docs/compliance-attestation.md) for the full analysis against Anthropic's April 2026 third-party policy.
116
-
117
- ## Survives real life
118
-
119
- Each agent is a long-running service. They survive reboots, network drops, and your laptop closing. But "always on" isn't enough on its own. Things still die. The product has to handle that gracefully or the illusion breaks.
120
-
121
- <p align="center"><img src="docs/diagrams/wake-audit-lifecycle.svg" width="700" alt="Wake-audit lifecycle: kill, crash-pane snapshot, auto-restart, agent boots with SWITCHROOM_PENDING_TURN, acks with three options"></p>
122
-
123
- - **Auto-restart.** Agent containers come up with `restart: unless-stopped`, and each service has a healthcheck — a crashed or wedged agent is brought back automatically. No silent dropped work.
124
- - **Resume protocol.** When an agent reboots mid-turn, `start.sh` exports `SWITCHROOM_PENDING_TURN=true` plus the original chat / message ids. The agent's first action on boot is to acknowledge the gap and ask the user how to proceed (start over, summarise and continue, or drop it).
125
- - **Wake-audit.** On every fresh boot the agent checks for owed replies, orphan sub-agents, and stale in-progress todos. If everything's clean it stays quiet. If it owed you a reply, it tells you.
126
- - **Token refresh.** The `switchroom-auth-broker` daemon owns the refresh loop and is the sole writer of every `credentials.json`. Per-account quota state fans out across the fleet in seconds; `auth.fallback_order` cycles when an account is exhausted.
40
+ While the agent works, the reply streams in place (about once a second, not one delayed dump at the end) and tool steps render as they run with `MM:SS` durations. Long-running work gets a Steer button so you can redirect mid-turn without killing it. When an agent delegates, a sub-agent block shows the live fleet, up to 5 members, each with its role and current tool:
127
41
 
128
- ## How it stacks up
42
+ ```
43
+ ⚙️ Working… · 00:12
44
+ refactor the auth module to use JWT
129
45
 
130
- | | Switchroom | Claude Code channels | OpenClaw | NanoClaw |
131
- |---|---|---|---|---|
132
- | Progress visibility | Live cards, pinned | Black box | None | None |
133
- | Runtime | Claude Code CLI | Claude Code CLI | Custom runtime | Agents SDK |
134
- | Auth | Pro/Max OAuth | Pro/Max OAuth | API key | API key |
135
- | Sub-agent tracking | Yes, in card | No | No | No |
136
- | Parallel task display | Labelled cards `(1/N)` | No | No | No |
137
- | Approval UX | Inline Telegram cards | None | None | None |
138
- | Config | YAML with cascade | None | JSON/TOML | Env vars |
139
- | Setup | `switchroom setup` | Built-in (limited) | Docker compose | Docker compose |
46
+ Read session.ts
47
+ ✅ Grep "cookie"
48
+ 🔵 Edit jwt.ts · 00:04
140
49
 
141
- The wedge against OpenClaw and NanoClaw isn't the substrate — it's the stock `claude` CLI under your subscription, instead of a custom runtime under your API key.
50
+ Sub-agents · 2 running
51
+ 🔵 implement · Edit jwt.ts
52
+ 🔵 test-runner · Bash npm test
53
+ [ Steer ]
54
+ ```
142
55
 
143
- ## Install
56
+ <p align="center"><img src="docs/diagrams/deterministic-status-anatomy.svg" width="760" alt="Deterministic status anatomy: the never-silent state strip (quiet, working, idle) feeding a working-status message with streamed tool steps at MM:SS, a sub-agent fleet block, and a Steer button. Posted and edited in the topic, never pinned."></p>
144
57
 
145
- Runs on the box you already have. The supported production runtime is Linux + Docker. **Canonical target: Ubuntu 24.04 LTS with ≥4 GiB RAM** (8 GiB recommended once you run more than one agent). Other Debian-derivatives work with the same script; non-apt distros need a manual prereq install. macOS (Docker Desktop) works for development but is not yet release-validated.
58
+ Older sub-agents collapse to `+N more` once the live set passes 5. Tool arguments are sanitised before they render: file paths show the basename only, token-shaped strings get redacted, so a secret never lands in a status message. The card is posted and edited in the topic, not pinned, so it never leaves a stale `⚙️ Working…` stranded at the top of your chat. [Full UX behaviour →](docs/telegram-plugin.md)
146
59
 
147
- > **Full new-user walkthrough — [`docs/install.md`](docs/install.md).** Zero to first Telegram message in ~15 minutes. Includes the [BotFather walkthrough](docs/botfather-walkthrough.md). Read that first if you're installing from scratch.
60
+ ## Quickstart
148
61
 
149
- > **Heads up on the package name.** The npm package was originally `switchroom-ai`. It's now just `switchroom`. The old name is deprecated and will stop receiving updates `npm install -g switchroom` is the current path.
62
+ Runs on the box you already have. Supported production runtime is Linux + Docker. Canonical target: Ubuntu 24.04 LTS, 4 GiB RAM minimum, 8 GiB once you run more than one agent. macOS (Docker Desktop) works for development, not yet release-validated.
150
63
 
151
- ### Fresh Linux box one script
64
+ **Fresh Linux box, one script:**
152
65
 
153
66
  ```bash
154
67
  curl -fsSL https://github.com/switchroom/switchroom/raw/main/scripts/install-deps.sh | sudo bash
155
68
  ```
156
69
 
157
- Installs Docker Engine + Compose v2, Node.js 20.11+, Bun, and the `@anthropic-ai/claude-code` + `switchroom` CLIs. Idempotent. Adds the invoking user to the `docker` group. Tested on Ubuntu 24.04 LTS and 26.04 LTS. Warns (does not block) on hosts under 4 GiB RAM.
158
-
159
- Then log out and back in so the docker group takes effect, and:
70
+ Installs Docker Engine + Compose v2, Node.js 20.11+, Bun, and the `@anthropic-ai/claude-code` + `switchroom` CLIs. Idempotent. Log out and back in so the docker group takes effect, then:
160
71
 
161
72
  ```bash
162
73
  switchroom setup # interactive: Telegram + vault + first agent
163
- switchroom apply # regenerate ~/.switchroom/compose/docker-compose.yml
164
- docker compose -p switchroom -f ~/.switchroom/compose/docker-compose.yml up -d # bring the fleet up
165
- switchroom auth add default --via-claude # OAuth your Claude Pro/Max account — run AFTER the fleet is up
74
+ switchroom apply # generate ~/.switchroom/compose/docker-compose.yml
75
+ docker compose -p switchroom -f ~/.switchroom/compose/docker-compose.yml up -d
76
+ switchroom auth add default --via-claude # OAuth your Pro/Max account, AFTER the fleet is up
166
77
  switchroom auth use default # make it the fleet-wide active account
167
78
  ```
168
79
 
169
- Auth comes *after* the fleet is up on purpose: the `switchroom-auth-broker` is the sole writer of credentials and doesn't exist until the compose stack is running (`switchroom auth …` beforehand just prints a "fleet not up" hint). After this you talk to the agent from Telegram and don't touch the server again. To catch a running host up later — pull images, refresh scaffolds, recreate — use **`switchroom update`**, not a raw `docker compose up` (a bare compose-up on a live fleet skips the operator restart-marker, so the boot cards render as crashes).
80
+ Auth comes after the fleet is up on purpose. The `switchroom-auth-broker` is the sole writer of credentials and does not exist until the compose stack is running. After this you talk to the agent from Telegram and never touch the server again. To catch a running host up later use `switchroom update`, not a raw `docker compose up` (a bare compose-up on a live fleet skips the operator restart-marker, so boot cards render as crashes).
170
81
 
171
- ### Already have Docker + Node 20.11
172
-
173
- ```bash
174
- sudo npm install -g bun @anthropic-ai/claude-code switchroom
175
- switchroom setup
176
- ```
177
-
178
- `bun` is a hard runtime dep — the `switchroom` CLI's entrypoint is a Bun script. A Node-only CLI build is on the roadmap but not yet shipped.
179
-
180
- ### From inside Claude Code (the on-ramp)
181
-
182
- If you already use Claude Code, this is the shortest path. Inside any session:
82
+ **Already in Claude Code?** Shortest path. Inside any session:
183
83
 
184
84
  ```
185
85
  /plugin marketplace add switchroom/switchroom
@@ -187,237 +87,88 @@ If you already use Claude Code, this is the shortest path. Inside any session:
187
87
  /switchroom:setup
188
88
  ```
189
89
 
190
- `/switchroom:setup` walks you through deps, `switchroom setup` (Telegram + vault + first agent), and `switchroom agent start`. Day-to-day: `/switchroom:start`, `/switchroom:stop`, `/switchroom:status`. See [`docs/publishing.md`](docs/publishing.md).
191
-
192
- ### Static binary (planned, not yet shipped)
193
-
194
- Pre-built single-binary releases (no Node or Bun required on the host) are scaffolded in [`install.sh`](install.sh) and referenced from the GitHub Releases page, but **the release workflow that publishes those binaries still does not exist as of v0.12.0**. Use the one-script or npm paths above. Tracking work: switching the release pipeline to actually upload `switchroom-linux-{amd64,arm64}` and `switchroom-macos-{amd64,arm64}` on every tag.
195
-
196
- ### One-shot happy path (no wizard)
197
-
198
- If you already have Telegram credentials in `~/.switchroom/switchroom.yaml` and one Anthropic account already added, skip `switchroom setup`. `agent create --profile` writes a minimal entry; the new agent inherits the fleet-wide active account automatically — no per-agent OAuth flow:
199
-
200
- ```bash
201
- switchroom agent create coach --profile health-coach
202
- switchroom apply && docker compose -p switchroom -f ~/.switchroom/compose/docker-compose.yml up -d
203
- ```
204
-
205
- ## Example configuration
206
-
207
- ```yaml
208
- switchroom:
209
- version: 1
210
-
211
- telegram:
212
- # Per-agent bot token (DM-only by default).
213
- bot_token: "vault:telegram-bot-token"
214
-
215
- memory:
216
- backend: hindsight
217
-
218
- defaults:
219
- model: claude-opus-4-7 # or claude-sonnet-4-6, claude-haiku-4-5
220
- tools: { allow: [all] }
221
- subagents:
222
- worker:
223
- description: "Implementation tasks"
224
- model: sonnet
225
- background: true
226
- isolation: worktree
227
- schedule:
228
- - cron: "0 8 * * 1-5"
229
- prompt: "Morning briefing"
230
- session:
231
- max_idle: 2h
232
-
233
- agents:
234
- assistant:
235
- topic_name: "General"
236
- memory: { collection: general }
237
-
238
- coach:
239
- topic_name: "Coach"
240
- extends: advisor
241
- soul:
242
- name: Coach
243
- ```
244
-
245
- See [docs/configuration.md](docs/configuration.md) for the full reference.
246
-
247
- ## Vault broker (cron secrets)
248
-
249
- Scheduled tasks run headless inside the agent container, so they can't prompt for the vault passphrase. The vault broker is a long-running container (`switchroom-vault-broker`) that holds the vault decrypted in memory after a one-time interactive unlock. Cron tasks fetch the specific keys they declare via a per-agent unix socket. The passphrase never sits on disk.
250
-
251
- **Declare per-cron secrets in `switchroom.yaml`:**
90
+ Full new-user walkthrough, zero to first Telegram message in ~15 minutes, plus the npm-only path, the no-wizard one-shot, the BotFather steps, and static-binary status: **[docs/install.md](docs/install.md)**.
252
91
 
253
- ```yaml
254
- agents:
255
- scout:
256
- schedule:
257
- - cron: "0 8 * * *"
258
- prompt: "Morning brief."
259
- secrets: [openai_api_key, polygon_api_key] # only these may be read
260
- ```
261
-
262
- `secrets: []` (the default) means the cron has no vault access.
263
-
264
- **Bootstrap once per host:**
265
-
266
- ```bash
267
- switchroom apply # writes broker into docker-compose.yml
268
- docker compose -p switchroom -f ~/.switchroom/compose/docker-compose.yml up -d switchroom-vault-broker
269
- switchroom vault broker unlock # prompt for passphrase, primes broker
270
- ```
271
-
272
- Or just run `switchroom vault get <key>` from a TTY. The broker offers to take the unlocked state with `[Y/n]` so you don't have to remember a separate unlock command.
273
-
274
- **Identity model (v0.7+).** Path-as-identity. The broker binds one socket per agent at `/run/switchroom/broker/<agent>/sock` inside its own container, hosted via a per-agent named volume that's also mounted at `/run/switchroom/broker/` inside `agent-<agent>`. The agent name is parsed unspoofably from the bind path — see `src/vault/broker/peercred.ts:socketPathToAgent()`. A compromised agent cannot pose as another agent's cron because it only ever sees its own socket on its mount. ACL is bind-time, never wire-time.
275
-
276
- The broker locks on `SIGTERM` (so a container restart zeros the in-memory state) and on demand via `switchroom vault broker lock`. Use `switchroom vault get <key> --no-broker` to bypass and prompt locally.
277
-
278
- Vault file (post-v0.7.12) lives at `~/.switchroom/vault/vault.enc` — a directory, not a single file, so atomic rename can use the parent as the staging dir. See [docs/vault.md](docs/vault.md) for the layout rationale.
279
-
280
- ### Auto-unlock on boot (opt-in)
281
-
282
- By default, the broker holds the unlocked state in memory only. Every restart (host reboot, service crash, reconcile that re-renders the unit) wipes it and requires `switchroom vault broker unlock` again. For unattended hosts where this is too painful, switchroom can encrypt the passphrase with a key derived from `/etc/machine-id` and have the broker unlock itself at boot:
283
-
284
- ```bash
285
- switchroom vault broker enable-auto-unlock # one-time setup, prompts for passphrase
286
- ```
287
-
288
- Done. The wizard prompts for your vault passphrase, encrypts it with AES-256-GCM keyed off `/etc/machine-id`, writes the result to `~/.switchroom/vault-auto-unlock` (mode 0600), flips `vault.broker.autoUnlock: true` in `switchroom.yaml`, restarts the broker, and verifies the vault came up unlocked. Every subsequent boot the broker reads + decrypts + unlocks itself.
289
-
290
- Disable with `switchroom vault broker disable-auto-unlock`.
291
-
292
- **Security tradeoff. Read this before enabling.** The encrypted blob lives at mode 0600 in your home directory; the encryption key is derived from `/etc/machine-id` plus a per-file random salt. Disk theft is safe (the blob doesn't decrypt on any other machine) and other UNIX users on the same box can't read it. But root on the host *can* read both the blob and the machine-id, so once root is on the machine the passphrase is recoverable. Same blast radius as the running broker process (anything with code-exec as you can already attach to the broker socket and exfiltrate secrets), but it shifts the convenience-vs-security knob: auto-unlock means a lost laptop is a lost vault even if the vault file itself is encrypted at rest. Use only on hosts you trust. See [docs/auto-unlock.md](docs/auto-unlock.md) for the full threat model and recovery instructions.
293
-
294
- ## CLI reference
295
-
296
- ```bash
297
- switchroom setup # Interactive wizard
298
- switchroom doctor # Health check
299
- switchroom apply # Reconcile + regenerate docker-compose.yml (self-elevates via sudo for scaffolds). Does NOT run docker — prints the `up` command
300
- switchroom update [--check|--status|--rebuild] # Operator catch-up: pull images + apply + recreate fleet + doctor
301
- switchroom restart [agent] [--force] # Bounce agent(s); drains in-flight turn by default
302
- switchroom version # Show versions + running agent health summary
303
-
304
- switchroom agent list # Status of all agents
305
- switchroom agent status <name> # Status of one agent
306
- switchroom agent add [name] # Wizard: scaffold a new agent end-to-end (#543)
307
- switchroom agent create <name> [--profile <p>] # Scaffold + install timers; --profile writes yaml entry
308
- switchroom agent bootstrap <name> --profile <p> --bot-token <t> # One-shot scaffold + auth + start
309
- switchroom agent reconcile <name|all> # Re-apply switchroom.yaml (without pulling/building)
310
- switchroom agent start|stop|restart <name> # Lifecycle (with preflight)
311
- switchroom agent interrupt <name> # Cancel in-flight turn without restarting
312
- switchroom agent unquarantine <name> # Clear a crash-quarantine and resume supervision
313
- switchroom agent rename <old> <new> # Rename an agent slug (#168)
314
- switchroom agent destroy <name> # Remove from compose + scaffold dir
315
- switchroom agent attach <name> # Interactive tmux session
316
- switchroom agent send <name> <slash-cmd> # Inject a slash command into the agent's tmux pane
317
- switchroom agent logs <name> [-f] # View logs
318
- switchroom agent grant <name> <tool> # Grant a tool permission
319
- switchroom agent permissions <name> # Show allow/deny list
320
- switchroom agent dangerous <name> [off] # Toggle full tool access
321
-
322
- switchroom soul path|show|reset <name> # Manage the agent's user-owned SOUL.md (persona)
323
- switchroom hostd install|status|uninstall|audit # Host-control daemon (/restart, /update apply, …)
324
- switchroom drive connect|disconnect <agent> # Per-agent Google Drive OAuth
325
- ```
92
+ ## What you get
326
93
 
327
- `switchroom --help` lists every verb (also `deps`, `issues`, `migrate`).
94
+ | Feature | What it does |
95
+ |---|---|
96
+ | **Deterministic UX** | Every topic shows an honest state: quiet, working since, or idle. Never a black box, never silent. The headline. |
97
+ | **Live step streaming** | The reply streams in place while tools run, with durations and a Steer button to redirect mid-turn. Not pinned, no stale cards. |
98
+ | **Sub-agent fleet view** | Delegated workers surface as live rows (up to 5, then `+N more`), each with role and current tool. Args sanitised before render. |
99
+ | **Claude Pro/Max auth** | OAuth, not API keys. No per-token billing. Fleet-wide active account plus fallback order, broker-owned refresh and credential fanout. |
100
+ | **Vault + approval kernel** | AES-256-GCM secrets. Per-agent least-privilege ACL. Anything extra needs your tap on an inline Telegram Approve/Deny card. |
101
+ | **Sub-agents** | Opus plans, Sonnet implements. Sub-agent work surfaces in the fleet block. |
102
+ | **Config cascade** | Defaults, then profiles, then per-agent YAML. Change one line, every agent updates. |
103
+ | **Scheduled tasks** | Cron-syntax tasks that fire across reboots. Headless secret access through the vault broker. |
104
+ | **Persistent memory** | Hindsight semantic memory, including a per-user mental model that carries across conversations. |
105
+ | **Always-on** | Long-running service per agent. Survives reboots, network drops, your laptop closing. Resumes mid-turn with a wake-audit. |
106
+ | **17 Telegram MCP tools** | reply, stream, react, edit, pin, delete, forward, typing, history, checklists (+update), stickers, GIFs, attachment download, ask-user, vault request access/save. |
328
107
 
329
- Profiles live in `profiles/` at the repo root. Bundled ones for `--profile`: `coding`, `default`, `executive-assistant`, `health-coach` (the `_base/` dir is framework-internal render templates and is not a user-selectable profile).
108
+ ## Subscription-honest, and safe by default
330
109
 
331
- `switchroom agent create <name> --profile <profile>` does two things in one step:
110
+ **Stock CLI, real OAuth.** Each agent runs the unmodified `claude` binary, authenticated with Anthropic through the same OAuth flow you use on the desktop app. No API key. No harness. No patched CLI. No proxied inference. One bill, the one you already pay. See the [Compliance Attestation](docs/compliance-attestation.md) for the full analysis against Anthropic's April 2026 third-party policy.
332
111
 
333
- 1. Adds an entry to `switchroom.yaml` under `agents:` with `extends: <profile>` and a derived `topic_name` (capitalized agent name). Edit the yaml afterwards to change the topic name, emoji, tools, etc.
334
- 2. Scaffolds the agent directory and registers the agent in `docker-compose.yml` on next `switchroom apply` (same as running `agent create` on an entry that already exists in yaml).
112
+ **Least privilege, and you hold the keys.** This is the core opinion. An agent never holds the vault passphrase and never sees a secret it was not given. Secrets live in an AES-256-GCM vault. Each agent reaches the vault broker over its own socket whose identity is the bind path, so a compromised agent cannot pose as another. An agent can read a key only if you listed it for that agent's task: that is the standing, least-privilege ACL. Anything beyond that is just-in-time. The agent asks, you get an inline Telegram Approve/Deny card showing exactly what and why, and only your tap mints a scoped, expiring grant. The agent cannot self-elevate.
335
113
 
336
- If the agent is already in yaml, `--profile` must match the existing `extends:` value or it errors. If the yaml entry has no `extends:` and you pass `--profile`, the flag is written in additively with a warning. Running `agent create` with no `--profile` on a missing entry keeps the old "Agent not defined in switchroom.yaml" error, now with a hint to use `--profile`.
114
+ <p align="center"><img src="docs/diagrams/approval-grant-flow.svg" width="700" alt="Approval grant flow: agent requests a key it does not have, broker stages a pending grant, you tap Allow on the Telegram card, broker mints a scoped TTL grant, the read proceeds"></p>
337
115
 
338
- Model aliases: the bare names `opus`, `sonnet`, `haiku` are accepted alongside the full IDs (`claude-opus-4-7`, `claude-sonnet-4-6`, `claude-haiku-4-5`). Use whichever reads cleaner in your config.
116
+ **The approval kernel gates risky actions too.** A separate kernel daemon handles action approvals on the same model: an agent that wants to write to a Google Doc requests it, ends its turn, and waits. You see the diff and tap Allow or Deny. Nothing destructive happens on the agent's say-so alone. TTL'd decisions expire, and every grant and denial is logged.
339
117
 
340
- ### Authentication (one OAuth, many agents)
118
+ ### How agents collaborate on files
341
119
 
342
- The **Anthropic account is the unit of authentication.** One OAuth flow per account, then every agent in the fleet inherits the fleet-wide active account. The `switchroom-auth-broker` daemon owns the refresh loop and is the sole writer of every `credentials.json`. Per-account quota state fans out across the fleet in seconds. See [`docs/auth.md`](docs/auth.md) for the full operator guide.
120
+ Switchroom's position: agents should collaborate with you on real documents, not paste walls of text into chat. The supported path is Google Drive. An agent reads and proposes changes, and every write or suggestion is gated through the approval kernel exactly like a credential request, so an agent never silently edits your Drive. Today Drive is opt-in per agent: you connect a Google account and enable it for the agents that should have it (`switchroom drive connect <agent>`), and the broker enforces that allowlist at runtime. It is not on by default for a fresh agent.
343
121
 
344
- ```bash
345
- switchroom auth add <label> --via-claude # New account, broader scope — recommended for first-time
346
- switchroom auth add <label> --from-oauth # Narrow scope=user:inference (rejected by agents in server: mode)
347
- switchroom auth add <label> --from-agent <name> # Seed from an existing agent's creds
348
- switchroom auth add <label> --from-credentials <path> # Import a credentials.json
349
- switchroom auth add <label> --via-claude --replace # Re-auth an existing label (drift recovery)
350
-
351
- switchroom auth list # Accounts + health + which one is fleet-active
352
- switchroom auth show [agent] # Full snapshot (fleet + agents + consumers), or one agent
353
- switchroom auth use <label> # Fleet-wide active swap
354
- switchroom auth rotate # Cycle to next non-exhausted in fallback_order
355
- switchroom auth rm <label> # Remove an account (refused if it's the only one)
356
-
357
- switchroom auth agent override <agent> <label> # Edge case: one agent on a different account
358
- switchroom auth agent override <agent> --clear # Back to fleet active
359
-
360
- switchroom auth refresh [label] # Diagnostic: force a refresh tick
361
- ```
362
-
363
- The same surface is reachable from Telegram in any agent's chat: `/auth show` (read-only), `/auth use <label>`, `/auth rotate`. Mutating verbs are admin-gated against the per-agent `admin: true` flag (the same flag that gates `/agents`, `/restart`, `/update`, etc.). One knob to make an agent the fleet control panel.
122
+ ## Survives real life
364
123
 
365
- ### Workspace (agent bootstrap layer)
124
+ Always-on is not enough on its own. Things still die. The product has to handle that or the illusion breaks.
366
125
 
367
- Each agent has a workspace directory (`~/.switchroom/agents/<name>/workspace/`) with editable stable files (`AGENTS.md`, `USER.md`, `IDENTITY.md`, `TOOLS.md`) and dynamic files (`MEMORY.md`, `memory/YYYY-MM-DD.md`, `HEARTBEAT.md`) injected into the model's context at turn time.
126
+ <p align="center"><img src="docs/diagrams/wake-audit-lifecycle.svg" width="700" alt="Wake-audit lifecycle: kill, crash-pane snapshot, auto-restart, agent boots with SWITCHROOM_PENDING_TURN, acks with three options"></p>
368
127
 
369
- `SOUL.md` (the persona) is a special case since v0.12.0: it's **user-owned and seeded once** — switchroom writes it at first scaffold (from the setup wizard's persona prompts or the profile default) and then *never overwrites it*, the deliberate inverse of the switchroom-managed `CLAUDE.md`. Edit it freely; `switchroom update` won't touch it. Use `switchroom soul reset <agent>` to re-seed from the profile (it backs the old one up first). See [docs/configuration.md § Persona & SOUL.md ownership](docs/configuration.md#persona--soulmd-ownership).
128
+ - **Auto-restart.** Agent containers run with `restart: unless-stopped` and only start once the auth-broker's healthcheck passes. The vault broker, approval kernel, and auth broker each have their own healthchecks, so a wedged dependency is caught instead of silently breaking agents.
129
+ - **Resume protocol.** When an agent reboots mid-turn it boots with `SWITCHROOM_PENDING_TURN` plus the original chat ids. Its first action is to acknowledge the gap and ask how to proceed: start over, summarise and continue, or drop it.
130
+ - **Wake-audit.** On every fresh boot the agent checks for owed replies, orphan sub-agents, and stale todos. Clean means it stays quiet. If it owed you a reply, it tells you.
131
+ - **Token refresh.** The `switchroom-auth-broker` owns the refresh loop and is the sole writer of every `credentials.json`. Per-account quota state fans out across the fleet in seconds, and `auth.fallback_order` cycles when an account is exhausted.
370
132
 
371
- ```bash
372
- switchroom workspace path <agent> # Print the workspace dir
373
- switchroom workspace show <agent> [file] # Print one workspace file (default AGENTS.md)
374
- switchroom workspace edit <agent> [file] # Open in $EDITOR (default AGENTS.md)
375
- switchroom workspace render <agent> --stable # Dump the stable bootstrap block (for start.sh)
376
- switchroom workspace render <agent> --dynamic # Dump the dynamic block (for UserPromptSubmit)
377
- switchroom workspace search <agent> <query...> # BM25-lite search over workspace markdown
378
- switchroom workspace commit <agent> [-m <msg>] # Git checkpoint of workspace state
379
- switchroom workspace status <agent> # git status on the workspace
380
- ```
133
+ ## How it stacks up
381
134
 
382
- ### Observability
135
+ | | Switchroom | Claude Code channels | OpenClaw | NanoClaw |
136
+ |---|---|---|---|---|
137
+ | Progress visibility | Deterministic status, never silent | Black box | None | None |
138
+ | Runtime | Claude Code CLI | Claude Code CLI | Custom runtime | Agents SDK |
139
+ | Auth | Pro/Max OAuth | Pro/Max OAuth | API key | API key |
140
+ | Sub-agent tracking | Yes, live fleet block | No | No | No |
141
+ | Parallel display | Shared fleet status, up to 5 rows | No | No | No |
142
+ | Approval UX | Inline Telegram cards | None | None | None |
143
+ | Config | YAML with cascade | None | JSON/TOML | Env vars |
144
+ | Setup | `switchroom setup` | Built-in (limited) | Docker compose | Docker compose |
383
145
 
384
- ```bash
385
- switchroom debug turn <agent> # Dump the exact prompt layering from the last turn
386
- switchroom memory setup|search|stats|reflect # Hindsight memory
387
- ```
146
+ The wedge against OpenClaw and NanoClaw is not the substrate. It is the stock `claude` CLI under your subscription, instead of a custom runtime under your API key. [vs OpenClaw](docs/vs-openclaw.md) and [vs NanoClaw](docs/vs-nanoclaw.md).
388
147
 
389
- The progress card driver also writes a per-agent `card-events.jsonl` audit log: every edit, pin, unpin, and tool-label transition the user sees in Telegram, captured locally so a debug session doesn't depend on Telegram's history. Tail it like any other journal.
148
+ ## Architecture
390
149
 
391
- ### Other
150
+ One long-running service per agent. Each agent runs the stock `claude` CLI, not a fork, not the Agents SDK, not a wrapped harness, authenticated directly with Anthropic over official OAuth. Switchroom is scaffolding and lifecycle around the CLI you would run by hand: a Telegram bot, an approval kernel, a vault broker, an auth broker, and Docker Compose for supervision.
392
151
 
393
- ```bash
394
- switchroom topics sync|list|cleanup # Telegram forum topics
395
- switchroom vault init|set|get|list|remove # Encrypted secrets
396
- switchroom handoff <agent> # Cross-session handoff summarizer
397
- switchroom web # Web dashboard
398
152
  ```
399
-
400
- ### Migrating credentials from OpenClaw
401
-
402
- `scripts/import-openclaw-credentials.ts` is a one-shot migration script that lifts `/data/openclaw-config/credentials/` into the Switchroom vault. It ships with a small set of default mappings for filenames OpenClaw documents out of the box.
403
-
404
- User-specific credential filenames (your custom bot tokens, SSH keys, and so on) belong in a local overlay file, not the source repository. Create `~/.switchroom/import-openclaw.yaml`:
405
-
406
- ```yaml
407
- # ~/.switchroom/import-openclaw.yaml
408
- files:
409
- telegram-bot-token-mybot: telegram/mybot-bot-token
410
- discord-bot-token-mybot: discord/mybot-bot-token
411
- my-server-ssh-key: ssh/my-server
412
- skip:
413
- compass-mac-cookies.json: "auto-managed by compass skill (8h TTL cache)"
414
- secrets_env:
415
- X_BEARER_TOKEN: x-api/bearer-token
416
- directories:
417
- garmin-tokens: garmin/tokens
153
+ You (Telegram)
154
+
155
+
156
+ @YourBot ──┬── switchroom-telegram MCP ──┬── agent supervisor ─── Claude Code CLI
157
+ │ (17 tools) │ (per-agent) │
158
+ │ │ ├─ .claude/agents/*.md (sub-agents)
159
+ ├─ Deterministic status ├─ Approval kernel ◄─────┤ settings.json (tools, hooks, MCP)
160
+ │ (quiet/working/idle) │ (action grants) ├─ Hindsight plugin (memory)
161
+ ├─ Live step streaming ├─ Vault broker ◄────────┤ Drive MCP, Playwright MCP, …
162
+ ├─ Sub-agent fleet block │ (per-agent ACL) ├─ in-agent scheduler sidecar
163
+ ├─ SQLite history ├─ Auth broker ◄─────────┤ (cron, fires across reboots)
164
+ └─ Emoji reactions │ (OAuth refresh, │
165
+ │ sole creds writer) │
166
+ ├─ hostd (host-control:
167
+ │ /restart, /update)
168
+ └─ Docker Compose restart (unless-stopped)
418
169
  ```
419
170
 
420
- Overlay entries win on collision with built-in defaults. Unknown files that appear in neither defaults nor the overlay surface as `warn` entries so nothing is silently dropped. Run `bun scripts/import-openclaw-credentials.ts --help` for flags including `--mapping <path>` to override the default overlay location.
171
+ See [`docs/architecture.md`](docs/architecture.md) for the process model, IPC layout, supervisor choice, and how each layer maps to the `claude` CLI.
421
172
 
422
173
  ## Documentation
423
174
 
@@ -425,43 +176,27 @@ Overlay entries win on collision with built-in defaults. Unknown files that appe
425
176
  |---|---|
426
177
  | **[Install](docs/install.md)** | Zero-to-first-message new-user walkthrough |
427
178
  | **[BotFather walkthrough](docs/botfather-walkthrough.md)** | Step-by-step bot creation in Telegram |
428
- | **[Changelog](CHANGELOG.md)** | Release notes, every version |
429
- | **[Configuration](docs/configuration.md)** | Full field reference, cascade semantics, profiles |
179
+ | **[Configuration](docs/configuration.md)** | Full field reference, cascade semantics, profiles, example config |
180
+ | **[CLI reference](docs/cli-reference.md)** | Every verb, grouped, with behaviour and usage |
430
181
  | **[Vault](docs/vault.md)** | Architecture, per-cron secrets, ACL, audit log, threat model |
431
- | **[Telegram Plugin](docs/telegram-plugin.md)** | Progress cards, 15 MCP tools, native checklists, sticker aliases, voice-in |
182
+ | **[Telegram Plugin](docs/telegram-plugin.md)** | Deterministic UX, 17 MCP tools, checklists, sticker aliases, voice-in |
432
183
  | **[Sub-Agents](docs/sub-agents.md)** | Model routing, delegation patterns, frontmatter spec |
433
184
  | **[Scheduling](docs/scheduling.md)** | Cron tasks (in-agent scheduler sidecar), model selection |
434
185
  | **[Session Management](docs/session-optimization.md)** | Continuity, compaction, freshness policy |
435
- | **[OpenClaw alternative](docs/vs-openclaw.md)** | Switchroom vs OpenClaw |
436
- | **[NanoClaw alternative](docs/vs-nanoclaw.md)** | Switchroom vs NanoClaw |
437
186
  | **[Compliance](docs/compliance-attestation.md)** | Anthropic compliance analysis |
187
+ | **[Changelog](CHANGELOG.md)** | Release notes, every version |
438
188
  | **[Telemetry](docs/posthog.md)** | What Switchroom reports to PostHog and how to opt out |
439
189
 
440
- ## Telemetry
441
-
442
- Switchroom reports anonymous usage events and errors to PostHog so I can spot regressions and understand which commands are used. **No personal data, code, or message content leaves your machine.** The anonymous ID lives at `~/.switchroom/analytics-id` and is a random UUID. Not tied to your username, email, IP, or machine identifier (we pass `disableGeoip: true` on every event).
443
-
444
- To opt out, set this in your shell profile:
445
-
446
- ```bash
447
- export SWITCHROOM_TELEMETRY_DISABLED=1
448
- ```
449
-
450
- Full event catalogue, dashboard links, and the source module at [docs/posthog.md](docs/posthog.md).
451
-
452
190
  ## FAQ
453
191
 
454
192
  **Can I use a Claude Pro or Max subscription instead of an API key?**
455
- Yes. That's the whole point. Switchroom runs the unmodified `claude` CLI with the same OAuth flow you use on the desktop app. No API key. No per-token billing.
193
+ Yes. That is the whole point. Switchroom runs the unmodified `claude` CLI with the same OAuth flow you use on the desktop app. No API key. No per-token billing.
456
194
 
457
195
  **How is this different from Claude Code's built-in Telegram channel?**
458
- The built-in channel is message in, message out, with zero visibility into what the agent is doing in between. Switchroom adds live progress cards that pin to the top of each topic and update as tools execute. You can always see what's happening, which is the bit the built-in channel gets wrong.
196
+ The built-in channel is message in, message out, with no visibility into what the agent is doing in between. Switchroom shows a deterministic status on every topic (quiet, working, idle) and streams the steps in place as tools run. You always know the state, which is the bit the built-in channel gets wrong.
459
197
 
460
198
  **Does it work with multiple agents at the same time?**
461
- Yes. Each agent gets its own Telegram forum topic. When multiple agents are working simultaneously, each has its own pinned progress card labelled `(1/N)`, `(2/N)` and so on.
462
-
463
- **Can I see what sub-agents are doing?**
464
- Yes. When an agent delegates to a sub-agent (a worker, a researcher), the sub-agent's activity shows up in its own indented section of the parent's progress card. You see the full hierarchy, not just the top-level agent.
199
+ Yes. Each agent gets its own Telegram forum topic with its own status. When an agent delegates, its sub-agents show up as live rows in a shared fleet block (up to 5, then `+N more`), each with role and current tool.
465
200
 
466
201
  **What does it cost to run?**
467
202
  A cheap Linux VPS (around $6/mo on Hetzner, DigitalOcean, wherever), plus your existing Claude Pro ($20/mo) or Max ($100/mo) subscription. Switchroom itself is MIT-licensed, free.
@@ -469,8 +204,9 @@ A cheap Linux VPS (around $6/mo on Hetzner, DigitalOcean, wherever), plus your e
469
204
  **Is this against Anthropic's terms of service?**
470
205
  No. Switchroom uses the official `claude` binary with the official OAuth flow. See [docs/compliance-attestation.md](docs/compliance-attestation.md) for the full analysis.
471
206
 
472
- **Is Switchroom an alternative to OpenClaw?**
473
- Yes. Same use case, but it uses your Claude subscription via OAuth instead of an API key, and runs the native `claude` binary instead of a custom runtime in Docker. See [vs-openclaw](docs/vs-openclaw.md).
207
+ ## Telemetry
208
+
209
+ Switchroom reports anonymous usage events and errors to PostHog so I can spot regressions and see which commands are used. **No personal data, code, or message content leaves your machine.** The anonymous ID at `~/.switchroom/analytics-id` is a random UUID, not tied to your username, email, IP, or machine identifier. Opt out with `export SWITCHROOM_TELEMETRY_DISABLED=1`. Full event catalogue at [docs/posthog.md](docs/posthog.md).
474
210
 
475
211
  ## License
476
212