switchroom 0.5.0 → 0.7.9

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.
Files changed (89) hide show
  1. package/README.md +142 -121
  2. package/bin/autoaccept.exp +29 -6
  3. package/dist/agent-scheduler/index.js +12261 -0
  4. package/dist/cli/autoaccept-poll.js +10 -0
  5. package/dist/cli/switchroom.js +27250 -25324
  6. package/dist/vault/approvals/kernel-server.js +12709 -0
  7. package/dist/vault/broker/server.js +15724 -0
  8. package/package.json +4 -3
  9. package/profiles/_base/start.sh.hbs +133 -0
  10. package/profiles/_shared/telegram-style.md.hbs +3 -3
  11. package/profiles/default/CLAUDE.md +3 -3
  12. package/profiles/default/CLAUDE.md.hbs +2 -2
  13. package/profiles/default/workspace/CLAUDE.md.hbs +9 -0
  14. package/skills/docx/VENDORED.md +1 -1
  15. package/skills/mcp-builder/VENDORED.md +1 -1
  16. package/skills/pdf/VENDORED.md +1 -1
  17. package/skills/pptx/VENDORED.md +1 -1
  18. package/skills/skill-creator/VENDORED.md +1 -1
  19. package/skills/switchroom-architecture/SKILL.md +8 -7
  20. package/skills/switchroom-cli/SKILL.md +23 -15
  21. package/skills/switchroom-health/SKILL.md +7 -7
  22. package/skills/switchroom-install/SKILL.md +36 -39
  23. package/skills/switchroom-manage/SKILL.md +4 -4
  24. package/skills/switchroom-status/SKILL.md +1 -1
  25. package/skills/webapp-testing/VENDORED.md +1 -1
  26. package/skills/xlsx/VENDORED.md +1 -1
  27. package/telegram-plugin/admin-commands/dispatch.test.ts +119 -1
  28. package/telegram-plugin/admin-commands/index.ts +71 -0
  29. package/telegram-plugin/ask-user.ts +1 -0
  30. package/telegram-plugin/card-event-log.ts +138 -0
  31. package/telegram-plugin/dist/bridge/bridge.js +178 -31
  32. package/telegram-plugin/dist/foreman/foreman.js +6875 -6526
  33. package/telegram-plugin/dist/gateway/gateway.js +13862 -11834
  34. package/telegram-plugin/dist/server.js +202 -40
  35. package/telegram-plugin/fleet-state.ts +25 -10
  36. package/telegram-plugin/foreman/foreman.ts +38 -3
  37. package/telegram-plugin/gateway/approval-callback.ts +126 -0
  38. package/telegram-plugin/gateway/approval-card.test.ts +90 -0
  39. package/telegram-plugin/gateway/approval-card.ts +127 -0
  40. package/telegram-plugin/gateway/approvals-commands.ts +126 -0
  41. package/telegram-plugin/gateway/boot-card.ts +31 -6
  42. package/telegram-plugin/gateway/boot-probes.ts +510 -72
  43. package/telegram-plugin/gateway/gateway.ts +822 -94
  44. package/telegram-plugin/gateway/ipc-protocol.ts +34 -1
  45. package/telegram-plugin/gateway/ipc-server.ts +35 -0
  46. package/telegram-plugin/gateway/startup-mutex.ts +110 -2
  47. package/telegram-plugin/hooks/hooks.json +19 -0
  48. package/telegram-plugin/hooks/tool-label-pretool.mjs +216 -0
  49. package/telegram-plugin/hooks/tool-label-stop.mjs +63 -0
  50. package/telegram-plugin/package.json +4 -1
  51. package/telegram-plugin/plugin-logger.ts +20 -1
  52. package/telegram-plugin/progress-card-driver.ts +202 -13
  53. package/telegram-plugin/progress-card.ts +2 -2
  54. package/telegram-plugin/quota-check.ts +1 -0
  55. package/telegram-plugin/registry/subagents-schema.ts +37 -0
  56. package/telegram-plugin/registry/subagents.test.ts +64 -0
  57. package/telegram-plugin/session-tail.ts +58 -5
  58. package/telegram-plugin/shared/bot-runtime.ts +48 -2
  59. package/telegram-plugin/subagent-watcher.ts +139 -7
  60. package/telegram-plugin/tests/_progress-card-harness.ts +4 -0
  61. package/telegram-plugin/tests/bg-agent-progress-card-757.test.ts +201 -0
  62. package/telegram-plugin/tests/boot-card-probe-target.test.ts +10 -34
  63. package/telegram-plugin/tests/boot-card-render.test.ts +6 -5
  64. package/telegram-plugin/tests/boot-probes.test.ts +564 -0
  65. package/telegram-plugin/tests/card-event-log.test.ts +145 -0
  66. package/telegram-plugin/tests/gateway-startup-mutex.test.ts +102 -0
  67. package/telegram-plugin/tests/ipc-server-validate-inject-inbound.test.ts +134 -0
  68. package/telegram-plugin/tests/progress-card-delay-842.test.ts +160 -0
  69. package/telegram-plugin/tests/quota-check.test.ts +37 -1
  70. package/telegram-plugin/tests/subagent-registry-bugs.test.ts +5 -0
  71. package/telegram-plugin/tests/subagent-watcher-stall-notification.test.ts +104 -1
  72. package/telegram-plugin/tests/subagent-watcher.test.ts +5 -0
  73. package/telegram-plugin/tests/tool-label-sidecar.test.ts +114 -0
  74. package/telegram-plugin/tests/two-zone-bg-done-when-all-terminal.test.ts +5 -3
  75. package/telegram-plugin/tests/two-zone-card-header-phases.test.ts +10 -0
  76. package/telegram-plugin/tests/two-zone-snapshot-extras.test.ts +58 -14
  77. package/telegram-plugin/tests/welcome-text.test.ts +57 -0
  78. package/telegram-plugin/tool-label-sidecar.ts +140 -0
  79. package/telegram-plugin/tool-labels.ts +55 -0
  80. package/telegram-plugin/two-zone-card.ts +27 -7
  81. package/telegram-plugin/uat/SETUP.md +160 -0
  82. package/telegram-plugin/uat/assertions.ts +140 -0
  83. package/telegram-plugin/uat/driver.ts +174 -0
  84. package/telegram-plugin/uat/harness.ts +161 -0
  85. package/telegram-plugin/uat/login.ts +134 -0
  86. package/telegram-plugin/uat/port-allocator.ts +71 -0
  87. package/telegram-plugin/uat/scenarios/smoke-clerk-reply.test.ts +61 -0
  88. package/telegram-plugin/welcome-text.ts +44 -2
  89. package/bin/bridge-watchdog.sh +0 -967
