@mu-crew/mu 3.8.1 → 3.9.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.
@@ -74,7 +74,7 @@ them, and is the only code that imports ink:
74
74
  **CLI and SDK surface.** Each operation is a typed SDK function plus a
75
75
  thin Commander wrapper. Public functions take operator-facing names and
76
76
  resolve them to surrogate ids exactly once. Typed errors map to exit
77
- codes 0-5. The same page covers testing layers and packaging:
77
+ codes 0-7. The same page covers testing layers and packaging:
78
78
  [architecture/sdk.md](architecture/sdk.md).
79
79
 
80
80
  ## Module map
@@ -90,11 +90,12 @@ hub that re-exports them. `src/tasks.ts`, `src/agents.ts`, `src/mux.ts`,
90
90
  | --- | --- |
91
91
  | `src/db.ts` | better-sqlite3 connection (WAL); schema v11 (11 tables, 3 views) in `applySchema`; installs capture; owns `SYNCED_ENTITIES`, `PORTABLE_TABLES`, `MACHINE_LOCAL_TABLES`; refuses older (`SchemaTooOldError`) or newer (`SchemaTooNewError`) DBs, exit 4 |
92
92
  | `src/hlc.ts` | hybrid logical clock, serialized as sortable TEXT |
93
- | `src/capture.ts` | TEMP triggers recording each portable write as an op |
93
+ | `src/capture.ts` | TEMP triggers recording each portable write as an op, and refusing natural-key changes |
94
94
  | `src/op-context.ts` | `withOpContext` (intent, actor, group); `withCaptureSuppressed` echo guard |
95
95
  | `src/apply.ts` | apply one op: per-field LWW, tombstones, substate pair repair, `reprojectDeferredOps` |
96
96
  | `src/undo.ts` | inverse ops for one group; refuses a superseded group |
97
97
  | `src/rebuild.ts` | replay the whole log into a new DB file |
98
+ | `src/compact.ts` | `mu db compact` (blank redundant note-tombstone payloads) and `mu db forget` (drop a torn-down workstream's ops); this machine only |
98
99
  | `src/legacy-ops.ts` | classifier for historical log-only intents; rebuild copies them unprojected |
99
100
  | `src/drift.ts` | cheap invariant (every row has an op) and `--deep` rebuild-diff |
100
101
  | `src/logs.ts` | typed reader over `ops`; `appendLog` and `emitEvent`, the writes triggers cannot cover |
@@ -121,13 +122,14 @@ hub that re-exports them. `src/tasks.ts`, `src/agents.ts`, `src/mux.ts`,
121
122
  | `src/agents/spawn-lock.ts` | per-session lock around topology and row insert |
122
123
  | `src/agents/transport.ts` | send routing: ctl for pi agents, mux paste otherwise, no fallback; `expectsCtl` |
123
124
  | `src/agents/abort.ts` | stop a pi turn through the control socket; abort + send (`--interrupt`) |
124
- | `src/agents/delegate.ts` | `delegateOutcome`: one wait result to `done` / `empty` / `died` / `timeout` / `pending` |
125
+ | `src/agents/delegate.ts` | `delegateOutcome`: one wait result to `done` / `empty` / `error` / `died` / `timeout` / `pending` |
125
126
  | `src/agents/wait.ts` | block until task-less agents leave `busy` |
126
127
  | `src/agents/adopt.ts` | register an existing pane as an agent |
127
128
  | `src/agents/kick.ts` | `mu agent kick`: signal a wedged pane's foreground process group |
128
129
  | `src/agents/errors.ts` | typed agent errors |
129
130
  | `src/agent-state.ts` | runtime state from ctl, herdr or murmur; `unknown` with a reason |
130
131
  | `src/reconcile.ts` | ghost prune and orphan surfacing |
132
+ | `src/mux/index.ts` | cluster barrel that `src/mux.ts` re-exports |
131
133
  | `src/mux/types.ts` | `MuxBackend` interface; `MuxError`, `PaneNotFoundError`, `NoMultiplexerError` |
132
134
  | `src/mux/detect.ts` | `MU_MUX` → `HERDR_ENV` → `$TMUX` → `PATH` ladder; `activeMux()` |
133
135
  | `src/mux/tmux.ts` | tmux backend: the `tmux()` wrapper, bracketed-paste send, pane validation |
package/docs/VISION.md CHANGED
@@ -109,7 +109,7 @@ which task.
109
109
  A workstream is a mux session: a tmux session on tmux, a herdr workspace
110
110
  on herdr. Its agents are panes in that session, so
111
111
  `tmux a -t mu-<workstream>` shows the whole crew. Workstreams on one
112
- machine are separate sessions, partitioned in the DB by `session_id`.
112
+ machine are separate sessions, partitioned in the DB by `workstream_id`.
113
113
  The crew survives detach and reattach.
114
114
 
115
115
  The multiplexer is a backend chosen by detection
@@ -161,7 +161,7 @@ win32-x64) or a C++ toolchain.
161
161
  - **Read views**: the `ready`, `blocked`, and `goals` views, and
162
162
  `mu state` as the state card.
163
163
  - **Typed verbs** that map to resource transitions (`task claim`,
164
- `task close`, `agent spawn --workspace`, `workstream destroy`).
164
+ `task close`, `agent spawn --workspace`, `workstream teardown`).
165
165
  - **`--json` on every read verb**, so scripts use `jq`, not table parsing.
166
166
  - **`mu sql`** underneath, as the explicit escape hatch.
167
167
 
