@mu-crew/mu 3.0.0 → 3.2.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.
@@ -115,7 +115,7 @@ it mu is just an agent runner.
115
115
  decides edge satisfaction: any `CLOSED` task satisfies its blockers.
116
116
  - **Substate** qualifies status and never touches edges:
117
117
  `OPEN/todo|parked`, `IN_PROGRESS/active`,
118
- `CLOSED/done|wontfix|duplicate|superseded`. Rule: **store intent,
118
+ `CLOSED/done|rejected|wontfix|duplicate|superseded`. Rule: **store intent,
119
119
  derive graph facts** — ready and blocked stay derived from edges.
120
120
  `OPEN/parked` is excluded from `ready` but stays in `goals`.
121
121
 
@@ -624,7 +624,9 @@ returning. Two steps, in order:
624
624
  - **Reality wins**: the mux is the source of truth for what panes
625
625
  exist. Reconciliation closes the gap on every `mu agent list`.
626
626
  - **Agent state is reported, not scraped or persisted.** `src/agent-state.ts`
627
- reads herdr's `paneStatus()` on herdr. On tmux, one `list-panes -a`
627
+ probes a pi agent's control socket ([§ Control socket](#control-socket)),
628
+ in parallel for all pi agents. For other CLIs it reads herdr's
629
+ `paneStatus()` on herdr. On tmux, one `list-panes -a`
628
630
  reads `@murmur_pane_state` and `@murmur_pane_since` for every local
629
631
  agent. Only agents without a local option use `murmur status --json`;
630
632
  that result is cached for 10 seconds. See murmur's
@@ -642,6 +644,67 @@ returning. Two steps, in order:
642
644
 
643
645
  ---
644
646
 