package/README.md CHANGED
@@ -9,29 +9,19 @@
9
9
  [![Trigger evals](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2Fmekenthompson%2F002f3482b19111d35e57c1903b3733e2%2Fraw%2Fswitchroom-trigger-evals.json)](https://buildkite.com/ken-thompson/switchroom)
10
10
  [![Quality evals](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2Fmekenthompson%2F002f3482b19111d35e57c1903b3733e2%2Fraw%2Fswitchroom-quality-evals.json)](https://buildkite.com/ken-thompson/switchroom)
11
11
 
12
- **Your Claude Pro or Max, as a fleet of always-on agents in Telegram. Opinionated UX, done properly.**
12
+ **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.
13
13
 
14
14
  > *I loved OpenClaw + Telegram. I wanted my Claude subscription. And the UX done properly. So I built this.*
15
15
 
16
- **Compliance-by-design.** Switchroom leverages Claude Code natively — unmodified `claude` CLI, no Agent SDK, no direct API. It sets up the CLI the way you would, then gets out of the way. See the [Compliance Attestation](docs/compliance-attestation.md) for detail.
16
+ [Latest release notes ](CHANGELOG.md)
17
17
 
18
- ## Right, so what's this about
18
+ ## See what your agent is doing
19
19
 
20
- 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.
21
-
22
- 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."
23
-
24
- 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.
25
-
26
- So I built this.
27
-
28
- ## What Switchroom is, and isn't
29
-
30
- Switchroom is an opinionated implementation of a Telegram plugin and agent lifecycle layer, sitting on top of the official `claude` CLI. No fork. No custom runtime in Docker. No API key interception. Your Claude Pro or Max subscription does the work, the same way it does on your desktop, authenticated via the same OAuth flow, fully compliant with Anthropic's terms.
20
+ 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.
31
21
 
32
- It is not trying to be a general-purpose LLM orchestrator. It doesn't care about OpenAI, Gemini, Llama, or swapping model providers. It is not a multi-channel bridge for Slack, Discord, Teams. It does one thing: makes Telegram the best possible interaction surface for Claude Code. Unashamedly.
22
+ No silent gaps. No ghosts. No squinting into a black box.
33
23
 
34
- The whole thing is built around one idea. Every time an agent starts work, a **progress card** pops into Telegram and stays pinned while the task runs. It updates in place as tools execute, so you see each Read, Bash, Edit, Grep happen as it happens.
24
+ <p align="center"><img src="docs/diagrams/progress-card-anatomy.jpg" width="700" alt="Annotated progress card: pin badge, user quote, last 5 steps, collapsed older, in-flight pulse, elapsed timer, sub-agent indent"></p>
35
25
 
36
26
  ```
37
27
  ⚙️ Working… · ⏱ 12s
@@ -43,63 +33,114 @@ The whole thing is built around one idea. Every time an agent starts work, a **p
43
33
  🤖 Edit src/auth/jwt.ts · 4s
44
34
  ```
45
35
 
46
- When the agent finishes, the card flips to Done and unpins. Two agents working at the same time? Each gets its own card, labelled `(1/2)` and `(2/2)`, so you can follow both without losing the plot.
36
+ The card is the headline UX. The rest of the product is in service of it.
37
+
38
+ - Cards update at most once every 5 seconds. Fast enough to follow, not so fast it floods.
39
+ - Last 5 steps stay visible. Older ones collapse into `(+N more earlier steps)`.
40
+ - Running steps show elapsed time so a stuck tool is obvious.
41
+ - Tool labels are deterministic, written by a `PreToolUse` hook, so the card never lies about what's running.
42
+ - Two agents working at once? Each gets its own card, labelled `(1/2)` and `(2/2)`.
43
+
44
+ ### Sub-agent visibility
45
+
46
+ 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.
47
47
 
48
- ## The UX bits that matter
48
+ ### Right, so what's this about
49
49
 
50
- - Cards update at most once every 5 seconds. Fast enough to follow, not so fast it floods
51
- - Last 5 steps are always visible, older ones collapse into `(+N more earlier steps)`
52
- - Running steps show elapsed time so you can tell if something's stuck
53
- - Sub-agents get their own section in the card, so nested work is visible, not hidden
54
- - No silent gaps. No ghosts.
50
+ 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.
51
+
52
+ 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."
53
+
54
+ 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.
55
+
56
+ So I built this.
57
+
58
+ ## What you get
59
+
60
+ | Feature | What it does |
61
+ |---|---|
62
+ | **Progress cards** | Pinned, in-place, every tool call visible. The headline UX. |
63
+ | **Claude Pro/Max auth** | OAuth, not API keys. No per-token billing. Multi-account fallback pool per agent. |
64
+ | **Approval kernel** | Inline allow/deny cards in Telegram for every gated tool. TTL'd grants, full audit trail. |
65
+ | **Sub-agents** | Opus plans, Sonnet implements. Sub-agent work surfaces in the parent card. |
66
+ | **Config cascade** | Defaults, then profiles, then per-agent YAML. Change one line, every agent updates. |
67
+ | **Scheduled tasks** | Cron-syntax tasks that fire across reboots. Headless secret access via the vault broker. |
68
+ | **Persistent memory** | Hindsight semantic memory with knowledge graphs and mental models. |
69
+ | **Session continuity** | Resume across restarts with freshness gating and a wake-audit. |
70
+ | **Encrypted vault** | AES-256-GCM for secrets. Optional auto-unlock keyed off `/etc/machine-id`. |
71
+ | **Drive MCP** | Read Google Docs, Sheets, and Drive files inline. Per-agent OAuth, no shared key. |
72
+ | **Card audit log** | Every progress-card edit appended to `card-events.jsonl` for retrospective debugging. |
73
+ | **15 Telegram MCP tools** | Reply, stream replies, edit, pin, react, native checklists, sticker aliases, voice-in transcription, attachments, history. |
55
74
 
56
75
  ## Architecture
57
76
 
58
- One Claude Code REPL per agent, dressed up with systemd and a Telegram bot. Two systemd units per agent: the Claude process (`switchroom-<agent>.service`) and its Telegram gateway (`switchroom-<agent>-gateway.service`). See [`docs/architecture.md`](docs/architecture.md) for the process model, IPC layout, and how each layer maps to the `claude` CLI.
77
+ 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.
59
78
 
60
79
  ```
61
80
  You (Telegram)
62
81
 
63
82
 
64
- @YourBot ──── switchroom-telegram MCP ──── Claude Code CLI
65
-
66
- ├─ Progress cards ├─ .claude/agents/*.md (sub-agents)
67
- ├─ Pin / unpin lifecycle ├─ settings.json (tools, hooks, MCP)
68
- ├─ SQLite history ├─ Hindsight plugin (memory)
69
- ├─ Emoji reactions └─ systemd (agent + cron timers)
70
- └─ Format conversion
83
+ @YourBot ──┬── switchroom-telegram MCP ──┬── agent supervisor ─── Claude Code CLI
84
+ (15 tools) (per-agent) │
85
+ │ │ ├─ .claude/agents/*.md (sub-agents)
86
+ ├─ Progress cards ├─ Approval kernel ◄─────┤ settings.json (tools, hooks, MCP)
87
+ ├─ Pin / unpin lifecycle │ (allow/deny broker) ├─ Hindsight plugin (memory)
88
+ ├─ SQLite history ├─ Vault broker ◄────────┤ Drive MCP, Playwright MCP, …
89
+ ├─ Card-events.jsonl audit │ (cron secrets, IPC) └─ scheduled tasks across reboots
90
+ ├─ Emoji reactions │
91
+ └─ Format conversion └─ Docker Compose restart (unless-stopped)
71
92
  ```
72
93
 
73
- Switchroom is **not a harness**. Each agent runs the unmodified `claude` binary, authenticated directly with Anthropic via official OAuth. No credential interception, no API key routing.
94
+ See [`docs/architecture.md`](docs/architecture.md) for the process model, IPC layout, supervisor choice, and how each layer maps to the `claude` CLI.
74
95
 
75
- ## Everything else you get
96
+ ## Approvals & safety
76
97
 
77
- | Feature | What it does |
78
- |---------|-------------|
79
- | **Claude Pro/Max auth** | OAuth, not API keys. No per-token billing. |
80
- | **Multi-agent** | Opus plans, Sonnet implements in the background. Sub-agent work surfaces in the card. |
81
- | **Config cascade** | Defaults, then profiles, then per-agent YAML. Change one line, every agent updates. |
82
- | **Scheduled tasks** | Cron-based systemd timers. Survive reboots. |
83
- | **Persistent memory** | Hindsight semantic memory with knowledge graphs. |
84
- | **Session continuity** | Resume sessions across restarts with freshness gating. |
85
- | **Encrypted vault** | AES-256-GCM for secrets. |
86
- | **12 Telegram MCP tools** | Reply, stream replies, pin, react, history, attachments, native checklists, all of it. |
98
+ 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.
99
+
100
+ <p align="center"><img src="docs/diagrams/approval-grant-flow.jpg" 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>
101
+
102
+ - **Inline cards.** Allow / Deny / Allow once / Allow for 1h. No leaving Telegram.
103
+ - **TTL'd grants.** "Allow Bash for 1h" expires automatically. No silent permanent escalation.
104
+ - **Audit trail.** Every grant, denial, and expiry written to a per-agent log you can replay.
105
+ - **Per-agent allowlist.** `switchroom agent grant <name> <tool>` for the boring ones you don't want to be asked about.
106
+
107
+ 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.
108
+
109
+ ### Compliance posture
87
110
 
88
- ## How it stacks up against the alternatives
111
+ 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.
112
+
113
+ ## Survives real life
114
+
115
+ 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.
116
+
117
+ <p align="center"><img src="docs/diagrams/wake-audit-lifecycle.jpg" width="700" alt="Wake-audit lifecycle: kill, crash-pane snapshot, auto-restart, agent boots with SWITCHROOM_PENDING_TURN, acks with three options"></p>
118
+
119
+ - **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.
120
+ - **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).
121
+ - **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.
122
+ - **Token refresh.** Runs unattended for weeks via a `refresh-tick` daemon. Multi-account fallback pool kicks in when the active slot hits its quota window.
123
+
124
+ ## How it stacks up
89
125
 
90
126
  | | Switchroom | Claude Code channels | OpenClaw | NanoClaw |
91
127
  |---|---|---|---|---|
92
- | Progress visibility | Live progress cards, pinned | None, black box | None | None |
128
+ | Progress visibility | Live cards, pinned | Black box | None | None |
93
129
  | Runtime | Claude Code CLI | Claude Code CLI | Custom runtime | Agents SDK |
94
130
  | Auth | Pro/Max OAuth | Pro/Max OAuth | API key | API key |
95
- | Sub-agent tracking | Yes, visible in card | No | No | No |
131
+ | Sub-agent tracking | Yes, in card | No | No | No |
96
132
  | Parallel task display | Labelled cards `(1/N)` | No | No | No |
133
+ | Approval UX | Inline Telegram cards | None | None | None |
97
134
  | Config | YAML with cascade | None | JSON/TOML | Env vars |
98
135
  | Setup | `switchroom setup` | Built-in (limited) | Docker compose | Docker compose |
99
136
 
137
+ 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.
138
+
100
139
  ## Install
101
140
 
102
- Ubuntu 24.04 LTS, 4GB RAM. Linux only.
141
+ Runs on the box you already have. The supported production runtime is Linux + Docker (Ubuntu 24.04 LTS with 4GB RAM is the canonical target; other Linux distros work with minor tweaks). `switchroom apply` scaffolds every agent and writes a `docker-compose.yml` from your `switchroom.yaml`; you bring the fleet up yourself with `docker compose -p switchroom -f ~/.switchroom/compose/docker-compose.yml up -d`. Five published images on GHCR (`switchroom-base`, `switchroom-agent`, `switchroom-broker`, `switchroom-kernel`, `switchroom-scheduler`) — no `docker build` on the operator's host. macOS (Docker Desktop) works for development but is not yet release-validated.
142
+
143
+ > **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.
103
144
 
104
145
  ### From inside Claude Code (the on-ramp)
105
146
 
@@ -111,15 +152,32 @@ If you already use Claude Code, this is the shortest path. Inside any session:
111
152
  /switchroom:setup
112
153
  ```
113
154
 
114
- `/switchroom:setup` walks you through deps, `switchroom setup` (Telegram + vault + first agent), and `switchroom agent start`. Once it's done you have `/switchroom:start`, `/switchroom:stop`, and `/switchroom:status` for day-to-day. See [`docs/publishing.md`](docs/publishing.md).
155
+ `/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).
115
156
 