@@ -249,10 +249,17 @@ mu agent spawn reviewer-1 --cli pi_big
249
249
  The exceptions are `mu log --tail` and the TUI (`mu state --tui`, or
250
250
  bare `mu` on a TTY). Both are:
251
251
  - owned by a human or parent script and gone when it ends;
252
- - read-only: TUI actions copy the `mu <verb>` command to the
253
- clipboard and exit, so every write is a fresh CLI call;
254
- - limited to stdin, stdout, and a poll timer: no sockets, watches, or
255
- subprocesses;
252
+ - free of user actions: TUI keys copy the `mu <verb>` command to
253
+ the clipboard and exit, so every change the operator asks for is a
254
+ fresh CLI call. The TUI's slow tick still does the housekeeping
255
+ any `mu` invocation does: ambient sync (ingest peer ops, flush the
256
+ local segment) and reconcile (prune agents whose pane died, which
257
+ reopens their tasks with a `[reaper]` note, and refresh pane
258
+ titles);
259
+ - limited to stdin, stdout, and a poll timer: no sockets or watches.
260
+ The TUI's slow tick runs short-lived probes (mux liveness, VCS
261
+ status) and ambient sync, and its handoffs (lazygit, tuicr, attach,
262
+ clipboard) run one foreground child each; none outlives the TUI;
256
263
  - TTY-gated: pipes, CI, `--json`, and `MU_NO_TUI=1` never enter the
257
264
  TUI, and plain `mu state` prints the static card.
258
265
 
@@ -276,5 +283,5 @@ the work, pi plus manual tmux is more transparent than mu.
276
283
 
277
284
  Known gaps, none promoted yet: `--evidence` grounds but does not verify,
278
285
  mutations have no declared idempotency keys, most mutations have no dry
279
- run (`workstream destroy`, `task delete`, and `undo` do), and the `role`
286
+ run (`workstream teardown`, `task delete`, and `undo` do), and the `role`
280
287
  field is stored but not enforced.
@@ -15,7 +15,7 @@ agent names, and file paths are in [reference/naming.md](reference/naming.md).
15
15
  | Term | Meaning | Don't use |
16
16
  | --- | --- | --- |
17
17
  | **workstream** | The unit of organization: one **mux session** and one DB partition. | "project", "session" (alone), "context" |
18
- | **scratch workstream** | The reserved workstream `scratch` for helpers outside any task DAG. Auto-created on first spawn with explicit `-w scratch`; `mu workstream init scratch` is rejected. | "throwaway ws", "temp workstream" |
18
+ | **scratch workstream** | The reserved workstream `scratch` for helpers outside any task DAG. Auto-created on first spawn with explicit `-w scratch`; `mu workstream init scratch` is rejected. Its session keeps a `_mu` window, so it stays open after the last agent closes; `teardown --empty` skips it. | "throwaway ws", "temp workstream" |
19
19
  | **multiplexer** / **mux** | The terminal program that owns panes: **tmux** or **herdr**. "mux" in code (`src/mux/`, `MU_MUX`), "multiplexer" in prose; one is active per invocation. | "terminal", "emulator", "backend" (alone) |
20
20
  | **mux backend** | An implementation of `MuxBackend` (`src/mux/tmux.ts`, `src/mux/herdr.ts`). Owns everything backend-specific. | "driver", "provider", "adapter" |
21
21
  | **mux detection** | Picks the mux backend: `MU_MUX` → `HERDR_ENV=1` → `$TMUX` → binary on `PATH` (tmux wins a tie) → `NoMultiplexerError`. | "auto-detect", "probe" |
@@ -105,7 +105,7 @@ agent names, and file paths are in [reference/naming.md](reference/naming.md).
105
105
  | **op** | One `ops` row, written by a trigger in the mutation's transaction. Holds only changed columns. | "event", "delta", "change" |
106
106
  | **ops log** | The `ops` table: the append-only record that sync, undo, and history read. | "event log", "journal", "WAL" |
107
107
  | **log entry** | An op as rendered by `mu log`, through one formatter (`src/log-render.ts`). | "message", "event" |
108
- | **kind** | The operator's channel tag on a log entry (`mu log --kind`), stored as the op's `entity`. | "category", "type" |
108
+ | **kind** | The operator's channel tag on a log entry (`mu log --kind`), stored as the op's `entity`. `workstream`, `task`, `edge`, and `note` are reserved. | "category", "type" |
109
109
  | **log ledger** | A convention: a custom `--kind` used as a watcher loop's durable dedupe record. | "state file" |
110
110
  | **intent** | The semantic label on an op (`task.close`), set once per SDK function via **op context**. | "verb", "action" |
111
111
  | **group** | The ops of one user action (`group_id`), undone as a unit. Any unique id prefix works. | "transaction", "batch" |
@@ -119,7 +119,7 @@ agent names, and file paths are in [reference/naming.md](reference/naming.md).
119
119
  | **resurrection** | A put newer than a seen tombstone, which legitimately recreates the row. | "undelete", "revive" |
120
120
  | **provenance** | Which HLC last wrote a field, derived from the ops log by query, never stored. | "version vector" |
121
121
  | **inverse op** | The op that reverts another; an ordinary op, so undo syncs and is undoable. | "rollback" |
122
- | **superseded** (group) | A later group wrote the same field, so `mu undo` refuses (exit 4) without `--force`. | "stale" |
122
+ | **superseded** (group) | A later group wrote the same field (other than `updated_at`), so `mu undo` refuses (exit 4) without `--force`. | "stale" |
123
123
  | **teardown** | Kill a workstream's mux session and delete its rows. `mu undo` restores rows, not panes or dirs. | "destroy", "nuke", "purge" |
