@martintrojer/mu 0.3.1 → 0.4.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.
@@ -1,26 +1,26 @@
1
1
  # mu — Usage Guide
2
2
 
3
- A practical, copy-pasteable tour of mu (current main; v0.3-track).
3
+ A practical, copy-pasteable tour of mu (current main; v0.4-track).
4
4
  Everything below works against the built CLI. Terms are canonical
5
5
  — see [VOCABULARY.md](VOCABULARY.md) for definitions; the complete
6
6
  current verb list is in `## CLI — complete verb list` of
7
7
  [skills/mu/SKILL.md](../skills/mu/SKILL.md).
8
8
 
9
- > **Status:** v0.3 wave (pre-1.0). ~60 typed verbs across 8
9
+ > **Status:** v0.4 wave (pre-1.0). ~60 typed verbs across 8
10
10
  > namespaces (`workstream`, `agent`, `task`, `workspace`, `log`,
11
11
  > `snapshot`, `archive`, `me`) plus bare top-level verbs
12
12
  > (`state`, `doctor`, `sql`, `undo`, `adopt`). Every verb accepts
13
13
  > `--json` (one allow-listed exception, `mu agent attach`),
14
14
  > per-agent VCS workspaces (jj/sl/git/none), activity log with
15
- > `--tail` subscription, canonical state card (`mu state`
16
- > default / `--hud` / `--mission` render modes), whole-DB
15
+ > `--tail` subscription, bare `mu` TTY dashboard, canonical static
16
+ > state card (`mu state` default / `--tui` render modes), whole-DB
17
17
  > snapshots auto-captured before destructive verbs +
18
18
  > `mu undo` / `mu snapshot {list,show}`, evidence on lifecycle
19
19
  > verbs, schema v7 (v5 surrogate INTEGER PKs + per-workstream
20
20
  > UNIQUE on operator-facing names; v6 added the `archive_*`
21
21
  > family additively; v7 dropped the dead `approvals` table).
22
22
  > See [CHANGELOG.md](../CHANGELOG.md) for the release entry,
