@mu-crew/mu 3.13.3 → 4.1.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.
Files changed (119) hide show
  1. package/AGENTS.md +73 -35
  2. package/README.md +71 -57
  3. package/dist/chunk-4ULY3ZKO.js +4482 -0
  4. package/dist/chunk-4ULY3ZKO.js.map +1 -0
  5. package/dist/chunk-7M634JMD.js +375 -0
  6. package/dist/chunk-7M634JMD.js.map +1 -0
  7. package/dist/{chunk-3BARPJLY.js → chunk-PZ5GOCWH.js} +16 -2
  8. package/dist/chunk-PZ5GOCWH.js.map +1 -0
  9. package/dist/chunk-SS7NQSSZ.js +3660 -0
  10. package/dist/chunk-SS7NQSSZ.js.map +1 -0
  11. package/dist/chunk-UCZPJX7K.js +14253 -0
  12. package/dist/chunk-UCZPJX7K.js.map +1 -0
  13. package/dist/{cli-CKOMHBXV.js → cli-TV3N47AZ.js} +7317 -4593
  14. package/dist/cli-TV3N47AZ.js.map +1 -0
  15. package/dist/cli.js +2 -3
  16. package/dist/cli.js.map +1 -1
  17. package/dist/errors-GRONKWFS.js +65 -0
  18. package/dist/extension/mu-pi.js +1632 -837
  19. package/dist/extension/mu-pi.js.map +1 -1
  20. package/dist/index.d.ts +4067 -2561
  21. package/dist/index.js +614 -297
  22. package/dist/index.js.map +1 -1
  23. package/dist/tui-FUBQ326D.js +1848 -0
  24. package/dist/tui-FUBQ326D.js.map +1 -0
  25. package/docs/ARCHITECTURE.md +118 -66
  26. package/docs/VOCABULARY.md +113 -49
  27. package/docs/guide/README.md +3 -3
  28. package/docs/guide/backends.md +8 -4
  29. package/docs/guide/cleanup.md +69 -33
  30. package/docs/guide/delegate.md +149 -66
  31. package/docs/guide/dispatch.md +148 -53
  32. package/docs/guide/getting-started.md +37 -44
  33. package/docs/guide/other-agent-clis.md +102 -0
  34. package/docs/guide/recovery.md +31 -50
  35. package/docs/guide/remote.md +122 -45
  36. package/docs/guide/sql.md +104 -24
  37. package/docs/guide/stop-a-worker.md +30 -37
  38. package/docs/guide/sync.md +11 -3
  39. package/docs/guide/upgrade.md +93 -43
  40. package/docs/reference/env.md +24 -32
  41. package/docs/reference/naming.md +14 -7
  42. package/docs/reference/surface-contract.md +153 -0
  43. package/package.json +33 -11
  44. package/prompts/mu-debug.md +3 -1
  45. package/prompts/mu-refute.md +2 -1
  46. package/prompts/mu-research.md +3 -1
  47. package/prompts/mu-review.md +2 -1
  48. package/prompts/mu-rules-audit.md +5 -4
  49. package/prompts/mu-sweep.md +2 -1
  50. package/prompts/mu-tournament.md +4 -2
  51. package/prompts/mu-until.md +3 -1
  52. package/prompts/ultrathink.md +1 -1
  53. package/skills/mu/SKILL.md +60 -291
  54. package/skills/mu/recipes/adversarial-review.md +12 -103
  55. package/skills/mu/recipes/brief.md +14 -61
  56. package/skills/mu/recipes/course-correct.md +23 -0
  57. package/skills/mu/recipes/findings.md +17 -111
  58. package/skills/mu/recipes/models.md +17 -92
  59. package/skills/mu/recipes/move.md +14 -0
  60. package/skills/mu/recipes/orchestrator-loop.md +20 -119
  61. package/skills/mu/recipes/recovery.md +8 -29
  62. package/skills/mu/recipes/remote-workers.md +17 -318
  63. package/skills/mu/recipes/tasks-or-calls.md +15 -78
  64. package/skills/mu/recipes/test-health.md +19 -77
  65. package/skills/mu/recipes/worker.md +17 -24
  66. package/dist/chunk-45KQLMEL.js +0 -2908
  67. package/dist/chunk-45KQLMEL.js.map +0 -1
  68. package/dist/chunk-G434JKV2.js +0 -641
  69. package/dist/chunk-G434JKV2.js.map +0 -1
  70. package/dist/chunk-KUGUFBWH.js +0 -12527
  71. package/dist/chunk-KUGUFBWH.js.map +0 -1
  72. package/dist/chunk-QITLNZEM.js +0 -87
  73. package/dist/chunk-QITLNZEM.js.map +0 -1
  74. package/dist/chunk-VIEYQMQA.js +0 -393
  75. package/dist/chunk-VIEYQMQA.js.map +0 -1
  76. package/dist/chunk-WI6AWCMK.js +0 -19
  77. package/dist/chunk-WI6AWCMK.js.map +0 -1
  78. package/dist/cli-CKOMHBXV.js.map +0 -1
  79. package/dist/devtools-LS4ZEPWM.js +0 -3772
  80. package/dist/devtools-LS4ZEPWM.js.map +0 -1
  81. package/dist/tui-LFJ5P4Y2.js +0 -40956
  82. package/dist/tui-LFJ5P4Y2.js.map +0 -1
  83. package/dist/tui-sync-worker.js +0 -17
  84. package/dist/tui-sync-worker.js.map +0 -1
  85. package/dist/yank-XDOONQSL.js +0 -13
  86. package/dist/yank-XDOONQSL.js.map +0 -1
  87. package/docs/ROADMAP.md +0 -123
  88. package/docs/VISION.md +0 -298
  89. package/docs/architecture/control-socket.md +0 -142
  90. package/docs/architecture/dag.md +0 -117
  91. package/docs/architecture/mux.md +0 -167
  92. package/docs/architecture/ops-log.md +0 -160
  93. package/docs/architecture/sdk.md +0 -187
  94. package/docs/architecture/sync.md +0 -159
  95. package/docs/architecture/tui.md +0 -165
  96. package/docs/guide/tui.md +0 -98
  97. package/docs/history/CHANGELOG-pre-3.md +0 -4866
  98. package/docs/img/tui-dashboard.png +0 -0
  99. package/docs/specs/2026-09-28-agent-state-from-murmur.md +0 -333
  100. package/docs/specs/2026-09-29-task-substates.md +0 -247
  101. package/docs/specs/2026-10-03-keep-driving-nudge.md +0 -163
  102. package/docs/specs/2026-10-21-fast-and-slow-gates.md +0 -229
  103. package/skills/mu/recipes/backlog-triage.md +0 -45
  104. package/skills/mu/recipes/codemode-driver.md +0 -118
  105. package/skills/mu/recipes/deep-research.md +0 -69
  106. package/skills/mu/recipes/drift-audit.md +0 -75
  107. package/skills/mu/recipes/fan-out.md +0 -42
  108. package/skills/mu/recipes/hypothesis-panel.md +0 -48
  109. package/skills/mu/recipes/long-run.md +0 -32
  110. package/skills/mu/recipes/loop-until-done.md +0 -45
  111. package/skills/mu/recipes/plan.md +0 -67
  112. package/skills/mu/recipes/refute.md +0 -55
  113. package/skills/mu/recipes/review-panel.md +0 -65
  114. package/skills/mu/recipes/rules-audit.md +0 -89
  115. package/skills/mu/recipes/tournament.md +0 -44
  116. package/skills/mu/recipes/ultrathink.md +0 -86
  117. package/skills/mu/recipes/watcher.md +0 -28
  118. package/skills/mu/recipes/waves.md +0 -21
  119. /package/dist/{chunk-3BARPJLY.js.map → errors-GRONKWFS.js.map} +0 -0