124
124
  | **drift** | The ops log and live tables disagree. Always a bug. | "inconsistency", "corruption" |
125
125
  | **shallow / deep check** | Drift checks in `mu doctor` and `mu doctor --deep` (full rebuild). | "fsck" |
@@ -128,7 +128,7 @@ agent names, and file paths are in [reference/naming.md](reference/naming.md).
128
128
  | **machine_id** | Per-state-dir uuid naming this DB's **peer** and **segment**. | "device id", "host id" |
129
129
  | **segment** | `<sync-dir>/<machine_id>.jsonl`: one machine's ops, single writer. | "replica", "shard" |
130
130
  | **peer** | Another machine, found by its segment; named by `machine_id` prefix. **Stale** after 24h unchanged. | "node", "remote" |
131
- | **watermark** | `sync_peers.last_applied_seq`: how many lines of a peer's segment have been applied. | "offset", "cursor" |
131
+ | **watermark** | `sync_peers.last_applied_seq`: how many lines of a peer's segment file have been applied. A conflict copy has its own, keyed by its file stem. | "offset", "cursor" |
132
132
  | **flush** / **ingest** | Write my ops to my segment; apply peers' from their watermarks. Every invocation, when `MU_SYNC_DIR` is set. | "push", "pull" |
133
133
  | **reprojection** | After ingest, apply deferred ops whose parent arrived from another peer. | "retry queue" |
134
134
  | **mixed fleet** | Machines sharing state across different OSes or filesystems. | "cluster" |
@@ -222,5 +222,4 @@ deprecated, always `spawning`, and never read.
222
222
  | **pi session** | pi's conversation |
223
223
  | **agent session** | Colloquial for an agent's lifetime; avoid in code |
224
224
 
225
- New columns and variables say `workstream_id`, not `session_id`. The
226
- `agents.session_id` column keeps its name for schema stability.
225
+ Columns and variables say `workstream_id`, never `session_id`.
@@ -134,5 +134,5 @@ when `MU_MANAGED_AGENT` is set (inside a mu-spawned pane) or
134
134
  `wait --json`, read, abort, close) and only format the CLI's
135
135
  `outcome` into a follow-up message. `delegateOutcome`
136
136
  (`src/agents/delegate.ts`) maps one wait result to `done`, `empty`,
137
- `died`, `timeout` or `pending`. The extension imports nothing from pi;
137
+ `error`, `died`, `timeout` or `pending`. The extension imports nothing from pi;
138
138
  it types pi's API structurally.
@@ -5,7 +5,7 @@ mu runs agents in multiplexer panes, tmux or herdr, behind one
5
5
 
6
6
  ## One mux session per workstream
7
7
 
8
- One workstream is one mux session and one `session_id` partition in
8
+ One workstream is one mux session and one `workstream_id` partition in
9
9
  `~/.local/state/mu/mu.db`. Workstreams on one machine are independent
10
10
  mux sessions.
11
11
 
@@ -107,7 +107,9 @@ as a capability, not a name check:
107
107
  reads before the first claim report `unknown`. A pi agent skips the
108
108
  fixed wait: mu checks the pane on every handshake tick (a dead pane
109
109
  rolls back at once) and scans for startup errors when the handshake
110
- ends.
110
+ ends. After an ok handshake the scan skips shell exec-failure lines
111
+ (`command not found`, `No such file or directory`): pi demonstrably
112
+ started, and a resumed session can show such lines as old tool output.
111
113
  - **herdr.** mu creates a bare pane and calls `startAgentInPane`, which
112
114
  subsumes both checks.
113
115
  - **pi, either backend.** After the agent starts, mu polls its control
@@ -103,7 +103,8 @@ prefix previews, and `--yes` applies.
103
103
  old key (drift-641). The `task_notes` delete trigger records `OLD.*`
104
104
  only when no put under the current key is in the log, and `'{}'`
105
105
  otherwise; `planUndo` folds the puts first and falls back to the
106
- tombstone payload. For the same reason `src/drift.ts` matches notes on
106
+ tombstone payload, and `applyOp` deletes by the payload's content
107
+ identity, falling back to the put under the same key. For the same reason `src/drift.ts` matches notes on
107
108
  the task-key prefix.
108
109
 
109
110
  ## Rewriting history (local only)