23
- > and [§ Not in 0.3.0](#whats-not-in-030-and-how-to-work-around-it)
23
+ > and [§ Not in 0.4.0](#whats-not-in-040-and-how-to-work-around-it)
24
24
  > at the bottom for the gaps that still need workarounds.
25
25
 
26
26
  *If anything below disagrees with `mu --help`, trust `mu --help`.*
@@ -33,7 +33,8 @@ current verb list is in `## CLI — complete verb list` of
33
33
  2. [Get oriented (`mu doctor`)](#2-get-oriented)
34
34
  3. [Create a workstream (`mu workstream init`)](#3-create-a-workstream)
35
35
  4. [Plan some work as a DAG (`mu task add`)](#4-plan-some-work-as-a-dag)
36
- 5. [See the graph (mission control)](#5-see-the-graph-mission-control)
36
+ 5. [See the graph (dashboard + state API)](#5-see-the-graph-dashboard--state-api)
37
+ 5b. [The TUI dashboard (interactive)](#5b-the-tui-dashboard-interactive)
37
38
  6. [Spawn a crew (`mu agent spawn`)](#6-spawn-a-crew)
38
39
  7. [Watch the crew live (`tmux attach`)](#7-watch-the-crew-live)
39
40
  8. [Send work to an agent (`mu agent send`)](#8-send-work-to-an-agent)
@@ -47,7 +48,7 @@ current verb list is in `## CLI — complete verb list` of
47
48
  15.5. [Archives — cross-workstream preservation](#155-archives--cross-workstream-preservation-of-task-graphs)
48
49
  16. [One-shot demo script](#16-one-shot-demo-script)
49
50
  17. [Mental model in three sentences](#mental-model-in-three-sentences)
50
- 18. [What's NOT in 0.3.0](#whats-not-in-030-and-how-to-work-around-it)
51
+ 18. [What's NOT in 0.4.0](#whats-not-in-040-and-how-to-work-around-it)
51
52
  19. [Where to go from here](#where-to-go-from-here)
52
53
 
53
54
  ---
@@ -140,6 +141,32 @@ tmux # if you're not already in one
140
141
 
141
142
  ## 2. Get oriented
142
143
 
144
+ For a human at an interactive terminal, bare `mu` is the home base:
145
+ it launches the read-only TUI with every workstream on the machine
146
+ loaded as tabs. Initial tab focus uses this ladder: `$MU_SESSION` when
147
+ it names a loaded workstream; then the current tmux session name when
148
+ it is `mu-<workstream>`; then best-effort cwd detection against
149
+ registered workspace paths; then cwd equal to the VCS-derived project
150
+ root of any loaded workstream's workspaces (ties broken by most-recent
151
+ workstream activity); then tab 0. If no workstream exists yet, it
152
+ prints help plus the one-paste start command:
153
+
154
+ ```bash
155
+ mu
156
+ # Get started: mu workstream init <name>
157
+ ```
158
+
159
+ For scripts, agents, CI, and pipes, bare `mu` deliberately does NOT
160
+ enter the TUI: when stdout is not a TTY it prints `mu --help`. Use
161
+ explicit typed verbs and `--json` for the API surface:
162
+
163
+ ```bash
164
+ mu state -w <workstream> --json
165
+ MU_NO_TUI=1 mu # force the non-TTY/help path even in a terminal
166
+ ```
167
+
168
+ Run the diagnostic once to check tmux + DB health:
169
+
143
170
  ```bash
144
171
  mu doctor
145
172
  ```
@@ -222,9 +249,10 @@ $ mu task list -w foo --json
222
249
  ```
223
250
 
224
251
  `count` is `items.length` pre-computed so `jq '.count'` is one less
225
- hop than `jq '.items | length'`. Future siblings (e.g. `baseRef` on
226
- `mu workspace commits`, `behindCount` on `mu workspace list`) layer
227
- on without breaking the existing two fields.
252
+ hop than `jq '.items | length'`. Future siblings layer on without
253
+ breaking the existing two fields. Today `mu workspace commits --json`
254
+ also includes `vcs`, `baseRef`, and `workspacePath` siblings because
255
+ that verb already computes the workspace's fork metadata.
228
256
 
229
257
  Applies to: `mu task list / next / owned-by / notes`,
230
258
  `mu workstream list`, `mu workstream destroy --empty` (dry-run),
@@ -329,8 +357,8 @@ Tasks have **mandatory** `impact` (1–100) and `effort-days` (>0).
329
357
  Edges are blocks-relationships, modelled as **`--blocked-by`** on `mu
330
358
  task add` (and `mu task reparent`): `--blocked-by design` means "this
331
359
  task is blocked by `design`; it can't start until `design` closes."
332
- Tasks are **scoped to a workstream** — mission control only shows
333
- tasks for the workstream you're in.
360
+ Tasks are **scoped to a workstream** — the dashboard and state views only show
361
+ tasks for the workstream you're viewing.
334
362
 
335
363
  ```bash
336
364
  # --workstream can be omitted if you're inside the workstream's tmux
@@ -357,112 +385,299 @@ Each task validates its id (`/^[a-z][a-z0-9_-]{0,63}$/`) and rejects
357
385
  duplicates. If you tried `mu task add x --blocked-by y` while `y`
358
386
  already transitively depended on `x`, mu would refuse with a `CycleError`.
359
387
 
360
- **Task ids are globally unique** (PRIMARY KEY across all workstreams)
361
- but tasks are scoped to one workstream. Cross-workstream blocks-edges
362
- are forbidden if `--blocks foo` resolves to a task in a different
388
+ **Task ids are per-workstream unique.** The same local id can exist in
389
+ multiple workstreams, so cross-workstream references use the qualified
390
+ form `<workstream>/<id>` when a global scope is needed. Blocks-edges
391
+ are always same-workstream — if a blocker resolves outside the target
363
392
  workstream, mu refuses with a `CrossWorkstreamEdgeError`.
364
393
 
365
394
  ---
366
395
 
367
- ## 5. See the graph (mission control)
396
+ ## 5. See the graph (dashboard + state API)
368
397
 
369
- ```bash
370
- mu --workstream auth-refactor
371
- # or, if your tmux session is mu-auth-refactor:
372
- mu
373
- ```
398
+ `mu` exposes one logical "what's going on?" view with two renderers:
374
399
 
375
- ```
376
- mu-auth-refactor
400
+ | Surface | Use it for |
401
+ | -------------------- | --------------------------------------------------------------- |
402
+ | **bare `mu`** | A human at a terminal — launches the interactive TUI dashboard. |
403
+ | **`mu state --tui`** | Same TUI, explicitly opt-in. Useful in scripts / aliases. |
404
+ | **`mu state`** | Static text card. JSON-friendly; pipeable; `watch`-able. |
405
+ | **`mu state --json`** | The canonical full snapshot. Agents and scripts read this. |
377
406
 
378
- Agents (0)
379
- (no agents)
407
+ The interactive surface is large enough to deserve its own section —
408
+ see [§ 5b. The TUI dashboard](#5b-the-tui-dashboard-interactive)
409
+ immediately below. The rest of this section is the static / JSON
410
+ contract.
380
411
 
381
- Tracks (1)
382
- Track 1: review (3 tasks, 1 ready, track)
412
+ ### Static state card (`mu state`)
383
413
 
384
- Ready (1)
385
- ┌────────┬─────────────────────┬────────┬────────┬──────┬───────┐
386
- │ id │ title │ impact │ effort │ ROI │ owner │
387
- ├────────┼─────────────────────┼────────┼────────┼──────┼───────┤
388
- design Design auth module │ 80 │ 2 │ 40.0 │ │
389
- └────────┴─────────────────────┴────────┴────────┴──────┴───────┘
390
- ```
391
-
392
- This is the answer to **"what should I work on next?"** without
393
- asking an LLM. Three sections:
394
-
395
- - **Agents** registry rows, status detected from each pane's
396
- scrollback, post-reconciliation
397
- - **Tracks** — independent subtrees the parallel-track union-find
398
- found. When two goals share a prerequisite, mu collapses them into
399
- ONE track ("merged") so two agents are never assigned tasks that
400
- share a dependency
401
- - **Ready** — actionable now, sorted by ROI (impact / effort)
402
-
403
- ### `mu state` render modes (default, `--hud`, `--mission`)
404
-
405
- `mu state` is one verb with three render modes — same data set,
406
- different presentation strategy. The flag picks the renderer; the
407
- JSON shape (`--json`) follows render mode (full vs stripped).
408
-
409
- ```bash
410
- mu state # default: full top-to-bottom card
411
- mu state --hud # dynamic-fit budget renderer (watch / popup / status-bar)
412
- mu state --mission # stripped 5-col glance card
413
- mu # bare alias for `mu state --mission`
414
- ```
415
-
416
- - **default (full card)** — every section: agents + orphans + tracks +
417
- ready / in-progress / blocked / recent-closed + workspaces + recent
418
- events. JSON-first by design (per Ilya's council critique: state
419
- cards as the default attention surface; SQL/raw verbs as the
420
- escape hatch underneath).
421
-
422
- - **`--hud`** — dynamic table layout that fills the terminal (or tmux
423
- pane) height + width with as much useful data as fits.
424
- `watch -n 5 mu state --hud -w X` for a refreshing pane;
425
- `tmux display-popup -E 'mu state --hud -w X'` for an on-demand
426
- popup; `#(mu state --hud -w X --json) | jq ...` for tmux
427
- status-bar interpolation. Sections (priority order):
428
- header / agents / ready / in-progress / tracks / recent-events.
429
- Truncated tables get a `… +N more (<verb>)` footer; lower-priority
430
- sections that can't fit are skipped entirely. Drops blocked /
431
- recent-closed / workspaces (not glanceable); operator drops
432
- `--hud` to see them.
433
-
434
- - **`--mission`** — stripped 5-col glance card (agents + orphans +
435
- tracks + ready). The bare-`mu` muscle-memory orient call
436
- ("what's going on?"). The full card with blocked / recent-closed /
437
- workspaces is too much for that intent; `--mission` is the
438
- intentional minimum-viable orient view.
439
-
440
- `--hud` and `--mission` are mutually exclusive.
441
-
442
- Multi-workstream: pass `-w` multiple values to render N workstreams
443
- in one card. `-w a,b,c`, `-w a -w b`, or any mix all work — see
444
- [CLI conventions](#cli-conventions-multi-value-flags). `--all` is
445
- sugar for "every workstream on this machine" (mutually exclusive with
446
- `-w`). N≥2 in `--hud` mode unions the per-ws sections with a leading
447
- `workstream` column; in default + `--mission` modes N≥2 stacks one
448
- per-workstream card after another. The `--json` envelope wraps in
449
- `{ workstreams: [...] }` when N≥2.
450
-
451
- JSON shapes (per render mode):
414
+ For an agent/script or a static capture, use explicit `mu state`:
415
+
416
+ ```bash
417
+ mu state -w auth-refactor # human-readable card
418
+ mu state -w auth-refactor --json # full snapshot
419
+ mu state --all --json # every workstream on this machine
420
+ ```
421
+
422
+ The static card includes every section the TUI cards summarize:
423
+ agents + orphans + tracks + ready / in-progress / blocked /
424
+ recent-closed tasks + workspaces + recent events. `--json` emits the
425
+ same full snapshot shape regardless of `--tui`.
426
+
427
+ **JSON shapes**
452
428
 
453
429
  - `mu state --json` (single-ws): flat `{ workstreamName, agents,
454
430
  orphans, tracks, ready, blocked, inProgress, recentClosed,
455
431
  workspaces, recent }`.
456
- - `mu state --hud --json`: SAME flat shape (`--hud` is a render flag;
457
- it doesn't change the machine view).
458
- - `mu state --mission --json`: STRIPPED only `{ workstreamName,
459
- agents, orphans, tracks, ready }`.
460
- - bare `mu --json`: same as `--mission --json`.
432
+ - `mu state --json` (multi-ws): wrapped `{ workstreams: [{...}, ...] }`.
433
+ - bare `mu --json`: prints `--help` rather than entering the TUI;
434
+ use `mu state --json` for the full snapshot.
435
+ - `--tui` is render-only and incompatible with `--json` (the TUI
436
+ has no JSON shape).
437
+
438
+ **Multi-workstream**: pass `-w` multiple values, or `--all`. See
439
+ [CLI conventions](#cli-conventions-multi-value-flags). In static
440
+ mode N≥2 stacks one per-workstream card after another.
441
+
442
+ > **Migrating from old state surfaces**: `mu state --hud` and
443
+ > `mu state --mission` were removed in v0.4; use `mu state --tui`
444
+ > for the interactive surface and `mu state --json` for the full
445
+ > snapshot. `tmux display-popup -E 'mu state -w X'` keeps working
446
+ > unchanged for popup-card use.
447
+
448
+ ---
449
+
450
+ ## 5b. The TUI dashboard (interactive)
451
+
452
+ The interactive TUI is mu's flagship human surface. It is **read-only
453
+ by design** — every act-intent `y`anks the canonical `mu` command to
454
+ your clipboard so you run mutations from your shell, with one
455
+ documented escape (`t` in git-show drills runs `tuicr` in the project
456
+ root, see below).
457
+
458
+ ### Launch
461
459
 
462
- > **Migrating from `mu hud`**: drop the `hud` verb and add `--hud`
463
- > to `mu state`. `tmux display-popup -E 'mu hud -w X'` becomes
464
- > `tmux display-popup -E 'mu state --hud -w X'`. The `mu hud` verb
465
- > was removed in v0.3 see [CHANGELOG.md](../CHANGELOG.md).
460
+ ```bash
461
+ mu # bare; opens the TUI when stdout is a TTY
462
+ mu state --tui # explicit; same surface
463
+ mu state --tui -w a,b # restrict to specific workstreams
464
+ mu state --tui --all # all workstreams (default for bare `mu`)
465
+ MU_NO_TUI=1 mu # force the help path even in a TTY
466
+ mu --json # also forces help; pipe `mu state --json`
467
+ ```
468
+
469
+ Quit with `q` or `Ctrl-C`. The dashboard restores your scrollback
470
+ on exit (alt-screen).
471
+
472
+ **Initial active tab** is picked from this ladder:
473
+ `$MU_SESSION` → current tmux session name (`mu-<ws>`) → cwd inside a
474
+ registered workspace → cwd at the VCS-derived project root of any
475
+ workstream (newest activity wins ties) → tab 0.
476
+
477
+ ### Layout: 10 cards, responsive columns
478
+
479
+ The dashboard renders 10 toggleable cards with rounded borders and
480
+ section headers inset into the top border line (lazygit / btop / k9s
481
+ convention):
482
+
483
+ | Slot | Card | Toggle | Popup | Content |
484
+ | ---- | ------------- | ------ | --------- | ---------------------------------------------------- |
485
+ | 0 | Commits | `0` | `Shift+0` | Recent project-root commits (git / jj / sl) |
486
+ | 1 | Agents | `1` | `Shift+1` | Active agents + status + cli + role |
487
+ | 2 | Tracks | `2` | `Shift+2` | Parallel tracks (union-find clusters) |
488
+ | 3 | Ready (Tasks) | `3` | `Shift+3` | Ready-to-claim tasks (no open blockers) |
489
+ | 4 | Activity log | `4` | `Shift+4` | Recent `agent_logs` events |
490
+ | 5 | Workspaces | `5` | `Shift+5` | Per-agent VCS workspaces + behind/dirty status |
491
+ | 6 | In-progress | `6` | `Shift+6` | IN_PROGRESS tasks owned by agents |
492
+ | 7 | Blocked | `7` | `Shift+7` | Tasks with at least one open blocker |
493
+ | 8 | Recent | `8` | `Shift+8` | Recently CLOSED tasks |
494
+ | 9 | Doctor | `9` | `Shift+9` | Cheap health checks (schema, ghosts, orphans, …) |
495
+ | — | DAG | — | `g` | Full task DAG forest (keybind-only) |
496
+ | — | All tasks | — | `t` | Sortable / filterable list of every task |
497
+
498
+ Digit toggles HIDE / SHOW the card on the dashboard; `Shift+digit`
499
+ opens the matching fullscreen popup. **Single-popup invariant**: only
500
+ one popup is visible at a time; `Esc` / `q` returns to the dashboard
501
+ with all toggles + tick rate preserved.
502
+
503
+ **Responsive layout**: cards stack below 120 cols, then reflow into
504
+ pair-aware 2 / 3 / 4-column layouts at 120 / 180 / 240 cols. Each
505
+ visible card gets a dynamic row budget so a noisy list cannot crowd
506
+ out its siblings; overflow shows as `+N more · Shift+N` inset into
507
+ the bottom border. On very short panes, the dashboard culls
508
+ low-priority cards (Doctor → Recent → Workspaces → …) and shows
509
+ `+N cards hidden · resize taller` until the surviving cards fit.
510
+
511
+ Dashboard ordering is slot-stable: within each rendered column,
512
+ non-stream cards are ordered by toggle digit ascending; stream cards
513
+ (Commits, Activity log) sit as natural trailers, with slot 0 trailing
514
+ last.
515
+
516
+ ### Multi-workstream tabs
517
+
518
+ When the TUI is launched with N≥2 workstreams (e.g. bare `mu` on a
519
+ machine with multiple workstreams, or `mu state --tui -w a,b,c`),
520
+ a compact tab strip renders above the cards:
521
+
522
+ ```
523
+ workstreams: ▸ auth-refactor · ui-rewrite · demo (Tab / Shift-Tab)
524
+ ```
525
+
526
+ - `Tab` cycles forward, `Shift-Tab` backward (suppressed inside
527
+ popups so the same key still navigates inside popups that bind
528
+ it locally).
529
+ - The active tab name appears in the status bar's right zone next
530
+ to the tick rate.
531
+ - Cards / popups always operate on the active tab — there's no
532
+ per-row workstream column.
533
+ - For N=1 the strip renders nothing (frame is byte-identical to
534
+ single-ws TUI).
535
+ - When the workstream set is wider than the terminal, the strip
536
+ windows around the active tab and shows `‹N` / `›N` counters
537
+ for hidden workstreams.
538
+
539
+ ### Popup drills
540
+
541
+ `Enter` in any list popup drills into the focused row. Where the
542
+ row is itself an entity (a task), a further `Enter` chains into the
543
+ shared read-only task-detail leaf (notes timeline):
544
+
545
+ - **Tracks popup (`Shift+2`)**: list of tracks → `Enter` opens the
546
+ track's task list → `Enter` opens that task's notes timeline.
547
+ - **Ready / In-progress / Blocked / Recent / All-tasks**:
548
+ list of tasks → `Enter` opens notes; `y` yanks `mu task show <id>`
549
+ (or `mu task claim` / `mu task close` / `mu task tree` depending
550
+ on popup).
551
+ - **Activity log popup (`Shift+4`)**: list of events → `Enter`
552
+ drills into the full untruncated payload of the focused event;
553
+ `y` yanks `mu log --since <seq-1> -n 1 -w <ws>`.
554
+ - **Workspaces popup (`Shift+5`)**: list of workspaces → `Enter`
555
+ opens the commits-since-fork list → `Enter` on a commit opens
556
+ the inline `git show <sha> --stat -p` view; `y` yanks `git show
557
+ <sha>`; `t` launches `tuicr -r <sha>`.
558
+ - **Commits popup (`Shift+0`)**: project-root recent commits →
559
+ `Enter` opens the backend's show view; `y` yanks the show
560
+ command; `t` launches `tuicr`.
561
+ - **Doctor popup (`Shift+9`)**: list of checks → `Enter` opens
562
+ the remediation paragraph for the focused check.
563
+ - **DAG popup (`g`)**: keybind-only; renders the active workstream's
564
+ full task DAG forest (one ASCII subtree per root, diamond-collapse
565
+ marker on repeated nodes).
566
+
567
+ One `Esc` / `q` backs out per recursion level. Drills auto-refresh
568
+ in step with the dashboard tick (fast 1s for SQL-derived bodies
569
+ like notes, slow 10s for subprocess git-show / scrollback). Scroll
570
+ position is preserved across refreshes; subprocess loaders keep the
571
+ prior body visible until the new one arrives so there's no
572
+ blank-flash mid-refetch.
573
+
574
+ ### Search / filter
575
+
576
+ `/` inside any list popup enters an incremental case-insensitive
577
+ substring filter (lazygit / k9s convention):
578
+
579
+ - Every printable character appends to the query; `Backspace` pops
580
+ one.
581
+ - `Esc` cancels (clears the query); `Enter` commits (keeps the
582
+ filter applied while letting `j/k` resume normal navigation).
583
+ - Press `/` again on a committed filter to refine.
584
+ - The filter blob is per-popup: agent name + status + cli + role;
585
+ track head id + title; task name + title + status + owner; log
586
+ verb + payload + source.
587
+ - Filter state is per-popup and dies with the popup.
588
+
589
+ Task-list popups also expose **per-status toggles** (`o` / `i` / `c`
590
+ / `r` / `d` toggle OPEN / IN_PROGRESS / CLOSED / REJECTED / DEFERRED
591
+ visibility; default all-on).
592
+
593
+ The All-tasks popup adds **sort cycle** on `s`: `roi` → `recency`
594
+ → `age` → `id`.
595
+
596
+ ### Mouse
597
+
598
+ Navigation-in only:
599
+
600
+ - Double-click a dashboard card → opens its popup.
601
+ - Scroll-wheel inside a popup list / drill body → moves the focused
602
+ cursor / scrolls the body.
603
+ - Double-click a popup row → drills one level deeper.
604
+
605
+ There is intentionally **no mouse back binding** — use `Esc` / `q`
606
+ to back out predictably.
607
+
608
+ ### Yank contract (`y`) and the `tuicr` escape (`t`)
609
+
610
+ Every popup row exposes one canonical `mu` command via `y`. The
611
+ command goes to your system clipboard (pbcopy / wl-copy / xclip /
612
+ xsel / clip.exe with OSC-52 fallback). You run it in your shell.
613
+ The TUI never executes a mutation.
614
+
615
+ The one user-driven escape from the read-only pledge is **`t`**
616
+ inside any `git show` drill (Workspaces popup or Commits popup):
617
+ mu suspends its alt-screen, runs `tuicr -r <sha>` in the project
618
+ root / workspace cwd, then restores the dashboard when tuicr exits.
619
+ This is a deliberate handoff — the operator drives another TUI
620
+ tool, not mu performing the mutation.
621
+
622
+ Task-list cards and popups colour-code status cells consistently
623
+ with the static CLI tables: OPEN cyan, IN_PROGRESS yellow, CLOSED
624
+ green, REJECTED red, DEFERRED dim/gray.
625
+
626
+ ### Polling tiers
627
+
628
+ The dashboard has two refresh tiers:
629
+
630
+ - **Fast tick** (default 1s, adjustable with `+` / `-` / `=` /
631
+ `0`): SQL-only. Refreshes tasks, tracks, workspace registry rows,
632
+ and the activity log.
633
+ - **Slow tick** (10s, fixed): subprocess-backed. Refreshes
634
+ tmux-derived agent liveness / orphans, workspace dirty flags,
635
+ recent project commits, and the Doctor summary.
636
+
637
+ The last slow-tier result is merged into every fast render so cards
638
+ do not flicker through a loading state. `r` / F5 refreshes both
639
+ tiers immediately. Tab / Shift-Tab triggers an eager slow refresh
640
+ for the newly active workstream.
641
+
642
+ ### Keymap reference
643
+
644
+ | Mode | Keys | Action |
645
+ | --------- | ---------------------------- | -------------------------------------------------------------- |
646
+ | dashboard | `0`-`9` | toggle card visibility |
647
+ | dashboard | `Shift+0`-`Shift+9` | open the matching popup |
648
+ | dashboard | `g` | open DAG popup (keybind-only) |
649
+ | dashboard | `t` | open All-tasks popup (keybind-only) |
650
+ | dashboard | `Tab` / `Shift-Tab` | cycle workstream tabs (N≥2) |
651
+ | dashboard | `+` / `-` / `=` / `0` | adjust fast tick rate (faster / slower / default / pause) |
652
+ | dashboard | `r` / `F5` | force refresh both tiers |
653
+ | dashboard | `?` / `F1` | open help overlay |
654
+ | any | `q` / `Ctrl-C` | quit (or back out of popup; quits at dashboard) |
655
+ | popup | `j` / `k` | move cursor / scroll |
656
+ | popup | `g` / `G` | jump top / bottom |
657
+ | popup | `Ctrl-D` / `Ctrl-U` | half-page down / up |
658
+ | popup | `PgDn` / `PgUp` | full page |
659
+ | popup | `Enter` | drill into focused row |
660
+ | popup | `Esc` / `q` | back out one level |
661
+ | popup | `y` | yank canonical `mu` command for focused row |
662
+ | popup | `/` | enter filter mode |
663
+ | filter | (printable) / `Backspace` | edit query |
664
+ | filter | `Esc` | cancel (clear query) |
665
+ | filter | `Enter` | commit (keep filter, return to nav) |
666
+ | task popup| `o` / `i` / `c` / `r` / `d` | toggle OPEN / IN_PROGRESS / CLOSED / REJECTED / DEFERRED |
667
+ | All-tasks | `s` | cycle sort key (roi → recency → age → id) |
668
+ | git-show | `t` | launch `tuicr -r <sha>` (alt-screen handoff) |
669
+
670
+ `?` shows the same table as a scrollable overlay (j/k/Ctrl-D/U/g/G
671
+ also work inside the overlay).
672
+
673
+ ### Read-only invariant
674
+
675
+ The TUI never executes a mutation. This is not a feature of the
676
+ implementation; it's a load-bearing pledge in `docs/ROADMAP.md`. If
677
+ a future TUI gesture tempts you to call into the SDK to mutate
678
+ state, file a roadmap entry first — the yank-and-run pattern is the
679
+ intentional cost we pay to keep the TUI inspectable, scriptable, and
680
+ recoverable from any shell.
466
681
 
467
682
  ---
468
683
 
@@ -561,9 +776,9 @@ mu agent list -w auth-refactor # surfaces orphans at the bottom
561
776
  # Orphan panes (1)
562
777
  # %15 title=worker-2 cli=pi
563
778
 
564
- mu adopt %15 -w auth-refactor # adopt by pane id
565
- mu adopt worker-2 -w auth-refactor # adopt by pane title (same effect)
566
- mu adopt %15 --name investigator -w auth-refactor # adopt and rename the pane
779
+ mu agent adopt %15 -w auth-refactor # adopt by pane id
780
+ mu agent adopt worker-2 -w auth-refactor # adopt by pane title (same effect)
781
+ mu agent adopt %15 --name investigator -w auth-refactor # adopt and rename the pane
567
782
  ```
568
783
 
569
784
  The pane title becomes the agent name (`mu`'s claim protocol
@@ -630,6 +845,28 @@ MU_SEND_DELAY_MS=300 mu agent send worker-1 "..." # faster, less safe
630
845
  MU_SEND_DELAY_MS=1000 mu agent send worker-1 "..." # slow remote
631
846
  ```
632
847
 
848
+ If the target agent has a workspace that is **stale** (≥10 commits
849
+ behind main — the same red bucket shown in `mu workspace list` and the
850
+ TUI Workspaces card), `mu agent send` prints a yellow stderr warning
851
+ but still sends by default:
852
+
853
+ ```bash
854
+ WARN: worker-1 workspace is 14 commits behind main (≥10 = stale)
855
+ Next:
856
+ Refresh first : mu workspace refresh worker-1 -w auth-refactor
857
+ ```
858
+
859
+ Use `--strict-staleness` when a wrapper should refuse instead of
860
+ warning:
861
+
862
+ ```bash
863
+ mu agent send worker-1 "..." -w auth-refactor --strict-staleness
864
+ ```
865
+
866
+ Agents without workspaces are skipped (common for read-only roles).
867
+ `--json` output includes `staleness: null` or `{agentName,
868
+ workstreamName, commitsBehindMain, isStale}`.
869
+
633
870
  ---
634
871
 
635
872
  ## 9. Read what an agent did
@@ -714,6 +951,29 @@ workstream prefix, `AgentNotFoundError` (exit 3, message names the
714
951
  workstream) when the named worker doesn't live there. Nothing is
715
952
  written on either failure.
716
953
 
954
+ When `--for` targets an agent with a stale workspace (≥10 commits
955
+ behind main), `mu task claim` warns on stderr and appends a refresh
956
+ hint, but the claim still succeeds by default:
957
+
958
+ ```bash
959
+ mu task claim build -w auth-refactor --for worker-2
960
+ # stderr: WARN: worker-2 workspace is 14 commits behind main (≥10 = stale)
961
+ # Next: Refresh first : mu workspace refresh worker-2 -w auth-refactor
962
+ ```
963
+
964
+ Pass `--strict-staleness` to refuse the claim instead with typed
965
+ `TaskClaimStaleWorkspaceError` (exit 4). This is useful for scripts
966
+ that should never dispatch work onto a stale parent:
967
+
968
+ ```bash
969
+ mu task claim build -w auth-refactor --for worker-2 --strict-staleness
970
+ ```
971
+
972
+ `--json` output includes `staleness: null` or `{agentName,
973
+ workstreamName, commitsBehindMain, isStale}`. Bare in-pane claims and
974
+ `--self` claims do not run this check because they do not assign work
975
+ to a named agent via `--for`.
976
+
717
977
  ### The orchestrator pattern: `--self`
718
978
 
719
979
  Not every action comes from a registered worker pane. Often the
@@ -723,7 +983,7 @@ a worker pane just for a 5-minute job. Two patterns split here:
723
983
 
724
984
  - **Worker** — a pane mu spawned (or you adopted). Has a row in the
725
985
  `agents` table. Identity = pane title. Claims with bare
726
- `mu task claim <id>`. `tasks.owner` is set to the worker's name.
986
+ `mu task claim <id>`. `tasks.owner_id` points at the worker row.
727
987
 
728
988
  - **Actor** — anything that *causes* a state change. Includes
729
989
  workers, but also includes the orchestrator. May or may not have
@@ -737,7 +997,7 @@ If the orchestrator tries `mu task claim some-task` directly:
737
997
  conflict: claimer 'pi-mu' (pane %6441) is not a registered mu agent.
738
998
  Working directly? Pass --self to attribute via log instead.
739
999
  Dispatching to a worker? Pass --for <worker> to assign.
740
- Want full registration? Run: mu adopt %6441
1000
+ Want full registration? Run: mu agent adopt %6441
741
1001
  ```
742
1002
 
743
1003
  Three actionable next steps. Pick one based on intent:
@@ -745,16 +1005,16 @@ Three actionable next steps. Pick one based on intent:
745
1005
  ```bash
746
1006
  # Orchestrator does the work itself (most common):
747
1007
  mu task claim some-task --self --evidence "trivial 5-line fix"
748
- # -> tasks.owner stays NULL
1008
+ # -> tasks.owner_id stays NULL
749
1009
  # -> agent_logs records 'task claim some-task by pi-mu --self (anonymous)'
750
1010
  # -> mu task show surfaces it as 'owner: (self: pi-mu)'
751
1011
 
752
1012
  # Orchestrator dispatches to a worker:
753
1013
  mu task claim some-task --for worker-1
754
- # -> tasks.owner = 'worker-1'
1014
+ # -> tasks.owner_id points at worker-1
755
1015
 
756
1016
  # Orchestrator wants to BE a registered worker (rare):
757
- mu adopt %6441 -w <ws> # only if pane is in mu-<ws> session
1017
+ mu agent adopt %6441 -w <ws> # only if pane is in mu-<ws> session
758
1018
  mu task claim some-task # now works as a normal worker claim
759
1019
  ```
760
1020
 
@@ -767,7 +1027,7 @@ to pane title, or `$USER`, or `unknown`):
767
1027
  mu task claim deploy --self --actor deploy-bot --evidence "prod release"
768
1028
  ```
769
1029
 
770
- When `tasks.owner IS NULL` because of `--self`, `mu task show` looks
1030
+ When `tasks.owner_id IS NULL` because of `--self`, `mu task show` looks
771
1031
  up the most recent `task claim` event for that task and surfaces it:
772
1032
 
773
1033
  ```
@@ -790,10 +1050,39 @@ mu task note design "DECISION: JWT, 24h expiry, refresh via cookie"
790
1050
  mu task note design "FILES: src/auth.rs:45-120"
791
1051
  ```
792
1052
 
793
- Read them via the SQL escape hatch:
1053
+ Read them via the typed verb:
1054
+
1055
+ ```bash
1056
+ mu task notes design # all notes, oldest first
1057
+ mu task notes design --tail 3 # only the last 3 (alias --last)
1058
+ mu task notes design --since 2026-01-01 # only notes after an ISO 8601 cutoff
1059
+ mu task notes design --since-claim # only notes since the most recent
1060
+ # 'task claim' event for this task
1061
+ # (auto-resolved from agent_logs)
1062
+ mu task notes design --tail 5 --json # collection envelope {items, count}
1063
+ ```
1064
+
1065
+ Filters compose: `--tail` slices the last N of whatever survived
1066
+ the timestamp filter. `--since` and `--since-claim` are mutually
1067
+ exclusive (both define a cutoff) — pick one. With no filters the
1068
+ output is unchanged from prior versions (every note, oldest-first).
1069
+
1070
+ `--since-claim` is the orchestrator-friendly form: dispatch flows
1071
+ often drop a multi-screen SPEC note BEFORE claiming, then the
1072
+ worker appends progress notes AFTER the claim. `--since-claim`
1073
+ slices off the SPEC so you see only the worker's reports. If no
1074
+ claim event exists for the task, it degrades to no filter (so the
1075
+ verb stays useful on un-claimed tasks).
1076
+
1077
+ Or, for ad-hoc shape, the SQL escape hatch:
794
1078
 
795
1079
  ```bash
796
- mu sql "SELECT author, content, created_at FROM task_notes WHERE task_id='design' ORDER BY id"
1080
+ mu sql "SELECT n.author, n.content, n.created_at
1081
+ FROM task_notes n
1082
+ JOIN tasks t ON t.id = n.task_id
1083
+ JOIN workstreams w ON w.id = t.workstream_id
1084
+ WHERE t.local_id='design' AND w.name='auth-refactor'
1085
+ ORDER BY n.id"
797
1086
  ```
798
1087
 
799
1088
  Convention for note content: `KEY: value` lines. Common keys are
@@ -821,6 +1110,18 @@ task re-enters the ready set (the canonical "hand it back to the
821
1110
  pool" workflow). `--reopen` is the escape hatch for forcing `OPEN`
822
1111
  from `CLOSED` / `REJECTED` / `DEFERRED`.
823
1112
 
1113
+ When the closing actor has a per-agent workspace and that workspace
1114
+ has uncommitted edits, a successful close adds one extra `Next:` hint
1115
+ reminding the actor to commit before the next wave:
1116
+
1117
+ ```bash
1118
+ cd $(mu workspace path worker-1 -w auth-refactor) && git commit -am 'Design auth module'
1119
+ ```
1120
+
1121
+ The hint is best-effort: no workspace, a clean workspace, the `none`
1122
+ backend, or a failed VCS dirty check simply omit it. The same
1123
+ `nextSteps` entry is present in `--json` output.
1124
+
824
1125
  `--if-ready` is the umbrella-on-wave-done shape: an orchestrator
825
1126
  fires `mu task close <umbrella> --if-ready` after each wave-task
826
1127
  finishes (or unconditionally as a final action). It's a no-op while
@@ -912,21 +1213,29 @@ one verify, one workspace recycle:
912
1213
  # The dispatch-pipeline recipe: cycle until in_flight is empty.
913
1214
  in_flight=( mufeedback-v03/foo mufeedback-v03/bar roadmap-v0-3/baz )
914
1215
  while (( ${#in_flight[@]} > 0 )); do
915
- closed=$(mu task wait "${in_flight[@]}" --first --timeout 90 --json \
916
- | jq -r '.firing.qualifiedId // empty')
1216
+ res=$(mu task wait "${in_flight[@]}" --first --timeout 90 --json)
1217
+ closed=$(jq -r '.firing.qualifiedId // empty' <<<"$res")
917
1218
  if [[ -z "$closed" ]]; then break; fi # timeout or exit 6 — see below
918
1219
 
919
- # 1. Cherry-pick the worker's HEAD (the worker is named in the
920
- # nextSteps array — or use `mu task show` to look up).
921
- worker=$(mu task show "${closed##*/}" -w "${closed%%/*}" --json | jq -r .ownerName)
1220
+ worker=$(jq -r '.firing.owner // empty' <<<"$res")
922
1221
  ws=${closed%%/*}
923
- # `mu workspace commits --json` knows the workspace's parent_ref
924
- # so this is one verb instead of `cd $(mu workspace path) && git log`.
925
- sha=$(mu workspace commits "$worker" -w "$ws" --json | jq -r '.[-1].sha')
926
- git cherry-pick "$sha"
1222
+
1223
+ # 1. Inspect, then run, the sha-pinned apply hint from nextSteps.
1224
+ # When the worker has commits since its fork point, the command is
1225
+ # `git cherry-pick <sha>` (or `<first>^..<last>` for multiple
1226
+ # commits). When the worker closed without committing, nextSteps
1227
+ # says so and points at manual `git diff` / `git apply` rescue.
1228
+ apply=$(jq -r '.nextSteps[0].command' <<<"$res")
1229
+ printf 'apply hint: %s\n' "$apply"
1230
+ if [[ "$apply" == git\ cherry-pick* ]]; then
1231
+ eval "$apply"
1232
+ else
1233
+ echo "manual rescue required; inspect the worker workspace before continuing"
1234
+ break
1235
+ fi
927
1236
 
928
1237
  # 2. Verify
929
- npm run typecheck && npm run lint && npm run test && npm run build
1238
+ npm run typecheck && npm run lint && npm run test:fast && npm run test && npm run build
930
1239
 
931
1240
  # 3. Refresh the workspace for the next dispatch (rebases onto
932
1241
  # fresh main WITHOUT killing the worker's LLM context). Default
@@ -934,6 +1243,10 @@ while (( ${#in_flight[@]} > 0 )); do
934
1243
  # overrides. Refuses on dirty WC; conflicts exit 5 with a `cd`
935
1244
  # hint to resolve in-place.
936
1245
  mu workspace refresh "$worker" -w "$ws"
1246
+ # Alt: `mu workspace recreate "$worker" -w "$ws"` does free + create
1247
+ # atomically — same shortcut, but throws away the worker's local
1248
+ # changes (the lossy escape: requires --force on a dirty WC).
1249
+ # Use when you don't care about replaying the worker's commits.
937
1250
 
938
1251
  # 4. Drop $closed from in_flight, dispatch the next task, repeat.
939
1252
  in_flight=( "${in_flight[@]/$closed}" )
@@ -1030,12 +1343,14 @@ mu sql "UPDATE tasks SET status='IN_PROGRESS'
1030
1343
 
1031
1344
  # What's blocking what (open tasks only) — same data as `mu task tree`
1032
1345
  # but as a flat join when you want a wider report. task_edges is keyed
1033
- # by tasks.id, not local_id.
1346
+ # by tasks.id, not local_id; join workstreams to scope the report.
1034
1347
  mu sql "SELECT b.local_id AS blocked, t.local_id AS by_task
1035
1348
  FROM tasks b
1349
+ JOIN workstreams w ON w.id = b.workstream_id
1036
1350
  JOIN task_edges e ON e.to_task_id = b.id
1037
1351
  JOIN tasks t ON t.id = e.from_task_id
1038
- WHERE t.status != 'CLOSED' AND b.status = 'OPEN'"
1352
+ WHERE w.name='mufeedback-v03'
1353
+ AND t.status != 'CLOSED' AND b.status = 'OPEN'"
1039
1354
 
1040
1355
  # Recursive CTE: every task that transitively blocks `launch` in a
1041
1356
  # given workstream (or use `mu task tree launch --json` for the same
@@ -1078,7 +1393,32 @@ Reconciliation runs on every `mu agent list` / `mu`. Three steps:
1078
1393
  3. **Surface orphan panes** — panes in the workstream's tmux session
1079
1394
  whose `pane.command` looks like an agent CLI (pi) but
1080
1395
  that aren't in the registry. **Not** auto-adopted; mu shows them
1081
- under "Orphan panes" and tells you `mu adopt <pane-id>` to register
1396
+ under "Orphan panes" and tells you `mu agent adopt <pane-id>` to register
1397
+
1398
+ ### A worker is wedged on an unbounded tool subprocess
1399
+
1400
+ A worker ran `find / -maxdepth 6 ...` (30-60 minutes on a populated
1401
+ home directory) or a busy-wait loop. `mu agent send` queues steering
1402
+ messages until the tool returns; `tmux send-keys C-c` against the
1403
+ pane doesn't propagate (the wrapping pi/claude/codex CLI catches it
1404
+ as TUI input). The escape hatch:
1405
+
1406
+ ```bash
1407
+ mu agent kick worker-1 # SIGINT (graceful, default)
1408
+ mu agent kick worker-1 --signal SIGTERM # polite escalation
1409
+ mu agent kick worker-1 --signal SIGKILL # hammer
1410
+ ```
1411
+
1412
+ `mu agent kick` looks up the pane's TTY via `tmux display-message
1413
+ -p '#{pane_tty}'`, asks `ps -t <tty>` for the foreground process
1414
+ group (the row whose `stat` field contains `+`), and signals the
1415
+ whole pgrp directly. Refuses with `NoForegroundProcessError` when
1416
+ the foreground IS the wrapping CLI itself — use `mu agent close`
1417
+ to close the agent.
1418
+
1419
+ Prevention: don't prompt workers to run filesystem-wide `find`,
1420
+ broad `grep -r /`, or unbounded busy-wait loops. Pass paths
1421
+ explicitly or scope to `$WORKSPACE`.
1082
1422
 
1083
1423
  ### You closed your terminal session
1084
1424
 
@@ -1219,6 +1559,19 @@ mu agent close reviewer-1
1219
1559
  `mu agent close` is idempotent: `killPane` swallows "pane already gone"
1220
1560
  errors; `deleteAgent` returns false (not throws) on a missing row.
1221
1561
 
1562
+ If the agent has a workspace, behaviour depends on its state:
1563
+
1564
+ - **Clean** (no uncommitted changes AND no commits since fork) — the
1565
+ workspace is silently auto-freed alongside the close, so a
1566
+ `--workspace` spawn that did no real work doesn't make you type
1567
+ `--discard-workspace` just to clean up.
1568
+ - **Dirty** (uncommitted changes OR commits since fork) — close refuses
1569
+ with `WorkspacePreservedError` (exit 4). Two resolutions: (a) `mu
1570
+ workspace free <agent>` first (optionally with `--commit` to capture
1571
+ pending changes), then `mu agent close <agent>`; or (b) `mu agent
1572
+ close <agent> --discard-workspace` to free both in one shot (lossy:
1573
+ any work in the workspace is gone).
1574
+
1222
1575
  ### Tear down the whole workstream
1223
1576
 
1224
1577
  `mu workstream destroy` is the symmetric counterpart of `mu workstream init`: it kills the
@@ -1282,8 +1635,10 @@ failure leaves the registry intact (you can retry); if you only want
1282
1635
  the DB cleared, use `mu sql` directly:
1283
1636
 
1284
1637
  ```bash
1285
- mu sql "DELETE FROM tasks WHERE workstream='auth-refactor'" # cascades
1286
- mu sql "DELETE FROM agents WHERE workstream='auth-refactor'"
1638
+ mu sql "DELETE FROM tasks
1639
+ WHERE workstream_id=(SELECT id FROM workstreams WHERE name='auth-refactor')" # cascades
1640
+ mu sql "DELETE FROM agents
1641
+ WHERE workstream_id=(SELECT id FROM workstreams WHERE name='auth-refactor')"
1287
1642
  ```
1288
1643
 
1289
1644
  Or nuke the entire DB:
@@ -1310,7 +1665,7 @@ subdirectory per source workstream:
1310
1665
  <bucket>/
1311
1666
  README.md # bucket-level summary (every source-ws + dates + totals)
1312
1667
  INDEX.md # union of all task tables; first column = source-ws
1313
- manifest.json # bucketVersion: 2 + per-source-ws sha256 + per-task sha256
1668
+ manifest.json # bucketVersion: 2, manifest_version: 2, per-source-ws task summaries + sha256s
1314
1669
  <source-ws>/
1315
1670
  README.md # per-source-ws (counts)
1316
1671
  INDEX.md # per-source-ws (table of every task)
@@ -1320,12 +1675,21 @@ subdirectory per source workstream:
1320
1675
  Bucket exports are **additive**: `mu workstream export -w X --out
1321
1676
  <bucket>` creates the bucket scaffolding plus `X/` on first use,
1322
1677
  and a follow-up call with `-w Y --out <same-bucket>` appends a
1323
- sibling `Y/` subdirectory without touching `X/`. Re-running with
1678
+ sibling `Y/` subdirectory without touching `X/`. The top-level
1679
+ `INDEX.md` is always the union from `manifest.sources`, so a later
1680
+ single-workstream refresh does not drop sibling workstreams from the
1681
+ bucket-wide task table. Re-running with
1324
1682
  the same `-w` is sha256-idempotent: only changed task files are
1325
1683
  rewritten (mtime preserved on identical files); tasks added since
1326
1684
  the previous export get fresh files; tasks deleted from the DB
1327
1685
  STAY on disk with a `> **Deleted from DB on <ts>**` banner so you
1328
- never lose context that may already be git-blamed.
1686
+ never lose context that may already be git-blamed. `manifest_version:
1687
+ 2` stores compact task summaries (`name`/`title`/`status`/`impact`/
1688
+ `effortDays`) beside the per-file sha256s; older v1 manifests are
1689
+ accepted on re-export; mu infers the missing summaries from existing
1690
+ per-task markdown when possible, falling back to placeholder values
1691
+ only if a task file is missing or unreadable, so the bucket remains
1692
+ appendable.
1329
1693
 
1330
1694
  ```bash
1331
1695
  # One-shot dump (bucket happens to contain just one source-ws)
@@ -1399,7 +1763,7 @@ Key properties:
1399
1763
  imported in its own SQLite transaction. A failure in source A
1400
1764
  rolls back A; sibling source B is unaffected.
1401
1765
  - **Refuses silent merges.** If the target workstream already
1402
- exists in the DB with tasks, the import errors with
1766
+ exists in the DB, the import errors with
1403
1767
  `WorkstreamAlreadyExistsError`. Recourse:
1404
1768
  `--workstream <new-name>` (single-source buckets only) or
1405
1769
  destroy the existing workstream first.
@@ -1413,8 +1777,6 @@ Key properties:
1413
1777
  - **Forward edge refs are deferred.** `blocked_by` / `blocks`
1414
1778
  arrays are validated against the bucket's id-set up front, then
1415
1779
  inserted after every task in the source-ws is created.
1416
- - **Pre-0.3 layouts refuse.** Buckets without a `bucketVersion: 2`
1417
- manifest throw `ImportLegacyLayoutError` with a re-export hint.
1418
1780
  - **Partial import.** Multi-source buckets accept either a
1419
1781
  per-source-ws subdir path (auto-detected via
1420
1782
  `README.md` + `INDEX.md` + `tasks/` + a parent
@@ -1462,12 +1824,16 @@ Key properties:
1462
1824
  - **Globally-unique labels.** Archive labels live in their own
1463
1825
  namespace (separate from workstream names). Pick once, reuse
1464
1826
  across years.
1465
- - **Additive accumulation.** `mu archive add <label> -w <ws>` is
1466
- idempotent at the (archive, source workstream) granularity.
1467
- Re-running on the same workstream is a no-op; adding a new task
1468
- to the source workstream and re-running picks up only the
1469
- delta. Two different workstreams under the same label coexist
1470
- as separate `(source_workstream, original_local_id)` rows.
1827
+ - **Snapshot-only accumulation.** `mu archive add <label> -w <ws>` is
1828
+ idempotent at the (archive, source workstream) granularity and is
1829
+ designed for end-of-milestone snapshot-and-destroy flows. Re-running
1830
+ on the same workstream is task-incremental: newly-created tasks are
1831
+ added, but notes and events for already-archived tasks stay pinned
1832
+ to the original snapshot and are NOT refreshed. If you need a full
1833
+ event-stream refresh for a source workstream, remove that source (or
1834
+ delete/re-create the archive label) and add it again. Two different
1835
+ workstreams under the same label coexist as separate
1836
+ `(source_workstream, original_local_id)` rows.
1471
1837
  - **Outlives the source.** `archived_tasks.source_workstream` is
1472
1838
  TEXT (not an FK), so the source workstream can be destroyed and
1473
1839
  the archive's snapshot of it stays queryable forever.
@@ -1547,8 +1913,8 @@ mu agent spawn worker-1 --workstream demo --cli sh
1547
1913
  mu agent spawn worker-2 --workstream demo --cli sh
1548
1914
 
1549
1915
  # Assign + observe
1550
- mu sql "UPDATE tasks SET owner='worker-1', status='IN_PROGRESS' WHERE local_id='design'"
1551
- mu --workstream demo
1916
+ mu task claim design -w demo --for worker-1 --evidence "demo assignment"
1917
+ mu state -w demo
1552
1918
 
1553
1919
  # Watch live (Ctrl+b d to detach)
1554
1920
  tmux attach -t mu-demo
@@ -1574,8 +1940,8 @@ rm -f ~/.local/state/mu/mu.db
1574
1940
  shared deps.
1575
1941
 
1576
1942
  3. **Agents claim tasks via their pane title — zero config.**
1577
- `mu task claim foo` from inside `worker-1`'s pane sets `tasks.owner='worker-1'`
1578
- atomically. mu reads the pane title via
1943
+ `mu task claim foo` from inside `worker-1`'s pane sets the task's
1944
+ `owner_id` to the `worker-1` agent row atomically. mu reads the pane title via
1579
1945
  `tmux display-message -t $TMUX_PANE -p '#{pane_title}'`, set on
1580
1946
  spawn. Two agents cannot claim the same task.
1581
1947
 
@@ -1585,9 +1951,9 @@ service of those three.
1585
1951
 
1586
1952
  ---
1587
1953
 
1588
- ## What's NOT in 0.3.0 (and how to work around it)
1954
+ ## What's NOT in 0.4.0 (and how to work around it)
1589
1955
 
1590
- <a id="whats-not-in-030-and-how-to-work-around-it"></a>
1956
+ <a id="whats-not-in-040-and-how-to-work-around-it"></a>
1591
1957
 
1592
1958
  The full roadmap with promotion criteria lives in
1593
1959
  [ROADMAP.md](ROADMAP.md). The short list of gaps you might hit
@@ -1596,13 +1962,13 @@ in real use:
1596
1962
  | Want | Workaround | Status |
1597
1963
  | --------------------------------------------- | ----------------------------------------------------------------------- | ------------- |
1598
1964
  | Multi-CLI status detection (per-CLI prompts) | Braille spinner fallback (`f68838f`) covers pi/pi-meta + every TUI wrapper using standard spinner glyphs. Per-CLI permission-prompt patterns still pi-only. | partially shipped |
1599
- | Pi extension (typed tools, HUD, wakeups) | `mu state --hud` covers the HUD use-case (run via `watch` / `tmux display-popup` / `status-right`). Other extension tools deferred. | partially shipped |
1965
+ | 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 |
1600
1966
  | Markdown agent-definition discovery | Spawn accepts `--cli` and `--command` directly; no template registry | dropped |
1601
1967
  | `mu run script.ts` (JS DSL) | Use `--json` + bash + jq | rejected |
1602
1968
  | Sync to GitHub Issues / Linear / Asana | Not in scope; explicitly rejected | — |
1603
- | ~~`mu task blocked`~~ (removed; the `blocked` SQL view is the abstraction) | `mu sql "SELECT local_id, status, title FROM blocked WHERE workstream='X'"` | removed-with-recipe |
1604
- | ~~`mu task goals`~~ (removed; same shape as `blocked` — view is the abstraction) | `mu sql "SELECT local_id, status, title FROM goals WHERE workstream='X'"` | removed-with-recipe |
1605
- | ~~`mu task search <pat>`~~ (removed; case-insensitive LIKE is one SQL line) | `mu sql "SELECT local_id, status, title FROM tasks WHERE workstream='X' AND LOWER(title) LIKE '%pat%'"` (add `LEFT JOIN task_notes` for the old `--in-notes`; drop the `WHERE workstream=` clause for the old `--all`) | removed-with-recipe |
1969
+ | ~~`mu task blocked`~~ (removed; the `blocked` SQL view is the abstraction) | `mu sql "SELECT b.local_id, b.status, b.title FROM blocked b JOIN workstreams w ON w.id=b.workstream_id WHERE w.name='X'"` | removed-with-recipe |
1970
+ | ~~`mu task goals`~~ (removed; same shape as `blocked` — view is the abstraction) | `mu sql "SELECT g.local_id, g.status, g.title FROM goals g JOIN workstreams w ON w.id=g.workstream_id WHERE w.name='X'"` | removed-with-recipe |
1971
+ | ~~`mu task search <pat>`~~ (removed; case-insensitive LIKE is one SQL line) | `mu sql "SELECT t.local_id, t.status, t.title FROM tasks t JOIN workstreams w ON w.id=t.workstream_id WHERE w.name='X' AND LOWER(t.title) LIKE '%pat%'"` (add `LEFT JOIN task_notes` for the old `--in-notes`; drop the workstream join/filter for the old `--all`) | removed-with-recipe |
1606
1972
 
1607
1973
  Anything in this table that bites you in real use is a candidate
1608
1974
  for **promotion**. Criteria: proven friction in ≥2 real workflows +