@@ -1,17 +0,0 @@
1
- import { createRequire as __muCreateRequire } from "node:module"; const require = __muCreateRequire(import.meta.url);
2
- import {
3
- ambientSyncPass,
4
- openDb
5
- } from "./chunk-45KQLMEL.js";
6
- import "./chunk-WI6AWCMK.js";
7
- import "./chunk-3BARPJLY.js";
8
-
9
- // src/cli/tui/sync-worker.ts
10
- import { parentPort, workerData } from "worker_threads";
11
- var port = parentPort;
12
- if (port === null) throw new Error("tui-sync-worker must run as a worker_thread");
13
- var db = openDb({ path: workerData.dbPath });
14
- port.on("message", () => {
15
- void ambientSyncPass(db, { quiet: true }).catch(() => null).then((result) => port.postMessage(result));
16
- });
17
- //# sourceMappingURL=tui-sync-worker.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/cli/tui/sync-worker.ts"],"sourcesContent":["// worker_thread entry for the TUI's ambient sync pass (built to\n// dist/tui-sync-worker.js). ingest and flush are synchronous SQLite and\n// file work (seconds on a first read, hundreds of ms per peer append on\n// a 40 MB segment), so on ink's thread they froze input and repaints.\n// Here they block only this thread. The worker owns its own connection\n// to the same DB file: WAL serves the TUI's reads meanwhile, and the\n// TUI sees the result on its next read.\n//\n// Protocol: the parent posts any message to start one pass, and the\n// worker replies with the pass's result (`null` if it threw) when the\n// pass is over. ambientSyncPass is total, and `quiet` keeps stderr off\n// the alternate screen.\n\nimport { parentPort, workerData } from \"node:worker_threads\";\nimport { openDb } from \"../../db.js\";\nimport { ambientSyncPass } from \"../../sync.js\";\n\nconst port = parentPort;\nif (port === null) throw new Error(\"tui-sync-worker must run as a worker_thread\");\nconst db = openDb({ path: (workerData as { dbPath: string }).dbPath });\nport.on(\"message\", () => {\n void ambientSyncPass(db, { quiet: true })\n .catch(() => null)\n .then((result) => port.postMessage(result));\n});\n"],"mappings":";;;;;;;;;AAaA,SAAS,YAAY,kBAAkB;AAIvC,IAAM,OAAO;AACb,IAAI,SAAS,KAAM,OAAM,IAAI,MAAM,6CAA6C;AAChF,IAAM,KAAK,OAAO,EAAE,MAAO,WAAkC,OAAO,CAAC;AACrE,KAAK,GAAG,WAAW,MAAM;AACvB,OAAK,gBAAgB,IAAI,EAAE,OAAO,KAAK,CAAC,EACrC,MAAM,MAAM,IAAI,EAChB,KAAK,CAAC,WAAW,KAAK,YAAY,MAAM,CAAC;AAC9C,CAAC;","names":[]}
@@ -1,13 +0,0 @@
1
- import { createRequire as __muCreateRequire } from "node:module"; const require = __muCreateRequire(import.meta.url);
2
- import {
3
- osc52Sequence,
4
- probeClipboardBackend,
5
- yank
6
- } from "./chunk-QITLNZEM.js";
7
- import "./chunk-3BARPJLY.js";
8
- export {
9
- osc52Sequence,
10
- probeClipboardBackend,
11
- yank
12
- };
13
- //# sourceMappingURL=yank-XDOONQSL.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/docs/ROADMAP.md DELETED
@@ -1,123 +0,0 @@
1
- # Roadmap
2
-
3
- If a feature is not here, it is not planned. Shipped work is in
4
- [CHANGELOG.md](../CHANGELOG.md). Pillars are in [VISION.md](VISION.md).
5
-
6
- ## Promotion criteria
7
-
8
- A roadmap item is built when all three hold:
9
-
10
- 1. **Proven friction.** A real user hit the gap in a real workflow at
11
- least twice.
12
- 2. **No pillar refactor.** It fits without bending a
13
- [VISION](VISION.md) pillar.
14
- 3. **Bounded scope.** It fits in <300 LOC, or a smaller subset does.
15
-
16
- Data-loss footguns ship on the first occurrence. Polish (bug fixes,
17
- ergonomics, error wording, docs) needs no promotion: ship it through the
18
- green gate.
19
-
20
- ## Anti-feature pledges
21
-
22
- mu does not add these unless one earns its way back under the criteria:
23
-
24
- - **A config file.** Config is CLI flags and env vars.
25
- - **A daemon, watcher, or background process** beyond tmux and SQLite.
26
- - **Abstractions with no current consumer** (the cautionary tale: a
27
- `RunContext` trait with no implementor).
28
- - **Wrappers around wrappers** (`TextStream`/`TextState`/`StreamResult`).
29
- - **Codegen, an embedded JS engine, macros, or decorators.** No workflow
30
- DSL.
31
- - **Templates or definitions for agent roles.** Spawn flags and the first
32
- message are the definition.
33
- - **Render layers beyond `cli-table3` + `picocolors`**, except `ink` in
34
- `src/cli/tui/`. If `ink` stops paying off, replace it. Never run two
35
- TUI stacks.
36
- - **Bundling pi.** It is a peer dependency.
37
- - **A plugin runtime, web UI, RPC, chat or docs integrations, a memory
38
- system, or a workflow engine.** A prior internal runtime collected all
39
- of these. Not inheriting them is the point.
40
-
41
- ## Candidates
42
-
43
- Each has a design but no proven friction yet. Most come from the prior
44
- runtime, which hit the gap and fixed it its own way. Parked tasks may
45
- track them; delete the row when one ships.
46
-
47
- | Item | Gap today | Shape | Size |
48
- | --- | --- | --- | --- |
49
- | **Tag mux sessions with their DB** | `teardown --empty` never removes a `mu-*` session that has no DB row, because another DB may own it. | Set `@mu_db=<abs path>` at session creation. Remove unregistered sessions whose tag matches this DB, and keep foreign or untagged ones. Optional mux method, since herdr may have no equivalent. | ~60 LOC |
50
- | **Token usage per run** | Orchestrators cannot budget a fan-out. | The extension reports pi's usage for the run, and `wait --json` carries it. Unknown stays unknown, never 0. No dollar estimates. | ~80 LOC |
51
- | **Global delegate cap** | `MU_DELEGATE_MAX` applies per pi session. Several orchestrators, plus workers running `mu agent spawn -w scratch`, are not capped in total. | Count live `scratch` agents from the DB in the queue check. | ~30 LOC |
52
- | **Wait for a non-pi CLI's prompt** | `spawn --send` treats a blank or booting screen as calm and can paste before the CLI is ready. | Settle only once the CLI has drawn its input prompt. This needs a screen pattern for each CLI, so it waits for real friction. | ~40 LOC |
53
- | **Absolute deadline in `wait`** | A delegate re-armed after `/reload` restarts its countdown. | Report `deadline` (epoch ms) alongside `timeoutS`. | ~20 LOC |
54
- | **Bounded `task tree`** | No depth or node limit. | Cap depth, nodes and edges, and report which cap was hit. | ~30 LOC |
55
- | **Design-doc review angles** | `review-panel`'s angles are for diffs. | Add rollout and back-compat, alternatives, and integration angles. Synthesize into unanimous findings, tensions, and the strongest objection. | recipe |
56
- | **Sender framing on sends** | The receiving agent cannot tell a peer's message from its human's. | Prefix text sent by a mu agent with `[mu: from <agent>]`. This touches prompts, so it follows logging the sender. | ~20 LOC |
57
- | **A third mux backend** | None: tmux and herdr are the two implementations that justify `MuxBackend`. | zellij, wezterm or kitty. A third needs its own friction evidence; the existing interface is not evidence. | — |
58
- | **Subscription-based wakeups** | `mu log --tail` polls once a second. | SQLite update hooks or `fs.watch` on the WAL. Lower latency for more machinery. | — |
59
-
60
- ## Next breaking release
61
-
62
- - **Schema v12 drops `agents.status`.** In v11 it stays for
63
- compatibility and always holds `spawning`.
64
- - **`mu agent list --json` becomes `{items, count}`.** It emits
65
- `{workstreamName, agents, orphans}`, the one collection off the
66
- documented shape (SKILL.md notes the exception). Conforming breaks
67
- `.agents` readers, `mu_delegate` among them.
68
-
69
- ## Open questions
70
-
71
- - **Capability tags on operations.** Today the only authorization is
72
- "the agent ran the verb". Enforce capabilities when an agent does real
73
- damage.
74
- - **Per-workstream config.** Resisted by the pledges, but "this
75
- workstream uses a different pi binary" is a gap env vars solve poorly.
76
- Revisit when a second user hits it.
77
-
78
- ## Explicitly rejected
79
-
80
- - **A JS or Lisp DSL** (`mu run`, `mu eval`, `mu repl`) <!-- doc-cli-drift:skip -->. bash, `jq`,
81
- and `--json` cover it.
82
- - **A `defineOperation()` registry.** No consumer after the DSL went.
83
- - **Markdown agent-definition discovery.** Spawn flags and the first
84
- message are the definition.
85
- - **mu as only a pi extension or only a library.** Children and humans
86
- could not drive it from a shell, and processes would fight over the DB.
87
- - **Two binaries (`mu-agents` + `mu-tasks`).** Agents and tasks need one
88
- transactional surface.
89
- - **A `TaskSurface` adapter.** The built-in graph is the point.
90
- - **Live sync through a daemon or remote backend.** Sync rides on
91
- ordinary mu calls and mu only reads and writes files. See
92
- [Rejected sync substrates](#rejected-sync-substrates).
93
- - **Network-opening verbs (`mu remote check`, `mu sync --push/--pull`)** <!-- doc-cli-drift:skip -->.
94
- Every network hop is an operator command. Spawn passes it through
95
- `--command` for the mux to run, or mu prints it as a next step. mu's
96
- own process stays ssh-free.
97
- - **An HTTP API over SQLite** or **a hosted mu.**
98
- - **Personal agent names (`alice`, `bob`).** Use role names
99
- (`worker-1`, `reviewer-1`).
100
- - **Pane scraping as a state fallback.** No state source means `unknown`.
101
- - **Runtime agent state in `mu.db`.** The copy goes stale. The control
102
- socket, herdr, or murmur owns the current reading.
103
- - **Reading murmur's `state.db`.** Use its public contract.
104
- - **murmur as a hard dependency.** pi agents and the task graph do not
105
- need it.
106
-
107
- ### Rejected sync substrates
108
-
109
- mu syncs through the SQLite ops log plus append-only JSONL segments.
110
- These alternatives were rejected:
111
-
112
- | Substrate | Why not |
113
- | --------- | ------- |
114
- | **Litestream** | Daemon, single writer, one-way to object storage, and `restore` overwrites the whole file. It does disaster recovery, not laptop↔devserver merges. |
115
- | **LiteFS** | FUSE, which on macOS means a macFUSE kernel extension. |
116
- | **rqlite / dqlite / Marmot** | A consensus cluster and daemons to sync two machines that are rarely both awake. |
117
- | **cr-sqlite** | Merges rows by primary key. mu's `INTEGER PRIMARY KEY AUTOINCREMENT` ids collide across machines and merge silently. Also WIP upstream, no DDL sync, and a native build matrix. |
118
- | **RocksDB / LMDB for the op log** | No triggers, so a forgotten capture silently corrupts undo and sync. Single-process access forces a daemon. Loses SQL views, JOINs, and `mu sql`. |
119
- | **SQLite file per peer** | A torn transfer makes the whole file unreadable instead of one JSONL line. `-wal`/`-shm` sidecars in a synced folder corrupt DBs. Page churn defeats delta transfer. |
120
- | **`MU_SYNC_PEERS` list** | A config file that must match on every machine. Peers come from segment filenames instead. |
121
- | **`mu sync --push/--pull <host>` <!-- doc-cli-drift:skip -->** | mu's first ssh egress, with ssh config, auth, and error mapping. mu prints an rsync line instead. |
122
-
123
- The chosen design adds no dependency beyond `better-sqlite3`.
package/docs/VISION.md DELETED
@@ -1,298 +0,0 @@
1
- # VISION: A persistent crew of agents
2
-
3
- Terms are defined in [VOCABULARY.md](VOCABULARY.md).
4
-
5
- ## What mu is
6
-
7
- mu is a small, durable control plane for a persistent crew of AI agents
8
- in multiplexer panes. Agents have names and roles, outlive sessions, and
9
- work on a built-in task graph, each in its own VCS workspace. Humans and
10
- agents drive them through one CLI. State lives in one SQLite file. Every
11
- feature is a typed verb over that state or a reconciled view of reality.
12
-
13
- ```bash
14
- mu agent spawn worker-1 --tab Backend --workspace
15
- mu agent spawn reviewer-1 --tab Review --workspace --role read-only
16
- mu task add --title "Build auth" --impact 80 --effort-days 3
17
- mu task claim build_auth --for worker-1 --evidence "have implementation plan"
18
- mu agent send worker-1 "Implement build_auth per the description"
19
- mu state # canonical state card
20
- mu log --tail # subscribe to every state change
21
- ```
22
-
23
- Everything else makes those lines recover from failure and scale to
24
- dozens of agents and hundreds of tasks.
25
-
26
- ## Why it exists
27
-
28
- Existing tools force a choice:
29
-
30
- - **Hidden subagents** (pi-subagents, the Claude Code and Codex task
31
- tools) hand one-shot work to a child you cannot see, steer, or keep
32
- talking to. Its transcript collapses into a result.
33
- - **tmux orchestration tools** spawn agents in panes but leave
34
- coordination to chat transcripts or filesystem conventions.
35
- - **Task trackers** (GitHub Issues, Linear) model the work but do not
36
- run the agents.
37
- - **Large orchestration platforms** hold the coordination state but
38
- carry breadth that costs more to maintain than it returns. See
39
- [What a prior runtime taught us](#what-a-prior-runtime-taught-us).
40
-
41
- mu joins the first three without becoming the fourth.
42
-
43
- ## Design principles
44
-
45
- ### 1. The CLI is the product
46
-
47
- Everything mu does works from a shell with no pi extension loaded. A
48
- feature that needs the extension does not ship. The pi extension
49
- (`mu link pi`) serves the control socket and the `mu_delegate` tool, and
50
- three rules keep it thin:
51
-
52
- 1. **The DB is canonical.** The extension reads and writes through the
53
- same modules as the CLI. It keeps no state of its own.
54
- 2. **Every operation works from the CLI.** No extension tool has logic
55
- the CLI lacks.
56
- 3. **The skill teaches the CLI.** A pi session without the extension
57
- still gets a working mu from [skills/mu/SKILL.md](../skills/mu/SKILL.md).
58
-
59
- Delegation is CLI: `mu agent spawn -w scratch`, `mu agent send`, and
60
- `mu agent wait --json`. `mu_delegate` only adds the callback that puts
61
- the answer into the parent conversation.
62
-
63
- ### 2. One DB is canonical
64
-
65
- All state lives in `~/.local/state/mu/mu.db` (SQLite WAL, safe for
66
- concurrent processes). In-memory state is a cache. The extension and the
67
- CLI share the DB, so they cannot diverge.
68
-
69
- ### 2b. One log records every change
70
-
71
- The `ops` table is the only change record. SQLite triggers write it in
72
- the same transaction as the mutation, so capture cannot be forgotten or
73
- drift from the data. Sync, undo, rebuild, and history are queries or
74
- replays over that log.
75
-
76
- Reads still go to the tables. mu is not event-sourced: the tables are
77
- the materialized view and triggers keep them in step with the ops.
78
- `mu doctor` rebuilds into a temp DB and diffs, so that is verified, not
79
- assumed. A capture bug therefore breaks sync, undo, and rebuild at once,
80
- which is why the drift check is load-bearing.
81
-
82
- ### 2c. Local-first, transport-agnostic
83
-
84
- Every machine keeps a complete DB. Machines exchange append-only JSONL
85
- segments, one per machine with a single writer, so no file is contended
86
- and any file mover works (Syncthing, rsync, scp, git, a USB stick). mu
87
- reads and writes files. The user owns transport. mu has no network
88
- code, no daemon, and no membership config: peers are found from the
89
- segment files present.
90
-
91
- ### 3. Reality wins reconciliation
92
-
93
- `mu agent list` queries the mux, prunes ghosts, and surfaces orphans.
94
- The DB records what mu last observed.
95
-
96
- ### 4. Agents are dumb workers; the task graph is the brain
97
-
98
- The task DAG is the center of mu, not a sidecar. Tasks require `impact`
99
- and `effort_days`. Edges are `blocks` relations. `ready`, `blocked`, and
100
- `goals` are SQL views. The parallel-track detector runs union-find and
101
- merges diamonds, so two agents never collide on a shared dependency.
102
-
103
- "What next?" and "can we parallelize?" are deterministic queries. The
104
- LLM decides what to type to an agent. The graph decides which agent gets
105
- which task.
106
-
107
- ### 5. One workstream per mux session
108
-
109
- A workstream is a mux session: a tmux session on tmux, a herdr workspace
110
- on herdr. Its agents are panes in that session, so
111
- `tmux a -t mu-<workstream>` shows the whole crew. Workstreams on one
112
- machine are separate sessions, partitioned in the DB by `workstream_id`.
113
- The crew survives detach and reattach.
114
-
115
- The multiplexer is a backend chosen by detection
116
- (`MU_MUX` → `HERDR_ENV` → `$TMUX` → `PATH`), like the VCS backend.
117
- Topology, the paste protocol, scrollback capture, pane-id validation,
118
- and identity fallback live behind `MuxBackend` in `src/mux/`. There are
119
- two implementations. A third needs its own friction evidence.
120
-
121
- ### 6. Pi-first, with reported agent state
122
-
123
- mu never infers agent state from pane text. Each state source reports it:
124
-
125
- | Agent | Send, abort, wait | State |
126
- | --- | --- | --- |
127
- | pi (any backend, local or remote over `ssh -L`) | control socket, exact | control socket |
128
- | other CLI on herdr | mux | herdr |
129
- | other CLI on tmux | mux (bracketed paste) | murmur |
130
-
131
- With no source, state is `unknown` with a reason. A pi agent whose
132
- socket does not answer fails loudly; mu never falls back to pasting.
133
- murmur is optional for pi agents.
134
-
135
- `MU_PI_COMMAND="pi-alt --some-flag"` swaps the pi command for every
136
- spawn. First-class support for other CLIs is not planned. It can earn promotion
137
- under the [ROADMAP](ROADMAP.md) criteria: spawn accepts any command and
138
- `agents.cli` is TEXT.
139
-
140
- ### 7. TypeScript on Node
141
-
142
- mu uses a few well-known npm dependencies (`commander`,
143
- `better-sqlite3`, `cli-table3`, `picocolors`, `execa`, and `ink` for the
144
- TUI). There is no native code to maintain and no build matrix.
145
-
146
- - **Types pay.** The typed error classes map to exit codes in
147
- `handle()`. The `assertXInWorkstream` family stays type-safe across
148
- namespaces. `noUncheckedIndexedAccess` has caught real bugs.
149
- - **`better-sqlite3` fits.** Synchronous calls match a short-lived CLI,
150
- and `db.transaction()` is the right shape.
151
- - **Iteration is fast.** Several substantive changes a day land in
152
- about 99k lines of `src/` and `test/`.
153
-
154
- The weak spot is cold start: Node takes 30–50 ms to boot, which matters
155
- only in tight loops. If that changes, Rust is the port target.
156
- `better-sqlite3` needs prebuilds (darwin-arm64/x64, linux-x64/arm64,
157
- win32-x64) or a C++ toolchain.
158
-
159
- ### 8. Schema-first: typed verbs over read views, SQL as escape hatch
160
-
161
- - **Read views**: the `ready`, `blocked`, and `goals` views, and
162
- `mu state` as the state card.
163
- - **Typed verbs** that map to resource transitions (`task claim`,
164
- `task close`, `agent spawn --workspace`, `workstream teardown`).
165
- - **`--json` on every read verb**, so scripts use `jq`, not table parsing.
166
- - **`mu sql`** underneath, as the explicit escape hatch.
167
-
168
- There is no DSL, no plugin system, no workflow engine, and no verb
169
- registry. The commander wiring in `src/cli.ts` is the verb surface. A new
170
- verb is one SDK function plus one commander block.
171
-
172
- ### 9. Observed vs claimed
173
-
174
- A mutating verb records what the caller said it relied on.
175
- `mu task close design --evidence "npm test exit 0"` lands in the ops log
176
- with the evidence. mu does not run the tests, but `mu log` can search
177
- the grounding for every change.
178
-
179
- ### 10. Get out of the model's way
180
-
181
- mu coordinates agents and does not reason about them. It does not own:
182
-
183
- - **Model selection or thinking levels.** No tiers, no provider matrix.
184
- pi already takes `--model` and `--thinking`. Pass them through the
185
- spawn command.
186
- - **Prompts.** No system-prompt templates and no role registry. The
187
- prompt is the spawn command plus the first message.
188
- - **Tool routing.** The agent CLI owns tool allowlists, MCP servers, and
189
- extensions.
190
- - **Output interpretation.** mu does not parse panes for state, facts,
191
- or tool calls. `--evidence` is recorded as given.
192
-
193
- The whole vendor surface is one lookup: `--cli <key>` reads
194
- `$MU_<KEY>_COMMAND`. Per-role models are a convention in your shell rc:
195
-
196
- ```bash
197
- export MU_PI_MINI_COMMAND="pi --model haiku:off" # → --cli pi_mini
198
- export MU_PI_BIG_COMMAND="pi --model opus:high" # → --cli pi_big
199
-
200
- mu agent spawn worker-1 --cli pi_mini
201
- mu agent spawn reviewer-1 --cli pi_big
202
- ```
203
-
204
- The skill's [model tiers](../skills/mu/recipes/models.md) are advice to
205
- the agent choosing; mu reads none of it.
206
-
207
- ## What it enables
208
-
209
- - **Visible crews.** Spawn agents once and send them work all day. Each
210
- sits in a pane you can attach to.
211
- - **Isolation.** Each agent gets a jj workspace, sl clone, git worktree,
212
- or `cp -a` copy, detected automatically and freed by `mu agent close`.
213
- - **Wakeups without a daemon.** `mu log --tail` and event-driven
214
- `mu agent wait` replace polling.
215
- - **Crash recovery.** The reaper reopens a dead agent's IN_PROGRESS
216
- tasks with a note.
217
-
218
- ## What it is not
219
-
220
- - **Not an orchestrator.** mu provides primitives. The policy (when to
221
- spawn, what to assign, when to free) is yours: bash, `jq` over
222
- `--json`, or an LLM following the bundled skill. There is no workflow
223
- engine and no `mu run script.ts`. <!-- doc-cli-drift:skip -->
224
- - **Not a chat protocol.** Agents communicate through notes, claims, and
225
- the ops log.
226
- - **Not a hidden-subagent runner.** A delegate is an ordinary agent in a
227
- visible pane: attach, steer, abort, or keep talking to it. It costs one
228
- pane and one pi process. A hidden subagent is lighter for many tiny
229
- calls. mu bets that visibility and steering are worth the cost. There
230
- are no agent types: the brief is plain text. A delegate's answer is
231
- not recorded in mu; only what the caller writes back (a note, a state
232
- change) is. The one extension-held list with no pane is the delegate
233
- queue: bounded, shown in pi's footer, cancellable, and named at
234
- shutdown when it is dropped.
235
- - **DB-undoable, not substrate-undoable.** `mu undo <group> --yes`
236
- restores rows. It does not revive killed panes or freed workspace
237
- directories. Reconciliation then reports ghosts and orphans.
238
-
239
- ## Key constraints
240
-
241
- 1. **A multiplexer is required** (tmux or herdr). `mu doctor` reports
242
- the backend. Agent verbs exit 5 when none is reachable.
243
- 2. **The agent never acknowledges a send.** For pi, the control socket
244
- confirms that pi accepted the message. On the mux path, mu confirms
245
- only that the text reached the pane and warns when it cannot confirm
246
- submission. Use `mu agent wait` or `mu log --tail` to see the effect.
247
- 3. **Recursion is opt-in.** `mu_delegate` is hidden in agents mu spawned
248
- (`MU_MANAGED_AGENT`). Agents have `mu` on PATH, but the skill tells
249
- them they are not the orchestrator.
250
- 4. **Every invocation is short-lived, except two interactive readers.**
251
- No daemon, no resident state outside SQLite, no background process.
252
- The exceptions are `mu log --tail` and the TUI (`mu state --tui`, or
253
- bare `mu` on a TTY). Both are:
254
- - owned by a human or parent script and gone when it ends;
255
- - free of user actions: TUI keys copy the `mu <verb>` command to
256
- the clipboard and exit, so every change the operator asks for is a
257
- fresh CLI call. The TUI's slow tick still does the housekeeping
258
- any `mu` invocation does: ambient sync (ingest peer ops, flush the
259
- local segment) and reconcile (prune agents whose pane died, which
260
- reopens their tasks with a `[reaper]` note, and refresh pane
261
- titles);
262
- - limited to stdin, stdout, and a poll timer: no sockets or watches.
263
- The TUI's slow tick runs short-lived probes (mux liveness, VCS
264
- status) and ambient sync, and its handoffs (lazygit, tuicr, attach,
265
- clipboard) run one foreground child each; none outlives the TUI;
266
- - TTY-gated: pipes, CI, `--json`, and `MU_NO_TUI=1` never enter the
267
- TUI, and plain `mu state` prints the static card.
268
-
269
- The control socket is not a third exception. The pi process serves it
270
- and it dies with that process. Any other long-lived process needs its
271
- own promotion; the [anti-feature pledges](ROADMAP.md#anti-feature-pledges)
272
- stand.
273
-
274
- ## What a prior runtime taught us
275
-
276
- A council review of mu's design ancestor, a larger internal multi-agent
277
- runtime, found it justified only as a durable coordination control
278
- plane. Its minimal core: sessions, an agent registry, a task graph,
279
- workspace ownership, an event log, wakeups, human approvals, a typed
280
- control API, read-only state cards, and orphan recovery. Everything else
281
- (chat, workflow DSLs, memory, dashboards) had to prove its worth.
282
-
283
- mu ships nine of the ten. Approvals are missing because 200+ dogfooded
284
- tasks never needed them. Below the point where coordination is itself
285
- the work, pi plus manual tmux is more transparent than mu.
286
-
287
- Two lessons back the orchestrator-pull remote design. The runtime built
288
- multiplexing, flow control, keepalive and retries above a transport that
289
- already provided them, then deleted them: don't rebuild what the
290
- transport gives. A child never dials its parent; the parent pulls.
291
- Tasks injected as prompts and read back through sentinel markers broke
292
- on line wrapping and lost notes: state belongs in the DB, not in pane
293
- text.
294
-
295
- Known gaps, none promoted yet: `--evidence` grounds but does not verify,
296
- mutations have no declared idempotency keys, most mutations have no dry
297
- run (`workstream teardown`, `task delete`, and `undo` do), and the `role`
298
- field is stored but not enforced.
@@ -1,142 +0,0 @@
1
- # Control socket
2
-
3
- mu drives a pi agent through a unix socket served by mu's own pi
4
- extension inside the agent's interactive pi. The pane stays pi's TUI,
5
- so a human attached to it shares the same session. There is no
6
- `pi --mode rpc`, no screen scraping and no daemon: the server lives and
7
- dies with the pi process. Overview: [ARCHITECTURE.md](../ARCHITECTURE.md).
8
-
9
- ## Path derivation
10
-
11
- `ctlSocketPath(ws, agent)` (`src/ctl/path.ts`) is
12
- `<state dir>/sock/<ws>/<agent>.sock`. The state dir is
13
- `dirname($MU_DB_PATH)` or the default. If the path exceeds 103 bytes
14
- (the macOS `sun_path` limit), it becomes
15
- `<state dir>/sock/h/<sha1(ws/agent)[:16]>.sock`.
16
-
17
- Nothing is stored. Spawn creates the directory (mode 0700), removes any
18
- stale socket file at the path, and injects the path as `MU_CTL_SOCK`
19
- into every pane. Every later verb derives the path again.
20
-
21
- ## Protocol v1
22
-
23
- `src/ctl/protocol.ts` imports nothing from mu, so the extension loads
24
- it standalone. Framing is JSON lines, one request per connection. Every
25
- reply carries `v: 1`; the client (`src/ctl/client.ts`) throws
26
- `CtlVersionError` on anything else.
27
-
28
- | op | does |
29
- | --- | --- |
30
- | `hello` | identity plus `ops` (served ops) and `extVersion` (the mu version the extension was built from) |
31
- | `status` | `state` (`busy` / `idle`), `since`, `runs`, `pending`, and pi's `sessionFile` when it has one |
32
- | `send` | `pi.sendUserMessage`; when busy, `mode` is `steer` or `followUp` (default). Replies with the `status` fields measured before the dispatch, so `runs` is the baseline for a later `wait` |
33
- | `wait` | resolves on pi's `agent_settled` after `afterRuns`, with the run's `lastText` and pi's `sessionFile` |
34
- | `abort` | `ctx.abort()` (pi's Esc) |
35
- | `interrupt` | busy: `ctx.abort()`, wait for `agent_settled` (up to `timeoutMs`), then `pi.sendUserMessage(text)` as a new run; idle: just the send. Replies `wasBusy`, `pending` (measured before the abort) and the `status` fields measured after the settle and before the send, so `runs` is the `wait` baseline for the new run. `timeout`: nothing sent |
36
- | `fresh` | new session plus prompt as one operation, through the internal `/mu-fresh` command; replies once the new run starts; refused with `busy` unless `force` |
37
- | `command` | `name` `new`, `reload` or `compact` (with optional `instructions`), through the internal `/mu-new`, `/mu-reload`, `/mu-compact` commands, which call `ctx.newSession()`, `ctx.reload()`, `ctx.compact()`. `new` replies once the session is replaced, `reload` after the `session_start` it causes, `compact` when compaction starts or with pi's error (`Nothing to compact ...`). Refused with `busy` unless `force` |
38
-
39
- `pi.sendUserMessage` dispatches only extension commands, not pi's
40
- built-in `/new`, `/reload`, `/compact`. Each internal command is an
41
- extension command, triggered with `expandPromptTemplates: true`, and
42
- its handler gets the command context that can replace or reload the
43
- session.
44
-
45
- These back `mu agent send` (`--fresh`, `--steer`, `--interrupt`, session commands), `mu agent wait`,
46
- `mu agent abort`, the agent state reading, and `mu_delegate`.
47
-
48
- ## Version skew
49
-
50
- An op the extension does not serve returns `unknown op: <op>` with its
51
- `ops` list. `mu agent send --interrupt` then composes `abort`, `wait`
52
- and `send` itself. Any other op raises `AgentExtensionOutdatedError` (exit 4), whose
53
- next steps are `/reload` through the mux (an extension that lacks the
54
- `command` op cannot run it over ctl) or a respawn. `mu doctor`
55
- probes every pi agent's socket (the `ctl` row) and flags an
56
- `extVersion` older than the installed mu or a missing op. Its extension
57
- row reports whether `mu link pi` installed the extension.
58
-
59
- ## Extension lifecycle
60
-
61
- `extension/mu-pi.ts` is inert unless `MU_CTL_SOCK` is set.
62
-
63
- - pi re-creates extension runtimes per session. The server, the run
64
- counters and an in-flight `fresh` therefore live in a process-global
65
- map (`Symbol.for("mu.pi.ctl")`).
66
- - Session replacement (`new`, `resume`, `fork`, `reload`) keeps the
67
- socket and its connections. Quit closes it.
68
-
69
- **Binding never steals a live socket.**
70
-
71
- 1. If the path already answers, another pi serves it (a nested `pi -p`
72
- inherited `MU_CTL_SOCK`). The extension leaves it alone and says so
73
- on stderr. Identity follows the bind: that pi, and one that loses
74
- the `link()` race, is nested and gets no keep-driving, close or
75
- refute nudge, though it inherited the agent's `MU_AGENT_NAME`.
76
- The decision lives in the process-global map, so `/reload` keeps it.
77
- 2. Otherwise it listens on a private name in the same directory, sets
78
- mode 0600, and hard-links that name onto the public path. libuv
79
- unlinks the listened-on name at close, so the public path must never
80
- be that name. `link()` fails on an existing path, so a racing pi
81
- keeps its socket.
82
- 3. It records the inode of the public path.
83
-
84
- On quit it unlinks the public path only if the inode is still its own,
85
- because another pi may own the path by then. On `session_start`, a
86
- server whose file was deleted or replaced closes and rebinds. That is
87
- how `/reload` recovers a lost socket file.
88
-
89
- ## No silent fallback
90
-
91
- `src/agents/transport.ts` routes sends. `expectsCtl` is true when the
92
- cli key resolves to pi (`pi` or `pi-meta` as argv0), or when a socket
93
- file exists at the agent's derived path. The file case covers
94
- `--cli helper --command "pi-meta ..."`, whose command is not stored.
95
-
96
- A pi agent whose socket does not answer raises
97
- `AgentCtlUnreachableError` for send and abort, and reads as `unknown`
98
- with reason `ctl missing` or `ctl refused` for state. mu never pastes
99
- into it or asks murmur instead.
100
-
101
- The mux paste path (bracketed paste, `MU_SEND_DELAY_MS`,
102
- `MU_SEND_READINESS_MS`) serves only:
103
-
104
- - non-pi CLIs;
105
- - adopted panes without ctl;
106
- - an explicit `--via mux`.
107
-
108
- A pi agent's `/new`, `/reload` and `/compact [instructions]` go
109
- through the `command` op. Any other slash command to a pi agent raises
110
- `AgentSlashCommandUnsupportedError` (exit 2), whose next step is the
111
- same text with `--via mux`. Text such as `/tmp/x.log ...` is a plain
112
- prompt, not a slash command. A pi agent therefore never reaches
113
- `capturePane` or the paste timing in `src/mux/input-timing.ts`.
114
-
115
- ## Remote agents
116
-
117
- The user's `ssh` command carries `-L <local derived path>:<remote path>`
118
- and puts `MU_CTL_SOCK=<remote path>` in the remote env.
119
- `mu agent remote-env` prints both and runs nothing
120
- (`src/cli/agents-remote.ts`). mu always connects to the local path, so
121
- local and remote agents share one transport, and state, send, wait and
122
- abort are exact without murmur.
123
-
124
- ssh does not create the local directory, which is why spawn makes it
125
- first. When ssh dies it leaves the local socket file behind. That file
126
- probes `refused`, and `mu agent close` or the reaper deletes it.
127
-
128
- ## Parent side: `mu_delegate`
129
-
130
- `extension/delegate.ts` registers the `mu_delegate` and
131
- `mu_delegate_cancel` tools in the operator's own pi. They are hidden
132
- when `MU_MANAGED_AGENT` is set (inside a mu-spawned pane) or
133
- `MU_DELEGATE=0`. The tools shell out to the `mu` CLI (spawn, send,
134
- `wait --json`, read, abort, close) and only format the CLI's
135
- `outcome` into a follow-up message. A call runs one
136
- `mu agent spawn --next-free --send <task> --json` (mu picks the free
137
- name under its spawn lock and returns the send's `runs`), then one
138
- `mu agent wait --after-runs <runs>` in the background; with `record`,
139
- a `mu task show` comes first. `delegateOutcome`
140
- (`src/agents/delegate.ts`) maps one wait result to `done`, `empty`,
141
- `error`, `died`, `timeout` or `pending`. The extension imports nothing from pi;
142
- it types pi's API structurally.