@@ -125,7 +125,9 @@ codes:
125
125
  | 2 | usage error (Commander's default) |
126
126
  | 3 | not found (agent, task, workspace) |
127
127
  | 4 | conflict (name collision, double claim, dirty tree, schema too old or new, outdated extension) |
128
- | 5 | substrate unavailable (no multiplexer, DB locked) |
128
+ | 5 | substrate unavailable or unsafe (no multiplexer, mux or VCS failure, DB locked, ops-log drift, a `mu doctor` FAIL row, timeout) |
129
+ | 6 | `mu task wait`: the reaper reopened a watched task (its pane died) |
130
+ | 7 | `mu task wait`: a watched task stalled (`--on-stall exit`) |
129
131
 
130
132
  Errors carry structured context (operation, target, attempted action)
131
133
  so `mu doctor` can show them readably.
@@ -18,6 +18,9 @@ Litestream, cr-sqlite or a peer list, and why the DB must never sit in
18
18
  `applyOp`.
19
19
  - Peer discovery is implicit: every non-self `*.jsonl` is a peer. A
20
20
  peer disappears only when its segment is deleted.
21
+ - A Syncthing conflict copy (`<machine>.sync-conflict-….jsonl`) is
22
+ ingested too, with its own watermark keyed by the file stem: it can
23
+ diverge from the original, so line counts are not comparable.
21
24
 
22
25
  **Single writer per file.** A machine appends only to its own segment,
23
26
  so nothing is contended and any folder syncer is adequate. Segments
@@ -35,8 +38,10 @@ anything else, unwrapping on ingest.
35
38
 
36
39
  ## Robustness layers
37
40
 
38
- Four checks stop ingest at the first bad record and advance the
39
- watermark only that far:
41
+ The first three checks stop ingest at the first bad record and advance
42
+ the watermark only that far. The fourth is whole-file: it reports the
43
+ defect without halting, because every line still present is valid, and
44
+ only a fresh copy of the file clears it:
40
45
 
41
46
  | Layer | Catches |
42
47
  | --- | --- |
@@ -54,8 +59,9 @@ distinguishes three cases:
54
59
 
55
60
  | Line | Result |
56
61
  | --- | --- |
57
- | damaged | halt |
62
+ | damaged (including a blank line) | halt |
58
63
  | a known machine-local entity | reported defect, skipped |
64
+ | a historical log-only intent (`workstream.export` prose from mu < 1.1) | skipped silently, as flush and `--from` skip it |
59
65
  | an unrecognised entity | applied as a no-op, no defect (a reader behind the writer is normal in a mixed fleet) |
60
66
 
61
67
  `mu sync` suggests `--repair` only for defects a re-read can clear.
@@ -71,8 +77,9 @@ poll outliving a command. It happens because you already run `mu`
71
77
  constantly.
72
78
 
73
79
  The seam is `handle()` (`src/cli/handle.ts`), which every verb passes
74
- through and which is already async. `syncPass` is async because it
75
- takes the file lock, while most verb bodies are synchronous
80
+ through and which is already async. It awaits `ambientIngest` and
81
+ `ambientFlush`; the flush is async because it takes the file lock,
82
+ while most verb bodies are synchronous
76
83
  better-sqlite3 code. One `await` before `fn(db)` and one after cover
77
84
  every verb.
78
85
 
@@ -86,7 +93,8 @@ every verb.
86
93
  `src/sync.ts` holds `ambientIngest`, `ambientFlush`, `ambientSyncPass`,
87
94
  `peerStatuses`, `ingestFromDb` (`mu sync --from <path>`) and
88
95
  `repairPeer` (`mu sync --repair <peer>`, a unique prefix; ambiguity is
89
- exit 4).
96
+ exit 4). A prefix names a machine, so it resets the watermark of the
97
+ peer's segment and of every conflict copy of it.
90
98
 
91
99
  ### Carve-outs
92
100
 
@@ -30,9 +30,10 @@ Press `?` in the TUI for the keymap.
30
30
  | `index.ts`, `escapes.ts` | `runTui` entry; alt-screen and SGR mouse-mode lifecycle; pure escape bytes |
31
31
  | `app.tsx` | `<App>` root: popup state machine, global keymap, tabs, footer |
32
32
  | `state.ts` | `useDashboardSnapshot` poll loop (fast and slow tiers) |
33
- | `keys.ts`, `keymap-spec.ts`, `help.tsx` | pure key dispatch; the keymap source of truth that drives both dispatch and the `?`/F1 overlay |
33
+ | `keys.ts`, `keymap-spec.ts`, `help.tsx` | pure key dispatch; the keymap source of truth that drives both dispatch and the `?` overlay |
34
34
  | `mouse.ts`, `use-popup-action-queue.ts` | vendored SGR mouse parser with double-click; one queued popup action per render |
35
35
  | `layout.ts`, `columns.ts`, `wrap-ansi.ts`, `use-terminal-size.ts` | responsive columns and row budgets; aligned clipping; ANSI-aware wrapping; resize hook |
36
+ | `popup-input.ts` | `usePopupInput`: popup keyboard, off while the help overlay covers the popup |
36
37
  | `titled-box.tsx`, `popup-shell.tsx`, `list-row.tsx`, `padded-rows.tsx`, `status-bar.tsx`, `tab-strip*.ts(x)` | chrome primitives |
37
38
  | `format-helpers.ts`, `agent-display.ts` | shared formatters and agent-row display |
38
39
  | `use-popup-filter.tsx`, `use-status-filter.tsx`, `use-notes-drill.ts` | `/` filter; status (`o`/`i`/`c`) and substate (`p`/`w`) toggles; shared notes drill |
@@ -45,9 +46,12 @@ Press `?` in the TUI for the keymap.
45
46
  `<App>` owns:
46
47
 
47
48
  - **Popup state:** `null` (dashboard) or one popup id. One popup at a
48
- time; `Esc` or `q` returns to the dashboard.
49
+ time; `Esc` or `q` returns to the dashboard. `?` over a popup hides
50
+ it (`display="none"`) and turns its keyboard off rather than
51
+ unmounting it, so its cursor, filter and drill survive the overlay.
49
52
  - **Card visibility:** toggled by `0`-`9`.
50
- - **Tick rate:** fast tick, 1s default, adjusted with `+` `-` `=` `0`.
53
+ - **Tick rate:** fast tick, 1s default, adjusted with `+` / `=` and
54
+ `-`. There is no reset key: `0` toggles the Commits card.
51
55
  - **Active workstream tab:** `Tab` / `Shift-Tab` cycles when there are
52
56
  two or more. A torn-down workstream keeps its tab position and
53
57
  renders dimmed with strikethrough until the name is recreated. A
@@ -98,7 +102,7 @@ the right border.
98
102
 
99
103
  Every popup row exposes one canonical `mu` command through `y`.
