@mu-crew/mu 1.6.0 → 2.0.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.
@@ -31,8 +31,8 @@ programmatic SDK are thin facades over the same core modules.
31
31
  │ │ mux/ │ │ schema │ │ jj │ │ capture │ │
32
32
  │ │ tmux │ │ queries │ │ sapling │ │ apply/sync │ │
33
33
  │ │ herdr │ │ tracks │ │ git │ │ doctor │ │
34
- │ │ detect │ │ claim │ │ none │ │ │ │
35
- │ │ state │ │ │ │ │ │ │ │
34
+ │ │ state │ │ claim │ │ none │ │ │ │
35
+ │ │ sources │ │ │ │ │ │ │ │
36
36
  │ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────────┬────────┘ │
37
37
  └───────┼─────────────┼─────────────┼─────────────────┼───────────┘
38
38
  ▼ ▼ ▼ ▼
@@ -291,17 +291,16 @@ That difference is expressed as a **capability**, not a name check:
291
291
  | --- | --- | --- |
292
292
  | `NewWindowOptions.command` etc. | carries the command | **refused** if non-empty |
293
293
  | `startAgentInPane()` | not implemented | implemented |
294
- | liveness / readiness wait | mu polls scrollback | the mux blocks until ready |
294
+ | liveness / readiness wait | mu checks pane life, startup errors, then murmur readiness when available | the mux blocks until ready |
295
295
 
296
296
  `spawnAgent` branches on `mux.startAgentInPane !== undefined`. When it
297
- is absent (tmux) the command rides along on the creation verb and the
298
- path is exactly what it always was. When present, mu creates the pane
299
- bare, then calls `startAgentInPane` — and **skips**
300
- `awaitSpawnLiveness` / `awaitSpawnReadiness`, because such a backend
301
- returns only once it has itself detected the agent and judged it ready
302
- for input, which is strictly stronger than what scrollback polling can
303
- prove. `MU_SPAWN_LIVENESS_MS` / `MU_SPAWN_READINESS_MS` are therefore
304
- tmux-tier knobs.
297
+ is absent (tmux), the command rides along on the creation verb. The
298
+ liveness check verifies that the pane survived and scans for startup
299
+ errors; when murmur is available, readiness also waits for the pane's
300
+ `@murmur_pane_state`. Without murmur, readiness is skipped and the first
301
+ send uses its input-timing checks. When `startAgentInPane` is present,
302
+ mu creates the pane bare, calls it, and skips the tmux checks because
303
+ the backend returns only after the agent is ready.
305
304
 
306
305
  The creation verbs **refuse** a non-empty command on a backend that
307
306
  cannot honour it rather than dropping it silently: a dropped command
@@ -579,15 +578,12 @@ surrogate INTEGER ids once and then stay on ids. See
579
578
 
580
579
  ## Reconciliation
581
580
 
582
- `mu agent list` always reconciles the registry against tmux reality before
583
- returning. Three steps, in order:
581
+ `mu agent list` always reconciles the registry against mux reality before
582
+ returning. Two steps, in order:
584
583
 
585
584
  1. **Prune ghosts.** For each `agents` row, if its `pane_id` no longer
586
- exists in tmux, delete the row.
587
- 2. **Detect status from scrollback.** For each surviving agent, capture
588
- the pane and run the per-CLI detector. Update `agents.status` if
589
- the detected value differs from the stored one.
590
- 3. **Surface orphans.** For each tmux pane in the workstream's session
585
+ exists in the mux, delete the row.
586
+ 2. **Surface orphans.** For each pane in the workstream's mux session
591
587
  that has no matching `agents` row but whose pane title looks like
592
588
  an agent name, add it to the orphans list. **Do not auto-adopt** —
593
589
  `mu agent list` shows orphans under a separate "(orphans)" section and
@@ -596,19 +592,21 @@ returning. Three steps, in order:
596
592
  `src/reconcile.ts` is the only implementation. Key properties:
597
593
 
598
594
  - **Reality wins**: the mux is the source of truth for what panes