647
+ ## Control socket
648
+
649
+ mu drives a pi agent through a unix socket served by mu's own pi
650
+ extension inside the agent's interactive pi. The pane stays pi's TUI;
651
+ a human attached to it shares the same session. No `pi --mode rpc`, no
652
+ screen scraping, no daemon: the server lives and dies with the pi
653
+ process.
654
+
655
+ **Path derivation** (`src/ctl/path.ts`). `ctlSocketPath(ws, agent)` is
656
+ `<state dir>/sock/<ws>/<agent>.sock`, where the state dir is
657
+ `dirname($MU_DB_PATH)` or the default. Past 103 bytes (macOS
658
+ `sun_path`) it becomes `<state dir>/sock/h/<sha1(ws/agent)[:16]>.sock`.
659
+ Nothing is stored: spawn injects the path as `MU_CTL_SOCK`, and every
660
+ later verb derives it again.
661
+
662
+ **Protocol v1** (`src/ctl/protocol.ts`, imported standalone by the
663
+ extension). JSON lines, one request per connection. Every reply carries
664
+ `v: 1`; the client throws `CtlVersionError` on anything else.
665
+
666
+ | op | does |
667
+ | --- | --- |
668
+ | `hello` | identity plus `ops` (served ops) and `extVersion` (mu version the extension was built from) |
669
+ | `status` | `state` (`busy` / `idle` / `needs_input`), `since`, `runs`, `pending` |
670
+ | `send` | `pi.sendUserMessage`; when busy, `mode` `steer` or `followUp` (default) |
671
+ | `wait` | resolves on pi's `agent_settled` after `afterRuns`, with the run's `lastText` |
672
+ | `abort` | `ctx.abort()` (pi's Esc) |
673
+ | `fresh` | new session + prompt as one operation via the internal `/mu-fresh` command; replies once the new run starts; refused with `busy` unless `force` |
674
+
675
+ An op the extension does not serve returns `unknown op: <op>` with its
676
+ `ops`; mu raises `AgentExtensionOutdatedError` (exit 4, next steps
677
+ `/reload` via mux or respawn), and `mu doctor` flags an `extVersion`
678
+ older than the installed mu.
679
+
680
+ **Extension lifecycle** (`extension/mu-pi.ts`). Inert unless
681
+ `MU_CTL_SOCK` is set. pi re-creates extension runtimes per session, so
682
+ the server, run counters and an in-flight `fresh` live in a
683
+ process-global map (`Symbol.for("mu.pi.ctl")`). Session replacement
684
+ (`new` / `resume` / `fork` / `reload`) keeps the socket; quit closes it.
685
+ Binding guards against stealing: if the path already answers (a nested
686
+ `pi -p` inherited `MU_CTL_SOCK`), the extension leaves it alone. It
687
+ listens on a private name and hard-links it into place, records the
688
+ inode, and unlinks the public path only if that inode is still its own.
689
+ On `session_start` a server whose path was deleted or replaced closes
690
+ and rebinds, which is how `/reload` recovers a lost file.
691
+
692
+ **No silent fallback** (`src/agents/transport.ts`). A pi agent
693
+ (`expectsCtl`) whose socket does not answer is `AgentCtlUnreachableError`
694
+ for send and abort, and `unknown` with reason `ctl missing` /
695
+ `ctl refused` for state. mu never pastes into it or asks murmur instead.
696
+ The mux paste path serves non-pi CLIs, adopted panes without ctl, slash
697
+ commands (`sendUserMessage` does not run them), and explicit `--via mux`.
698
+
699
+ **Remote agents.** The user's `ssh` command carries
700
+ `-L <local derived path>:<remote path>` plus `MU_CTL_SOCK=<remote path>`
701
+ in the remote env; `mu agent remote-env` prints both and runs nothing.
702
+ mu always connects to the local path, so local and remote share one
703
+ transport. ssh leaves the local socket file behind when it dies; it
704
+ probes `refused`, and close or the reaper deletes it.
705
+
706
+ ---
707
+
645
708
  ## Modules (actual src/ layout)
646
709
 
647
710
  Mostly-flat `src/`: root `.ts` modules plus cohesive subclusters
@@ -661,6 +724,9 @@ separately below.
661
724
  | `src/apply.ts` | **The apply path** — capture's counterpart: given one op, local or from a peer, make the tables reflect it. Also owns `reprojectDeferredOps` ([§ ambient sync hook](#the-ambient-sync-hook)). |
662
725
  | `src/undo.ts` + `src/cli/undo.ts` | **Undo as inverse ops**: inverses for one `group_id`, derived from log provenance, refusing a superseded group (exit 4; `--force` overrides). Bare form lists undoable groups (`-n` widens), a prefix previews, `--yes` applies. |
663
726
  | `src/project-root.ts` | `detectProjectRoot` — the launch-cwd ladder bare `mu` uses to guess which workstream to focus. Pure filesystem walk; no DB. |
727
+ | `extension/mu-pi.ts` + `extension/delegate.ts` | **The mu pi extension**, built to `dist/extension/mu-pi.js`. Child side (`serveCtl`): serves `$MU_CTL_SOCK` in a pi mu spawned. Parent side (`registerDelegate`, hidden when `MU_MANAGED_AGENT` is set or `MU_DELEGATE=0`): the `mu_delegate` / `mu_delegate_cancel` tools, which shell out to the `mu` CLI (spawn, send, `wait --json`, read, abort, close) and only format the CLI's `outcome` into a follow-up message. Imports nothing from pi; only the protocol from mu. |
728
+ | `src/ctl/*.ts` | **Control socket** client side ([§ Control socket](#control-socket)): `path.ts` (`ctlSocketPath`, `remoteCtlSocketPath`, `MU_CTL_SOCK`), `protocol.ts` (v1 request/reply types, `CTL_OPS`, line framing; imports nothing from mu so the extension can load it), `client.ts` (`ctlRequest`, `ctlProbe`, typed version / unknown-op errors). |
729
+ | `src/link.ts` + `src/cli/link.ts` | **Link** — `mu link pi`: `linkPi` writes the extension shim (or `--copy`), `linkSkill` symlinks the skill, `inspectLinks` reports `ok` / `missing` / `stale-copy` / `foreign` / `dangling` for doctor. Filesystem only: the verb runs outside `handle()` (no DB). |
664
730
  | `src/rebuild.ts` + `src/cli/rebuild.ts` | **Rebuild** — disaster recovery: `rebuildInto` replays the whole log into a NEW DB file via `applyOp`. The verb renders the report: counts, `--json`, an `mv`-swap `Next:` step, a warning that `agents` / `vcs_workspaces` are not reconstructible. |
665
731
  | `src/legacy-ops.ts` | Permanent compatibility classifier for historical log-only intents whose entity looks projectable. Rebuild copies them without projection; segment flush never emits them. |
666
732
  | `src/drift.ts` | **Drift detection**, two tiers: `checkDrift` (`mu doctor --deep`) rebuilds into a temp DB and diffs field-by-field; `checkCheapDriftInvariant` (the default) asserts every live row has ≥1 op naming its key. Plus `driftRemediation`. |
@@ -678,7 +744,7 @@ separately below.
678
744
  | `src/agent-state.ts` | Runtime agent-state resolver. Reads herdr's native state or murmur's local pane options and remote JSON; maps source states, returns `unknown` with a reason, and caches remote murmur reads for 10 seconds. |
679
745
  | `src/reconcile.ts` | Ghost prune + orphan surface; "reality wins" |
680
746
  | `src/agents.ts` | Hub: CRUD + send / read / list / close + liveness + reaper. Re-exports `src/agents/*`; pane-title composition (`composeAgentTitle`) lives here. |
681
- | `src/agents/*.ts` | Agent-lifecycle internals: `spawn.ts` (spawn, CLI resolution, liveness wait *or* backend `startAgentInPane`, pane create-or-reuse, rollback), `spawn-lock.ts` (per-session lock around topology+finalize), `wait.ts`, `adopt.ts`, `kick.ts` (signal a wedged pane's pgid), `errors.ts`. |
747
+ | `src/agents/*.ts` | Agent-lifecycle internals: `spawn.ts` (spawn, CLI resolution, liveness wait *or* backend `startAgentInPane`, pane create-or-reuse, rollback), `spawn-lock.ts` (per-session lock around topology+finalize), `wait.ts`, `adopt.ts`, `kick.ts` (signal a wedged pane's pgid), `abort.ts` (stop a pi turn via the control socket), `transport.ts` (send routing: ctl for pi agents, mux paste otherwise, no fallback), `delegate.ts` (`delegateOutcome`: one wait result → `done` / `empty` / `died` / `timeout` / `pending`, surfaced by `mu agent wait --json`), `errors.ts`. |
682
748
  | `src/dag.ts` | Shared DAG read/render helpers: `loadFullDag` plus pure `renderForest` / `renderTaskTree`, reused by `mu task tree` and the TUI DAG popup. |
683
749
  | `src/tasks/*.ts` | Task-graph internals: `core.ts` (row shapes, id resolution), `id.ts`, `queries.ts` (reads), `edit.ts` (+ delete cascade preview), `edges.ts` (+ cycle check), `status.ts` (TaskStatus), `sort.ts`, `claim.ts` (atomic CAS), `lifecycle.ts` (close/open), `wait.ts`, `errors.ts`. |
684
750
  | `src/tracks.ts` | Parallel-tracks union-find with diamond merge |
@@ -785,7 +851,7 @@ DB must never sit in `MU_SYNC_DIR`:
785
851
  4. **Executes the operation** — agent ops shell out to tmux (and to
786
852
  jj/sl/git for workspaces); task ops are pure SQL.
787
853
  5. **Reconciles with reality** — read paths prune ghosts and surface
788
- orphans; runtime agent state is read from murmur or herdr without a DB write.
854
+ orphans; runtime agent state is read from the control socket (pi), murmur or herdr without a DB write.
789
855
  6. **Records ops** — automatically, per [§ The ops log](#the-ops-log-read-this-first);
790
856
  machine-local changes go through `emitEvent`. `mu log --tail`
791
857
  subscribers see the new ops on the next 1-second poll.
@@ -1013,9 +1079,10 @@ Single npm package `mu` (see `package.json`):
1013
1079
  - `dist/index.js` + `dist/index.d.ts` — programmatic API + types for SDK callers
1014
1080
  - `skills/mu/SKILL.md` — bundled skill (the only non-`dist` asset shipped)
1015
1081
 
1016
- `tsup` bundles two entries (`index`, `cli`) from `src/`. No runtime
1017
- build step on the user's machine. There is no pi-extension entry — pi
1018
- is a peer dep — and no bundled `agents/*.md` or `prompts/*.md`:
1082
+ `tsup` bundles three entries: `index` and `cli` from `src/`, and
1083
+ `extension/mu-pi` (the pi extension `mu link pi` points at). No runtime
1084
+ build step on the user's machine. pi stays a peer dep — the extension
1085
+ types pi's API structurally — and there is no bundled `agents/*.md` or `prompts/*.md`:
1019
1086
  per-role agent guidance lives in the user's project repo.
1020
1087
 
1021
1088
  The dependency list lives in `package.json`; the rule for adding
@@ -389,8 +389,8 @@ table/key/field is the reproduction. See
389
389
  `mu` refuses to open a pre-v11 DB (`SchemaTooOldError`, exit 4) or a DB
390
390
  newer than it understands (`SchemaTooNewError`, exit 4) and leaves the
391
391
  file alone. The retained `scripts/migrate.ts` sidecar auto-detects v7,
392
- v8, v9, or v10 and writes a fresh v11 DB. It never migrates in place.
393
- Legacy `REJECTED` tasks become `CLOSED/wontfix` and `DEFERRED` tasks
392
+ v8, v9, v10, or v11 and writes a fresh v11 DB. It never migrates in place.
393
+ Legacy `REJECTED` tasks become `CLOSED/rejected` and `DEFERRED` tasks
394
394
  become `OPEN/parked`; their original op payloads remain unchanged.
395
395
  Pre-1.0 archives restore as live workstreams under their original
396
396
  `source_workstream` names unless you pass `--drop-archives`.
@@ -422,7 +422,26 @@ To update the source-installed copy: pull from upstream, then
422
422
  `npm install -g .` from inside the checkout. The `prepare` script
423
423
  rebuilds before linking the new dist/.
424
424
 
425
- ### Install the bundled skill
425
+ ### Link pi (`mu link pi`)
426
+
427
+ For pi agents, one verb installs both pieces mu needs:
428
+
429
+ ```bash
430
+ mu link pi # extension shim + skill symlink; then restart pi (or /reload)
431
+ mu link pi --copy # inline the extension instead (pinned; relink after upgrades)
432
+ ```
433
+
434
+ - `~/.pi/agent/extensions/mu.ts` — a one-line shim that re-exports the
435
+ installed `dist/extension/mu-pi.js`, so `npm install -g` upgrades the
436
+ extension with no relink.
437
+ - `~/.agents/skills/mu` — a symlink to the package's `skills/mu`.
438
+
439
+ mu refuses to replace a real directory at the skill path (exit 4), and
440
+ replaces a symlink pointing elsewhere (e.g. a dev checkout) only with
441
+ `--force`. `--extension-only` / `--skill-only` install one piece.
442
+ `MU_PI_HOME` overrides the home directory both paths are rooted at.
443
+
444
+ ### Install the bundled skill (other agents)
426
445
 
427
446
  Mu ships a skill at `skills/mu/SKILL.md` that teaches the LLM running
428
447
  inside an agent pane how to use mu. The canonical install path is the
@@ -839,9 +858,15 @@ Next:
839
858
 
840
859
  **When to use which:** `mu workstream init <name>` the moment
841
860
  coordination *is* the work — multiple agents, dependencies, gated
842
- review. `scratch` for a single driveable helper.
843
- [`pi-subagents`](https://github.com/nicobailon/pi-subagents) when you
844
- only need a one-shot result back.
861
+ review. `scratch` for a single driveable helper. A **delegate** when
862
+ you only need a one-shot answer back: the `mu_delegate` tool (shipped
863
+ in the extension `mu link pi` installs) spawns a scratch agent, sends
864
+ the task, returns at once, and delivers the answer as a follow-up
865
+ message. From a shell it is `mu agent spawn -w scratch` + `send` +
866
+ `mu agent wait --json` (see [`mu agent wait`](#mu-agent-wait-the-task-less-counterpart-to-mu-task-wait)).
867
+ Unlike a hidden subagent, the delegate's pane stays attachable: watch
868
+ it, steer it, abort it, or pass `keep: true` and keep talking to it.
869
+ Each delegate costs one pane and one pi process.
845
870
 
846
871
  ### The log ledger: durable watcher dedupe + memory
847
872
 
@@ -1370,7 +1395,37 @@ From any shell with mu on `$PATH`:
1370
1395
  mu agent send worker-1 "echo hello from outside"
1371
1396
  ```
1372
1397
 
1373
- mu uses the **canonical bracketed-paste protocol** internally:
1398
+ **Which transport.** A pi agent (`--cli pi`, a command whose argv0
1399
+ is `pi` / `pi-meta`, or any agent whose control socket file exists)
1400
+ gets the text through its **control socket**: the
1401
+ mu pi extension calls `pi.sendUserMessage` inside the pane's own pi, so
1402
+ nothing is pasted or scraped. When pi is busy the message queues as a
1403
+ follow-up; `--steer` interrupts the current run instead. If the socket
1404
+ does not answer, the send fails with `AgentCtlUnreachableError` (exit 1)
1405
+ and pastes nothing; `--via mux` pastes explicitly. A pi started before
1406
+ you upgraded mu still runs the old extension: an op it lacks (say
1407
+ `--fresh`) fails with `AgentExtensionOutdatedError` (exit 4). Type
1408
+ `/reload` in its pane (`mu agent send <name> '/reload' --via mux`) to
1409
+ load the new extension and keep the session, or respawn it. `mu doctor`'s
1410
+ `ctl` row flags such agents before you hit the error. Text starting with
1411
+ `/` (`/new`, `/compact`) is a TUI command that `sendUserMessage` would
1412
+ hand to the model as plain text, so it always goes through the mux.
1413
+ Non-pi CLIs always go through the mux. `--json` reports `transport:
1414
+ "ctl"|"mux"` (and pi's `state` for ctl). An adopted pi pane was started
1415
+ without `MU_CTL_SOCK`; `mu agent adopt` warns and prints the path to
1416
+ restart pi with.
1417
+
1418
+ **Fresh session + prompt.** For a pi agent, `mu agent send <name>
1419
+ --fresh '<prompt>'` starts a new session and sends the prompt into it
1420
+ as one operation inside pi (the extension's internal `/mu-fresh`
1421
+ command runs `newSession` and sends from the new session). It returns
1422
+ once the prompt's run has started, so a send right after it lands in
1423
+ the new session. Use it instead of sending `/new` then the prompt. A
1424
+ busy pi refuses with exit 4 (`mu agent abort` first, or `--force` to
1425
+ abandon the running turn). Non-pi CLIs have no control socket: use the
1426
+ two-step `/new` + prompt there and verify the prompt landed.
1427
+
1428
+ For the mux path, mu uses the **canonical bracketed-paste protocol**:
1374
1429
 
1375
1430
  0. wait for the pane to stop being mid-modal (`MU_SEND_READINESS_MS`,
1376
1431
  default 15000; 0 disables)
@@ -1630,8 +1685,9 @@ mu task unpark polish # OPEN/parked → OPEN/todo
1630
1685
 
1631
1686
  Every task carries a **substate** next to its status, rendered as a pair.
1632
1687
  `OPEN` is `todo` or `parked`; `IN_PROGRESS` is `active`; `CLOSED` is
1633
- `done`, `wontfix`, `duplicate`, or `superseded`. `--as` picks the closed
1634
- substate. Anything but `done` requires `--why`, stored as a note in the
1688
+ `done`, `rejected`, `wontfix`, `duplicate`, or `superseded`. `--as` picks
1689
+ the closed substate: `rejected` when the proposal itself was declined,
1690
+ `wontfix` when it is valid but not worth doing. Anything but `done` requires `--why`, stored as a note in the
1635
1691
  same transaction. Any `CLOSED/*` satisfies a blocker, so a non-`done`
1636
1692
  close prints the dependents it unblocked.
1637
1693
 
@@ -1739,8 +1795,20 @@ Mirrors `mu task wait`'s shape: `--any`/`--first` fire on the first
1739
1795
  agent (default: all must finish); `--first` prints the firing agent's
1740
1796
  ref; `--json` carries `nextSteps`; refs may be qualified
1741
1797
  `<workstream>/<name>`. Exit codes: `0` met, `5` timeout, `6` a watched
1742
- agent's pane died. On tmux, murmur supplies the state. `unknown` never
1743
- fires the wait; run `mu doctor` for the missing-source reason.
1798
+ agent's pane died (for a pi agent: its control socket stopped
1799
+ answering). For pi agents the wait is one request to the mu extension,
1800
+ answered when pi settles, so it returns immediately rather than on the
1801
+ next poll, and `--json` adds each settled pi agent's `lastText`: the
1802
+ text of its final assistant message, verbatim (`""` when the run ended
1803
+ without text; capped at 64 KiB). For other CLIs murmur (tmux) or herdr
1804
+ supplies the state, and `lastText` is absent; read the pane instead.
1805
+ Every `--json` agent row also carries `outcome`: `done` (settled with
1806
+ text), `empty` (settled without text, or no `lastText` source), `died`,
1807
+ `timeout`, or `pending` (`--any` fired on another agent). The
1808
+ `mu_delegate` tool in the mu pi extension is this verb plus spawn and
1809
+ send: it formats `outcome` into a follow-up message.
1810
+ `unknown` never fires the wait; run `mu doctor` for the missing-source
1811
+ reason.
1744
1812
 
1745
1813
  **Don't reach for this verb to catch a worker that asked a question.**
1746
1814
  It fires on `busy → needs_input`, so it does detect the case — but it
@@ -1984,13 +2052,47 @@ Reconciliation runs on every `mu agent list` / `mu`. Two steps:
1984
2052
  registry. **Not** auto-adopted; mu lists them under "Orphan panes"
1985
2053
  with the `mu agent adopt <pane-id>` hint
1986
2054
 
2055
+ ### A pi worker on another machine
2056
+
2057
+ `mu agent remote-env` prints the ssh pieces; mu itself runs no ssh:
2058
+
2059
+ ```bash
2060
+ eval "$(mu agent remote-env worker-1 --shell)" # sets MU_SSH_ARGS, MU_REMOTE_ENV
2061
+ mu agent spawn worker-1 --command \
2062
+ "ssh $MU_SSH_ARGS dev -t 'cd ~/ws/worker-1 && $MU_REMOTE_ENV \$SHELL -ilc \"pi --approve\"'"
2063
+ ```
2064
+
2065
+ `$SHELL -ilc` runs pi in an interactive login shell, so the remote
2066
+ `~/.zshrc` / `~/.bashrc` (PATH, provider env) loads; ssh's own
2067
+ non-interactive shell skips it. `MU_SSH_ARGS` also forces a direct
2068
+ connection (`-o ControlMaster=no -o ControlPath=none`): a multiplexed
2069
+ ssh never binds the local end of the forward.
2070
+
2071
+ `MU_SSH_ARGS` forwards the remote pi's control socket to the local path
2072
+ mu always connects to, so send, state, `mu agent wait` and
2073
+ `mu agent abort` behave as for a local agent. The host needs
2074
+ `mu link pi`, and its sshd must allow the forward
2075
+ (`AllowStreamLocalForwarding`). Full recipe and traps:
2076
+ `skills/mu/REMOTE_WORKERS.md`.
2077
+
1987
2078
  ### A worker is wedged on an unbounded tool subprocess
1988
2079
 
1989
2080
  A worker ran `find / -maxdepth 6 ...` (30-60 minutes on a populated
1990
2081
  home directory) or a busy-wait loop. `mu agent send` queues steering
1991
2082
  messages until the tool returns; `tmux send-keys C-c` against the
1992
2083
  pane doesn't propagate (the wrapping pi/claude/codex CLI catches it
1993
- as TUI input). The escape hatch:
2084
+ as TUI input). For a pi agent, abort the turn first:
2085
+
2086
+ ```bash
2087
+ mu agent abort worker-1 # Esc through the control socket; waits for pi to settle
2088
+ ```
2089
+
2090
+ `mu agent abort` kills the running tool (the bash child dies with it)
2091
+ and returns once pi settles (exit 5 after `--timeout`, default 30s).
2092
+ An idle agent is left alone. A follow-up queued before the abort does
2093
+ NOT run: pi puts it back into the pane's editor box, unsent. Re-send
2094
+ it if you still want it. Non-pi agents, or a pi whose socket is
2095
+ gone, need the signal escape hatch:
1994
2096
 
1995
2097
  ```bash
1996
2098
  mu agent kick worker-1 # SIGINT (graceful, default)
@@ -2400,10 +2502,10 @@ mv "$DB" "${DB}.old-kept" && mv "${DB}.v11" "$DB"
2400
2502
  mu doctor
2401
2503
  ```
2402
2504
 
2403
- The script auto-detects v7, v8, v9, or v10, opens it read-only, compares
2505
+ The script auto-detects v7, v8, v9, v10, or v11, opens it read-only, compares
2404
2506
  its SHA-256 before and after, and never overwrites a target unless
2405
2507
  `--force` is explicit. v9/v10 history is retained unchanged; legacy
2406
- statuses map onto substates (`REJECTED` → `CLOSED/wontfix`, `DEFERRED` →
2508
+ statuses map onto substates (`REJECTED` → `CLOSED/rejected`, `DEFERRED` →
2407
2509
  `OPEN/parked`) while projecting into v11, and no migration notes are
2408
2510
  written. Where an undo or a note hid the legacy status, the script
2409
2511
  records a `migrate.substate` op; `--recover <db>` reruns that recovery
@@ -2504,7 +2606,7 @@ to `mu sql`, twice in one session". File it in [ROADMAP.md](ROADMAP.md).
2504
2606
 
2505
2607
  | Doc | What's in it |
2506
2608
  | -------------------------------------------- | ------------------------------------------------------- |
2507
- | [README.md](../README.md) | Project overview, install, comparison vs `pi-subagents` |
2609
+ | [README.md](../README.md) | Project overview, install, comparison vs hidden subagents |
2508
2610
  | [CHANGELOG.md](../CHANGELOG.md) | Release notes |
2509
2611
  | [ROADMAP.md](ROADMAP.md) | What's next, with promotion criteria + rejected ideas |
2510
2612
  | [VOCABULARY.md](VOCABULARY.md) | Canonical terms — source of truth for every word |
@@ -2575,7 +2677,7 @@ The backend is resolved once per process and cached.
2575
2677
  | Attach hint | `tmux a -t mu-<ws>` | `herdr session attach mu-<ws>` |
2576
2678
  | Pane borders | 4-side border showing agent name + status glyph | no-op — herdr owns its pane chrome; mu-managed panes carry the label instead |
2577
2679
  | Layout | `select-layout` | no-op — herdr splits are explicit, geometry is yours |
2578
- | Agent state | murmur pane options for local agents; `murmur status --json` for remote agents | herdr reports state via `paneStatus()` |
2680
+ | Agent state | pi agents: the control socket. Others: murmur pane options for local agents; `murmur status --json` for remote agents | pi agents: the control socket. Others: herdr reports state via `paneStatus()` |
2579
2681
  | Focus | mu creates detached | `--no-focus` on every mutating call, always. `detached: false` still gets you a detached workspace; run `herdr workspace focus` yourself. |
2580
2682
  | Isolation seam | `MU_TMUX_SOCKET` (`-L <name>`) | `MU_HERDR_SESSION` (`--session <name>`, its own socket) |
2581
2683
  | `mu agent send` / `read` | six-step paste/Enter protocol | one atomic `agent prompt --wait` |
package/docs/VISION.md CHANGED
@@ -34,9 +34,9 @@ tasks.
34
34
 
35
35
  Existing tools force a choice:
36
36
 
37
- - **Pi-subagents** is great for one-shot focused delegation but the
38
- children it spawns are short-lived, pi-only, and not driveable from
39
- outside their parent pi session.
37
+ - **Hidden subagents** (pi-subagents, the Claude Code and Codex task
38
+ tools) delegate one-shot work to a child you cannot see, steer, or
39
+ keep talking to; its transcript collapses into a result.
40
40
  - **Tmux-orchestration tools** spawn agents in panes but leave
41
41
  coordination to chat transcripts or filesystem conventions.
42
42
  - **Task trackers** (GitHub Issues, Linear, even tg) model the work but
@@ -65,6 +65,11 @@ The pi extension is a UX skin. Everything mu does must work from a
65
65
  shell with no pi anywhere. A feature that requires the extension
66
66
  doesn't ship.
67
67
 
68
+ Delegation is CLI: `mu agent spawn -w scratch` + `mu agent send` +
69
+ `mu agent wait --json`. The `mu_delegate` tool adds only the callback
70
+ that puts the answer into the parent conversation, and that is
71
+ presentation.
72
+
68
73
  ### 2. One DB is canonical
69
74
 
70
75
  All state lives in `~/.local/state/mu/mu.db`. SQLite WAL; multiple
@@ -307,9 +312,12 @@ orchestrator stays in charge.
307
312
  It runs agents that do those things.
308
313
  - **Not a chat protocol.** Agents communicate through the work graph
309
314
  (notes, claim, status) and the **ops log** read through `mu log`.
310
- - **Not a replacement for pi-subagents.** Different problem (persistent
311
- crew vs one-shot focused delegation). Install both; they share the
312
- agent-frontmatter format.
315
+ - **Not a hidden-subagent runner.** A one-shot **delegate** is an
316
+ ordinary agent in a visible pane: attach, steer, abort, or keep
317
+ talking to it. The cost is one pane and one pi process per delegate;
318
+ a hidden subagent is lighter for many tiny calls. mu bets that
319
+ visibility and steering are worth that for agent work. No agent
320
+ types: the brief is ad-hoc text.
313
321
  - **Not a hosted service.** Local-first SQLite. Zero ops, no accounts.
314
322
  Your machine is the deployment.
315
323
  - **Not a verifier.** The verbs trust the caller. `task close
@@ -348,9 +356,10 @@ orchestrator stays in charge.
348
356
  `mu log --tail` for confirmation. This is by design — the
349
357
  alternative requires a protocol every CLI would have to speak.
350
358
 
351
- 5. **Recursion is opt-in.** Default `maxSubagentDepth: 0`. Children
352
- get the `mu` binary on PATH but the bundled skill says "you are
353
- not the orchestrator."
359
+ 5. **Recursion is opt-in.** The `mu_delegate` tool is hidden in
360
+ agents mu spawned (`MU_MANAGED_AGENT`), so a delegate cannot spawn
361
+ delegates through it. Agents get the `mu` binary on PATH, but the
362
+ bundled skill says "you are not the orchestrator."
354
363
 
355
364
  6. **Subscriptions are polling-based.** `mu log --tail` polls
356
365
  SQLite once per second. SQLite handles the concurrency; latency
@@ -16,21 +16,21 @@ defined here, fix the doc. If you need a new term, add it here first.
16
16
  | **mux backend** | An implementation of the `MuxBackend` interface (`src/mux/tmux.ts`, `src/mux/herdr.ts`). Owns everything backend-specific: session/window/pane topology, the send protocol, scrollback capture, **pane id** validation, and **actor** identity fallback. Sibling of **VCS backend**; same `src/vcs/` shape. | "driver", "provider", "adapter" |
17
17
  | **mux session** | The mux-level container holding one workstream's panes. On tmux it is a **tmux session** named `mu-<workstream>`; on herdr it is a herdr *workspace* labelled `mu-<workstream>`. Use the generic term in cross-backend prose and the specific term when the backend matters. | "mux workspace" (collides with mu's VCS **workspace**), "session" alone |
18
18
  | **mux detection** | The ladder that picks the active **mux backend**: `MU_MUX` override → `HERDR_ENV=1` → `$TMUX` → whichever binary is on `PATH` (tmux wins a tie as the incumbent) → `NoMultiplexerError`. `HERDR_ENV` outranks `$TMUX` because it is the narrower signal: only a herdr-managed pane sets it, and herdr may itself be running inside tmux. | "auto-detect" (vague), "probe" |
19
- | **scratch workstream**| The reserved workstream named `scratch` for off-the-cuff agent use — spin up a helper you'll keep talking to without crew/DAG ceremony. Explicit `-w scratch` (no implicit fallback); auto-created on first spawn; `mu workstream init scratch` is rejected (loud). Agents in it are still **agents**, NOT pi-subagents. Task-less is fine; the DAG stays opt-in. | "throwaway ws", "temp workstream", "subagent" |
19
+ | **scratch workstream**| The reserved workstream named `scratch` for off-the-cuff agent use — spin up a helper you'll keep talking to without crew/DAG ceremony. Explicit `-w scratch` (no implicit fallback); auto-created on first spawn; `mu workstream init scratch` is rejected (loud). Agents in it are still **agents**; a **delegate** is a scratch agent started for one task. Task-less is fine; the DAG stays opt-in. | "throwaway ws", "temp workstream", "subagent" |
20
20
  | **tmux session** | The literal tmux session a workstream lives in — the tmux flavour of a **mux session** | "session" alone (ambiguous) |
21
21
  | **window** | The mid-level grouping inside a **mux session** (tmux window / herdr tab); identified by `window_name` | "tab" (except as the frontmatter field name) |
22
22
  | **pane** | One shell view inside a **window**; identified by **pane id** | "terminal", "shell" |
23
23
  | **pane id** | The mux's stable handle for a **pane**. Shape is backend-specific — tmux `%15`, herdr `w1:p1` — so validation lives on the **mux backend**, never as a global regex. Stable for the pane's lifetime; never reused after close. Distinct from a tmux pane *index* (`0`, `1`, …), which is volatile and must never be stored. | "pane number", "pane index" |
24
24
  | **pane title** | The string identifying which **agent** occupies a pane. **Equals the agent's name.** Set via `select-pane -T` on tmux, and via `herdr pane rename` (the pane *label*) on herdr. A fallback for **actor** identity — `$MU_AGENT_NAME` is consulted first. | "pane name" |
25
25
  | **window name** | The **window**'s name. **Equals the agent's `tab:` value** (groups one or more agents). | "tab name" (in code; `tab:` only in frontmatter) |
26
- | **agent** | A named worker running in a pane; identity = `$MU_AGENT_NAME`, falling back to **pane title**; row in `agents` table | "subagent" (reserved for pi-subagents), "worker" (only the specific role) |
26
+ | **agent** | A named worker running in a pane; identity = `$MU_AGENT_NAME`, falling back to **pane title**; row in `agents` table | "subagent" (means a hidden child in other tools), "worker" (only the specific role) |
27
27
  | **worker** | An **agent** in its role-as-task-claimer. Synonym for the registered side of identity — a row in `agents`, owns tasks via the FK. | (when ambiguous, prefer **agent**) |
28
28
  | **remote worker** | A worker whose process and workspace are on another machine while its pane and orchestrator DB remain local. The durable inventory record is a task-note line in the exact `REMOTE: <host>:<path>` shape; pair it with `REMOTE_BASE: <agent>:<sha>` so `mu task claim --for <agent>` can print the one-shot poll-and-close step. `mu state` lists `REMOTE:` lines and `mu state --json` exposes them as `remoteWorkers`. | "remote agent record", "host registry" |
29
29
  | **actor** | The party that *caused* a state change. May or may not be a registered worker. Recorded in `ops.actor` for every op. The orchestrator running mu from a top-level shell is an actor but not a worker. | "caller", "author" (only on notes) |
30
30
  | **crew** | *Informal* collective noun for the agents in a workstream | (no API surface; prose only) |
31
31
  | **task** | A node in the DAG. Has mandatory `impact` and `effort_days`. Has a **task status** and a **substate**, shown as a pair (`OPEN/parked`, `CLOSED/wontfix`). | "issue", "ticket", "item" |
32
32
  | **task status** | The lifecycle phase, one of 3: **OPEN** = not started; **IN_PROGRESS** = claimed and active; **CLOSED** = ended. Status alone decides edge satisfaction: any `CLOSED/*` satisfies a `--blocked-by` edge. | "state" |
33
- | **substate** | What the **task status** means for this task. Never null; each status has a default. `OPEN`: `todo` (default), `parked`. `IN_PROGRESS`: `active`. `CLOSED`: `done` (default), `wontfix`, `duplicate`, `superseded`. Rendered as a pair `STATUS/substate`; a default substate renders as the bare status. Valid pairs live in the `task_substates` table. Never affects edges. Refer to a non-`done` close as a *closed substate*. | "resolution", "reason", "sub-status" |
33
+ | **substate** | What the **task status** means for this task. Never null; each status has a default. `OPEN`: `todo` (default), `parked`. `IN_PROGRESS`: `active`. `CLOSED`: `done` (default), `rejected` (the proposal was declined), `wontfix` (valid, not worth doing), `duplicate`, `superseded`. Rendered as a pair `STATUS/substate`; a default substate renders as the bare status. Valid pairs live in the `task_substates` table. Never affects edges. Refer to a non-`done` close as a *closed substate*. | "resolution", "reason", "sub-status" |
34
34
  | **park** | Verb: set an `OPEN` task to `OPEN/parked` with a required `--why` note. A parked task leaves **ready** and `next`, `claim` refuses it without `--force`, and it stays in **goals**. Its dependents stay blocked. Refuses `IN_PROGRESS` (release first) and `CLOSED` (open first). | "defer", "snooze", "shelve" |
35
35
  | **unpark** | Verb: return an `OPEN/parked` task to `OPEN/todo`. | "resume", "reactivate" |
36
36
  | **task DAG** / **graph** | The directed acyclic graph of tasks. | "task list", "todo", "tree" (it's a DAG, not a tree) |
@@ -65,7 +65,7 @@ defined here, fix the doc. If you need a new term, add it here first.
65
65
  | **stale workspace** | A workspace whose `parent_ref` is N commits behind the project's default branch HEAD (per the workspace's local refs cache). Rendered as a color-coded `behind` column (green ≤2, yellow 3–9, red ≥10) in `mu workspace list` and `mu state`; ≥10 triggers a one-line warn in `mu state`, `mu task claim --for`, and `mu agent send` (or refusal on the two dispatch verbs with `--strict-staleness`). Pure observation — mu never auto-fetches. | "out of date", "drifting" |
66
66
  | **refresh** | `mu workspace refresh <agent>` — rebase the agent's workspace onto a fresh base (default = backend's tracked main; `--from <ref>` overrides) WITHOUT touching the agent or pane. Refuses on dirty WC; surfaces conflicts as exit 5 with a resolve-in-place hint. The `none` backend errors (no VCS to rebase). | "recycle", "reset" (overloaded) |
67
67
  | **backend** | Implementation of `MuxBackend` or `VcsBackend`. Always qualify which (**mux backend** / **VCS backend**) when both are in scope. | "driver", "provider" |
68
- | **state source** | The system that reports **agent state**: herdr's `paneStatus()` on herdr, or murmur on tmux. mu reads local murmur pane options in one tmux call and uses `murmur status --json` only for remote workers. No source yields `unknown` with a reason. | "scraper" |
68
+ | **state source** | The system that reports **agent state**: the **control socket** for pi agents; otherwise herdr's `paneStatus()` on herdr, or murmur on tmux. mu reads local murmur pane options in one tmux call and uses `murmur status --json` only for remote workers. No source yields `unknown` with a reason; a pi agent whose socket does not answer is `unknown` (`ctl missing` / `ctl refused`), never a murmur reading. | "scraper" |
69
69
  | **op** | The atomic unit of change: one row in the `ops` table, written by a trigger inside the same transaction as the mutation it records. Carries `(hlc, machine_id, group_id, actor, intent, entity, key, op, payload)`. A **semantic partial update** — the payload holds only the columns that actually changed, which is what makes per-field merge free. | "event", "delta", "change" (all overloaded) |
70
70
  | **ops log** | The `ops` table: the single append-only record of every change, and the substrate **sync**, **undo**, and history are all queries or replays over. Canonical and ACID because it lives in `mu.db` alongside the tables it records. | "event log", "journal", "WAL" (reserved by SQLite) |
71
71
  | **intent** | The semantic label on an **op** (`task.close`, `task.reparent`, `agent.spawn`). Set once per public SDK function via **op context**, not per mutation. `mu log` renders prose from it through one formatter. | "verb" (overloaded by CLI verbs), "action" |
@@ -74,7 +74,7 @@ defined here, fix the doc. If you need a new term, add it here first.
74
74
  | **apply** | Verb: land one **op** into the portable tables, honouring the **merge rules**. `applyOp` / `applyOps` in `src/apply.ts`. Always runs capture-suppressed (`applying=1`), so applying never mints a new op — that is the echo guard. Idempotent: applying the same op twice changes nothing, which is what makes `mu sync --repair` merely "re-read that peer's **segment** from zero". Distinct from **ingest**, which is the sync-side loop that reads a segment and calls apply per op. | "merge" (that's the rule set), "import", "replay" (replay is undo) |
75
75
  | **merge rules** | The per-entity conflict policy the **apply** path implements. note/message = **grow-only set**; task/workstream = **per-field LWW**; edge = **LWW-element-set**; KNOWN machine-local entities (`MACHINE_LOCAL_ENTITIES`: agent, workspace, event, broadcast) are rejected as a bad peer; an UNRECOGNISED entity is a forward-compatible no-op — recorded, projected nowhere, never a defect, because a reader may legitimately be older than a writer in a mixed fleet. One rule per entity, chosen so that convergence needs no coordination between machines. | "conflict resolution" (implies prompting a human), "CRDT" (only loosely true) |
76
76
  | **per-field LWW** | Last-writer-wins applied PER FIELD, not per row: each field of a task independently keeps the value written by the newest **HLC** that touched THAT field. Free, because ops are **semantic partial updates**. Row-level LWW is explicitly rejected — with agent crews on two machines, a crew closing a task while the operator edits its impact is concurrent by construction, and row-level would silently discard one edit. | "row LWW", "column versioning" (implies version vectors we don't need) |
77
- | **grow-only set** | An entity that is only ever inserted, never updated: **notes** and log messages. Two machines therefore cannot disagree about a note's content, so there is nothing to resolve — apply is insert-if-absent. Identity is `(task, author, content)`, since a note's surrogate id is assigned per machine and cannot identify it across machines. | "append-only log" (that's the ops log), "set" |
77
+ | **grow-only set** | An entity that is only ever inserted, never updated: **notes** and log messages. Two machines therefore cannot disagree about a note's content, so there is nothing to resolve — apply is insert-if-absent. Identity is `(task, author, content, created_at)`, since a note's surrogate id is assigned per machine and cannot identify it across machines. `created_at` is part of it because machine-written text repeats (the reaper's "previous owner gone", dispatch's "CLAIM: dispatch"), and each repeat is a separate note. | "append-only log" (that's the ops log), "set" |
78
78
  | **LWW-element-set** | The rule for **edges**: set membership where both the add and the remove carry an **HLC**, so a remove and a later re-add converge in either arrival order. An edge has no fields worth merging — it is present or absent. | "add-wins set", "2P-set" (a 2P-set can't re-add) |
79
79
  | **tombstone** | An `op='del'` row. There is NO tombstone table: a tombstone is an ordinary op carrying an **HLC**, so out-of-order arrival is just "compare HLCs", the same comparison the update path makes. A late **put** older than a seen tombstone loses; a tombstone older than a seen put loses. | "deletion marker", "grave" |
80
80
  | **teardown** | Removing a workstream: `mu workstream teardown` kills its **mux session** and deletes its rows, writing **tombstone** ops so the history survives in the **ops log**. **Reversible** — `mu undo <group>` restores every row it removed, so no backup is warranted before running it. Dry-run by default; `--yes` commits. `mu workstream list --torn-down` lists past teardowns with the **group** to undo. NOT called "destroy": that word says irreversible, which is wrong, and it drove real operators to hoard pre-flight DB copies. What teardown does not restore is what was never in the DB: killed panes and freed **workspace** dirs. | "destroy", "delete workstream", "nuke", "purge" |
@@ -104,7 +104,10 @@ defined here, fix the doc. If you need a new term, add it here first.
104
104
  | **operation** | A canonical mu verb (e.g. `mu task add`). Each verb is a thin CLI wrapper over a typed function in `src/*.ts` — the SDK and the CLI share one surface. | "command" (overloaded), "action" |
105
105
  | **reconcile** | Verb: re-derive registry rows from substrate reality (the **mux**). Always runs in `mu agent list` and `mu doctor`. | "sync", "refresh" |
106
106
  | **adopt** | Verb (`mu agent adopt`): register an existing pane as a managed **agent**. The inverse of `mu agent list`'s 'orphan' state. Pane must be in the workstream's **mux session**. | "import", "absorb" |
107
- | **pi-subagents** | A different package by Nico Bailon for in-pi focused delegation. Mu and pi-subagents are complementary, not competing. | conflating with mu |
107
+ | **control socket** / **ctl** | The per-agent unix socket the mu pi extension serves inside a pi agent's own interactive session (`$MU_CTL_SOCK`). mu derives its path from workstream and agent name, so nothing is stored. Carries `hello`, `status`, `send`, `wait` (resolves on pi's `agent_settled`), `abort` and `fresh` (new session + prompt) as JSON lines. The pane stays pi's normal TUI. **ctl state** is whether it answers: `ok`, `missing` or `refused`. A running pi keeps the extension it loaded at start; an **outdated extension** lacks an op this mu sends (`AgentExtensionOutdatedError`, exit 4) or reports an older `extVersion` in `hello` (doctor warns). pi's `/reload` re-imports it. | "rpc" (pi's `--mode rpc` replaces the TUI; mu does not use it), "daemon" |
108
+ | **delegate** | A **scratch workstream** agent started for one task: spawn, send the task, return at once, and hand its answer back when its control-socket `wait` resolves. The `mu_delegate` tool in the mu pi extension does this in one call; the CLI equivalent is `mu agent spawn -w scratch` + `mu agent send` + `mu agent wait --json`. The pane stays visible: attach, steer, abort, or keep talking to it. No agent types; the brief is ad-hoc text. | "subagent", "child agent", "worker" (a worker claims DAG tasks) |
109
+ | **lastText** | The text of the final assistant message of a pi agent run. The mu pi extension records it on `agent_end` and returns it verbatim in the control-socket `wait` reply; `mu agent wait --json` passes it through. mu never interprets it. | "result", "answer file", "output" (alone) |
110
+ | **hidden subagent** | A child agent in other tools (pi-subagents, pi's example subagent extension, Claude Code and Codex task tools) that runs out of sight: no pane to attach to, no way to steer it or keep talking to it, and its transcript collapses into a result. Usually comes with built-in agent types. mu's counterpart is the **delegate**. | "subagent" alone in mu prose |
108
111
  | **TUI** | The interactive ink-based dashboard launched by bare `mu` in a TTY or explicitly by `mu state --tui`. Lives in `src/cli/tui/`. Read-only against SQLite (yanks, never executes). | "GUI", "interactive mode" |
109
112
  | **dashboard** | The TUI's main screen — the grid of cards above the status bar. | "home screen", "main view" |
110
113
  | **card** | A glanceable summary tile on the dashboard, identified by its toggle digit (0-9). Wrapped in a TitledBox. | "panel", "section" (overloaded) |
@@ -180,8 +183,9 @@ Agent state is runtime data and is not persisted. The codepoints live in
180
183
  | `needs_permission` | nf-fa-lock | Waiting for a human answer |
181
184
  | `unknown` | nf-fa-question_circle | No state source or no usable reading; the reason accompanies the state |
182
185
 
183
- The state source is herdr on the herdr backend and murmur on the tmux
184
- backend. mu maps murmur `working` to `busy`, `blocked` to
186
+ The state source is the control socket for pi agents, else herdr on the
187
+ herdr backend and murmur on the tmux backend. The extension's `busy`
188
+ maps to `busy` and `idle` to `needs_input`. mu maps murmur `working` to `busy`, `blocked` to
185
189
  `needs_permission`, and `idle`, `done`, and `crashed` to `needs_input`.
186
190
  The retained `agents.status` column is deprecated and always contains
187
191
  `spawning`; no runtime read uses it.
@@ -192,6 +196,9 @@ The retained `agents.status` column is deprecated and always contains
192
196
  | --------------------- | --------------------------------------------------------------------------- |
193
197
  | `mu task release feature_a`| Clears the task owner for `feature_a`. The agent who claimed it is unaffected. |
194
198
  | `mu agent close alice` | Terminates alice's pane and removes from registry. Destructive. |
199
+ | `mu agent send alice --fresh '…'` | New pi session + prompt as one control-socket operation; returns once the prompt's run started. Refused while busy unless `--force` (abandons the turn). pi only. |
200
+ | `mu agent abort alice` | Stops a pi agent's current turn through its control socket (pi's Esc) and waits for it to settle. No-op when idle. Queued follow-ups return to the pane's editor unsent. Non-pi agents: use kick. |
201
+ | `mu agent remote-env alice` | Prints (never runs) the ssh socket-forward args and identity env for a remote pi agent's `--command`, so its control socket answers at the local derived path. |
195
202
  | `mu agent kick alice` | Signals (default SIGINT) the foreground process group of alice's pane TTY. For wedged tool subprocesses (`find /`, busy-wait); the wrapping CLI itself is untouched. Refuses when the foreground IS the wrapping CLI. |
196
203
  | `mu agent wait alice bob --first` | Blocks until an agent finishes (busy → any other state). The task-less counterpart to `mu task wait` for scratch/off-the-cuff helpers that own no task. `--any`/`--first` fire on the first; default all. Exit 0 met, 5 timeout, 6 a watched pane died. |
197
204
  | *(none)* | There is no detach verb. Use tmux detach to leave a workstream attached session without killing panes. |
@@ -240,7 +247,7 @@ Don't use them in mu code or docs:
240
247
 
241
248
  | Avoided word | Why | Use instead |
242
249
  | ---------------- | ---------------------------------------------------------------- | ---------------------------------------------------- |
243
- | "subagent" | Pi-subagents owns this term in our ecosystem | "agent" (mu's unit) or quote `pi-subagents` explicitly |
250
+ | "subagent" | In other tools it means a hidden, opaque child agent; mu has none | "agent" (mu's unit), "delegate" (one-task scratch agent), or "hidden subagent" when contrasting |
244
251
  | "session" | Pi has its own "session"; tmux has "session"; herdr has a third (server-level); ambiguous alone | "workstream" (mu's unit), "mux session" (cross-backend), or "tmux session" (literal) |
245
252
  | "workspace" (mux sense) | mu already uses "workspace" for a VCS-isolated checkout. herdr's session-level container is also called a workspace, and letting both senses in would make `mu workspace list` ambiguous | "mux session"; say "herdr workspace" only when describing herdr's own CLI |
246
253
  | "project" | Means a `.pi/` project root; conflict with mu's organizational unit | "workstream" |
@@ -283,6 +290,7 @@ exist to keep names canonical, not to replace `--help`.
283
290
  | `mu sync --repair <peer>` | Reset a **watermark** to re-read that peer's **segment** from zero. The universal repair, safe because ingest is idempotent via `UNIQUE (machine_id, hlc)`. Takes a `machine_id` or any unique prefix. |
284
291
  | `mu undo <group>` | Emit inverse ops for one **group**. Granular (touches only that action's rows), composable, and itself an op — so it syncs and is itself undoable. |
285
292
  | `mu rebuild <file>` | Materialize a fresh DB by replaying the **ops log** in HLC order. The disaster-recovery path; prints the swap command rather than overwriting in place. Replays EVERYTHING the log knows, including machine-local ops — a rebuild is local recovery, not **ingest**, so it does not filter to synced entities. Carries `machine_identity` (and its HLC clock) across, so the rebuilt DB is the SAME peer. `agents` / `vcs_workspaces` are NOT rebuilt — they have no capture triggers so leave no ops, and `pane_id` / absolute paths would be lies after recovery; the summary reports the loss and says to re-spawn. |
293
+ | `mu link pi` | Install mu into pi: the **extension shim** (`~/.pi/agent/extensions/mu.ts`, a re-export of the installed `dist/extension/mu-pi.js`, marked `// mu:shim`) and a symlink `~/.agents/skills/mu` → the package's `skills/mu`. `--copy` inlines the extension (pinned). Never replaces a real directory; replaces a foreign symlink only with `--force` (`LinkConflictError`, exit 4). Root overridable via `MU_PI_HOME`. |
286
294
  | `mu db backup <file>` | `VACUUM INTO` copy of the whole DB. The "one file I can scp" convenience; real DR is **segments** + `mu rebuild`. |
287
295
 
288
296
  A one-off directory needs no flag — `MU_SYNC_DIR=/mnt/usb mu state`
@@ -375,7 +383,7 @@ reading commands ("alice claims design" sounds like a person;
375
383
  task). Role-based names also make `mu agent list` and tmux's window
376
384
  list legible at a glance.
377
385
 
378
- The roles align with pi-subagents' role taxonomy:
386
+ Common roles:
379
387
 
380
388
  `worker` long-lived implementer; the default
381
389
  `reviewer` reads diffs/code; usually `--role read-only`
@@ -419,9 +427,9 @@ XDG-Base-Directory-Spec compliant. The state directory resolves as:
419
427
  - **Never** place `MU_DB_PATH` inside `MU_SYNC_DIR`. Syncing a live
420
428
  SQLite file (and its `-wal`/`-shm` sidecars) corrupts it. `mu doctor`
421
429
  checks for this.
422
- - mu does NOT consult any agent-template directory. If pi-subagents
423
- is installed, its `~/.pi/agent/agents/` and `.pi/agents/` paths
424
- are pi-subagents' concern — not mu's.
430
+ - mu does NOT consult any agent-template directory (such as
431
+ `~/.pi/agent/agents/` or `.pi/agents/`). Roles are names and
432
+ briefs, never templates.
425
433
 
426
434
  ### Env vars (mu state location)
427
435
 
@@ -433,6 +441,7 @@ XDG-Base-Directory-Spec compliant. The state directory resolves as:
433
441
  | `MU_SESSION` | Override active workstream name (when not auto-detectable) | n/a |
434
442
  | `MU_MUX` | Force the **mux backend**, bypassing **mux detection**. Load-bearing: every verb reaches its multiplexer through `activeMux()`, so an unknown value fails the invocation rather than silently running on tmux. | wins over all detection |
435
443
  | `MU_SYNC_DIR` | Shared folder holding one **segment** per machine. Unset = sync off, zero cost. The WHOLE cluster configuration; there is no peer list. | n/a |
444
+ | `MU_DELEGATE` | `0` hides the `mu_delegate` tool in the mu pi extension (kill switch for benchmarks or a model it confuses). The `mu doctor` extension row says so. | n/a |
436
445
 
437
446
  ### Env vars passed to spawned children
438
447
 
@@ -449,9 +458,11 @@ XDG-Base-Directory-Spec compliant. The state directory resolves as:
449
458
  | `MU_HERDR_SESSION` | Drive a NAMED herdr server (`--session <name>`, its own socket) instead of the user's default one. The herdr analogue of `MU_TMUX_SOCKET`, and the isolation seam any herdr integration test must use so the suite can never observe — or destroy — real panes. herdr **mux backend** only. |
450
459
  | `MU_<UPPER_CLI>_COMMAND` | Override the executable launched for `--cli <cli>` (e.g. `MU_PI_COMMAND=pi-alt` makes `--cli pi` exec `pi-alt`; hyphens in the cli key become underscores in the env var name, so `--cli pi-meta` reads `MU_PI_META_COMMAND`). Accepts multi-word strings (`MU_PI_COMMAND="pi-alt --some-flag"`); tmux exec's via a shell. Reconcile also treats the resolved binary as agent-worthy when surfacing orphan panes. When this env var supplies the override (and `--command` did not), the spawn-success line surfaces the env-var name (`Spawned worker-1 (pi-meta via $MU_PI_META_COMMAND)`) so stale aliases are visible without `mu agent show`. |
451
460
  | `MU_SPAWN_LIVENESS_MS` | **tmux-tier knob** (herdr's `agent start` blocks until the agent is ready, so there is nothing to poll for). After spawn, wait this many ms then verify the pane is still alive AND scan the tail of its scrollback for known startup-error patterns (provider auth failures — `No API key found for X`, `401 Unauthorized`, … — plus shell-level `command not found` / `No such file or directory` when the spawned binary vanished post-pre-flight). Default 1500. Set to 0 to disable (useful in CI). On detected death the DB row is rolled back and `AgentDiedOnSpawnError` is thrown with the captured scrollback; on a startup-error match (pane alive but parked at an error prompt) the row is rolled back and `AgentSpawnStartupError` is thrown with the matched line + remediation hints. The complementary pre-flight check (PATH lookup of `--cli`'s resolved binary BEFORE any side effect) is not env-tunable; on miss it throws `AgentSpawnCliNotFoundError` with no orphan workspace / pane / row. |
461
+ | `MU_CTL_SOCK` | Set by spawn in every agent pane: the agent's control socket path, derived from (state dir, workstream, agent). The mu pi extension serves it; mu connects to it. |
462
+ | `MU_SPAWN_CTL_MS` | Budget for spawn's control-socket handshake with a pi agent, polled every 250ms (default `30000`; `0` disables). On timeout spawn still exits 0, reports `ctl: missing\|refused`, and warns on stderr. `mu agent spawn --no-ctl` skips it per spawn. |
452
463
 
453
- These mirror pi-subagents' `PI_SUBAGENT_*` env vars in spirit but live
454
- in a separate namespace so the two can coexist in one pi session.
464
+ They use the `MU_` namespace so they never collide with env vars other
465
+ pi extensions set in the same session.
455
466
 
456
467
  ---
457
468
 
@@ -57,7 +57,7 @@ Consequences:
57
57
  | D8 | Apply resolves the pair before writing: a status-only op, or an unknown substate from a newer peer, falls back to the status default. The original payload stays in `ops`. | The foreign key fails the whole commit. Version skew must not break sync; the foreign key is there to catch our own bugs. |
58
58
  | D9 | Non-`done` close and `park` require `--why`, stored as an ordinary note in the same transaction. | Keeps the rationale, the one good part of the notes-only convention. |
59
59
  | D10 | Start with `parked` as the only non-default `OPEN` substate. No `--until`. | YAGNI. mu has no daemon to run the automatic un-park. Adding `triage` later is one seeded row. |
60
- | D11 | One shared legacy mapping: `REJECTED → CLOSED/wontfix`, `DEFERRED → OPEN/parked`. | Replaces `normalizeTaskStatus` (currently folds both onto `OPEN`). Apply, rebuild, undo and migration all use it, so recovery happens on every path. |
60
+ | D11 | One shared legacy mapping: `REJECTED → CLOSED/rejected` (3.1.0; 3.0.0 used `wontfix`), `DEFERRED → OPEN/parked`. | Replaces `normalizeTaskStatus` (currently folds both onto `OPEN`). Apply, rebuild, undo and migration all use it, so recovery happens on every path. |
61
61
  | D12 | Migrate v10 → v11 through `scripts/migrate.ts`, not in `openDb`. | Keeps the "production never migrates in place" rule. SQLite cannot add a foreign key to an existing table, so a new target is required anyway. |
62
62
 
63
63
  ## Schema (v11)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mu-crew/mu",
3
- "version": "3.0.0",
3
+ "version": "3.2.0",
4
4
  "description": "A persistent, observable crew of pi agents running in one tmux or herdr session per workstream, coordinated through a built-in task DAG.",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -55,9 +55,9 @@
55
55
  "test:fast": "vitest run --config vitest.fast.config.ts",
56
56
  "test:watch": "vitest",
57
57
  "test:watch:fast": "vitest --config vitest.fast.config.ts",
58
- "lint": "biome check src test scripts",
59
- "lint:fix": "biome check --write src test scripts",
60
- "format": "biome format --write src test scripts",
58
+ "lint": "biome check src test scripts extension",
59
+ "lint:fix": "biome check --write src test scripts extension",
60
+ "format": "biome format --write src test scripts extension",
61
61
  "typecheck": "tsc --noEmit && tsc -p tsconfig.test.json --noEmit",
62
62
  "prepare": "npm run build",
63
63
  "test:stress": "tools/test-stress.sh"