100
104
  `yank.ts` probes pbcopy, wl-copy, xclip, xsel and clip.exe, then falls
101
- back to OSC-52 over stderr.
105
+ back to OSC-52 written to `/dev/tty`, since ink owns stdout.
102
106
 
103
107
  The handoffs leave the alt screen, disable mouse mode, run a
104
108
  foreground subprocess, and restore both on exit:
@@ -114,16 +118,22 @@ A TUI gesture that mutates state needs a ROADMAP entry first.
114
118
 
115
119
  Mouse support uses SGR mouse mode. `mouse.ts` parses
116
120
  `ESC[<button;x;y;M/m`, detects double-clicks, and exposes `useMouse()`.
117
- A double-click on a card emits `setCursor` then `drill` through the
118
- action queue, which consumes one action per render, so the cursor lands
119
- before the drill reads the focused row.
121
+ A double-click on a card opens its popup. A double-click on a popup row
122
+ emits one `clickRow` action (the 0-based body row) through the action
123
+ queue. The popup maps that row through its own scroll window and
124
+ header strips (`clickedItem` in `popups/scroll.ts`), then focuses and
125
+ drills the item. App cannot do the mapping: only the popup knows its
126
+ window start and strip rows.
120
127
 
121
128
  `keys.ts` holds `dispatchGlobalKey` (dashboard), `dispatchPopupKey`
122
129
  (popup), and `shouldSwallowGlobalKey` (keys a popup consumes).
123
130
 
124
131
  ## Drill recursion
125
132
 
126
- List popups drill with `Enter`. `DrillScrollView`
133
+ List popups drill with `Enter`. Each popup pins the drilled row's
134
+ identity at `Enter` (task, agent, check, commit sha, log seq, track
135
+ goals), so a refresh that reorders or extends the list cannot move an
136
+ open drill to another row. `DrillScrollView`
127
137
  (`popups/drill.tsx`) is the shared scrollable text leaf: Workspaces'
128
138
  git show, Agents' scrollback, Activity log payloads, and Doctor
129
139
  remediation. Task popups drill into `TaskDetailDrill`
@@ -27,7 +27,7 @@ mu picks the backend from the first rule that matches:
27
27
  | Workstream | session `mu-<ws>` | workspace labelled `mu-<ws>` |
28
28
  | Window | window | tab |
29
29
  | Pane id | `%15` | `w1:p1` |
30
- | Attach | `tmux attach -t mu-<ws>` | `herdr session attach mu-<ws>` |
30
+ | Attach | `tmux attach -t mu-<ws>` | `herdr workspace focus <id>`, then `herdr` outside a herdr pane |
31
31
  | pi agent state | control socket | control socket |
32
32
  | Other agents' state | murmur | herdr |
33
33
  | Send to non-pi agents | bracketed paste, then Enter | one atomic `agent prompt` |
@@ -44,8 +44,10 @@ Where each agent's state comes from, per backend:
44
44
  `/proc/<pid>/fd/0`, which macOS does not have.
45
45
  - `mu agent read --lines` cannot recover rows that scrolled off a pane
46
46
  on the alternate screen.
47
- - `MU_<CLI>_COMMAND` overrides apply on tmux only. herdr picks the
48
- agent binary itself.
47
+ - herdr starts the agent binary itself (`herdr agent start --kind
48
+ <cli>`), so spawn refuses `--command` and `MU_<CLI>_COMMAND` with
49
+ exit 2 instead of running a different binary. Unset the variable, or
50
+ spawn with `MU_MUX=tmux`.
49
51
  - herdr creates every workspace detached. Run `herdr workspace focus`
50
52
  yourself.
51
53
 
@@ -22,14 +22,18 @@ mu workstream teardown auth --yes # kills the session, deletes the rows
22
22
  Teardown kills the tmux session first, then deletes the agents, tasks,
23
23
  edges, notes, and workspaces. Running it again is a no-op.
24
24
 
25
- To sweep every workstream that has no tasks, agents, or workspaces,
26
- including `mu-*` tmux sessions with no DB row:
25
+ To sweep every workstream that has no tasks, agents, or workspaces:
27
26
 
28
27
  ```bash
29
28
  mu workstream teardown --empty
30
29
  mu workstream teardown --empty --yes
31
30
  ```
32
31
 
32
+ The sweep skips `scratch`, whose idle session is kept open on purpose.
33
+ It never takes a `mu-*` session that has no workstream row in this DB:
34
+ it may be a live workstream from another DB. The output names
35
+ such sessions; tear one down by name once you know it is unused.
36
+
33
37
  ## Undo a teardown
34
38
 
35
39
  Teardown writes tombstone ops, so the ops log keeps everything. You do
@@ -40,7 +44,8 @@ mu workstream list --torn-down
40
44
  mu undo 7a40e6cc --yes
41
45
  ```
42
46
 
43
- The list shows one row per teardown, newest first. `← recreated since`
47
+ The list shows one row per teardown, newest first. A `--empty` sweep
48
+ writes one row per workstream, so undo each one. `← recreated since`
44
49
  means the name is in use again, so the undo would change nothing.
45
50
  Killed panes and freed workspace directories do not come back.
46
51
 
@@ -95,7 +95,9 @@ Exit codes: `0` met, `5` timeout, `6` pane died. Non-pi agents have no
95
95
  ## Keep a helper
96
96
 
97
97
  Pass `keep: true`, or spawn a named agent in `scratch`. It is created
98
- on first spawn, needs no tasks, and cannot be `init`ed.
98
+ on first spawn, needs no tasks, and cannot be `init`ed. Its `mu-scratch`
99
+ session stays open after the last agent closes; remove it with
100
+ `mu workstream teardown scratch --yes`.
99
101
 
100
102
  ```bash