599
- exist. The DB records what we last *observed*. Reconciliation closes
600
- the gap on every `mu agent list`.
601
- - **Status detection is backend-dependent**, expressed as the optional
602
- `MuxBackend.paneStatus?()`. A backend that omits it (tmux) means "ask
603
- the detector": `src/detect.ts` scrapes scrollback (`busy` /
604
- `needs_input` / `needs_permission` via a known pi marker, with a
605
- Braille-spinner fallback for other CLIs). A backend that implements it
606
- (herdr) classifies panes natively across every agent kind it
607
- recognises, and mu takes its word — `src/detect.ts` is bypassed
608
- entirely on that backend. herdr's `working` → `busy`, `blocked` →
609
- `needs_permission`, `idle` / `done` / `unknown` → `needs_input`.
610
- `unknown` must never become `free`: herdr documents that it does not
611
- prove completion, and no detector of either kind may mint `free`.
595
+ exist. Reconciliation closes the gap on every `mu agent list`.
596
+ - **Agent state is reported, not scraped or persisted.** `src/agent-state.ts`
597
+ reads herdr's `paneStatus()` on herdr. On tmux, one `list-panes -a`
598
+ reads `@murmur_pane_state` and `@murmur_pane_since` for every local
599
+ agent. Only agents without a local option use `murmur status --json`;
600
+ that result is cached for 10 seconds. See murmur's
601
+ [stable contract](https://github.com/mu-crew/murmur/blob/main/ARCHITECTURE.md#contract).
602
+ - **Mapping:** murmur `working` → `busy`, `blocked` →
603
+ `needs_permission`, and `idle` / `done` / `crashed` → `needs_input`.
604
+ herdr supplies mu's runtime states directly.
605
+ - **Absence is `unknown`.** Readings carry reasons such as `murmur not
606
+ installed`, `murmur pi extension not linked`, `murmur has no row`,
607
+ `ambiguous: N hosts`, `remote snapshot stale`, and `pane gone`.
608
+ - **`agents.status` is deprecated.** Inserts write `spawning` to satisfy
609
+ the v10 schema. No code updates or reads the column as runtime state.
612
610
  - **No silent adoption**: orphans are reported, never claimed.
613
611
  - **`mu doctor` calls the same routine** and reports counts.
614
612
 
@@ -645,10 +643,10 @@ separately below.
645
643
  | `src/disk-recon.ts` | **Disk↔DB reconciliation** — the only module that reads the STATE DIR and compares it to the DB, in both directions: a `vcs_workspaces` row whose path is gone (`ws-rows`), a dir with no row (`ws-dirs`, reusing `listAllOrphanWorkspaces`), plus residue nothing references (`ws-empty` / `db-copies` / `exports` / `locks`). Emits `FleetHazard` so doctor's renderer, `--json` and the TUI card need no new shape. Default tier is `readdir` + `stat` at depth 2 (~1ms); `measureWorkspaceUsage` recurses for bytes and is `--disk` only, because its cost scales with the checkouts. **Report-only by construction** — every finding carries its cleanup command and the module runs none of them. |
646
644
  | `src/op-context.ts` | The **op context** seam: `withOpContext(db, {intent, actor, group}, fn)` labels every op in a scope, restored in a `finally`. Nested scopes inherit the group, which puts a cascade under one `mu undo`. `withCaptureSuppressed` is the echo guard. |
647
645
  | `src/mux.ts` | **Mux backend hub** — re-exports `src/mux/*`, same shape as `src/vcs.ts`. The public `MuxBackend` surface every call site imports. |
648
- | `src/mux/*.ts` | **Multiplexer backends**: `types.ts` (the `MuxBackend` interface + `MuxError` / `PaneNotFoundError`), `detect.ts` (`MU_MUX` → `HERDR_ENV` → `$TMUX` → `PATH` ladder + test seam), `tmux.ts`, `herdr.ts`. The backend owns topology, the send protocol, capture, **pane id** validation (tmux `%15` vs herdr `w1:p1`), the identity fallback, and optionally native pane status (`paneStatus?()`) and agent start (`startAgentInPane?()`) — so no global pane-id regex and no per-call-site branching. The send protocol is where the two diverge most: tmux needs a six-step paste/Enter dance to survive a modal swallowing the Enter, while herdr's `agent prompt --wait` is one atomic call. |
646
+ | `src/mux/*.ts` | **Multiplexer backends**: `types.ts` (the `MuxBackend` interface + `MuxError` / `PaneNotFoundError`), `detect.ts` (`MU_MUX` → `HERDR_ENV` → `$TMUX` → `PATH` ladder + test seam), `tmux.ts`, `herdr.ts`, and `input-timing.ts`. The backend owns topology, capture, **pane id** validation (tmux `%15` vs herdr `w1:p1`), identity fallback, and optional native agent state (`paneStatus?()`) and agent start (`startAgentInPane?()`). `input-timing.ts` reads pane text only to decide when tmux input can be submitted; it does not report agent state. |
649
647
  | `src/tmux.ts` | Re-export of the tmux backend, kept for genuinely tmux-only concerns: the `MU_TMUX_SOCKET` test seam and the shared `sleep` / `setSleepForTests` poll seam. Everything else imports `src/mux.ts` and goes through `activeMux()`. |
650
- | `src/detect.ts` | Pi status detector (`busy` / `needs_input` / `needs_permission`) + Braille-spinner fallback. Used when the mux cannot classify panes itself — i.e. always on tmux, never on herdr. |
651
- | `src/reconcile.ts` | Ghost prune + status detect + orphan surface; "reality wins" |
648
+ | `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. |
649
+ | `src/reconcile.ts` | Ghost prune + orphan surface; "reality wins" |
652
650
  | `src/agents.ts` | Hub: CRUD + send / read / list / close + liveness + reaper. Re-exports `src/agents/*`; pane-title composition (`composeAgentTitle`) lives here. |
653
651
  | `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`. |
654
652
  | `src/dag.ts` | Shared DAG read/render helpers: `loadFullDag` plus pure `renderForest` / `renderTaskTree`, reused by `mu task tree` and the TUI DAG popup. |
@@ -660,7 +658,7 @@ separately below.
660
658
  | `src/log-render.ts` | **The ONE op → prose formatter.** `renderOp` maps an intent (+ key + payload fields) to `{verb, subject, detail}`; plus `renderOpLine`, `opSubject`, `parseOpKey`. Pure and colour-free, so CLI and TUI share one phrasing. |
661
659
  | `src/vcs/*.ts` | One backend per file (`git.ts`, `jj.ts`, `sl.ts`, `none.ts`) plus `types.ts` (the `VcsBackend` interface), `helpers.ts`, and `index.ts` (detection precedence `jj` → `sl` → `git` → none; `backendByName`). |
662
660
  | `src/workspace/*.ts` | Per-agent VCS workspaces, a registry on top of `vcs.ts`: `core.ts` (row shapes, paths, errors), `crud.ts`, `decorate.ts` (staleness + dirty), `orphans.ts`. |
663
- | `src/glyphs.ts` | **The ONE glyph vocabulary.** `AGENT_STATUS_GLYPH` + `agentStatusGlyph()` for agent status, the `GLYPH` record for every other state symbol (blocked, dirty, stale, ok/warn/fail, filter toggles), and `superscriptDigit` for TUI card-header keys. All classic Nerd Font `nf-fa-*` — single-codepoint and one cell wide, so `cli-table3` columns line up. Keyed by MEANING, so re-pointing a symbol is a one-line edit. `busy` matches murmur's dash `running`: same panes, same symbol. |
661
+ | `src/glyphs.ts` | **The ONE glyph vocabulary.** `AGENT_STATE_GLYPH` + `agentStateGlyph()` for runtime agent state, the `GLYPH` record for every other state symbol (blocked, dirty, stale, ok/warn/fail, filter toggles), and `superscriptDigit` for TUI card-header keys. All classic Nerd Font `nf-fa-*` — single-codepoint and one cell wide, so `cli-table3` columns line up. |
664
662
  | `src/output.ts` | NextStep type + `printNextSteps` + `errorNextSteps` plumbing for self-documenting output |
665
663
  | `src/shell-quote.ts` | `shellQuote` — POSIX single-quoting for tokens interpolated into copy-pasteable next-step hints. |
666
664
  | `src/state.ts` | SDK seam for `mu state`: `loadWorkstreamSnapshotFast` (pure SQL, TUI 1s tick), `loadWorkstreamSnapshotSlow` (subprocesses), `mergeSnapshotFastSlow`, `loadWorkstreamSnapshot`. Also parses exact `REMOTE:` task-note inventory and `REMOTE_BASE:` dispatch metadata. Opt-in: `withDirty`, `withDoctor`, `withRecentCommits`, `withAllTasks`. |
@@ -670,7 +668,7 @@ separately below.
670
668
  | `src/cli/tui/*.tsx` | The interactive ink TUI, lazy-imported by `src/cli/state.ts`. **The only place ink/react are imported** — a ROADMAP pledge. Per-file roles: [§ TUI architecture](#tui-architecture). |
671
669
  | `src/cli/tasks/*.ts` | Sub-cluster of the `mu task` namespace: `queries.ts` (list/next/owned-by + `mu me tasks` / `mu me next`), `lifecycle.ts` (close/open), `edit.ts`, `edges.ts` (+ delete cascade preview), `claim.ts`, `tree.ts`, `wire.ts` (Commander glue). |
672
670
  | `src/index.ts` | SDK entrypoint (re-exports) |
673
- | `skills/mu/SKILL.md` | Bundled skill teaching the LLM the model + verb list + jq pipelines |
671
+ | `skills/mu/SKILL.md` | Bundled skill teaching the LLM the orchestration model and operational traps; `mu --help` owns the verb reference. |
674
672
 
675
673
  ### What the rows above leave out
676
674
 
@@ -756,9 +754,8 @@ DB must never sit in `MU_SYNC_DIR`:
756
754
  better-sqlite3's `db.transaction(fn)()` wrapper.
757
755
  4. **Executes the operation** — agent ops shell out to tmux (and to
758
756
  jj/sl/git for workspaces); task ops are pure SQL.
759
- 5. **Reconciles with reality** — for read-paths that need accuracy
760
- (`mu agent list`, state views), queries tmux for live pane
761
- state and updates the DB (ghost prune + status detect).
757
+ 5. **Reconciles with reality** — read paths prune ghosts and surface
758
+ orphans; runtime agent state is read from murmur or herdr without a DB write.
762
759
  6. **Records ops** — automatically, per [§ The ops log](#the-ops-log-read-this-first);
763
760
  machine-local changes go through `emitEvent`. `mu log --tail`
764
761
  subscribers see the new ops on the next 1-second poll.
@@ -773,7 +770,6 @@ The extension points. A new impl of each is small.
773
770
  | Seam | Add a new impl by... |
774
771
  | ------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
775
772
  | `VcsBackend` | Implementing `detect / createWorkspace / freeWorkspace / isClean / commitsBehind / rebaseTo / commitsSinceBase / recentCommits / showCommit` (~80–150 LOC; jj/sl/git/none are working examples) |
776
- | Per-CLI `Detector` | Adding patterns to `detectPiStatus` (vanilla pi `to interrupt)`; pi-meta + every TUI wrapper covered by Braille spinner glyph fallback `[\u2800-\u28FF]`) |
777
773
  | New typed verb | An SDK function in the relevant `src/*.ts`; a `cmd<Verb>` in the matching `src/cli/<namespace>.ts`; one commander block in `buildProgram()`, wrapped in `handle()` and routed through `printNextSteps` |
778
774
  | New schema migration| Bump `CURRENT_SCHEMA_VERSION` in `src/db.ts` and mirror the shape in `CURRENT_SCHEMA`. Keep startup migration-free; extend the retained fresh-target sidecar only when an old released schema needs a bridge. Recipe: [scripts/README.md](../scripts/README.md) |
779
775
  | New syncable field | Add the column to a portable table, extend the capture trigger's changed-column comparison, confirm the apply path writes it. The UPDATE trigger MUST emit only changed columns — a full-row payload silently regresses field merge to row-level LWW |
package/docs/ROADMAP.md CHANGED
@@ -85,41 +85,6 @@ These have a clear design but haven't yet hit promotion criterion
85
85
  1 (friction in ≥2 real workflows). They earn implementation when
86
86
  real use surfaces them.
87
87
 
88
- ### Per-CLI status detection (claude, codex, …)
89
-
90
- **Partly superseded on the herdr backend**, which classifies panes
91
- natively across the agent kinds it knows — mu takes its word there and
92
- skips scrollback scraping entirely. This item stays live for the tmux
93
- backend, where mu has to do its own detecting.
94
-
95
- mu is a pi orchestrator. The Braille-spinner fallback catches every
96
- TUI wrapper using standard spinner glyphs (U+2800–U+28FF), so
97
- pi-meta + solo + many vanilla TUIs (claude, codex) work without a
98
- per-CLI detector.
99
-
100
- For patterns the spinner fallback misses (permission prompts,
101
- specific busy markers), a per-CLI `Detector` registry keyed by
102
- CLI name (~50 LOC per CLI) is the obvious shape. Promote when a
103
- real specific-prompt-misclassification surfaces.
104
-
105
- Pattern sketch:
106
-
107
- | CLI | Busy patterns | Permission patterns |
108
- | ------ | -------------------------------------- | --------------------------------------------------------- |
109
- | Claude | `to interrupt`, `\(.*[↑↓].*tokens\)` | `Allow once`, `Allow for this session`, `Esc to cancel` |
110
- | Codex | `esc to interrupt)`, `to cancel` | `enter to confirm`, `enter to submit \| esc to cancel` |
111
- | Pi | (well-known mu-defined marker) | (well-known mu-defined marker) — shipped |
112
-
113
- Critical subtleties any new detector must keep:
114
-
115
- - **Tail-window extraction**: take last ~100 lines, strip trailing
116
- blanks, then take last ~20. Already implemented for pi in
117
- `src/detect.ts`; the registry version factors it out.
118
- - **Permission detection uses a narrower window than busy
119
- detection** to prevent already-answered prompts re-triggering.
120
- - **Permission overrides busy** — if a permission prompt is
121
- visible, agent is `NeedsPermission`, not `Busy`.
122
-
123
88
  ### A third mux backend (zellij, wezterm, kitty)
124
89
 
125
90
  **Not promoted.** tmux and herdr are two real implementations, which
@@ -152,76 +117,22 @@ Listed so we don't pretend they're settled.
152
117
 
153
118
  ---
154
119
 
155
- ## murmur, and remote agents (noted 2026-08-28, revised 2026-09-02)
156
-
157
- [murmur](https://github.com/mu-crew/murmur) is a sibling tool at
158
- `~/hacking/murmur`, on npm as `@mu-crew/murmur`: every coding
159
- agent on every machine in one attention-sorted list you can jump
160
- from. It is **built and in daily use** — the original note here
161
- described it as four design documents, and two of its conclusions
162
- moved since:
163
-
164
- - **Current state only, not an event log.** Each node publishes one
165
- complete snapshot; a peer's answer is replaced in one write and
166
- absence means absence. No history, watermarks or HLC. murmur's own
167
- architecture says a generic `put`/`del` op-log "would simply *be*
168
- `mu`" — so the two differ in substrate now, not just scope.
169
- - **No control channel, ever.** murmur observes and jumps; "not a
170
- remote terminal" is a stated non-goal. The phase-2 prompt/abort
171
- surface this note once counted on will not come from there.
172
-
173
- **What it would give mu**, both prerequisites for `mu spawn --on
174
- <host>`: **global agent addressing** (`agents.pane_id` is
175
- machine-local; murmur's `(host_id, agent_id)` is not, and identity is
176
- kept separate from `session/window/pane` so it survives a move), and
177
- **remote observation** (read the fold, get agents on other machines
178
- for free — including ones mu never spawned). It would not give mu the
179
- control channel; remote *driving* is unbuilt in both tools.
180
-
181
- **The cheap seam already works.** murmur's pi extension reads
182
- `MU_MANAGED_AGENT` / `MU_AGENT_NAME` / `MU_WORKSTREAM` / `MU_ROLE`
183
- off the pane and marks such agents `orchestrated` — "crew" — hidden
184
- unless `blocked` or `crashed`, since a supervisor consumes anything
185
- else. mu already exports the first three (`src/agents/spawn.ts`,
186
- `paneEnv`) and did nothing. Zero lines: the baseline any tighter
187
- coupling has to beat. `MU_ROLE` is the one murmur reads and mu does
188
- NOT set, so a crew row's role column is always null — a one-line fix
189
- if that column ever proves useful, not a reason to couple further.
190
-
191
- **No conflict with the sync design.** § Rejected sync substrates and
192
- § Explicitly rejected both stand: murmur is a **separate binary**
193
- owning the ssh egress, and if mu ever consumes it, it does so by
194
- reading a local store, never by growing a network stack. A merge must
195
- keep the mu half transport-free. murmur independently reached mu's
196
- two conclusions from mu's own code: ambient rather than daemon (a
197
- collect rides ordinary invocations, rate-limited with jitter so the
198
- status bar cannot drive an ssh fan-out) and sync must never fail a
199
- command (every peer error caught and categorised).
200
-
201
- **Why not merge now.** Remote observation is the easy half and is now
202
- shipped; orchestration is the hard half and murmur removes none of
203
- it. Repos and credentials must exist on the target, worktree setup is
204
- a local filesystem operation, cherry-pick handoff assumes artifacts
205
- move between machines, and "which node runs this task" is a scheduler
206
- question mu does not have.
207
-
208
- **The step that tests it** is now available, not scheduled: make mu a
209
- *client* — drop its own status tracking and read murmur's snapshot.
210
- Small change, real experiment. But `paneStatus()` / `src/detect.ts`
211
- is not hurting, and trading a working local detector for a
212
- cross-machine dependency needs a reason from real use (promotion
213
- criterion 1), not the mere existence of the alternative. Keep the
214
- *observe* vs *orchestrate* seam in mind even then — it is what lets
215
- murmur watch agents mu never spawned, and a merge should say why it
216
- beats that.
217
-
218
- **Doors already open**, free: same language and runtime (TypeScript
219
- on npm, since pi is an npm package), `(host_id, agent_id)` addressing
220
- mu would need anyway, and the `MU_*` env contract.
221
-
222
- **Do not** start the merge, add ssh to mu, or fold murmur's schema
223
- into `mu.db` on the strength of this note. It records an intention
224
- and its preconditions.
120
+ ## murmur and remote agents
121
+
122
+ [murmur](https://github.com/mu-crew/murmur) is a separate binary for
123
+ current agent state across machines. It owns collection and ssh egress;
124
+ mu remains transport-free and owns the task DAG, claims, workspaces, and
125
+ panes.
126
+
127
+ **Done in 2.0:** mu reads murmur for agent state. See
128
+ [Agent state from murmur](specs/2026-09-28-agent-state-from-murmur.md).
129
+
130
+ ---
131
+
132
+ ### Next schema change
133
+
134
+ Drop the deprecated `agents.status` column at the first v11 migration.
135
+ In 2.0 it remains for schema compatibility and always contains `spawning`.
225
136
 
226
137
  ---
227
138
 
@@ -275,3 +186,7 @@ Listed so we don't rediscover them.
275
186
  - **A "hosted" mu** — your machine is the deployment.
276
187
  - **Anthropomorphic agent names (`alice`, `bob`)** — use
277
188
  role-based names (`worker-1`, `reviewer-1`).
189
+ - **Pane scraping as an agent-state fallback** — absence of a state source is `unknown`, not a guessed state.
190
+ - **Storing runtime agent state in `mu.db`** — the copy goes stale; murmur or herdr owns the current reading.
191
+ - **Reading murmur's `state.db`** — use murmur's stable public contract, not its private schema.
192
+ - **murmur as a hard dependency** — installs that only use the task graph do not need murmur or its native dependencies.
@@ -240,6 +240,7 @@ environment
240
240
  $TMUX : set
241
241
  $TMUX_PANE : %21
242
242
  $MU_SESSION : auth
243
+ agent state : ok agent state from murmur 1.0.0
243
244
 
244
245
  db
245
246
  path : /tmp/mu-play.db
@@ -469,11 +470,11 @@ alias mu="node $PWD/dist/cli.js"
469
470
  See [README.md § Install](../README.md#install) for the full set of
470
471
  install patterns.
471
472
 
472
- mu requires a terminal multiplexer. tmux ≥ 3.0 is the complete
473
- backend and what the rest of this guide assumes; herdr is supported
474
- for topology but not yet for spawn/send/read (see
475
- [§ 20](#20-multiplexer-backends-tmux-and-herdr)). Make sure you're
476
- inside a session before proceeding:
473
+ mu requires a terminal multiplexer. tmux ≥ 3.0 is the backend the rest
474
+ of this guide assumes; herdr also supports spawn, send, and read (see
475
+ [§ 20](#20-multiplexer-backends-tmux-and-herdr)). On tmux, install
476
+ murmur for runtime agent state. Make sure you're inside a session before
477
+ proceeding:
477
478
 
478
479
  ```bash
479
480
  tmux # if you're not already in one
@@ -1042,7 +1043,7 @@ convention):
1042
1043
  | Slot | Card | Toggle | Popup | Content |
1043
1044
  | ---- | ------------- | ------ | --------- | ---------------------------------------------------- |
1044
1045
  | 0 | Commits | `0` | `Shift+0` | Recent project-root commits (git / jj / sl) |
1045
- | 1 | Agents | `1` | `Shift+1` | Active agents + status + cli + role |
1046
+ | 1 | Agents | `1` | `Shift+1` | Active agents + runtime state + cli + role |
1046
1047
  | 2 | Tracks | `2` | `Shift+2` | Parallel tracks (union-find clusters) |
1047
1048
  | 3 | Ready (Tasks) | `3` | `Shift+3` | Ready-to-claim tasks (no open blockers) |
1048
1049
  | 4 | Activity log | `4` | `Shift+4` | Recent ops rendered as prose |
@@ -1226,7 +1227,7 @@ and recoverable from any shell.
1226
1227
 
1227
1228
  ## 6. Spawn a crew
1228
1229
 
1229
- For a demo with live status detection, spawn pi agents:
1230
+ For a demo with live agent state, install and link murmur, then spawn pi agents:
1230
1231
 
1231
1232
  ```bash
1232
1233
  mu agent spawn worker-1 --workstream auth-refactor # default --cli is pi
@@ -1250,7 +1251,7 @@ What just happened:
1250
1251
  session
1251
1252
  3. mu set the pane title to `worker-1` via `tmux select-pane -T worker-1`
1252
1253
  — **this is the claim protocol identity**
1253
- 4. mu inserted a row in `agents` with `pane_id=%15`, `status=spawning`
1254
+ 4. mu inserted a row in `agents` with `pane_id=%15`; the deprecated `status` column remains `spawning`
1254
1255
 
1255
1256
  If the DB insert fails after the pane was created, mu kills the pane
1256
1257
  to avoid leaking. If the same name was already taken, mu rejects
@@ -1703,7 +1704,7 @@ mu sql "SELECT name FROM sqlite_master WHERE type IN ('table','view') ORDER BY t
1703
1704
  ### `mu agent wait`: the task-less counterpart to `mu task wait`
1704
1705
 
1705
1706
  Scratch helpers usually own no task, so `mu task wait` has nothing to
1706
- watch. `mu agent wait` blocks on the agent's runtime status: an agent
1707
+ watch. `mu agent wait` blocks on the agent's runtime state: an agent
1707
1708
  **fires** when it goes **busy → any other state**. It must be observed
1708
1709
  busy first, so an already-idle agent does NOT fire instantly — you're
1709
1710
  waiting for *this* work to finish. Replaces `sleep` polling loops.
@@ -1719,8 +1720,8 @@ Mirrors `mu task wait`'s shape: `--any`/`--first` fire on the first
1719
1720
  agent (default: all must finish); `--first` prints the firing agent's
1720
1721
  ref; `--json` carries `nextSteps`; refs may be qualified
1721
1722
  `<workstream>/<name>`. Exit codes: `0` met, `5` timeout, `6` a watched
1722
- agent's pane died. Status detection is pi-only (a non-pi pane always
1723
- reads `needs_input`, so it never goes busy and the wait times out).
1723
+ agent's pane died. On tmux, murmur supplies the state. `unknown` never
1724
+ fires the wait; run `mu doctor` for the missing-source reason.
1724
1725
 
1725
1726
  **Don't reach for this verb to catch a worker that asked a question.**
1726
1727
  It fires on `busy → needs_input`, so it does detect the case — but it
@@ -1843,9 +1844,10 @@ For unattended waits, always pass `--on-stall exit`; otherwise the default
1843
1844
  warning leaves the wait polling. Two orthogonal flags govern the behaviour:
1844
1845
 
1845
1846
  * `--stuck-after <seconds>` — the **trigger**. An IN_PROGRESS task
1846
- whose owner has been in `needs_input` for `>= N` seconds is marked
1847
- as needing attention. Default `300` (5 min); pass `0` to disable
1848
- detection entirely (no warn AND no exit).
1847
+ whose owner has been in `needs_input` for `>= N` seconds according
1848
+ to the state source is marked as needing attention. `unknown` never
1849
+ counts. Default `300` (5 min); pass `0` to disable detection entirely
1850
+ (no warn AND no exit).
1849
1851
  * `--on-stall <action>` — the **action** when the trigger fires.
1850
1852
  Two values:
1851
1853
  * `warn` (default) — yellow attention warning to stderr (deduped per
@@ -1954,14 +1956,11 @@ You killed it from another tmux client, or its CLI crashed:
1954
1956
  mu agent list # worker-1's row prunes itself (ghost detected)
1955
1957
  ```
1956
1958
 
1957
- Reconciliation runs on every `mu agent list` / `mu`. Three steps:
1959
+ Reconciliation runs on every `mu agent list` / `mu`. Two steps:
1958
1960
 
1959
1961
  1. **Prune ghost rows** — DB row whose `pane_id` no longer exists in
1960
- tmux gets deleted
1961
- 2. **Detect status from scrollback** — for survivors, capture the
1962
- pane and re-derive status (busy / needs_input / needs_permission /
1963
- spawning) per the pi-status detector
1964
- 3. **Surface orphan panes** — panes in the workstream's session whose
1962
+ the mux gets deleted
1963
+ 2. **Surface orphan panes** — panes in the workstream's session whose
1965
1964
  `pane.command` looks like an agent CLI but that aren't in the
1966
1965
  registry. **Not** auto-adopted; mu lists them under "Orphan panes"
1967
1966
  with the `mu agent adopt <pane-id>` hint
@@ -2449,7 +2448,7 @@ rm -f /tmp/mu-demo.db
2449
2448
  set on spawn. Two agents cannot claim the same task.
2450
2449
 
2451
2450
  Everything else (`mu sql`, send/read, the bracketed-paste protocol,
2452
- ghost reconciliation) is plumbing in service of those three.
2451
+ agent-state sources, ghost reconciliation) is plumbing in service of those three.
2453
2452
 
2454
2453
  ---
2455
2454
 
@@ -2461,7 +2460,6 @@ in real use:
2461
2460
 
2462
2461
  | Want | Workaround | Status |
2463
2462
  | --------------------------------------------- | ----------------------------------------------------------------------- | ------------- |
2464
- | Multi-CLI status detection (per-CLI prompts) | Braille spinner fallback covers pi/pi-meta + every TUI wrapper using standard spinner glyphs. Per-CLI permission-prompt patterns are pi-only. | partially shipped |
2465
2463
  | Pi extension (typed tools, HUD, wakeups) | `mu state --tui` (interactive) covers the dashboard use-case; plain `mu state` (static) is the `watch` / `tmux display-popup` / `status-right` substrate. Other extension tools deferred. | partially shipped |
2466
2464
  | Markdown agent-definition discovery | Spawn accepts `--cli` and `--command` directly; no template registry | dropped |
2467
2465
  <!-- doc-cli-drift:skip-start -->
@@ -2504,7 +2502,8 @@ your platform.
2504
2502
 
2505
2503
  mu drives exactly one multiplexer per invocation. tmux is the
2506
2504
  incumbent; [herdr](https://github.com/herdrdev/herdr) is the second
2507
- backend, and spawn, send, read and status detection all work on it. The
2505
+ backend. Spawn, send, and read work on both; agent state comes from herdr
2506
+ on herdr and from murmur on tmux. The
2508
2507
  remaining gaps are narrow and listed under
2509
2508
  [Known limits on herdr](#known-limits-on-herdr) — the notable one is
2510
2509
  that `mu agent kick` is Linux-only there.
@@ -2554,7 +2553,7 @@ The backend is resolved once per process and cached.
2554
2553
  | Attach hint | `tmux a -t mu-<ws>` | `herdr session attach mu-<ws>` |
2555
2554
  | Pane borders | 4-side border showing agent name + status glyph | no-op — herdr owns its pane chrome; mu-managed panes carry the label instead |
2556
2555
  | Layout | `select-layout` | no-op — herdr splits are explicit, geometry is yours |
2557
- | Status detection | scrollback scraping (`src/detect.ts`) | herdr classifies panes natively via `paneStatus()`; the scraper is bypassed |
2556
+ | Agent state | murmur pane options for local agents; `murmur status --json` for remote agents | herdr reports state via `paneStatus()` |
2558
2557
  | Focus | mu creates detached | `--no-focus` on every mutating call, always. `detached: false` still gets you a detached workspace; run `herdr workspace focus` yourself. |
2559
2558
  | Isolation seam | `MU_TMUX_SOCKET` (`-L <name>`) | `MU_HERDR_SESSION` (`--session <name>`, its own socket) |
2560
2559
  | `mu agent send` / `read` | six-step paste/Enter protocol | one atomic `agent prompt --wait` |
package/docs/VISION.md CHANGED
@@ -137,19 +137,11 @@ lives behind `MuxBackend` in `src/mux/`. Two implementations, not an
137
137
  anticipatory abstraction: a third earns its way in on its own
138
138
  friction evidence.
139
139
 
140
- ### 6. Pi-first, and what the mux knows
140
+ ### 6. Pi-first, with reported agent state
141
141
 
142
- Mu's own status detection (`busy` / `needs_input` / `needs_permission`)
143
- is pi-only, plus a Braille-spinner fallback that catches most TUI
144
- wrappers. `--cli <name>` accepts other strings, but on the tmux
145
- backend no other CLI ships with a detector, so an unrecognized pane
146
- tends to read `needs_input`.
147
-
148
- A mux that classifies panes itself changes this. The herdr backend
149
- reports agent state natively across the agent kinds it knows, so on
150
- that backend mu takes the mux's word and skips scrollback scraping
151
- entirely. Detection is a property of the **mux backend**, not a fixed
152
- property of mu.
142
+ mu does not infer agent state from pane text. The herdr backend reports
143
+ state through the mux. On tmux, murmur's pi extension reports state from
144
+ inside the agent process; without murmur, state is `unknown` with a reason.
153
145
 
154
146
  `--cli` and `MU_<UPPER_CLI>_COMMAND` stay useful for swapping the pi
155
147
  binary: set `MU_PI_COMMAND=<name>` once and every spawn picks it up.
@@ -244,9 +236,9 @@ mu does not own:
244
236
  - **Tool routing decisions.** Pi (and any other CLI you spawn) owns
245
237
  tool allowlists, MCP servers, extensions. Mu doesn't proxy or
246
238
  inspect them.
247
- - **Output interpretation.** Mu reads pane contents to detect
248
- `busy / needs_input / idle / done` (a 4-state classification).
249
- It does not parse model output for facts, claims, or tool calls.
239
+ - **Output interpretation.** Mu does not parse pane contents for agent
240
+ state, facts, claims, or tool calls. murmur or herdr reports runtime
241
+ agent state.
250
242
  The `--evidence` payload is whatever the agent says it is; mu
251
243
  records it without interpretation.
252
244
 
@@ -344,12 +336,10 @@ orchestrator stays in charge.
344
336
  `~/.local/state/mu/mu.db`. Cross-machine state moves as
345
337
  append-only JSONL segments in a shared folder; you own transport.
346
338
 
347
- 3. **Pi-first.** mu's own status detection targets pi. `--cli pi` is
348
- the meaningful default; `--cli` accepts other strings as a key for
349
- the `MU_<UPPER_CLI>_COMMAND` env var resolver. On tmux no other CLI
350
- has a detector; on a backend that classifies panes itself (herdr),
351
- mu defers to it. Either way we optimize for
352
- false-negative-then-poll over false-positive-then-act.
339
+ 3. **Pi-first.** `--cli pi` is the meaningful default; `--cli` accepts
340
+ other strings as a key for the `MU_<UPPER_CLI>_COMMAND` env var
341
+ resolver. Agent state comes from murmur on tmux and herdr on herdr;
342
+ absence is `unknown` rather than an inferred value.
353
343
 
354
344
  4. **Send is delivered, not acknowledged.** `mu agent send` gets the
355
345
  text into the pane and warns loudly when it cannot confirm
@@ -46,16 +46,14 @@ defined here, fix the doc. If you need a new term, add it here first.
46
46
  | **owner** | The **worker** name in `tasks.owner`. Set by claim. NULL when the task is unowned OR was claimed via `--self` (anonymous, attributed via `ops.actor` instead). Owners are **machine-local** — they reference `agents`, so ownership never syncs. | "claimer", "assignee" |
47
47
  | **anonymous claim** | A claim made via `--self` where the **actor** isn't a registered **worker**. `tasks.owner` stays NULL; the actor is recorded in `ops.actor` for the auto-emitted `task.claim` op. The orchestrator-doing-direct-work pattern. | "self-claim" (in code; "anonymous claim" in prose), "unowned claim" |
48
48
  | **release** | Verb: clear `tasks.owner` | "unclaim", "unassign" |
49
- | **free** | Verb: mark an agent's `status = 'free'` (idle, available) | "park", "idle" (verb) |
50
- | **status** | Persisted enum on `agents` (busy/needs_input/free/...) | "state" (use only "lifecycle state") |
51
- | **lifecycle state** | A position in the agent state machine | "state" alone, "phase" |
49
+ | **agent state** | Runtime state reported by the **state source**: `busy`, `needs_input`, `needs_permission`, or `unknown`. It is not persisted in mu. | "agent status", "lifecycle state" |
52
50
  | **role** | `full-access` or `read-only` capability flag | "permission" (avoid), "tier" |
53
51
  | **persistent** | Agent that stays alive across tasks | "long-lived" (only in prose) |
54
52
  | **one-shot** | Agent that exists for a single task and then terminates | "ephemeral", "transient" |
55
53
  | **workspace** | A VCS-isolated checkout (jj workspace / sl worktree / git worktree / cp). **In mu, "workspace" is always the VCS sense** — never the mux sense. herdr calls its session-level container a "workspace" too; in mu docs and code that is a **mux session**. | "branch" (it has one but isn't one), "checkout" (only for `none` backend), the herdr sense |
56
54
  | **ghost** | An `agents` row whose `pane_id` no longer exists in the **mux**. Pruned by **reconcile**, which runs in `mu agent list` and `mu doctor`. | "dead agent" (a dead agent may still have a pane), "stale row" |
57
55
  | **reaper** | The part of reconcile that releases a **ghost**'s tasks: `IN_PROGRESS → OPEN`, owner cleared. Makes `mu task wait` exit 6. | "garbage collector", "janitor" |
58
- | **needs attention** | The condition `mu task wait --stuck-after` detects: an IN_PROGRESS task whose **owner** has sat in `needs_input` for >= N seconds. Deliberately cause-NEUTRAL — the predicate cannot distinguish a worker that finished without closing, one waiting on an answer, or one at an approval prompt, and those need opposite responses. So every surface names the observation and points at `mu agent read <owner>`. A worker asking a question is desirable behaviour, so never phrase this as worker negligence. Formerly `agent_close_discipline_gap`, which presumed the first cause; now `agent_attention_required`. | "stuck" / "stalled" (the `stuck` JSON field and exit-7 `STALL_DETECTED` keep their names for compatibility), "close discipline", "negligent worker" |
56
+ | **needs attention** | The condition `mu task wait --stuck-after` detects: an IN_PROGRESS task whose **owner** has been in `needs_input` for at least N seconds according to murmur's `since`. `unknown` never counts. The predicate cannot distinguish a worker that finished without closing, one waiting on an answer, or one at an approval prompt, so every surface points at `mu agent read <owner>`. | "stuck" / "stalled" (the `stuck` JSON field and exit-7 `STALL_DETECTED` keep their names for compatibility), "close discipline", "negligent worker" |
59
57
  | **workspace orphan** | A directory under `<state-dir>/workspaces/<workstream>/` with no row in `vcs_workspaces`. Blocks subsequent `--workspace` spawns. Surfaced by `mu workspace orphans -w X`, `mu state -w X`, and `mu doctor`'s `ws-dirs` row. | "stray dir", "leftover workspace" |
60
58
  | **stranded** | A **workspace orphan** whose parent *workstream* row is also gone. Reported separately because no workstream-scoped verb can reach it — `mu workspace orphans --all` and `mu doctor` are the only surfaces. | "double orphan", "abandoned" |
61
59
  | **missing workspace dir** | The INVERSE of a **workspace orphan**: a `vcs_workspaces` row whose `path` is not on disk (usually a hand-run `rm -rf` where `mu workspace free` was wanted). More dangerous than an orphan, because every read surface reports the row as a healthy workspace and the next send fails inside the VCS backend rather than at the row that lied. `mu doctor`'s `ws-rows` row. | "broken workspace", "dangling row" |
@@ -64,7 +62,7 @@ defined here, fix the doc. If you need a new term, add it here first.
64
62
  | **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" |
65
63
  | **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) |
66
64
  | **backend** | Implementation of `MuxBackend` or `VcsBackend`. Always qualify which (**mux backend** / **VCS backend**) when both are in scope. | "driver", "provider" |
67
- | **detector** | Per-CLI pattern matcher for busy/permission/ready, used when the **mux backend** cannot report status itself. On tmux that is always (`detectPiStatus` in `src/detect.ts`, covering vanilla pi + any TUI wrapper using Braille spinner glyphs); on herdr the mux classifies panes natively across its known agent kinds, so the detector is bypassed. The seam is the optional `MuxBackend.paneStatus?()`: absent means "ask the detector". herdr's states map `working` → `busy`, `blocked` → `needs_permission`, and `idle` / `done` / `unknown` → `needs_input` — `unknown` never becomes `free`, since herdr documents that it does not prove completion, and no detector of either kind may mint `free`. Other CLIs spawned via `--cli <other>` may misclassify on tmux; trust scrollback over the emoji. | "matcher", "parser" |
65
+ | **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
66
  | **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) |
69
67
  | **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) |
70
68
  | **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" |
@@ -165,36 +163,25 @@ window.
165
163
 
166
164
  ---
167
165
 
168
- ## Status, lifecycle, and the verbs that touch them
169
-
170
- ### Agent status enum (persisted in `agents.status`)
171
-
172
- Glyphs are named, not spelled out: the codepoints live in
173
- `src/glyphs.ts` (`AGENT_STATUS_GLYPH`) and a table that duplicates
174
- them has drifted from production once already.
175
-
176
- | Value | Glyph slot | Meaning |
177
- | ----------------- | --------------------- | ---------------------------------------------- |
178
- | `spawning` | nf-fa-hourglass_start | Pane created, agent process booting |
179
- | `busy` | nf-fa-play | Actively working (detector saw busy marker) |
180
- | `needs_input` | nf-fa-moon_o | Idle prompt visible, waiting for input |
181
- | `needs_permission`| nf-fa-lock | Permission prompt visible (e.g., "Allow once") |
182
- | `free` | nf-fa-check_circle | Available; retained for persisted/runtime status compatibility |
183
- | `managed` | — | Under external orchestration; mu observes only |
184
- | `unreachable` | nf-fa-question_circle | Transport down, status uncertain |
185
- | `terminated` | nf-fa-times_circle | Process gone, awaiting reaping |
186
-
187
- All of them are classic Nerd Font `nf-fa-*` (Font Awesome 4)
188
- codepoints, single-codepoint and one cell wide, so `cli-table3`
189
- columns line up. `busy` is deliberately the same glyph murmur's dash
190
- paints for a `running` pane — they drive the same panes, so one symbol
191
- means one thing on both surfaces. Non-agent state glyphs (blocked,
192
- dirty, stale, ok/warn/fail, filter toggles) come from the `GLYPH`
193
- record in the same file.
194
-
195
- **Source of truth:** the substrate — the **mux backend**, plus the
196
- **detector** when that backend cannot classify panes itself. The DB is
197
- a cache; `mu agent list` reconciles on every call.
166
+ ## State, lifecycle, and the verbs that touch them
167
+
168
+ ### Agent state
169
+
170
+ Agent state is runtime data and is not persisted. The codepoints live in
171
+ `src/glyphs.ts` (`AGENT_STATE_GLYPH`).
172
+
173
+ | Value | Glyph slot | Meaning |
174
+ | --- | --- | --- |
175
+ | `busy` | nf-fa-play | Actively working |
176
+ | `needs_input` | nf-fa-moon_o | Waiting for input |
177
+ | `needs_permission` | nf-fa-lock | Waiting for a human answer |
178
+ | `unknown` | nf-fa-question_circle | No state source or no usable reading; the reason accompanies the state |
179
+
180
+ The state source is herdr on the herdr backend and murmur on the tmux
181
+ backend. mu maps murmur `working` to `busy`, `blocked` to
182
+ `needs_permission`, and `idle`, `done`, and `crashed` to `needs_input`.
183
+ The retained `agents.status` column is deprecated and always contains
184
+ `spawning`; no runtime read uses it.
198
185
 
199
186
  ### Agent lifecycle verbs
200
187