@mu-crew/mu 1.6.0

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.
@@ -0,0 +1,277 @@
1
+ # Roadmap
2
+
3
+ The single forward-looking doc. If a feature isn't here, it isn't
4
+ planned.
5
+
6
+ Canonical terms: [VOCABULARY.md](VOCABULARY.md). Pillars:
7
+ [VISION.md](VISION.md). Module layout:
8
+ [ARCHITECTURE.md](ARCHITECTURE.md). Shipped history:
9
+ [CHANGELOG.md](../CHANGELOG.md).
10
+
11
+ ---
12
+
13
+ ## Promotion criteria
14
+
15
+ A roadmap item earns implementation when **all three** are true:
16
+
17
+ 1. **Proven friction.** A real user hits the missing feature in a
18
+ real workflow at least twice. Imagined polish doesn't count.
19
+ 2. **No pillar refactor.** Fits the current substrate without
20
+ bending any pillar in [VISION.md](VISION.md).
21
+ 3. **Bounded scope.** Fits in <300 LOC or has a clear smaller
22
+ subset that does.
23
+
24
+ **Exceptions.** Data-loss footguns (silent destruction of user
25
+ artifacts) ship on the *first* occurrence. Polish — bug fixes,
26
+ ergonomic tweaks, error-message wording, doc tightening — doesn't
27
+ need promotion at all; just ship clean (typecheck + lint + tests +
28
+ build).
29
+
30
+ ---
31
+
32
+ ## Anti-feature pledges
33
+
34
+ We will NOT, until each one earns its way back via the criteria
35
+ above:
36
+
37
+ - **Config file.** All config is CLI flags or env vars.
38
+ - **Daemon / watcher / background process** beyond what tmux and
39
+ SQLite give us.
40
+ - **Anticipatory abstractions** with zero current consumer (the
41
+ cautionary tale: a `RunContext` trait with no implementor).
42
+ - **Wrappers around wrappers** (cautionary tale:
43
+ `TextStream`/`TextState`/`StreamResult`).
44
+ - **Codegen, embedded JS engine, macros, decorators** beyond
45
+ TypeScript itself. No workflow DSL.
46
+ - **Template/definition system for agent roles.** Spawn flags +
47
+ the orchestrator's first message ARE the definition.
48
+ - **Render layers beyond `cli-table3` + `picocolors`**, except
49
+ `ink` confined to `src/cli/tui/`. No second TUI stack alongside
50
+ `ink` — if `ink` ever stops paying off, *replace* it; don't
51
+ stack stacks.
52
+ - **Bundle pi.** It's a peer dep.
53
+ - **Plugin runtime, web UI, RPC, chat/docs integrations, memory
54
+ system, workflow engine.** Rejected as a class — these are
55
+ exactly the accumulations a prior internal multi-agent runtime
56
+ collected, and not inheriting them is the point.
57
+
58
+ ---
59
+
60
+ ## Rejected sync substrates
61
+
62
+ Recorded so we don't relitigate them. mu syncs through an ops log in
63
+ SQLite plus append-only JSONL **segments**; every alternative below
64
+ was considered and rejected for the reason given.
65
+
66
+ | Substrate | Why not |
67
+ | --------- | ------- |
68
+ | **Litestream** | Daemon; single-writer; one-way to object storage; `restore` is a whole-file clobber. Solves disaster recovery, not laptop↔devserver. No answer for "I added 3 tasks on the devserver while the laptop was offline." |
69
+ | **LiteFS** | FUSE. On macOS that means macFUSE — a kernel extension needing SIP gymnastics on Apple Silicon. Dead on arrival for a mixed-OS fleet. |
70
+ | **rqlite / dqlite / Marmot** | Raft or NATS clusters. A consensus cluster to sync a task graph between two machines that are usually not both awake. Requires daemons (anti-feature pledge). |
71
+ | **cr-sqlite** | Merges rows by primary key, and every mu entity table is `INTEGER PRIMARY KEY AUTOINCREMENT` — laptop's task `id=5` and devserver's *different* task `id=5` merge into one row, column-by-column. Silent data loss. Fixing it means UUID PKs, i.e. tearing out the surrogate-PK substrate. Also: upstream still says WIP, DDL doesn't sync, and it's a native extension needing a darwin/linux × arm/x64 × glibc/musl build matrix. |
72
+ | **RocksDB / LMDB for the op log** | No triggers, so capture becomes a convention every future call site must remember — and a forgotten op is now silent corruption of undo *and* sync. Single-process-exclusive, so it forces a daemon. mu's read side is ~96 prepared statements, ~70 JOINs, 3 views and recursive CTEs — all hand-rolled. Loses `mu sql`. |
73
+ | **SQLite file per peer (instead of JSONL)** | A torn transfer makes the *whole* file unopenable, versus losing one JSONL line. `-wal`/`-shm` sidecars in a synced folder are the canonical way to corrupt a DB. Page churn defeats rsync/Syncthing delta transfer, where append-only files are the best case. |
74
+ | **`MU_SYNC_PEERS` membership list** | A config file with extra steps, and it would have to be kept consistent across every machine — the drift problem it appears to solve. Peers are discovered from segment filenames instead. |
75
+ | **`mu sync --push/--pull <host>` <!-- doc-cli-drift:skip -->** | Would make mu shell out to ssh/scp — its first network egress — dragging in ssh config, jump hosts, ProxyCommand, ports, identity files, interactive prompts, and network-vs-auth error mapping. A remote backend wearing a small hat. mu prints a copy-pasteable rsync line instead. |
76
+
77
+ The chosen design's net dependency change is **zero**: still just
78
+ `better-sqlite3`.
79
+
80
+ ---
81
+
82
+ ## Possible — small additions with an obvious shape
83
+
84
+ These have a clear design but haven't yet hit promotion criterion
85
+ 1 (friction in ≥2 real workflows). They earn implementation when
86
+ real use surfaces them.
87
+
88
+ ### Per-CLI status detection (claude, codex, …)
89
+
90
+ **Partly superseded on the herdr backend**, which classifies panes
91
+ natively across the agent kinds it knows — mu takes its word there and
92
+ skips scrollback scraping entirely. This item stays live for the tmux
93
+ backend, where mu has to do its own detecting.
94
+
95
+ mu is a pi orchestrator. The Braille-spinner fallback catches every
96
+ TUI wrapper using standard spinner glyphs (U+2800–U+28FF), so
97
+ pi-meta + solo + many vanilla TUIs (claude, codex) work without a
98
+ per-CLI detector.
99
+
100
+ For patterns the spinner fallback misses (permission prompts,
101
+ specific busy markers), a per-CLI `Detector` registry keyed by
102
+ CLI name (~50 LOC per CLI) is the obvious shape. Promote when a
103
+ real specific-prompt-misclassification surfaces.
104
+
105
+ Pattern sketch:
106
+
107
+ | CLI | Busy patterns | Permission patterns |
108
+ | ------ | -------------------------------------- | --------------------------------------------------------- |
109
+ | Claude | `to interrupt`, `\(.*[↑↓].*tokens\)` | `Allow once`, `Allow for this session`, `Esc to cancel` |
110
+ | Codex | `esc to interrupt)`, `to cancel` | `enter to confirm`, `enter to submit \| esc to cancel` |
111
+ | Pi | (well-known mu-defined marker) | (well-known mu-defined marker) — shipped |
112
+
113
+ Critical subtleties any new detector must keep:
114
+
115
+ - **Tail-window extraction**: take last ~100 lines, strip trailing
116
+ blanks, then take last ~20. Already implemented for pi in
117
+ `src/detect.ts`; the registry version factors it out.
118
+ - **Permission detection uses a narrower window than busy
119
+ detection** to prevent already-answered prompts re-triggering.
120
+ - **Permission overrides busy** — if a permission prompt is
121
+ visible, agent is `NeedsPermission`, not `Busy`.
122
+
123
+ ### A third mux backend (zellij, wezterm, kitty)
124
+
125
+ **Not promoted.** tmux and herdr are two real implementations, which
126
+ is what justifies `MuxBackend` existing at all — the interface was
127
+ extracted to serve a concrete second case, not in anticipation of an
128
+ nth. A third backend needs its own friction evidence under the
129
+ [promotion criteria](#promotion-criteria) like anything else. "The
130
+ abstraction already exists" is not evidence.
131
+
132
+ ### Subscription-based wakeups
133
+
134
+ `mu log --tail` polls SQLite once per second. SQLite update hooks
135
+ (via better-sqlite3) or `fs.watch` on the WAL would drop latency
136
+ at the cost of more machinery. Promote when someone hits the
137
+ cliff.
138
+
139
+ ---
140
+
141
+ ## Open questions
142
+
143
+ Listed so we don't pretend they're settled.
144
+
145
+ - **Capability tags on operations.** mu's only authorization
146
+ surface today is "the agent ran the verb." Promote capability
147
+ enforcement when an agent actually does damage.
148
+ - **Per-workstream config.** Resisted (anti-feature pledge). "This
149
+ workstream uses one pi binary, that one uses another" is a real
150
+ gap env vars don't solve cleanly. Revisit when a second user
151
+ hits it.
152
+
153
+ ---
154
+
155
+ ## murmur, and remote agents (noted 2026-08-28, revised 2026-09-02)
156
+
157
+ [murmur](https://github.com/mu-crew/murmur) is a sibling tool at
158
+ `~/hacking/murmur`, on npm as `@mu-crew/murmur`: every coding
159
+ agent on every machine in one attention-sorted list you can jump
160
+ from. It is **built and in daily use** — the original note here
161
+ described it as four design documents, and two of its conclusions
162
+ moved since:
163
+
164
+ - **Current state only, not an event log.** Each node publishes one
165
+ complete snapshot; a peer's answer is replaced in one write and
166
+ absence means absence. No history, watermarks or HLC. murmur's own
167
+ architecture says a generic `put`/`del` op-log "would simply *be*
168
+ `mu`" — so the two differ in substrate now, not just scope.
169
+ - **No control channel, ever.** murmur observes and jumps; "not a
170
+ remote terminal" is a stated non-goal. The phase-2 prompt/abort
171
+ surface this note once counted on will not come from there.
172
+
173
+ **What it would give mu**, both prerequisites for `mu spawn --on
174
+ <host>`: **global agent addressing** (`agents.pane_id` is
175
+ machine-local; murmur's `(host_id, agent_id)` is not, and identity is
176
+ kept separate from `session/window/pane` so it survives a move), and
177
+ **remote observation** (read the fold, get agents on other machines
178
+ for free — including ones mu never spawned). It would not give mu the
179
+ control channel; remote *driving* is unbuilt in both tools.
180
+
181
+ **The cheap seam already works.** murmur's pi extension reads
182
+ `MU_MANAGED_AGENT` / `MU_AGENT_NAME` / `MU_WORKSTREAM` / `MU_ROLE`
183
+ off the pane and marks such agents `orchestrated` — "crew" — hidden
184
+ unless `blocked` or `crashed`, since a supervisor consumes anything
185
+ else. mu already exports the first three (`src/agents/spawn.ts`,
186
+ `paneEnv`) and did nothing. Zero lines: the baseline any tighter
187
+ coupling has to beat. `MU_ROLE` is the one murmur reads and mu does
188
+ NOT set, so a crew row's role column is always null — a one-line fix
189
+ if that column ever proves useful, not a reason to couple further.
190
+
191
+ **No conflict with the sync design.** § Rejected sync substrates and
192
+ § Explicitly rejected both stand: murmur is a **separate binary**
193
+ owning the ssh egress, and if mu ever consumes it, it does so by
194
+ reading a local store, never by growing a network stack. A merge must
195
+ keep the mu half transport-free. murmur independently reached mu's
196
+ two conclusions from mu's own code: ambient rather than daemon (a
197
+ collect rides ordinary invocations, rate-limited with jitter so the
198
+ status bar cannot drive an ssh fan-out) and sync must never fail a
199
+ command (every peer error caught and categorised).
200
+
201
+ **Why not merge now.** Remote observation is the easy half and is now
202
+ shipped; orchestration is the hard half and murmur removes none of
203
+ it. Repos and credentials must exist on the target, worktree setup is
204
+ a local filesystem operation, cherry-pick handoff assumes artifacts
205
+ move between machines, and "which node runs this task" is a scheduler
206
+ question mu does not have.
207
+
208
+ **The step that tests it** is now available, not scheduled: make mu a
209
+ *client* — drop its own status tracking and read murmur's snapshot.
210
+ Small change, real experiment. But `paneStatus()` / `src/detect.ts`
211
+ is not hurting, and trading a working local detector for a
212
+ cross-machine dependency needs a reason from real use (promotion
213
+ criterion 1), not the mere existence of the alternative. Keep the
214
+ *observe* vs *orchestrate* seam in mind even then — it is what lets
215
+ murmur watch agents mu never spawned, and a merge should say why it
216
+ beats that.
217
+
218
+ **Doors already open**, free: same language and runtime (TypeScript
219
+ on npm, since pi is an npm package), `(host_id, agent_id)` addressing
220
+ mu would need anyway, and the `MU_*` env contract.
221
+
222
+ **Do not** start the merge, add ssh to mu, or fold murmur's schema
223
+ into `mu.db` on the strength of this note. It records an intention
224
+ and its preconditions.
225
+
226
+ ---
227
+
228
+ ## Pi extension and the three rules
229
+
230
+ If a pi extension lands (typed `mu_*` tools, HUD widget, wakeups)
231
+ bundled in this same npm package, three rules stay non-negotiable:
232
+
233
+ 1. **The DB is canonical.** All state in `<state-dir>/mu.db`.
234
+ Extension reads/writes through the same modules the CLI uses.
235
+ No extension-only state.
236
+ 2. **Every operation works from the CLI.** No tool registered in
237
+ the extension has logic that doesn't exist in the CLI.
238
+ 3. **The skill teaches the CLI.** Pi sessions without the
239
+ extension still get a working mu by following
240
+ [skills/mu/SKILL.md](../skills/mu/SKILL.md).
241
+
242
+ If those three rules hold, mu stays driveable from a shell forever
243
+ and the extension stays thin.
244
+
245
+ ---
246
+
247
+ ## Explicitly rejected (one-liners)
248
+
249
+ Listed so we don't rediscover them.
250
+
251
+ - **JS / Lisp DSL** (`mu run` / `mu eval` / `mu repl`) <!-- doc-cli-drift:skip --> — bash +
252
+ jq + `--json` covers the gap. A workflow DSL is a maintenance
253
+ liability.
254
+ - **`defineOperation()` registry framework** — no consumer left
255
+ after the DSL was rejected.
256
+ - **Markdown agent-definition discovery** — spawn flags + first
257
+ message already are the definition.
258
+ - **mu as a pi extension only (no CLI)** — children couldn't drive
259
+ mu; humans couldn't debug from a shell.
260
+ - **mu as a library only (no CLI)** — multiple processes would
261
+ fight over the DB.
262
+ - **Two binaries (`mu-agents` + `mu-tasks`)** — agent ↔ task
263
+ integration needs one transactional surface.
264
+ - **`TaskSurface` adapter abstraction** — the built-in graph IS
265
+ the killer feature.
266
+ - **Live cross-machine state sync via a daemon or remote backend** —
267
+ local-first SQLite. The ops-log sync is ambient but daemon-free
268
+ (flush/ingest ride on ordinary mu invocations) and transport-free
269
+ (mu reads and writes files; the user moves them). Rejected
270
+ storage/replication substrates are in
271
+ [§ Rejected sync substrates](#rejected-sync-substrates).
272
+ - **Network-opening remote verbs (`mu remote check`, `mu sync --push/--pull`)** <!-- doc-cli-drift:skip --> — mu manages panes and a task graph on one machine; every network hop is an operator-owned command, either passed through `agent spawn --command` for the mux to run or printed as a copy-pasteable next step. Keeping mu's own process ssh-free avoids an ssh config/auth/host/error model. Spawn does not violate this boundary: the mux, not mu, runs the operator's string.
273
+ - **HTTP API on top of SQLite** — write your own RPC if you need
274
+ one.
275
+ - **A "hosted" mu** — your machine is the deployment.
276
+ - **Anthropomorphic agent names (`alice`, `bob`)** — use
277
+ role-based names (`worker-1`, `reviewer-1`).