101
103
  mu agent spawn helper -w scratch
@@ -103,9 +103,11 @@ mu workspace refresh worker-1 -w auth
103
103
  ```
104
104
 
105
105
  It rebases onto the remote's main (`origin/HEAD`); without a remote,
106
- pass `--from main`. It refuses on a dirty tree. On a conflict it exits 5
107
- with a `cd` hint. To throw the workspace away instead, run
108
- `mu workspace free worker-1 -w auth`.
106
+ pass `--from main`. It refuses on a dirty tree. On a conflict it exits 5:
107
+ git and sl abort the rebase and leave the workspace as it was, while jj
108
+ keeps the rebase with the conflicts committed in place. After you resolve,
109
+ run refresh again so mu records the new base. To throw the workspace away
110
+ instead, run `mu workspace free worker-1 -w auth`.
109
111
 
110
112
  Claim and send warn when the worker's workspace is 10 or more commits
111
113
  behind main. `--strict-staleness` makes that a refusal (exit 4).
@@ -7,7 +7,7 @@ read the same rules in
7
7
  [recipes/recovery.md](../../skills/mu/recipes/recovery.md).
8
8
 
9
9
  ```bash
10
- mu doctor # exit 0 healthy, 5 on drift
10
+ mu doctor # exit 0 healthy (WARN rows too), 5 on drift or a FAIL row
11
11
  mu doctor --deep # rebuild the ops log and diff every field
12
12
  mu doctor --disk # add per-workspace byte usage
13
13
  ```
@@ -30,6 +30,8 @@ mu agent adopt %15 -w auth
30
30
  mu agent adopt %15 -w auth --name investigator
31
31
  ```
32
32
 
33
+ On herdr, pass its pane id instead (`mu agent adopt w1:p2 -w auth`).
34
+
33
35
  The pane title becomes the agent name. The pane must be in the
34
36
  `mu-<workstream>` session. An adopted pi has no control socket until
35
37
  you restart it with the `MU_CTL_SOCK` path that `adopt` prints.
@@ -86,7 +88,8 @@ mu undo 6380dd3d --yes # apply
86
88
  - Any unique prefix of a group id works.
87
89
  - The undo is its own group, so undoing it again is redo.
88
90
  - If later work changed the same fields, undo exits 4 and names the
89
- conflict. `--force --yes` overrides and discards the newer work.
91
+ conflict. A later write to `updated_at` alone, such as `mu task note`,
92
+ does not count. `--force --yes` overrides and discards the newer work.
90
93
  - Undo restores rows only. Killed panes and freed workspace
91
94
  directories do not come back.
92
95
 
@@ -113,21 +116,29 @@ command.
113
116
  | `ws-rows` | a workspace row whose directory is gone; the next send fails in the VCS |
114
117
  | `ws-dirs` | a workspace directory with no row; it blocks the next `--workspace` spawn with that name |
115
118
  | `ws-empty` | a workstream directory with no checkouts |
116
- | `db-copies` | stray `mu.db*` files that nothing reads |
119
+ | `db-copies` | stray `mu.db*` files that nothing reads, including the `pre-compact` / `pre-forget` backups |
117
120
  | `exports` | output left by the removed export verb |
118
121
  | `locks` | lock directories older than one hour, from a spawn or flush that died |
119
122
 
120
123
  To list workspace orphans with cleanup commands, run
121
- `mu workspace orphans -w auth`. Remove a git-backed orphan with
122
- `git worktree remove --force <path>` from the project root.
124
+ `mu workspace orphans -w auth`. An orphan has no row, so
125
+ `mu workspace free` cannot remove it. Remove a git-backed orphan with
126
+ `git worktree remove --force <path>` from the project root, and any
127
+ other with `rm -rf <path>`.
123
128
 
124
129
  ## Rename a workstream
125
130
 
126
- There is no rename verb. Every child table cascades on the name:
131
+ There is no rename verb, and `mu sql` refuses to change
132
+ `workstreams.name`. The name is part of the key that every op is filed
133
+ under, so a raw rename leaves the ops log on the old name: `mu doctor`
134
+ reports drift, and sync or `mu rebuild` brings the old name back.
135
+ Re-create the workstream instead:
127
136
 
128
137
  ```bash
129
- mu sql "UPDATE workstreams SET name='auth-refactor' WHERE name='auth-refator'"
130
- tmux rename-session -t mu-auth-refator mu-auth-refactor
138
+ mu db backup /tmp/mu-before-rename.db
139
+ mu workstream init auth-refactor
140
+ # re-add the tasks you still need, with -w auth-refactor
141
+ mu workstream teardown auth-refator --yes
131
142
  ```
132
143
 
133
144
  Names start with a lowercase letter, use letters, digits, `_`, or
package/docs/guide/sql.md CHANGED
@@ -20,8 +20,8 @@ mu task list -w auth --json | jq '.count'
20
20
  ```
21
21
 
22
22
  Exit codes: `2` usage error, `3` not found, `4` conflict, `5`
23
- timeout or a multiplexer, VCS, or drift failure, `6` reaped pane,
24
- `7` stall.
23
+ timeout, a locked DB, or a multiplexer, VCS, drift, or `mu doctor`
24
+ FAIL-row failure, `6` reaped pane, `7` stall.
25
25
 
26
26
  ## Read the schema
27
27
 
@@ -66,7 +66,14 @@ mu sql "WITH RECURSIVE prereqs(id) AS (
66
66
  ```