116
- ### One-liner (fresh box)
157
+ ### One-liner (static binary)
117
158
 
118
159
  ```bash
119
- curl -fsSL https://get.switchroom.ai | bash
160
+ curl -fsSL https://github.com/switchroom/switchroom/raw/main/install.sh | sh
120
161
  ```
121
162
 
122
- Bootstraps bun, node 22, the claude CLI, and switchroom. Idempotent. Safe to re-run. Source is [`install.sh`](install.sh) in this repo.
163
+ Auto-detects your platform (linux / macos) and arch (amd64 / arm64), downloads the matching pre-built binary from the latest [GitHub release](https://github.com/switchroom/switchroom/releases/latest), verifies its SHA256, and drops it in `/usr/local/bin` (or `~/.local/bin` if not writable). Source is [`install.sh`](install.sh).
164
+
165
+ The static binary still needs the `claude` CLI to run agents: `npm i -g @anthropic-ai/claude-code` (Node 20.11+).
166
+
167
+ **Manual install** if you'd rather not pipe to sh:
168
+
169
+ ```bash
170
+ # Pick the artifact for your platform/arch from the latest release page
171
+ curl -fsSL -o switchroom https://github.com/switchroom/switchroom/releases/latest/download/switchroom-linux-amd64
172
+ chmod +x switchroom
173
+ sudo mv switchroom /usr/local/bin/
174
+ ```
175
+
176
+ Replace `switchroom-linux-amd64` with `switchroom-linux-arm64`, `switchroom-macos-amd64`, or `switchroom-macos-arm64` as needed. Verify against `switchroom-checksums.txt` from the same release.
177
+
178
+ **macOS Gatekeeper note.** Releases are not yet Apple-code-signed. After installing on macOS you may need to clear the quarantine xattr so the binary will run: `xattr -d com.apple.quarantine /usr/local/bin/switchroom`. The `install.sh` one-liner handles this automatically.
179
+
180
+ **Mac (Sequoia+) one-time.** macOS 15 adds a second-stage notarization check that the `xattr` strip alone does not bypass — you may still see a Gatekeeper "cannot verify the developer" dialog the first time you run `switchroom`. `install.sh` attempts `sudo spctl --add /usr/local/bin/switchroom` automatically (best-effort, ignored if sudo isn't available). If the dialog still fires, run that `spctl --add` manually, or open System Settings → Privacy & Security → "Open Anyway" once.
123
181
 
124
182
  Then:
125
183
 
@@ -127,7 +185,8 @@ Then:
127
185
  switchroom setup # interactive Telegram wiring
128
186
  switchroom agent create coach --profile health-coach # scaffold your first agent
129
187
  switchroom auth login coach # link your Pro or Max session
130
- switchroom agent start coach # go
188
+ switchroom apply # write docker-compose.yml
189
+ docker compose -p switchroom -f ~/.switchroom/compose/docker-compose.yml up -d
131
190
  ```
132
191
 
133
192
  After the last command you talk to the agent from Telegram. You don't touch the server again.
@@ -139,7 +198,7 @@ npm install -g @anthropic-ai/claude-code switchroom
139
198
  switchroom setup
140
199
  ```
141
200
 
142
- Node 20.11+. `switchroom setup` is the interactive first-time wizard scaffolds config, handles Telegram wiring, sets up the vault.
201
+ Node 20.11+. `switchroom setup` is the interactive first-time wizard. Scaffolds config, handles Telegram wiring, sets up the vault.
143
202
 
144
203
  ### One-shot happy path (no wizard)
145
204
 
@@ -148,18 +207,18 @@ If you already have Telegram credentials in `~/.switchroom/switchroom.yaml`, ski
148
207
  ```bash
149
208
  switchroom agent create coach --profile health-coach
150
209
  switchroom auth login coach
151
- switchroom agent start coach
210
+ switchroom apply && docker compose -p switchroom -f ~/.switchroom/compose/docker-compose.yml up -d
152
211
  ```
153
212
 
154
- ## Example Configuration
213
+ ## Example configuration
155
214
 
156
215
  ```yaml
157
216
  switchroom:
158
217
  version: 1
159
218
 
160
219
  telegram:
220
+ # Per-agent bot token (DM-only by default).
161
221
  bot_token: "vault:telegram-bot-token"
162
- forum_chat_id: "-1001234567890"
163
222
 
164
223
  memory:
165
224
  backend: hindsight
@@ -195,11 +254,7 @@ See [docs/configuration.md](docs/configuration.md) for the full reference.
195
254
 
196
255
  ## Vault broker (cron secrets)
197
256
 
198
- Scheduled tasks run headless via `systemd --user` timers, so they cannot prompt
199
- for the vault passphrase. The vault broker is a long-running user-level systemd
200
- unit that holds the vault decrypted in memory after a one-time interactive
201
- unlock. Cron tasks fetch the specific keys they declare via a unix socket; the
202
- passphrase never sits on disk.
257
+ 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-broker`) that holds the vault decrypted in memory after a one-time interactive unlock. Cron tasks fetch the specific keys they declare via a host-shared unix socket. The passphrase never sits on disk.
203
258
 
204
259
  **Declare per-cron secrets in `switchroom.yaml`:**
205
260
 
@@ -217,69 +272,39 @@ agents:
217
272
  **Bootstrap once per host:**
218
273
 
219
274
  ```bash
220
- switchroom update # installs the broker systemd unit
275
+ switchroom apply # writes broker into docker-compose.yml
276
+ docker compose -p switchroom -f ~/.switchroom/compose/docker-compose.yml up -d switchroom-broker
221
277
  switchroom vault broker unlock # prompt for passphrase, primes broker
222
278
  ```
223
279
 
224
- Or just run `switchroom vault get <key>` from a TTY the broker offers to
225
- take the unlocked state with `[Y/n]` so you don't have to remember a separate
226
- unlock command.
280
+ 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.
227
281
 
228
- **Identity model.** On Linux, the broker reads `/proc/<pid>/cgroup` to find
229
- the connecting cron's systemd unit (`switchroom-<agent>-cron-<i>.service`).
230
- Cgroup membership is set by systemd as root and is unspoofable from
231
- userspace, so a compromised agent cannot pose as another agent's cron and
232
- read its keys. macOS and other platforms degrade to UID-only via the socket
233
- file mode 0600 — fine for desktop use, not recommended for production cron.
282
+ **Identity model.** The broker reads `/proc/<pid>/cgroup` on the host to find the connecting cron's container (`switchroom-<agent>-scheduler` or `switchroom-<agent>`), which Docker sets unspoofably from userspace, so a compromised agent cannot pose as another agent's cron and read its keys. macOS (Docker Desktop) degrades to UID-only via the socket file mode 0600. Fine for desktop use, not recommended for production cron.
234
283
 
235
- The broker locks on `SIGTERM` (so a `restart` zeros the in-memory state)
236
- and on demand via `switchroom vault broker lock`. Use
237
- `switchroom vault get <key> --no-broker` to bypass and prompt locally.
284
+ 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.
238
285
 
239
- Unit installed at `~/.config/systemd/user/switchroom-vault-broker.service`.
286
+ Broker socket lives at `~/.switchroom/vault-broker.sock` (host-mounted into every agent container).
240
287
 
241
288
  ### Auto-unlock on boot (opt-in)
242
289
 
243
- By default, the broker holds the unlocked state in memory only every
244
- restart (host reboot, service crash, reconcile that re-renders the unit)
245
- wipes it and requires `switchroom vault broker unlock` again. For
246
- unattended hosts where this is too painful, switchroom can encrypt the
247
- passphrase with a key derived from `/etc/machine-id` and have the broker
248
- unlock itself at boot:
290
+ 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:
249
291
 
250
292
  ```bash
251
293
  switchroom vault broker enable-auto-unlock # one-time setup, prompts for passphrase
252
294
  ```
253
295
 
254
- Done. The wizard prompts for your vault passphrase, encrypts it with
255
- AES-256-GCM keyed off `/etc/machine-id`, writes the result to
256
- `~/.config/switchroom/auto-unlock.bin` (mode 0600), flips
257
- `vault.broker.autoUnlock: true` in `switchroom.yaml`, restarts the
258
- broker, and verifies the vault came up unlocked. Every subsequent boot
259
- the broker reads + decrypts + unlocks itself.
296
+ 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.
260
297
 
261
298
  Disable with `switchroom vault broker disable-auto-unlock`.
262
299
 
263
- **Security tradeoff read this before enabling.** The encrypted blob
264
- lives at mode 0600 in your home directory; the encryption key is
265
- derived from `/etc/machine-id` plus a per-file random salt. This means
266
- disk theft is safe (the blob doesn't decrypt on any other machine) and
267
- other UNIX users on the same box can't read it — but root on the host
268
- *can* read both the blob and the machine-id, so once root is on the
269
- machine the passphrase is recoverable. Same blast radius as the
270
- running broker process (anything with code-exec as you can already
271
- attach to the broker socket and exfiltrate secrets), but it shifts the
272
- convenience-vs-security knob: auto-unlock means a lost laptop is a lost
273
- vault even if the vault file itself is encrypted at rest. Use only on
274
- hosts you trust. See [docs/auto-unlock.md](docs/auto-unlock.md) for the
275
- full threat model and recovery instructions.
276
-
277
- ## CLI Reference
300
+ **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.
301
+
302
+ ## CLI reference
278
303
 
279
304
  ```bash
280
305
  switchroom setup # Interactive wizard
281
306
  switchroom doctor # Health check
282
- switchroom update # Pull latest + rebuild + reconcile + restart
307
+ switchroom apply # Reconcile + (re)write docker-compose.yml; bring fleet up via `docker compose ... up -d`
283
308
  switchroom restart [agent] [--force] # Bounce agent(s); drains in-flight turn by default
284
309
  switchroom version # Show versions + running agent health summary
285
310
 
@@ -292,7 +317,7 @@ switchroom agent reconcile <name|all> # Re-apply switchroom.yaml (withou
292
317
  switchroom agent start|stop|restart <name> # Lifecycle (with preflight)
293
318
  switchroom agent interrupt <name> # Cancel in-flight turn without restarting
294
319
  switchroom agent rename <old> <new> # Rename an agent slug (#168)
295
- switchroom agent destroy <name> # Tear down systemd units + scaffold dir
320
+ switchroom agent destroy <name> # Remove from compose + scaffold dir
296
321
  switchroom agent attach <name> # Interactive tmux session
297
322
  switchroom agent logs <name> [-f] # View logs
298
323
  switchroom agent grant <name> <tool> # Grant a tool permission
@@ -305,7 +330,7 @@ Profiles live in `profiles/` at the repo root. Bundled ones for `--profile`: `co
305
330
  `switchroom agent create <name> --profile <profile>` does two things in one step:
306
331
 
307
332
  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.
308
- 2. Scaffolds the agent directory and installs the systemd unit (same as running `agent create` on an entry that already exists in yaml).
333
+ 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).
309
334
 
310
335
  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`.
311
336
 
@@ -313,10 +338,7 @@ Model aliases: the bare names `opus`, `sonnet`, `haiku` are accepted alongside t
313
338
 
314
339
  ### Authentication (multi-account slot pool)
315
340
 
316
- Each agent has a pool of Claude OAuth slots. The **active** slot is what
317
- the agent uses; other slots are automatic fallbacks when the active slot
318
- hits its quota window. Every `<slot>` option defaults to the active slot
319
- if omitted.
341
+ Each agent has a pool of Claude OAuth slots. The **active** slot is what the agent uses; other slots are automatic fallbacks when the active slot hits its quota window. Every `<slot>` option defaults to the active slot if omitted.
320
342
 
321
343
  ```bash
322
344
  switchroom auth status # All agents, one table
@@ -333,15 +355,11 @@ switchroom auth list <agent> [--json] # Show slots: health, quota st
333
355
  switchroom auth rm <agent> <slot> [--force] # Remove a slot (refuses active/last slot)
334
356
  ```
335
357
 
336
- The fallback pool also works from Telegram. The switchroom MCP plugin
337
- exposes the same verbs as `/auth add|use|list|rm` inside the chat.
358
+ The fallback pool also works from Telegram. The switchroom MCP plugin exposes the same verbs as `/auth add|use|list|rm` inside the chat.
338
359
 
339
360
  ### Workspace (agent bootstrap layer)
340
361
 
341
- Each agent has a workspace directory (`~/.switchroom/agents/<name>/workspace/`)
342
- with editable stable files (`AGENTS.md`, `SOUL.md`, `USER.md`, `IDENTITY.md`,
343
- `TOOLS.md`) and dynamic files (`MEMORY.md`, `memory/YYYY-MM-DD.md`,
344
- `HEARTBEAT.md`) that are injected into the model's context at turn time.
362
+ Each agent has a workspace directory (`~/.switchroom/agents/<name>/workspace/`) with editable stable files (`AGENTS.md`, `SOUL.md`, `USER.md`, `IDENTITY.md`, `TOOLS.md`) and dynamic files (`MEMORY.md`, `memory/YYYY-MM-DD.md`, `HEARTBEAT.md`) that are injected into the model's context at turn time.
345
363
 
346
364
  ```bash
347
365
  switchroom workspace path <agent> # Print the workspace dir
@@ -361,6 +379,8 @@ switchroom debug turn <agent> # Dump the exact prompt layeri
361
379
  switchroom memory setup|search|stats|reflect # Hindsight memory
362
380
  ```
363
381
 
382
+ 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.
383
+
364
384
  ### Other
365
385
 
366
386
  ```bash
@@ -374,7 +394,7 @@ switchroom web # Web dashboard
374
394
 
375
395
  `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.
376
396
 
377
- User-specific credential filenames (your custom bot tokens, SSH keys, and so on) belong in a local overlay file not in the source repository. Create `~/.switchroom/import-openclaw.yaml`:
397
+ 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`:
378
398
 
379
399
  ```yaml
380
400
  # ~/.switchroom/import-openclaw.yaml
@@ -395,12 +415,13 @@ Overlay entries win on collision with built-in defaults. Unknown files that appe
395
415
  ## Documentation
396
416
 
397
417
  | Guide | Description |
398
- |-------|-------------|
418
+ |---|---|
419
+ | **[Changelog](CHANGELOG.md)** | Release notes, every version |
399
420
  | **[Configuration](docs/configuration.md)** | Full field reference, cascade semantics, profiles |
400
421
  | **[Vault](docs/vault.md)** | Architecture, per-cron secrets, ACL, audit log, threat model |
401
- | **[Telegram Plugin](docs/telegram-plugin.md)** | Progress cards, 10 MCP tools, emoji reactions |
422
+ | **[Telegram Plugin](docs/telegram-plugin.md)** | Progress cards, 15 MCP tools, native checklists, sticker aliases, voice-in |
402
423
  | **[Sub-Agents](docs/sub-agents.md)** | Model routing, delegation patterns, frontmatter spec |
403
- | **[Scheduling](docs/scheduling.md)** | Cron tasks, systemd timers, model selection |
424
+ | **[Scheduling](docs/scheduling.md)** | Cron tasks (per-agent scheduler container), model selection |
404
425
  | **[Session Management](docs/session-optimization.md)** | Continuity, compaction, freshness policy |
405
426
  | **[OpenClaw alternative](docs/vs-openclaw.md)** | Switchroom vs OpenClaw |
406
427
  | **[NanoClaw alternative](docs/vs-nanoclaw.md)** | Switchroom vs NanoClaw |
@@ -409,7 +430,7 @@ Overlay entries win on collision with built-in defaults. Unknown files that appe
409
430
 
410
431
  ## Telemetry
411
432
 
412
- Switchroom reports anonymous usage events and errors to PostHog so we 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).
433
+ 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).
413
434
 
414
435
  To opt out, set this in your shell profile:
415
436
 
@@ -431,7 +452,7 @@ The built-in channel is message in, message out, with zero visibility into what
431
452
  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.
432
453
 
433
454
  **Can I see what sub-agents are doing?**
434
- Yes. When an agent delegates to a sub-agent (a worker, a researcher), the sub-agent's activity shows up in its own section of the progress card. You see the full hierarchy, not just the top-level agent.
455
+ 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.
435
456
 
436
457
  **What does it cost to run?**
437
458
  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.
@@ -34,13 +34,36 @@ expect {
34
34
  # spawned shell. Without this the loop hangs forever and expect
35
35
  # owns stdin, dropping any injected keystrokes.
36
36
  }
37
+ -re {Loading.{1,30}development.{1,30}channels} {
38
+ # NEW dev-channels prompt wording (Claude Code mid-2026+):
39
+ # WARNING: Loading development channels
40
+ # ❯ 1. I am using this for local development
41
+ # 2. Exit
42
+ # Option 1 ("local development") is already highlighted, so just
43
+ # press Enter — no Down keystroke needed. Tightly scoped to the
44
+ # literal "Loading … development channels" warning header so we
45
+ # don't over-match anywhere else.
46
+ sleep 0.5
47
+ send "\r"
48
+ exp_continue
49
+ }
50
+ -re {using this for local development} {
51
+ # Belt-and-suspenders for the new prompt: match on the option-row
52
+ # text in case the WARNING header has scrolled out of the capture
53
+ # window. Same scoping discipline — "local development" is unique
54
+ # to this prompt and won't appear in per-tool confirmations.
55
+ sleep 0.5
56
+ send "\r"
57
+ exp_continue
58
+ }
37
59
  -re {I.{0,5}accept.{0,80}development.{0,10}channels} {
38
- # Dev-channels acknowledgement — shown once per machine when
39
- # --dangerously-load-development-channels is first used. Tightly
40
- # scoped to the literal "development channels" phrase to avoid
41
- # over-matching per-tool confirmations that start with "Yes, I
42
- # accept" e.g. "Yes, I accept this file edit." Those must fall
43
- # through to the plugin's permission_request flow.
60
+ # LEGACY dev-channels acknowledgement — old wording before the
61
+ # 2026 TUI rename ("Yes, I accept the use of development channels").
62
+ # Kept as a fallback for older Claude Code releases. Tightly scoped
63
+ # to the literal "development channels" phrase to avoid over-matching
64
+ # per-tool confirmations that start with "Yes, I accept" e.g.
65
+ # "Yes, I accept this file edit." Those must fall through to the
66
+ # plugin's permission_request flow.
44
67
  sleep 0.5
45
68
  send "\033\[B\r"
46
69
  exp_continue