67
67
 
68
68
  `mu sql` runs writes too and prints the number of rows changed. Writes
69
- are captured as ops, so `mu undo` can revert them.
69
+ are captured as ops under one `sql.write` group per call, so `mu undo`
70
+ reverts the whole call. `--confirm-rows N` counts rows removed by
71
+ `ON DELETE CASCADE` too: deleting a task with one edge counts 2.
72
+
73
+ `mu sql` refuses to change a natural-key column (`workstreams.name`,
74
+ `tasks.workstream_id`, `tasks.local_id`, a note's task, or an edge's
75
+ endpoints). The ops log files every change under that key, so changing
76
+ it would desync sync, undo, and `mu doctor`. Add a new row instead.
70
77
 
71
78
  ## Pass list flags
72
79
 
@@ -55,7 +55,9 @@ freed with it. If the workspace has edits or commits, close refuses
55
55
  with exit 4. Then do one of these:
56
56
 
57
57
  - Run `mu workspace free worker-1 -w auth --commit` to commit pending
58
- changes and free the workspace, then close.
58
+ changes and free the workspace, then close. For git, if the commits
59
+ are on no branch, mu keeps them on a new branch
60
+ `mu/<workstream>/<agent>-<sha>`.
59
61
  - Run `mu agent close worker-1 -w auth --discard-workspace` to free
60
62
  and close in one step. You lose the changes.
61
63
 
package/docs/guide/tui.md CHANGED
@@ -67,9 +67,9 @@ pane. The dashboard comes back when the tool exits.
67
67
  `/` filters the popup's rows by substring as you type. `Enter` keeps
68
68
  the filter. `Esc` clears it.
69
69
 
70
- In task popups, `o`, `i`, and `c` toggle `OPEN`, `IN_PROGRESS`, and
71
- `CLOSED`. In the DAG and All-tasks popups, `p` toggles parked tasks and
72
- `w` toggles tasks closed as anything but `done`. In All tasks, `b`
70
+ In the DAG and All-tasks popups, `o`, `i`, and `c` toggle `OPEN`,
71
+ `IN_PROGRESS`, and `CLOSED`, `p` toggles parked tasks, and `w` toggles
72
+ tasks closed as anything but `done`. In All tasks, `b`
73
73
  cycles the blocked filter and `s` cycles the sort: ROI, recency, age,
74
74
  id.
75
75
 
@@ -16,8 +16,9 @@ that both paths start from.
16
16
 
17
17
  - `--copy` copies the extension instead of the shim. You then have to
18
18
  relink after every upgrade.
19
- - `--force` replaces a skill symlink that points elsewhere, such as a
20
- dev checkout. mu never replaces a real directory there (exit 4).
19
+ - `--force` replaces an extension or skill symlink that points
20
+ elsewhere, such as a dev checkout. mu replaces the link, never its
21
+ target, and never replaces a real skill directory (exit 4).
21
22
  - `--extension-only` and `--skill-only` install one part.
22
23
 
23
24
  For agents other than pi, see [README § Install](../../README.md#install).
@@ -50,7 +51,12 @@ newer one with `SchemaTooNewError`. Both exit 4 and leave the file
50
51
  alone. `scripts/migrate.ts` converts a v7, v8, v9, or v10 DB into a new
51
52
  v11 file. It never writes in place.
52
53
 
54
+ The npm package does not ship `scripts/` or `src/`, so run the recipe
55
+ from a git checkout of mu at the version you upgraded to (`npm install`
56
+ there first; `npx tsx` fetches tsx on first use).
57
+
53
58
  ```bash
59
+ git clone --branch "v$(mu --version)" https://github.com/mu-crew/mu && cd mu && npm install
54
60
  DB=${MU_DB_PATH:-$HOME/.local/state/mu/mu.db}
55
61
  BACKUP="$HOME/mu-old-backup-$(date +%Y%m%d-%H%M%S).db"
56
62
  sqlite3 "$DB" ".backup '$BACKUP'"
@@ -1,6 +1,8 @@
1
1
  # Environment variables
2
2
 
3
- Every env var mu reads or sets. mu has no config file. Durations are
3
+ Every env var mu reads or sets, except standard ones it reads as any
4
+ tool does (`HOME`, `PATH`, `SHELL`, `USER`, `TERM`, `DISPLAY`,
5
+ `WAYLAND_DISPLAY`). mu has no config file. Durations are
4
6
  milliseconds; `0` disables a wait unless noted.
5
7
 
6
8
  ## State and workstream
@@ -9,7 +11,7 @@ milliseconds; `0` disables a wait unless noted.
9
11
  | --- | --- | --- |
10
12
  | `MU_DB_PATH` | SQLite file path. Wins over the state dir. | `<state-dir>/mu.db` |
11
13
  | `MU_STATE_DIR` | State directory. | `$XDG_STATE_HOME/mu` |
12
- | `XDG_STATE_HOME` | XDG state base; mu appends `mu/`. | `~/.local/state` |
14
+ | `XDG_STATE_HOME` | XDG state base; mu appends `mu/`. An empty or relative value is ignored. | `~/.local/state` |
13
15
  | `MU_SESSION` | Active workstream when `-w` is absent. | current `mu-<name>` mux session |
14
16
  | `MU_SYNC_DIR` | Folder holding one segment per machine. This is the whole sync setup; there is no peer list. | unset: sync off |
15
17
 
@@ -19,7 +21,10 @@ milliseconds; `0` disables a wait unless noted.
19
21
  | --- | --- | --- |
20
22
  | `MU_MUX` | Force `tmux` or `herdr`, skipping mux detection. An unknown value fails the invocation. | detected |
21
23
  | `HERDR_ENV` | Read only. `1` (set by herdr in its panes) selects herdr; outranks `$TMUX`. | |
22
- | `MU_TMUX_SOCKET` | tmux socket name (`-L <name>`). tmux only. | `$TMUX` |
24
+ | `TMUX`, `TMUX_PANE` | Read only. Either one (set by tmux in its panes) selects tmux. `$TMUX` gates reading the current session name, which resolves the workstream when `-w` and `MU_SESSION` are absent. `$TMUX_PANE` identifies the calling agent (`mu me`, `mu log`, task claim errors). | |
25
+ | `HERDR_WORKSPACE_ID` | Read only. Set by herdr in its panes; pane listing with no target reads this workspace first. | |
26
+ | `HERDR_PANE_ID` | Read only. Set by herdr in its panes; fallback rung of actor identity after `MU_AGENT_NAME`. | |
27
+ | `MU_TMUX_SOCKET` | Test isolation. Runs every tmux call on a private server (`-L <name>`) with `-f /dev/null`, so `~/.tmux.conf` is not loaded. tmux only. | unset: tmux's default server |
23
28
  | `MU_HERDR_SESSION` | Named herdr server (`--session <name>`). herdr only. Tests must set it to a non-default name. | herdr default session |
24
29
  | `MU_BANNER_QUIET` | `1` disables mu's pane border and banner decorations. tmux only. | unset |
25
30
 
@@ -27,7 +32,7 @@ milliseconds; `0` disables a wait unless noted.
27
32
 
28
33
  | Name | Effect | Default |
29
34
  | --- | --- | --- |
30
- | `MU_<CLI>_COMMAND` | Executable for `--cli <cli>`; hyphens become underscores (`--cli pi-meta` reads `MU_PI_META_COMMAND`). May hold arguments. `--command` wins; the spawn line names the env var when it supplied the command. | the cli value |
35
+ | `MU_<CLI>_COMMAND` | Executable for `--cli <cli>`; hyphens become underscores (`--cli pi-meta` reads `MU_PI_META_COMMAND`). May hold arguments. `--command` wins; the spawn line names the env var when it supplied the command. On herdr, spawn refuses both with exit 2. | the cli value |
31
36
  | `MU_SPAWN_CTL_MS` | Budget for the control-socket handshake with a new pi agent, polled every 250ms. On timeout spawn still exits 0, reports `ctl: missing` or `ctl: refused`, and warns. `--no-ctl` skips it per spawn. | `30000` |
32
37
  | `MU_SPAWN_LIVENESS_MS` | tmux only. Wait this long, then check the pane is alive and its scrollback has no startup error (auth failure, `command not found`). Failure rolls back the row and throws `AgentDiedOnSpawnError` or `AgentSpawnStartupError`. A pi agent skips the wait: the pane is checked on each ctl handshake tick and scanned when it ends. | `1500` |
33
38
  | `MU_SPAWN_LOCK_TIMEOUT_MS` | How long a spawn waits for the per-workstream spawn lock. | `15000` |
@@ -37,9 +37,9 @@ clears all blockers.
37
37
  | Id | Shape | Unique within |
38
38
  | --- | --- | --- |
39
39
  | Agent name | `[a-z][a-z0-9_-]*`, ≤ 32 chars | workstream |
40
- | Task `local_id` | same | DB |
41
- | Workstream name | same; mux session is `mu-<name>` | DB |
42
- | Window (`--tab`) | `[A-Za-z][A-Za-z0-9 _-]*`, ≤ 32 chars | workstream |
40
+ | Task `local_id` | `[a-z][a-z0-9_-]*`, ≤ 64 chars | workstream |
41
+ | Workstream name | `[a-z][a-z0-9_-]*`, ≤ 32 chars; mux session is `mu-<name>` | DB |
42
+ | Window (`--tab`) | not validated; passed to the mux as given. Avoid `:` and `.`, which tmux reads as target separators | workstream |
43
43
 
44
44
  `scratch` is reserved: it is created only by the first
45
45
  `mu agent spawn <name> -w scratch`, and `mu workstream init scratch`
@@ -87,7 +87,6 @@ The state dir is `MU_STATE_DIR`, else `$XDG_STATE_HOME/mu`, else
87
87
  | Path | Contents |
88
88
  | --- | --- |
89
89
  | `<state-dir>/mu.db` | The SQLite DB for every workstream |
90
- | `<state-dir>/workstreams/<workstream>/` | Per-workstream artifacts, created lazily |
91
90
  | `<state-dir>/workspaces/<workstream>/<agent>/` | Per-agent VCS workspace, created by `mu agent spawn --workspace` |
92
91
  | `<sync-dir>/<machine_id>.jsonl` | This machine's segment; `<sync-dir>` is `MU_SYNC_DIR` |
93
92
  | `<sync-dir>/<machine_id>.manifest` | `{count, last_hlc, sha256}` for segment verification |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mu-crew/mu",
3
- "version": "3.8.1",
3
+ "version": "3.9.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"
@@ -264,8 +264,8 @@ Close the task as your last action, or the orchestrator's wait hangs.
264
264
  ## Guardrails
265
265
 
266
266
  Task ownership outranks agent state. Coordinate through task notes and
267
- the activity log. Keep edges within one workstream and reserve the `mu_`
268
- task-id prefix. Give workers bounded paths and commands.
267
+ the activity log. Keep edges within one workstream. Give workers
268
+ bounded paths and commands.
269
269
 
270
270
  ## Recipes
271
271