@martintrojer/mu 0.3.1
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.
- package/AGENTS.md +343 -0
- package/README.md +189 -0
- package/dist/cli.js +11260 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +3130 -0
- package/dist/index.js +6312 -0
- package/dist/index.js.map +1 -0
- package/docs/ARCHITECTURE.md +481 -0
- package/docs/ROADMAP.md +542 -0
- package/docs/USAGE_GUIDE.md +1631 -0
- package/docs/VISION.md +440 -0
- package/docs/VOCABULARY.md +349 -0
- package/package.json +76 -0
- package/skills/mu/SKILL.md +523 -0
|
@@ -0,0 +1,1631 @@
|
|
|
1
|
+
# mu — Usage Guide
|
|
2
|
+
|
|
3
|
+
A practical, copy-pasteable tour of mu (current main; v0.3-track).
|
|
4
|
+
Everything below works against the built CLI. Terms are canonical
|
|
5
|
+
— see [VOCABULARY.md](VOCABULARY.md) for definitions; the complete
|
|
6
|
+
current verb list is in `## CLI — complete verb list` of
|
|
7
|
+
[skills/mu/SKILL.md](../skills/mu/SKILL.md).
|
|
8
|
+
|
|
9
|
+
> **Status:** v0.3 wave (pre-1.0). ~60 typed verbs across 8
|
|
10
|
+
> namespaces (`workstream`, `agent`, `task`, `workspace`, `log`,
|
|
11
|
+
> `snapshot`, `archive`, `me`) plus bare top-level verbs
|
|
12
|
+
> (`state`, `doctor`, `sql`, `undo`, `adopt`). Every verb accepts
|
|
13
|
+
> `--json` (one allow-listed exception, `mu agent attach`),
|
|
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
|
|
17
|
+
> snapshots auto-captured before destructive verbs +
|
|
18
|
+
> `mu undo` / `mu snapshot {list,show}`, evidence on lifecycle
|
|
19
|
+
> verbs, schema v7 (v5 surrogate INTEGER PKs + per-workstream
|
|
20
|
+
> UNIQUE on operator-facing names; v6 added the `archive_*`
|
|
21
|
+
> family additively; v7 dropped the dead `approvals` table).
|
|
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)
|
|
24
|
+
> at the bottom for the gaps that still need workarounds.
|
|
25
|
+
|
|
26
|
+
*If anything below disagrees with `mu --help`, trust `mu --help`.*
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Table of contents
|
|
31
|
+
|
|
32
|
+
1. [Setup](#1-setup)
|
|
33
|
+
2. [Get oriented (`mu doctor`)](#2-get-oriented)
|
|
34
|
+
3. [Create a workstream (`mu workstream init`)](#3-create-a-workstream)
|
|
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)
|
|
37
|
+
6. [Spawn a crew (`mu agent spawn`)](#6-spawn-a-crew)
|
|
38
|
+
7. [Watch the crew live (`tmux attach`)](#7-watch-the-crew-live)
|
|
39
|
+
8. [Send work to an agent (`mu agent send`)](#8-send-work-to-an-agent)
|
|
40
|
+
9. [Read what an agent did (`mu agent read`)](#9-read-what-an-agent-did)
|
|
41
|
+
10. [The claim protocol from inside a pane (`mu task claim`)](#10-the-claim-protocol--from-inside-an-agents-pane)
|
|
42
|
+
11. [Drop notes (durable context) (`mu task note`)](#11-drop-notes-durable-context)
|
|
43
|
+
12. [Close out a task](#12-close-out-a-task)
|
|
44
|
+
13. [The SQL escape hatch (`mu sql`)](#13-the-sql-escape-hatch-is-your-friend)
|
|
45
|
+
14. [Recovery scenarios](#14-recovery-scenarios)
|
|
46
|
+
15. [Cleanup](#15-cleanup)
|
|
47
|
+
15.5. [Archives — cross-workstream preservation](#155-archives--cross-workstream-preservation-of-task-graphs)
|
|
48
|
+
16. [One-shot demo script](#16-one-shot-demo-script)
|
|
49
|
+
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
|
+
19. [Where to go from here](#where-to-go-from-here)
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 1. Setup
|
|
56
|
+
|
|
57
|
+
From npm (the common path):
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npm install -g @martintrojer/mu
|
|
61
|
+
mu --version # → the current version
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Update later via `npm install -g @martintrojer/mu@latest`.
|
|
65
|
+
|
|
66
|
+
From a local checkout (when hacking on mu itself):
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npm install -g . # `prepare` script auto-builds; `mu` lands on $PATH
|
|
70
|
+
mu --version # → the current version (see package.json)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
To update the source-installed copy: pull from upstream, then
|
|
74
|
+
`npm install -g .` from inside the checkout. The `prepare` script
|
|
75
|
+
rebuilds before linking the new dist/.
|
|
76
|
+
|
|
77
|
+
### Install the bundled skill
|
|
78
|
+
|
|
79
|
+
Mu ships a skill at `skills/mu/SKILL.md` that teaches the LLM
|
|
80
|
+
running inside an agent pane how to use mu (the in-pane working
|
|
81
|
+
loop, the subscribe-vs-poll pattern, the verb-list reference).
|
|
82
|
+
The canonical install path is the
|
|
83
|
+
[skills CLI](https://github.com/vercel-labs/skills) — it auto-
|
|
84
|
+
detects every supported agent on your system (pi, claude-code,
|
|
85
|
+
codex, opencode, cursor, ...) and installs into the right per-agent
|
|
86
|
+
location:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
npx skills add martintrojer/mu # interactive: pick scope + agents
|
|
90
|
+
npx skills add martintrojer/mu -g -y # global, no prompts (pi: ~/.pi/agent/skills/mu/)
|
|
91
|
+
npx skills update mu # later, to refresh
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
If you installed mu from a local checkout (hacking on the skill
|
|
95
|
+
itself), point the skills CLI at the checkout instead so edits flow
|
|
96
|
+
straight through:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
npx skills add ./skills/mu # local-path source format (symlinks)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
If you'd rather not use the skills CLI, mu's skill is just a
|
|
103
|
+
directory with a `SKILL.md` — symlink or copy it into the agent's
|
|
104
|
+
skills dir directly. For pi, that's `~/.pi/agent/skills/mu/` (per-
|
|
105
|
+
user global) or `.pi/skills/mu/` (per-project). The convention
|
|
106
|
+
`~/.agents/skills/mu/` (cross-tool location) is also picked up by
|
|
107
|
+
pi and several other agents:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
# From an npm-global install
|
|
111
|
+
mkdir -p ~/.agents/skills
|
|
112
|
+
ln -sf "$(npm root -g)/@martintrojer/mu/skills/mu" ~/.agents/skills/mu
|
|
113
|
+
|
|
114
|
+
# Or from a checkout
|
|
115
|
+
ln -sf "$PWD/skills/mu" ~/.agents/skills/mu
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### For mu hackers: alias to the build output
|
|
119
|
+
|
|
120
|
+
If you're hacking on mu itself and want fastest iteration, alias
|
|
121
|
+
directly to the build output instead:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
npm install # deps only
|
|
125
|
+
npm run build # produces dist/
|
|
126
|
+
alias mu="node $PWD/dist/cli.js"
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
See [README.md § Install](../README.md#install) for the full set of
|
|
130
|
+
install patterns.
|
|
131
|
+
|
|
132
|
+
mu requires tmux ≥ 3.0. Make sure you're inside a tmux session before
|
|
133
|
+
proceeding:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
tmux # if you're not already in one
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## 2. Get oriented
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
mu doctor
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Expected output:
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
mu doctor
|
|
151
|
+
tmux: ok (tmux 3.6a)
|
|
152
|
+
$TMUX: set
|
|
153
|
+
db: ok (/Users/you/.mu/mu.db)
|
|
154
|
+
workstream: none (pass --workstream or run inside an mu-<name> tmux session)
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
The "workstream: none" line is expected — we haven't joined one yet.
|
|
158
|
+
|
|
159
|
+
Get the full command list:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
mu --help
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Every verb's `--help` is exhaustive (flags, defaults,
|
|
166
|
+
interactions). Every successful invocation also prints a dim
|
|
167
|
+
`Next:` block of suggested follow-up commands at the bottom —
|
|
168
|
+
you never have to leave the terminal to learn what to do next.
|
|
169
|
+
|
|
170
|
+
Every verb accepts `--json` for machine-readable output. Errors
|
|
171
|
+
in `--json` mode emit a `{ error, message, nextSteps, exitCode }`
|
|
172
|
+
record to stderr; the `nextSteps` array carries actionable
|
|
173
|
+
resolutions you can `eval` directly. (One verb opts out:
|
|
174
|
+
`mu agent attach`, which prints a `tmux attach` command for a
|
|
175
|
+
human to copy.)
|
|
176
|
+
|
|
177
|
+
### CLI conventions: validation errors
|
|
178
|
+
|
|
179
|
+
Every operator-error path — missing required option, unknown option,
|
|
180
|
+
unknown subcommand, missing positional, type-coercion failure, mutex
|
|
181
|
+
flags, range checks — produces a uniform surface:
|
|
182
|
+
|
|
183
|
+
- **Human path**: red `error: <msg>` on stderr, then the failing
|
|
184
|
+
subcommand's `--help` block (same text as `mu <verb> --help`),
|
|
185
|
+
then exit **2**.
|
|
186
|
+
- **`--json` path**: a structured envelope on stderr:
|
|
187
|
+
|
|
188
|
+
```json
|
|
189
|
+
{
|
|
190
|
+
"error": "UsageError",
|
|
191
|
+
"message": "--self and --for are mutually exclusive",
|
|
192
|
+
"nextSteps": [],
|
|
193
|
+
"exitCode": 2,
|
|
194
|
+
"usage": {
|
|
195
|
+
"command": "mu task claim",
|
|
196
|
+
"synopsis": "mu task claim [options] <id>",
|
|
197
|
+
"description": "...",
|
|
198
|
+
"args": [{"name": "id", "required": true, "variadic": false, "description": ""}],
|
|
199
|
+
"options": [{"flags": "--self", "description": "...", "mandatory": false, "valueRequired": false}, ...]
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
`usage.options[].mandatory` is `true` when the operator MUST pass
|
|
205
|
+
the option (`.requiredOption()` in commander terms). `valueRequired`
|
|
206
|
+
is `true` when the option's argument can't be omitted if the flag
|
|
207
|
+
IS passed (`<value>` form vs bare flag). The two are independent.
|
|
208
|
+
|
|
209
|
+
Exit 2 is the consistent code for the whole operator-error class —
|
|
210
|
+
commander mistakes and handler-thrown `UsageError`s alike. Other
|
|
211
|
+
classes keep their own codes (3 = not found, 4 = conflict, 5 =
|
|
212
|
+
substrate, 6 = reaper, 7 = stall).
|
|
213
|
+
|
|
214
|
+
### CLI conventions: `--json` collection envelope
|
|
215
|
+
|
|
216
|
+
Collection-read verbs emit a canonical `{items: T[], count: number}`
|
|
217
|
+
shape on stdout:
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
$ mu task list -w foo --json
|
|
221
|
+
{"items":[{"name":"a",...},{"name":"b",...}],"count":2}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
`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.
|
|
228
|
+
|
|
229
|
+
Applies to: `mu task list / next / owned-by / notes`,
|
|
230
|
+
`mu workstream list`, `mu workstream destroy --empty` (dry-run),
|
|
231
|
+
`mu archive list / search`, `mu workspace list / orphans / commits`,
|
|
232
|
+
`mu snapshot list`, `mu log -n N` (read).
|
|
233
|
+
|
|
234
|
+
Two deliberate carve-outs:
|
|
235
|
+
- **`mu sql --json`** keeps bare-array rows. The verb is the typed-
|
|
236
|
+
escape hatch; row shape is per-query, not part of the typed
|
|
237
|
+
contract.
|
|
238
|
+
- **`mu log --tail --json`** emits NDJSON (one JSON object per line)
|
|
239
|
+
because it's a stream, not a collection. Stream consumers want one
|
|
240
|
+
envelope per row, not a single envelope that grows forever.
|
|
241
|
+
|
|
242
|
+
Singleton verbs (`mu task show`, `mu agent show`, `mu workstream
|
|
243
|
+
init`, `mu task close`, ...) keep their existing object envelopes
|
|
244
|
+
with named top-level fields (`{task, blockers, dependents, notes}`,
|
|
245
|
+
`{taskName, ..., nextSteps}`, etc.). The `items + count` envelope is
|
|
246
|
+
for collection reads only.
|
|
247
|
+
|
|
248
|
+
### CLI conventions: multi-value flags
|
|
249
|
+
|
|
250
|
+
Multi-value flags accept either repeated invocations
|
|
251
|
+
(`--blocked-by a --blocked-by b`) or a comma-separated value
|
|
252
|
+
(`--blocked-by a,b`) or any mix (`--blocked-by a,b --blocked-by c`).
|
|
253
|
+
All three forms collapse to the same list internally. The
|
|
254
|
+
syntactic signal is `<value...>` in the help-text metavar (the
|
|
255
|
+
triple-dot); the parenthetical "repeat or comma-separate; or both"
|
|
256
|
+
reinforces it. Variadic positionals (e.g. `mu task wait a b c`) keep
|
|
257
|
+
their Unix-style space-separated shape — operands are not commas.
|
|
258
|
+
Single-valued flags (`-w`, `--by`, `--title`, ...) stay single. The
|
|
259
|
+
`--status` filter on `mu task list` and `mu task next` accepts the
|
|
260
|
+
same multi-value shape (`--status OPEN,IN_PROGRESS`,
|
|
261
|
+
`--status OPEN --status CLOSED`, or any mix) and returns the union.
|
|
262
|
+
Missing `--status` keeps today's no-filter shape (no auto-default).
|
|
263
|
+
`mu task wait --status` stays single — the verb means "wait until
|
|
264
|
+
reaches THIS status".
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## 3. Create a workstream
|
|
269
|
+
|
|
270
|
+
A **workstream** is mu's unit of organization. One workstream = one
|
|
271
|
+
tmux session = one logical project. Multiple workstreams on the same
|
|
272
|
+
machine are isolated (partitioned in the SQLite registry by
|
|
273
|
+
`session_id`); they never see each other's agents.
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
mu workstream init auth-refactor
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
```
|
|
280
|
+
Created workstream auth-refactor (tmux session mu-auth-refactor)
|
|
281
|
+
Attach with: tmux a -t mu-auth-refactor
|
|
282
|
+
Spawn agents with: mu agent spawn <name> -w auth-refactor
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Behind the scenes: `tmux new-session -d -s mu-auth-refactor` plus a
|
|
286
|
+
placeholder window so the session is non-empty. The session sits there
|
|
287
|
+
detached, waiting for agents.
|
|
288
|
+
|
|
289
|
+
To see what's already on the machine before picking a name:
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
mu workstream list
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
```
|
|
296
|
+
┌──────┬───────┬────────┬───────┬───────┬───────┐
|
|
297
|
+
│ name │ tmux │ agents │ tasks │ edges │ notes │
|
|
298
|
+
├──────┼───────┼────────┼───────┼───────┼───────┤
|
|
299
|
+
│ r6a │ alive │ 0 │ 2 │ 1 │ 1 │
|
|
300
|
+
│ r6b │ — │ 0 │ 0 │ 0 │ 0 │
|
|
301
|
+
└──────┴───────┴────────┴───────┴───────┴───────┘
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
The list is the **union** of three sources: distinct
|
|
305
|
+
`agents.workstream`, distinct `tasks.workstream`, and tmux sessions
|
|
306
|
+
matching `mu-*`. So a freshly-`init`'d workstream with no tasks/agents
|
|
307
|
+
still shows up (via its tmux session), and a workstream whose tmux
|
|
308
|
+
session was killed externally still shows up (via its surviving DB
|
|
309
|
+
rows) so you can `mu workstream destroy` to clean up properly.
|
|
310
|
+
|
|
311
|
+
### How mu finds your active workstream
|
|
312
|
+
|
|
313
|
+
Every command after `init` needs to know which workstream you're in.
|
|
314
|
+
Resolution order, first match wins:
|
|
315
|
+
|
|
316
|
+
1. **`--workstream <name>` flag** explicitly
|
|
317
|
+
2. **`MU_SESSION` env var** (`export MU_SESSION=auth-refactor`)
|
|
318
|
+
3. **Current tmux session name** (mu reads `tmux display-message -p '#S'` and strips the `mu-` prefix)
|
|
319
|
+
4. Error if none of the above
|
|
320
|
+
|
|
321
|
+
The third option is the most ergonomic. Once you `tmux a -t
|
|
322
|
+
mu-auth-refactor`, every command "just works" without flags.
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## 4. Plan some work as a DAG
|
|
327
|
+
|
|
328
|
+
Tasks have **mandatory** `impact` (1–100) and `effort-days` (>0).
|
|
329
|
+
Edges are blocks-relationships, modelled as **`--blocked-by`** on `mu
|
|
330
|
+
task add` (and `mu task reparent`): `--blocked-by design` means "this
|
|
331
|
+
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.
|
|
334
|
+
|
|
335
|
+
```bash
|
|
336
|
+
# --workstream can be omitted if you're inside the workstream's tmux
|
|
337
|
+
# session or have $MU_SESSION exported.
|
|
338
|
+
mu task add design \
|
|
339
|
+
--workstream auth-refactor \
|
|
340
|
+
--title "Design auth module" \
|
|
341
|
+
--impact 80 --effort-days 2
|
|
342
|
+
|
|
343
|
+
mu task add build \
|
|
344
|
+
--workstream auth-refactor \
|
|
345
|
+
--title "Build auth module" \
|
|
346
|
+
--impact 80 --effort-days 5 \
|
|
347
|
+
--blocked-by design
|
|
348
|
+
|
|
349
|
+
mu task add review \
|
|
350
|
+
--workstream auth-refactor \
|
|
351
|
+
--title "Review auth module" \
|
|
352
|
+
--impact 60 --effort-days 1 \
|
|
353
|
+
--blocked-by build
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
Each task validates its id (`/^[a-z][a-z0-9_-]{0,63}$/`) and rejects
|
|
357
|
+
duplicates. If you tried `mu task add x --blocked-by y` while `y`
|
|
358
|
+
already transitively depended on `x`, mu would refuse with a `CycleError`.
|
|
359
|
+
|
|
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
|
|
363
|
+
workstream, mu refuses with a `CrossWorkstreamEdgeError`.
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
## 5. See the graph (mission control)
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
mu --workstream auth-refactor
|
|
371
|
+
# or, if your tmux session is mu-auth-refactor:
|
|
372
|
+
mu
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
```
|
|
376
|
+
mu-auth-refactor
|
|
377
|
+
|
|
378
|
+
Agents (0)
|
|
379
|
+
(no agents)
|
|
380
|
+
|
|
381
|
+
Tracks (1)
|
|
382
|
+
Track 1: review (3 tasks, 1 ready, track)
|
|
383
|
+
|
|
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):
|
|
452
|
+
|
|
453
|
+
- `mu state --json` (single-ws): flat `{ workstreamName, agents,
|
|
454
|
+
orphans, tracks, ready, blocked, inProgress, recentClosed,
|
|
455
|
+
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`.
|
|
461
|
+
|
|
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).
|
|
466
|
+
|
|
467
|
+
---
|
|
468
|
+
|
|
469
|
+
## 6. Spawn a crew
|
|
470
|
+
|
|
471
|
+
For a real demo with status detection, spawn pi agents:
|
|
472
|
+
|
|
473
|
+
```bash
|
|
474
|
+
mu agent spawn worker-1 --workstream auth-refactor # default --cli is pi
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
To play around without needing pi installed, use `--cli sh`:
|
|
478
|
+
|
|
479
|
+
```bash
|
|
480
|
+
mu agent spawn worker-1 --workstream auth-refactor --cli sh
|
|
481
|
+
mu agent spawn worker-2 --workstream auth-refactor --cli sh
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
```
|
|
485
|
+
Spawned worker-1 (sh) in window worker-1 of mu-auth-refactor, pane %15
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
What just happened:
|
|
489
|
+
|
|
490
|
+
1. mu checked the agents table — no `worker-1` yet, OK to proceed
|
|
491
|
+
2. mu created a tmux window named `worker-1` in the `mu-auth-refactor`
|
|
492
|
+
session
|
|
493
|
+
3. mu set the pane title to `worker-1` via `tmux select-pane -T worker-1`
|
|
494
|
+
— **this is the claim protocol identity**
|
|
495
|
+
4. mu inserted a row in `agents` with `pane_id=%15`, `status=spawning`
|
|
496
|
+
|
|
497
|
+
If the DB insert fails after the pane was created, mu kills the pane
|
|
498
|
+
to avoid leaking. If the same name was already taken, mu rejects
|
|
499
|
+
**before** calling tmux.
|
|
500
|
+
|
|
501
|
+
### Naming convention (lint, not a rule)
|
|
502
|
+
|
|
503
|
+
mu accepts any name matching `/^[a-z][a-z0-9_-]{0,31}$/`, but the
|
|
504
|
+
recommended shape is **`<role>-<n>`** — a lowercase role plus the
|
|
505
|
+
smallest unused integer suffix (e.g. `worker-1`, `reviewer-2`,
|
|
506
|
+
`scout-12`). Names that diverge (`worker-tests`, `alice`, `db-leader`,
|
|
507
|
+
`x-y-1`) still spawn successfully but trigger a one-line stderr hint:
|
|
508
|
+
|
|
509
|
+
```
|
|
510
|
+
hint: agent name "worker-tests" does not match the smallest-unused-suffix
|
|
511
|
+
convention (<role>-<n>; e.g. worker-1, reviewer-2). Accepted; consider
|
|
512
|
+
renaming if you spawn additional workers.
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
The hint is suppressed under `--json` so script callers stay clean.
|
|
516
|
+
|
|
517
|
+
### Multiple agents in one window (split panes)
|
|
518
|
+
|
|
519
|
+
Give them a shared `--tab`:
|
|
520
|
+
|
|
521
|
+
```bash
|
|
522
|
+
mu agent spawn reviewer-1 --workstream auth-refactor --cli sh --tab Review --role read-only
|
|
523
|
+
mu agent spawn audit --workstream auth-refactor --cli sh --tab Review
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
The `Review` window holds whichever agents share `--tab Review`.
|
|
527
|
+
|
|
528
|
+
### Spawn options
|
|
529
|
+
|
|
530
|
+
| Flag | Meaning |
|
|
531
|
+
| ---------------------------- | ------------------------------------------------------- |
|
|
532
|
+
| `--cli <name>` | Logical CLI family (effectively always `pi`; the flag exists as a key for `MU_<UPPER_CLI>_COMMAND` resolution) |
|
|
533
|
+
| `--command <cmd>` | Executable launched in the pane. Defaults to `$MU_<UPPER_CLI>_COMMAND` (e.g. `MU_PI_COMMAND=pi-alt`) and finally to the `--cli` value |
|
|
534
|
+
| `--tab <name>` | Group with other agents under this window name |
|
|
535
|
+
| `--role <full-access\|read-only>` | Capability flag; stored but not yet enforced |
|
|
536
|
+
| `--cwd <path>` | Initial working directory for the pane |
|
|
537
|
+
| `-w, --workstream <name>` | Required if not auto-detectable |
|
|
538
|
+
|
|
539
|
+
On systems where the local `pi` binary is installed under a different
|
|
540
|
+
name, set `MU_PI_COMMAND=<name>` once in your shell rc and every
|
|
541
|
+
`mu agent spawn --cli pi` will exec the right binary; reconcile
|
|
542
|
+
also treats that binary's panes as agent-worthy when surfacing orphans.
|
|
543
|
+
|
|
544
|
+
`MU_PI_COMMAND` (and `--command`) accept a multi-word string — tmux
|
|
545
|
+
exec's it via a shell, so embedded flags survive intact. If your pi
|
|
546
|
+
build needs extra flags (e.g. to skip a single-instance lock), set
|
|
547
|
+
`MU_PI_COMMAND="pi-alt --some-flag"` and every spawn picks them up.
|
|
548
|
+
Same pattern for `MU_CLAUDE_COMMAND` / `MU_CODEX_COMMAND` once those
|
|
549
|
+
land.
|
|
550
|
+
|
|
551
|
+
### Adopt an existing tmux pane
|
|
552
|
+
|
|
553
|
+
Not every agent gets born via `mu agent spawn`. Sometimes you
|
|
554
|
+
launched a `pi` (or `claude`, or `codex`) by hand for a one-off
|
|
555
|
+
task, decided mid-flow it deserves to be in the graph, and now
|
|
556
|
+
want to drive it via `mu`. Or `mu` crashed mid-spawn and left an
|
|
557
|
+
orphan pane with no DB row. Either way:
|
|
558
|
+
|
|
559
|
+
```bash
|
|
560
|
+
mu agent list -w auth-refactor # surfaces orphans at the bottom
|
|
561
|
+
# Orphan panes (1)
|
|
562
|
+
# %15 title=worker-2 cli=pi
|
|
563
|
+
|
|
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
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
The pane title becomes the agent name (`mu`'s claim protocol
|
|
570
|
+
invariant), so adopting a pane titled `worker-2` registers it as
|
|
571
|
+
agent `worker-2` with no further config. Use `--name` when the
|
|
572
|
+
pane's current title isn't a valid agent name (or when you want a
|
|
573
|
+
different name).
|
|
574
|
+
|
|
575
|
+
Adopt is **idempotent**: running it twice on the same pane is a
|
|
576
|
+
no-op. It's also **scope-aware**: the pane must be in the
|
|
577
|
+
`mu-<workstream>` tmux session, otherwise the adopt is rejected
|
|
578
|
+
(no silent cross-session moves).
|
|
579
|
+
|
|
580
|
+
---
|
|
581
|
+
|
|
582
|
+
## 7. Watch the crew live
|
|
583
|
+
|
|
584
|
+
The killer property: you can attach the workstream's tmux session and
|
|
585
|
+
see everything.
|
|
586
|
+
|
|
587
|
+
```bash
|
|
588
|
+
tmux attach -t mu-auth-refactor
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
You see one tmux window per agent (or a window with split panes if
|
|
592
|
+
they share a `--tab`).
|
|
593
|
+
|
|
594
|
+
| Tmux key | What it does |
|
|
595
|
+
| -------------- | --------------------------------------------- |
|
|
596
|
+
| `Ctrl+b w` | Pick a window (interactive list) |
|
|
597
|
+
| `Ctrl+b n`/`p` | Cycle next/previous window |
|
|
598
|
+
| `Ctrl+b d` | Detach from the session (mu doesn't care) |
|
|
599
|
+
|
|
600
|
+
mu does not require you to be attached. Detach freely.
|
|
601
|
+
|
|
602
|
+
---
|
|
603
|
+
|
|
604
|
+
## 8. Send work to an agent
|
|
605
|
+
|
|
606
|
+
From any shell with mu on `$PATH`:
|
|
607
|
+
|
|
608
|
+
```bash
|
|
609
|
+
mu agent send worker-1 "echo hello from outside"
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
mu uses the **canonical bracketed-paste protocol** internally:
|
|
613
|
+
|
|
614
|
+
1. `tmux copy-mode -q` (silent if not in copy mode)
|
|
615
|
+
2. `tmux set-buffer` (loads text into a uniquely-named buffer)
|
|
616
|
+
3. `tmux paste-buffer -p -d -r` (`-p` = bracketed paste, `-d` = delete
|
|
617
|
+
buffer after paste, `-r` = preserve LF)
|
|
618
|
+
4. wait `MU_SEND_DELAY_MS` ms (default 500)
|
|
619
|
+
5. `tmux send-keys Enter`
|
|
620
|
+
|
|
621
|
+
This means special characters (`/`, `?`, `!`, `$`, `&&`, `|`, `*`,
|
|
622
|
+
…) arrive at the agent's CLI **literally** — not interpreted by tmux's
|
|
623
|
+
copy-mode or by the agent's TUI shortcuts. Naive `tmux send-keys`
|
|
624
|
+
would let the agent's TUI hijack `/` for "search forward" and similar.
|
|
625
|
+
|
|
626
|
+
The send delay is configurable per call:
|
|
627
|
+
|
|
628
|
+
```bash
|
|
629
|
+
MU_SEND_DELAY_MS=300 mu agent send worker-1 "..." # faster, less safe
|
|
630
|
+
MU_SEND_DELAY_MS=1000 mu agent send worker-1 "..." # slow remote
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
---
|
|
634
|
+
|
|
635
|
+
## 9. Read what an agent did
|
|
636
|
+
|
|
637
|
+
```bash
|
|
638
|
+
mu agent read worker-1 # full scrollback
|
|
639
|
+
mu agent read worker-1 -n 50 # last 50 lines
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
Both go through `tmux capture-pane`. No state change.
|
|
643
|
+
|
|
644
|
+
---
|
|
645
|
+
|
|
646
|
+
## 10. The claim protocol — from inside an agent's pane
|
|
647
|
+
|
|
648
|
+
This is where mu's design really shines. An agent (the LLM running in
|
|
649
|
+
a pane) can run `mu task claim foo` **with no agent name argument** — mu
|
|
650
|
+
figures out it's "worker-1" from the pane title.
|
|
651
|
+
|
|
652
|
+
To try this manually, attach to the workstream and switch to worker-1's
|
|
653
|
+
window:
|
|
654
|
+
|
|
655
|
+
```bash
|
|
656
|
+
tmux attach -t mu-auth-refactor # if not attached
|
|
657
|
+
# Ctrl+b w, pick "worker-1" interactively
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
Then in worker-1's pane (a real shell, since `--cli sh`):
|
|
661
|
+
|
|
662
|
+
```bash
|
|
663
|
+
mu task claim design
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
```
|
|
667
|
+
Claimed design for worker-1 (OPEN → IN_PROGRESS)
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
What happened behind the scenes:
|
|
671
|
+
|
|
672
|
+
1. mu reads `$TMUX_PANE` (set by tmux for every pane in the session)
|
|
673
|
+
to get the pane id (e.g. `%15`)
|
|
674
|
+
2. Calls `tmux display-message -t %15 -p '#{pane_title}'` → returns
|
|
675
|
+
`worker-1`
|
|
676
|
+
3. Atomic SQLite transaction:
|
|
677
|
+
```sql
|
|
678
|
+
UPDATE tasks
|
|
679
|
+
SET owner = 'worker-1',
|
|
680
|
+
status = CASE WHEN status = 'OPEN' THEN 'IN_PROGRESS' ELSE status END,
|
|
681
|
+
updated_at = ?
|
|
682
|
+
WHERE local_id = 'design'
|
|
683
|
+
AND (owner IS NULL OR owner = 'worker-1')
|
|
684
|
+
```
|
|
685
|
+
4. If 0 rows changed, mu distinguishes "task doesn't exist" from
|
|
686
|
+
"already owned by someone else" and throws the right typed error
|
|
687
|
+
|
|
688
|
+
Two agents trying to claim the same task → second one fails with
|
|
689
|
+
"already owned by worker-1." Re-claim by the same agent is idempotent.
|
|
690
|
+
|
|
691
|
+
You can also claim explicitly from outside any pane:
|
|
692
|
+
|
|
693
|
+
```bash
|
|
694
|
+
mu task claim build --for worker-2
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
`--for` accepts EITHER a bare worker name (`worker-2`, resolved in
|
|
698
|
+
the task's workstream — today's behaviour) OR a qualified ref
|
|
699
|
+
`<workstream>/<name>` for **cross-workstream dispatch**
|
|
700
|
+
(`task_claim_for_cross_workstream`):
|
|
701
|
+
|
|
702
|
+
```bash
|
|
703
|
+
# Task lives in mufeedback-v03; worker-1 lives in roadmap-v0-3.
|
|
704
|
+
# Per-workstream worker pools mean the orchestrator routinely has a
|
|
705
|
+
# free worker in one workstream and a queued task in another.
|
|
706
|
+
mu task claim some-task -w mufeedback-v03 --for roadmap-v0-3/worker-1
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
The agent stays in its own workstream — only `tasks.owner_id`
|
|
710
|
+
points across the boundary (it's an INTEGER FK to `agents.id`,
|
|
711
|
+
workstream-agnostic at the schema level). A bad qualifier surfaces
|
|
712
|
+
typed errors: `WorkstreamNotFoundError` (exit 3) on a missing
|
|
713
|
+
workstream prefix, `AgentNotFoundError` (exit 3, message names the
|
|
714
|
+
workstream) when the named worker doesn't live there. Nothing is
|
|
715
|
+
written on either failure.
|
|
716
|
+
|
|
717
|
+
### The orchestrator pattern: `--self`
|
|
718
|
+
|
|
719
|
+
Not every action comes from a registered worker pane. Often the
|
|
720
|
+
*orchestrator* (a top-level pi session, a human at a shell, a
|
|
721
|
+
deploy script) wants to do small work directly without spinning up
|
|
722
|
+
a worker pane just for a 5-minute job. Two patterns split here:
|
|
723
|
+
|
|
724
|
+
- **Worker** — a pane mu spawned (or you adopted). Has a row in the
|
|
725
|
+
`agents` table. Identity = pane title. Claims with bare
|
|
726
|
+
`mu task claim <id>`. `tasks.owner` is set to the worker's name.
|
|
727
|
+
|
|
728
|
+
- **Actor** — anything that *causes* a state change. Includes
|
|
729
|
+
workers, but also includes the orchestrator. May or may not have
|
|
730
|
+
a row in `agents`. The actor is *always* recorded in the
|
|
731
|
+
auto-emitted `agent_logs` event for every state change
|
|
732
|
+
(the `source` field).
|
|
733
|
+
|
|
734
|
+
If the orchestrator tries `mu task claim some-task` directly:
|
|
735
|
+
|
|
736
|
+
```
|
|
737
|
+
conflict: claimer 'pi-mu' (pane %6441) is not a registered mu agent.
|
|
738
|
+
Working directly? Pass --self to attribute via log instead.
|
|
739
|
+
Dispatching to a worker? Pass --for <worker> to assign.
|
|
740
|
+
Want full registration? Run: mu adopt %6441
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
Three actionable next steps. Pick one based on intent:
|
|
744
|
+
|
|
745
|
+
```bash
|
|
746
|
+
# Orchestrator does the work itself (most common):
|
|
747
|
+
mu task claim some-task --self --evidence "trivial 5-line fix"
|
|
748
|
+
# -> tasks.owner stays NULL
|
|
749
|
+
# -> agent_logs records 'task claim some-task by pi-mu --self (anonymous)'
|
|
750
|
+
# -> mu task show surfaces it as 'owner: (self: pi-mu)'
|
|
751
|
+
|
|
752
|
+
# Orchestrator dispatches to a worker:
|
|
753
|
+
mu task claim some-task --for worker-1
|
|
754
|
+
# -> tasks.owner = 'worker-1'
|
|
755
|
+
|
|
756
|
+
# Orchestrator wants to BE a registered worker (rare):
|
|
757
|
+
mu adopt %6441 -w <ws> # only if pane is in mu-<ws> session
|
|
758
|
+
mu task claim some-task # now works as a normal worker claim
|
|
759
|
+
```
|
|
760
|
+
|
|
761
|
+
`--self` is **only** for unregistered actors. Workers continue to
|
|
762
|
+
claim with bare `mu task claim` — nothing changes for them. The
|
|
763
|
+
`--actor <name>` flag overrides the auto-detected actor name (defaults
|
|
764
|
+
to pane title, or `$USER`, or `unknown`):
|
|
765
|
+
|
|
766
|
+
```bash
|
|
767
|
+
mu task claim deploy --self --actor deploy-bot --evidence "prod release"
|
|
768
|
+
```
|
|
769
|
+
|
|
770
|
+
When `tasks.owner IS NULL` because of `--self`, `mu task show` looks
|
|
771
|
+
up the most recent `task claim` event for that task and surfaces it:
|
|
772
|
+
|
|
773
|
+
```
|
|
774
|
+
owner : (self: pi-mu)
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
So provenance is preserved — it just lives in `agent_logs` rather
|
|
778
|
+
than being conflated with the FK that points at registered workers.
|
|
779
|
+
|
|
780
|
+
---
|
|
781
|
+
|
|
782
|
+
## 11. Drop notes (durable context)
|
|
783
|
+
|
|
784
|
+
Notes are append-only. They survive across sessions and across agent
|
|
785
|
+
restarts. This is the cure for LLM context loss: when the next agent
|
|
786
|
+
picks up a task, they can read the full history.
|
|
787
|
+
|
|
788
|
+
```bash
|
|
789
|
+
mu task note design "DECISION: JWT, 24h expiry, refresh via cookie"
|
|
790
|
+
mu task note design "FILES: src/auth.rs:45-120"
|
|
791
|
+
```
|
|
792
|
+
|
|
793
|
+
Read them via the SQL escape hatch:
|
|
794
|
+
|
|
795
|
+
```bash
|
|
796
|
+
mu sql "SELECT author, content, created_at FROM task_notes WHERE task_id='design' ORDER BY id"
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
Convention for note content: `KEY: value` lines. Common keys are
|
|
800
|
+
`FILES`, `DECISION`, `VERIFIED`, `BLOCKED`, `NEXT`. Mu doesn't
|
|
801
|
+
enforce these — they're for the agents reading them.
|
|
802
|
+
|
|
803
|
+
---
|
|
804
|
+
|
|
805
|
+
## 12. Close out a task
|
|
806
|
+
|
|
807
|
+
```bash
|
|
808
|
+
mu task close design # OPEN/IN_PROGRESS → CLOSED
|
|
809
|
+
mu task close umbrella --if-ready # close ONLY if every blocker
|
|
810
|
+
# is terminal (CLOSED / REJECTED
|
|
811
|
+
# / DEFERRED); else no-op + list
|
|
812
|
+
# the still-blocking ids
|
|
813
|
+
mu task open design # CLOSED → OPEN (e.g. closed by mistake)
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
Both are idempotent (closing an already-CLOSED task prints a no-op
|
|
817
|
+
message and exits 0). Owner is intentionally left intact — use
|
|
818
|
+
`mu task release <id>` to clear ownership when an agent bails on a
|
|
819
|
+
task mid-flight. `IN_PROGRESS` auto-flips back to `OPEN` so the
|
|
820
|
+
task re-enters the ready set (the canonical "hand it back to the
|
|
821
|
+
pool" workflow). `--reopen` is the escape hatch for forcing `OPEN`
|
|
822
|
+
from `CLOSED` / `REJECTED` / `DEFERRED`.
|
|
823
|
+
|
|
824
|
+
`--if-ready` is the umbrella-on-wave-done shape: an orchestrator
|
|
825
|
+
fires `mu task close <umbrella> --if-ready` after each wave-task
|
|
826
|
+
finishes (or unconditionally as a final action). It's a no-op while
|
|
827
|
+
any blocker is still OPEN / IN_PROGRESS, and prints the still-
|
|
828
|
+
blocking ids + a `mu task wait` Next: hint so the operator can pick
|
|
829
|
+
back up. Once the last blocker reaches a terminal status (CLOSED /
|
|
830
|
+
REJECTED / DEFERRED), the same command closes the umbrella.
|
|
831
|
+
JSON shape on the no-op path: `{ skipped: "not_ready", changed:
|
|
832
|
+
false, blockingIds: ["..."], ... }`. Exit code 0 either way — the
|
|
833
|
+
no-op is success.
|
|
834
|
+
|
|
835
|
+
```bash
|
|
836
|
+
mu task release design # clear owner; IN_PROGRESS → OPEN
|
|
837
|
+
# (CLOSED / REJECTED / DEFERRED preserved)
|
|
838
|
+
mu task release design --reopen # clear owner AND force status to OPEN
|
|
839
|
+
# (un-close + release in one verb)
|
|
840
|
+
```
|
|
841
|
+
|
|
842
|
+
Now run `mu` again — `build` has become ready (its only blocker
|
|
843
|
+
`design` is now closed):
|
|
844
|
+
|
|
845
|
+
```
|
|
846
|
+
Ready (1)
|
|
847
|
+
┌───────┬──────────────────┬────────┬────────┬──────┬───────┐
|
|
848
|
+
│ id │ title │ impact │ effort │ ROI │ owner │
|
|
849
|
+
├───────┼──────────────────┼────────┼────────┼──────┼───────┤
|
|
850
|
+
│ build │ Build auth module│ 80 │ 5 │ 16.0 │ │
|
|
851
|
+
└───────┴──────────────────┴────────┴────────┴──────┴───────┘
|
|
852
|
+
```
|
|
853
|
+
|
|
854
|
+
---
|
|
855
|
+
|
|
856
|
+
## 13. The SQL escape hatch is your friend
|
|
857
|
+
|
|
858
|
+
Most routine operations have a typed verb — prefer those (and prefer
|
|
859
|
+
`--json` for scripting). `mu sql` is for the rare cases the typed
|
|
860
|
+
verbs don't cover: ad-hoc joins, manual recovery, exploring schema.
|
|
861
|
+
The schema is 8 core tables (`workstreams`, `agents`, `tasks`,
|
|
862
|
+
`task_edges`, `task_notes`, `agent_logs`, `vcs_workspaces`,
|
|
863
|
+
`snapshots`), 5 archive tables (`archives`, `archived_tasks`,
|
|
864
|
+
`archived_edges`, `archived_notes`, `archived_events`), 1 meta table
|
|
865
|
+
(`schema_version`), plus three views (`ready`, `blocked`, `goals`):
|
|
866
|
+
|
|
867
|
+
```bash
|
|
868
|
+
mu sql "SELECT name FROM sqlite_master WHERE type IN ('table','view') ORDER BY type, name"
|
|
869
|
+
```
|
|
870
|
+
|
|
871
|
+
### Prefer the typed verb where one exists
|
|
872
|
+
|
|
873
|
+
| Want | Typed verb |
|
|
874
|
+
| ----------------------------------------------------- | --------------------------------------- |
|
|
875
|
+
| Tasks owned by an agent (current workstream) | `mu task owned-by <agent> [--json]` |
|
|
876
|
+
| Tasks owned by ANY same-named worker (all workstreams)| `mu task owned-by <agent> --all [--json]`|
|
|
877
|
+
| Highest-ROI ready task | `mu task next [-w] [-n K] [--json]` |
|
|
878
|
+
| What did I touch most recently / what's stale | `mu task list --sort recency` / `--sort age` |
|
|
879
|
+
| Visualise what blocks what | `mu task tree <id> [--json]` |
|
|
880
|
+
| Show row + edges + notes | `mu task show <id> [--json]` |
|
|
881
|
+
| Delete + cascade edges/notes (two-phase: bare = dry-run; `--yes` commits) | `mu task delete <id>` / `mu task delete <id> --yes` |
|
|
882
|
+
| Add / remove a single edge | `mu task block` / `mu task unblock` |
|
|
883
|
+
| Replace all blockers atomically | `mu task reparent <id> --blocked-by ...` |
|
|
884
|
+
| Modify scalar fields | `mu task update <id> [--title ...]` |
|
|
885
|
+
| Read the activity log / subscribe to events | `mu log [--tail] [--kind event]` |
|
|
886
|
+
| Block until tasks reach a status (orchestrator wait) | `mu task wait <ref> [<ref>...] [--first|--any] [--timeout S]` |
|
|
887
|
+
|
|
888
|
+
### `mu task wait`: cross-workstream refs + `--first` returns WHICH
|
|
889
|
+
|
|
890
|
+
Each `<ref>` is either a bare task name (resolves via `-w` /
|
|
891
|
+
`$MU_SESSION` / tmux session) or a qualified `<workstream>/<name>`
|
|
892
|
+
ref. When all refs are qualified, `-w` is not required; mixed lists
|
|
893
|
+
are allowed (bare uses `-w`, qualified uses its prefix).
|
|
894
|
+
|
|
895
|
+
```bash
|
|
896
|
+
# All-bare with -w — today's classic shape, unchanged
|
|
897
|
+
mu task wait build_a build_b -w mufeedback-v03 --timeout 1200
|
|
898
|
+
|
|
899
|
+
# All-qualified — cross-workstream wait, no -w needed
|
|
900
|
+
mu task wait roadmap-v0-3/archive_phase2 mufeedback-v03/cli_audit --timeout 1800
|
|
901
|
+
|
|
902
|
+
# Mixed — bare uses -w; qualified ignores it
|
|
903
|
+
mu task wait cli_audit roadmap-v0-3/archive_phase2 -w mufeedback-v03
|
|
904
|
+
```
|
|
905
|
+
|
|
906
|
+
`--first` is an alias for `--any` that ALSO prints the firing ref's
|
|
907
|
+
qualified id to stdout (and adds a `firing` field to `--json`). Use
|
|
908
|
+
it to drive a single-shot dispatch loop — one wait, one cherry-pick,
|
|
909
|
+
one verify, one workspace recycle:
|
|
910
|
+
|
|
911
|
+
```bash
|
|
912
|
+
# The dispatch-pipeline recipe: cycle until in_flight is empty.
|
|
913
|
+
in_flight=( mufeedback-v03/foo mufeedback-v03/bar roadmap-v0-3/baz )
|
|
914
|
+
while (( ${#in_flight[@]} > 0 )); do
|
|
915
|
+
closed=$(mu task wait "${in_flight[@]}" --first --timeout 90 --json \
|
|
916
|
+
| jq -r '.firing.qualifiedId // empty')
|
|
917
|
+
if [[ -z "$closed" ]]; then break; fi # timeout or exit 6 — see below
|
|
918
|
+
|
|
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)
|
|
922
|
+
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"
|
|
927
|
+
|
|
928
|
+
# 2. Verify
|
|
929
|
+
npm run typecheck && npm run lint && npm run test && npm run build
|
|
930
|
+
|
|
931
|
+
# 3. Refresh the workspace for the next dispatch (rebases onto
|
|
932
|
+
# fresh main WITHOUT killing the worker's LLM context). Default
|
|
933
|
+
# base = origin/HEAD (git) / trunk() (jj/sl); --from <ref>
|
|
934
|
+
# overrides. Refuses on dirty WC; conflicts exit 5 with a `cd`
|
|
935
|
+
# hint to resolve in-place.
|
|
936
|
+
mu workspace refresh "$worker" -w "$ws"
|
|
937
|
+
|
|
938
|
+
# 4. Drop $closed from in_flight, dispatch the next task, repeat.
|
|
939
|
+
in_flight=( "${in_flight[@]/$closed}" )
|
|
940
|
+
done
|
|
941
|
+
```
|
|
942
|
+
|
|
943
|
+
The `--json` shape on success is `{ firing, all, timedOut, nextSteps,
|
|
944
|
+
... }`:
|
|
945
|
+
|
|
946
|
+
* `firing` — `{ workstreamName, name, qualifiedId, status, owner }`
|
|
947
|
+
on `--first` / `--any` success; `null` on `--all` success or on
|
|
948
|
+
timeout.
|
|
949
|
+
* `all` — array of refs that REACHED the target (with
|
|
950
|
+
`qualifiedId` + `reachedAt`).
|
|
951
|
+
* `timedOut` — array of refs that did NOT reach the target. Empty on
|
|
952
|
+
clean success; populated on partial-progress timeout.
|
|
953
|
+
* `nextSteps`— the same hint list printed to stdout (cherry-pick,
|
|
954
|
+
verify, free + recreate, or `mu task show` for unmet refs).
|
|
955
|
+
|
|
956
|
+
### Wait exit codes (`mu task wait`)
|
|
957
|
+
|
|
958
|
+
`mu task wait` polls the watched tasks every second (cheap indexed
|
|
959
|
+
SELECT + a per-poll reconcile of every workstream in the wait set)
|
|
960
|
+
and exits with one of:
|
|
961
|
+
|
|
962
|
+
| Exit | Meaning |
|
|
963
|
+
|------|-------------------------------------------------------------------------|
|
|
964
|
+
| `0` | The wait condition was met (`--all` reached, or `--any` / `--first` saw at least one). |
|
|
965
|
+
| `5` | `--timeout` expired before the condition was met. `--json` payload still includes `all` (refs that did reach) and `timedOut` (refs that didn't). |
|
|
966
|
+
| `6` | **REAPER_DETECTED.** A WATCHED task transitioned `IN_PROGRESS → OPEN` between polls because the reconciler detected the owning pane was dead and the reaper flipped the task back. Scoped to the wait set: a reaper-flip in some other workstream (or some other task in the same workstream) does NOT trigger exit 6. Fires only when the wait target is `CLOSED` (the default) — with `--status OPEN` a reaper-flip TO open IS the success and the wait returns `0`. Re-dispatch a worker (`mu agent spawn ... && mu task claim --for ...`) and re-run the wait. (`task_wait_reconcile_dead_panes` + `task_wait_cross_workstream`) |
|
|
967
|
+
| `7` | **STALL_DETECTED.** Only with `--on-stall exit`. The existing `--stuck-after` predicate fired on a watched task (IN_PROGRESS, owner alive but in `needs_input` for `>= --stuck-after` seconds) and the wait threw instead of polling forward. Same target=CLOSED carve-out as exit 6 (with `--status OPEN`/etc the worker reaching `needs_input` might BE the success path; `--on-stall exit` is downgraded to warn-only). Stderr names the task + owner + age. Exit 7 is the **ambiguous** sibling of exit 6: dead pane (6) is unambiguous (re-dispatch); idle agent (7) might be transient (operator decides poke vs release). If both fire in the same poll, exit 6 wins (reaper-flip moves status off `IN_PROGRESS`, so the stuck-check's predicate naturally fails). (`task_wait_stall_action_flag`) |
|
|
968
|
+
|
|
969
|
+
The per-poll reconcile means a worker pane that died **before** you
|
|
970
|
+
ran `mu task wait` is also reaped on the first tick — you'll see exit
|
|
971
|
+
`6` in well under a second instead of running out the `--timeout`.
|
|
972
|
+
For cross-workstream waits the reconcile loops over every workstream
|
|
973
|
+
in the wait set (so a dead pane in workstream B is reaped while you
|
|
974
|
+
wait on its task there too).
|
|
975
|
+
|
|
976
|
+
### `mu task wait`: stall detection (`--stuck-after` + `--on-stall`)
|
|
977
|
+
|
|
978
|
+
Two orthogonal flags govern the stall behaviour:
|
|
979
|
+
|
|
980
|
+
* `--stuck-after <seconds>` — the **trigger**. An IN_PROGRESS task
|
|
981
|
+
whose owner has been in `needs_input` for `>= N` seconds is marked
|
|
982
|
+
stuck. Default `300` (5 min); pass `0` to disable detection
|
|
983
|
+
entirely (no warn AND no exit).
|
|
984
|
+
* `--on-stall <action>` — the **action** when the trigger fires.
|
|
985
|
+
Two values:
|
|
986
|
+
* `warn` (default) — yellow `STUCK` warning to stderr (deduped per
|
|
987
|
+
task per wait call), corroborating `agent stalled <name> owns
|
|
988
|
+
<task> for <secs>s` event in `agent_logs`, and `wait` keeps
|
|
989
|
+
polling. The behaviour pre-`task_wait_stall_action_flag`,
|
|
990
|
+
byte-for-byte.
|
|
991
|
+
* `exit` — same emit + persist, then **exit 7**
|
|
992
|
+
(`STALL_DETECTED`). The unattended-orchestrator escape: a
|
|
993
|
+
wrapping policy can branch on 7 (idle, ambiguous — poke vs
|
|
994
|
+
release) vs 6 (dead pane, unambiguous — re-dispatch). Suppressed
|
|
995
|
+
when `--status` is anything other than `CLOSED` (mirrors
|
|
996
|
+
exit-6's carve-out: with `--status OPEN` reaching `needs_input`
|
|
997
|
+
might BE the success path).
|
|
998
|
+
|
|
999
|
+
```bash
|
|
1000
|
+
# Default: warn at 5 min, keep polling. Today's behaviour.
|
|
1001
|
+
mu task wait build_a build_b -w mufeedback-v03 --timeout 1800
|
|
1002
|
+
|
|
1003
|
+
# Tune the trigger; same warn-only action.
|
|
1004
|
+
mu task wait build_a -w mufeedback-v03 --stuck-after 60
|
|
1005
|
+
|
|
1006
|
+
# Exit on stall (cron-driven wrapper):
|
|
1007
|
+
mu task wait build_a -w mufeedback-v03 --on-stall exit
|
|
1008
|
+
# exit 0 → closed
|
|
1009
|
+
# exit 5 → timeout
|
|
1010
|
+
# exit 6 → dead pane (re-dispatch)
|
|
1011
|
+
# exit 7 → idle agent (poke or release — inspect first)
|
|
1012
|
+
|
|
1013
|
+
# Tune both. Exit at 60s of needs_input:
|
|
1014
|
+
mu task wait build_a -w mufeedback-v03 --stuck-after 60 --on-stall exit
|
|
1015
|
+
|
|
1016
|
+
# Disable both warn AND exit (--stuck-after 0 wins):
|
|
1017
|
+
mu task wait build_a -w mufeedback-v03 --stuck-after 0 --on-stall exit
|
|
1018
|
+
```
|
|
1019
|
+
|
|
1020
|
+
### Common ad-hoc queries
|
|
1021
|
+
|
|
1022
|
+
```bash
|
|
1023
|
+
# Set task to IN_PROGRESS without claiming (claim does this automatically;
|
|
1024
|
+
# this covers the rare manual case). local_id is per-workstream unique,
|
|
1025
|
+
# so always scope by workstream_id to avoid hitting a same-named task in
|
|
1026
|
+
# another workstream.
|
|
1027
|
+
mu sql "UPDATE tasks SET status='IN_PROGRESS'
|
|
1028
|
+
WHERE local_id='build'
|
|
1029
|
+
AND workstream_id=(SELECT id FROM workstreams WHERE name='mufeedback-v03')"
|
|
1030
|
+
|
|
1031
|
+
# What's blocking what (open tasks only) — same data as `mu task tree`
|
|
1032
|
+
# but as a flat join when you want a wider report. task_edges is keyed
|
|
1033
|
+
# by tasks.id, not local_id.
|
|
1034
|
+
mu sql "SELECT b.local_id AS blocked, t.local_id AS by_task
|
|
1035
|
+
FROM tasks b
|
|
1036
|
+
JOIN task_edges e ON e.to_task_id = b.id
|
|
1037
|
+
JOIN tasks t ON t.id = e.from_task_id
|
|
1038
|
+
WHERE t.status != 'CLOSED' AND b.status = 'OPEN'"
|
|
1039
|
+
|
|
1040
|
+
# Recursive CTE: every task that transitively blocks `launch` in a
|
|
1041
|
+
# given workstream (or use `mu task tree launch --json` for the same
|
|
1042
|
+
# data structured). local_id is per-workstream, so resolve the seed
|
|
1043
|
+
# under a workstream filter.
|
|
1044
|
+
mu sql "WITH RECURSIVE prereqs(id) AS (
|
|
1045
|
+
SELECT t.id FROM tasks t
|
|
1046
|
+
JOIN workstreams w ON w.id = t.workstream_id
|
|
1047
|
+
WHERE t.local_id='launch' AND w.name='mufeedback-v03'
|
|
1048
|
+
UNION
|
|
1049
|
+
SELECT e.from_task_id FROM task_edges e, prereqs
|
|
1050
|
+
WHERE e.to_task_id = prereqs.id
|
|
1051
|
+
)
|
|
1052
|
+
SELECT t.local_id, t.title, t.status
|
|
1053
|
+
FROM prereqs JOIN tasks t ON t.id = prereqs.id"
|
|
1054
|
+
```
|
|
1055
|
+
|
|
1056
|
+
`mu sql` accepts both reads and writes. Reads are pretty-printed as a
|
|
1057
|
+
table; writes report `<n> rows affected`.
|
|
1058
|
+
|
|
1059
|
+
---
|
|
1060
|
+
|
|
1061
|
+
## 14. Recovery scenarios
|
|
1062
|
+
|
|
1063
|
+
### An agent's pane dies externally
|
|
1064
|
+
|
|
1065
|
+
You killed it from another tmux client, or its CLI crashed:
|
|
1066
|
+
|
|
1067
|
+
```bash
|
|
1068
|
+
mu agent list # worker-1's row prunes itself (ghost detected)
|
|
1069
|
+
```
|
|
1070
|
+
|
|
1071
|
+
Reconciliation runs on every `mu agent list` / `mu`. Three steps:
|
|
1072
|
+
|
|
1073
|
+
1. **Prune ghost rows** — DB row whose `pane_id` no longer exists in
|
|
1074
|
+
tmux gets deleted
|
|
1075
|
+
2. **Detect status from scrollback** — for survivors, capture the
|
|
1076
|
+
pane and re-derive status (busy / needs_input / needs_permission /
|
|
1077
|
+
spawning) per the pi-status detector
|
|
1078
|
+
3. **Surface orphan panes** — panes in the workstream's tmux session
|
|
1079
|
+
whose `pane.command` looks like an agent CLI (pi) but
|
|
1080
|
+
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
|
|
1082
|
+
|
|
1083
|
+
### You closed your terminal session
|
|
1084
|
+
|
|
1085
|
+
The workstream's tmux session keeps running detached. Reconnect with
|
|
1086
|
+
`tmux a -t mu-auth-refactor`. Agents are alive; the DB has the
|
|
1087
|
+
registry; everything resumes. mu is daemon-free — every `mu`
|
|
1088
|
+
invocation is a short-lived process that re-reads from
|
|
1089
|
+
`~/.local/state/mu/mu.db`.
|
|
1090
|
+
|
|
1091
|
+
### The mu DB seems wrong
|
|
1092
|
+
|
|
1093
|
+
```bash
|
|
1094
|
+
sqlite3 ~/.local/state/mu/mu.db .schema # inspect
|
|
1095
|
+
sqlite3 ~/.local/state/mu/mu.db .tables # list
|
|
1096
|
+
mu doctor # quick health check
|
|
1097
|
+
rm ~/.local/state/mu/mu.db # nuke (last resort; loses task graph and registry)
|
|
1098
|
+
```
|
|
1099
|
+
|
|
1100
|
+
### You ran a destructive verb and want to undo it
|
|
1101
|
+
|
|
1102
|
+
Every destructive verb (`mu task delete`, `mu workstream destroy
|
|
1103
|
+
--yes`, `mu task close/reject/defer/release`, `mu agent close`,
|
|
1104
|
+
`mu workspace free`) auto-captures a
|
|
1105
|
+
whole-DB snapshot before it mutates. Restore the latest with
|
|
1106
|
+
`mu undo`:
|
|
1107
|
+
|
|
1108
|
+
```bash
|
|
1109
|
+
mu undo # dry-run: shows the snapshot summary, does NOT restore
|
|
1110
|
+
mu undo --yes # commit the restore
|
|
1111
|
+
mu undo --to 12 --yes # restore a specific snapshot id
|
|
1112
|
+
|
|
1113
|
+
mu snapshot list # newest-first: id / ver / label / workstream / size
|
|
1114
|
+
mu snapshot show 12 # full metadata for one snapshot
|
|
1115
|
+
|
|
1116
|
+
# Manual cleanup (auto-GC also runs on every capture)
|
|
1117
|
+
mu snapshot prune # dry-run summary of the GC policy
|
|
1118
|
+
mu snapshot prune --yes # apply the GC policy now
|
|
1119
|
+
mu snapshot prune --keep-last 50 --yes
|
|
1120
|
+
mu snapshot prune --older-than 7d --yes
|
|
1121
|
+
mu snapshot prune --stale-version --yes # drop schema_version != current rows
|
|
1122
|
+
mu snapshot prune --all --yes # nuke everything (auto-snapshots a safety-net first)
|
|
1123
|
+
mu snapshot delete 12 # surgical removal of one row + its .db file
|
|
1124
|
+
```
|
|
1125
|
+
|
|
1126
|
+
The `ver` column in `mu snapshot list` shows each snapshot's
|
|
1127
|
+
`schema_version`; rows whose version doesn't match the live DB
|
|
1128
|
+
(post-schema-bump) render dimmed and are unrestorable
|
|
1129
|
+
(`mu undo` raises `SnapshotVersionMismatchError`). Drop them in
|
|
1130
|
+
bulk with `mu snapshot prune --stale-version --yes`.
|
|
1131
|
+
|
|
1132
|
+
Two important caveats:
|
|
1133
|
+
|
|
1134
|
+
- **Tmux state is NOT rolled back.** A snapshot is a copy of
|
|
1135
|
+
`mu.db` only. After restore, mu reconciles every workstream and
|
|
1136
|
+
reports `agents pruned` (DB row → dead pane) and `orphan panes
|
|
1137
|
+
surfaced` (live pane the restored DB doesn't know about) so you
|
|
1138
|
+
can see exactly where DB and tmux disagree. On-disk workspace
|
|
1139
|
+
dirs that `mu workspace free` removed are NOT recreated either.
|
|
1140
|
+
- **Each restore captures a pre-restore snapshot first.** That
|
|
1141
|
+
means a second `mu undo` rolls forward to the snapshot taken
|
|
1142
|
+
just before the previous restore — there is no separate
|
|
1143
|
+
`mu redo`, and there doesn't need to be.
|
|
1144
|
+
|
|
1145
|
+
Snapshots live next to the live DB at
|
|
1146
|
+
`<state-dir>/snapshots/<id>.db`. They GC opportunistically:
|
|
1147
|
+
on every capture, drop any row past the count cap OR past the
|
|
1148
|
+
age cap (whichever fires first). Defaults: keep the 100 newest
|
|
1149
|
+
+ everything from the last 14 days. Override with
|
|
1150
|
+
`MU_SNAPSHOT_KEEP_LAST` (default 100) / `MU_SNAPSHOT_MAX_AGE_DAYS`
|
|
1151
|
+
(default 14); typo'd values fall back to the default.
|
|
1152
|
+
|
|
1153
|
+
### Workspace orphans (dirs on disk with no DB row)
|
|
1154
|
+
|
|
1155
|
+
A `--workspace` spawn that aborted partway, an `mu agent close`
|
|
1156
|
+
from an earlier mu version, or a manual `rm` of `vcs_workspaces`
|
|
1157
|
+
rows can leave dirs in `<state-dir>/workspaces/<workstream>/<agent>/`
|
|
1158
|
+
that have no DB row. They're invisible to `mu workspace list` but
|
|
1159
|
+
they BLOCK subsequent `--workspace` spawns under the same name.
|
|
1160
|
+
|
|
1161
|
+
```bash
|
|
1162
|
+
mu state -w <workstream> # 'Workspace orphans' section in yellow
|
|
1163
|
+
mu workspace orphans -w <workstream> # focused list + cleanup recipe
|
|
1164
|
+
```
|
|
1165
|
+
|
|
1166
|
+
For each orphan, the cleanup is one of:
|
|
1167
|
+
|
|
1168
|
+
```bash
|
|
1169
|
+
# git-backed workspace: also prunes the worktree registry
|
|
1170
|
+
(cd <project-root> && git worktree remove --force <orphan-path>)
|
|
1171
|
+
|
|
1172
|
+
# any backend (last resort)
|
|
1173
|
+
rm -rf <orphan-path>
|
|
1174
|
+
```
|
|
1175
|
+
|
|
1176
|
+
The `Next:` block from `mu workspace orphans` interpolates the
|
|
1177
|
+
actual paths so you can copy-paste.
|
|
1178
|
+
|
|
1179
|
+
### You typo'd a workstream name and want to rename it
|
|
1180
|
+
|
|
1181
|
+
The `workstreams.name` column has `ON UPDATE CASCADE` on every
|
|
1182
|
+
child-table foreign key, so renaming a workstream is a single SQL
|
|
1183
|
+
statement that propagates atomically through `agents`, `tasks`,
|
|
1184
|
+
`agent_logs`, and `vcs_workspaces`:
|
|
1185
|
+
|
|
1186
|
+
```bash
|
|
1187
|
+
# 1. Validate the new name fits the rules (or mu will reject it on
|
|
1188
|
+
# next use). Lowercase alpha first, then alnum/_/-, ≤32 chars,
|
|
1189
|
+
# no '.' or ':' (tmux mangles them), no 'mu-' prefix.
|
|
1190
|
+
# 2. Rename in the DB. Single statement; cascades to every child.
|
|
1191
|
+
mu sql "UPDATE workstreams SET name='auth-refactor' WHERE name='auth-refator'"
|
|
1192
|
+
|
|
1193
|
+
# 3. Rename the tmux session too (only if it's currently alive).
|
|
1194
|
+
tmux rename-session -t mu-auth-refator mu-auth-refactor
|
|
1195
|
+
```
|
|
1196
|
+
|
|
1197
|
+
Mu doesn't ship a typed `mu workstream rename` verb because the
|
|
1198
|
+
schema does the work — wrapping a single safe statement adds
|
|
1199
|
+
surface area without buying anything (no atomicity to preserve, no
|
|
1200
|
+
validation to add, no side effects beyond the optional `tmux
|
|
1201
|
+
rename-session`). The recipe above is the canonical answer.
|
|
1202
|
+
|
|
1203
|
+
The same `ON UPDATE CASCADE` makes future `mu sql` renames safe
|
|
1204
|
+
for `tasks.local_id` and `agents.name` too, if you ever need to
|
|
1205
|
+
untypo those.
|
|
1206
|
+
|
|
1207
|
+
---
|
|
1208
|
+
|
|
1209
|
+
## 15. Cleanup
|
|
1210
|
+
|
|
1211
|
+
### Close individual agents
|
|
1212
|
+
|
|
1213
|
+
```bash
|
|
1214
|
+
mu agent close worker-1 # kills pane + drops registry row
|
|
1215
|
+
mu agent close worker-2
|
|
1216
|
+
mu agent close reviewer-1
|
|
1217
|
+
```
|
|
1218
|
+
|
|
1219
|
+
`mu agent close` is idempotent: `killPane` swallows "pane already gone"
|
|
1220
|
+
errors; `deleteAgent` returns false (not throws) on a missing row.
|
|
1221
|
+
|
|
1222
|
+
### Tear down the whole workstream
|
|
1223
|
+
|
|
1224
|
+
`mu workstream destroy` is the symmetric counterpart of `mu workstream init`: it kills the
|
|
1225
|
+
workstream's tmux session AND deletes every DB row tagged with the
|
|
1226
|
+
workstream name (agents, tasks, edges, notes — edges and notes go via
|
|
1227
|
+
FK cascade on tasks). The workstream resolves the same way as every
|
|
1228
|
+
other verb: `--workstream <name>` flag > `$MU_SESSION` > current tmux
|
|
1229
|
+
session (with the `mu-` prefix stripped).
|
|
1230
|
+
|
|
1231
|
+
The verb is two-phase by default: a bare `mu workstream destroy` prints a dry-run
|
|
1232
|
+
summary so you can verify what's about to disappear, and exits without
|
|
1233
|
+
touching anything. Pass `-y` / `--yes` to actually destroy.
|
|
1234
|
+
|
|
1235
|
+
```bash
|
|
1236
|
+
mu workstream destroy --workstream auth-refactor # dry-run: shows counts, exits
|
|
1237
|
+
mu workstream destroy --workstream auth-refactor --yes # actually does it
|
|
1238
|
+
|
|
1239
|
+
# Or, from inside the workstream's tmux session:
|
|
1240
|
+
mu workstream destroy --yes # workstream auto-detected
|
|
1241
|
+
|
|
1242
|
+
# Atomic: archive THEN destroy. Refuses if the archive label
|
|
1243
|
+
# doesn't already exist (run `mu archive create <label>` first).
|
|
1244
|
+
mu workstream destroy -w auth-refactor --archive v0-3-wave --yes
|
|
1245
|
+
|
|
1246
|
+
# Sweep every empty workstream (zero tasks, agents, vcs_workspaces)
|
|
1247
|
+
# in one call. Tmux session presence and audit-only
|
|
1248
|
+
# agent_logs do NOT disqualify. Also surfaces unregistered `mu-*`
|
|
1249
|
+
# tmux sessions (test litter or remnants from a partial destroy that
|
|
1250
|
+
# nuked the DB row but left the session behind) — the matching
|
|
1251
|
+
# predicate is narrow on purpose: ONLY sessions starting with `mu-`,
|
|
1252
|
+
# arbitrary tmux sessions the operator runs for unrelated work are
|
|
1253
|
+
# never touched. Mutually exclusive with -w and --archive. Dry-run
|
|
1254
|
+
# lists what WOULD be destroyed (created_at renders as `—` for
|
|
1255
|
+
# tmux-only entries); --yes captures ONE snapshot for the whole
|
|
1256
|
+
# batch and best-effort destroys each.
|
|
1257
|
+
mu workstream destroy --empty # dry-run: table of empties
|
|
1258
|
+
mu workstream destroy --empty --yes # destroy them all
|
|
1259
|
+
```
|
|
1260
|
+
|
|
1261
|
+
```
|
|
1262
|
+
Workstream auth-refactor (tmux session mu-auth-refactor)
|
|
1263
|
+
tmux session : alive (will be killed)
|
|
1264
|
+
agents : 3
|
|
1265
|
+
tasks : 10 (edges: 12, notes: 7)
|
|
1266
|
+
|
|
1267
|
+
Destroyed auth-refactor: killed tmux=true, agents=3, tasks=10, edges=12, notes=7
|
|
1268
|
+
```
|
|
1269
|
+
|
|
1270
|
+
It's idempotent on every leg: missing tmux session is fine, zero DB
|
|
1271
|
+
rows is fine, repeated `mu workstream destroy` against an already-gone workstream
|
|
1272
|
+
prints "nothing to destroy" and exits 0.
|
|
1273
|
+
|
|
1274
|
+
A whole-DB snapshot is captured before the destroy runs. If you
|
|
1275
|
+
regret it, `mu undo --yes` restores the DB — but the tmux session
|
|
1276
|
+
that was killed and any per-agent workspace dirs that were freed
|
|
1277
|
+
are NOT brought back. See
|
|
1278
|
+
[§ 14: You ran a destructive verb and want to undo it](#you-ran-a-destructive-verb-and-want-to-undo-it).
|
|
1279
|
+
|
|
1280
|
+
The tmux session is killed BEFORE the DB rows so an unexpected tmux
|
|
1281
|
+
failure leaves the registry intact (you can retry); if you only want
|
|
1282
|
+
the DB cleared, use `mu sql` directly:
|
|
1283
|
+
|
|
1284
|
+
```bash
|
|
1285
|
+
mu sql "DELETE FROM tasks WHERE workstream='auth-refactor'" # cascades
|
|
1286
|
+
mu sql "DELETE FROM agents WHERE workstream='auth-refactor'"
|
|
1287
|
+
```
|
|
1288
|
+
|
|
1289
|
+
Or nuke the entire DB:
|
|
1290
|
+
|
|
1291
|
+
```bash
|
|
1292
|
+
rm ~/.local/state/mu/mu.db # next mu invocation re-creates an empty schema
|
|
1293
|
+
```
|
|
1294
|
+
|
|
1295
|
+
### Preserve the conversation as markdown before destroying
|
|
1296
|
+
|
|
1297
|
+
A workstream's task graph + notes IS the project memory — the
|
|
1298
|
+
durable record of what was decided and why. `mu workstream destroy`
|
|
1299
|
+
blows that away (a snapshot is taken, but it's a binary `.db` only
|
|
1300
|
+
readable through `mu undo`). For code review, project handoff,
|
|
1301
|
+
git-checked-in artifacts, or just `grep`, render the workstream as
|
|
1302
|
+
plain markdown first.
|
|
1303
|
+
|
|
1304
|
+
Exports use a **bucket** layout (`bucketVersion: 2`, mu ≥ 0.3):
|
|
1305
|
+
the `--out` directory is a multi-source bucket whose top-level
|
|
1306
|
+
contains a bucket-wide README/INDEX/manifest, and one
|
|
1307
|
+
subdirectory per source workstream:
|
|
1308
|
+
|
|
1309
|
+
```
|
|
1310
|
+
<bucket>/
|
|
1311
|
+
README.md # bucket-level summary (every source-ws + dates + totals)
|
|
1312
|
+
INDEX.md # union of all task tables; first column = source-ws
|
|
1313
|
+
manifest.json # bucketVersion: 2 + per-source-ws sha256 + per-task sha256
|
|
1314
|
+
<source-ws>/
|
|
1315
|
+
README.md # per-source-ws (counts)
|
|
1316
|
+
INDEX.md # per-source-ws (table of every task)
|
|
1317
|
+
tasks/<id>.md # one .md per task; YAML frontmatter + notes
|
|
1318
|
+
```
|
|
1319
|
+
|
|
1320
|
+
Bucket exports are **additive**: `mu workstream export -w X --out
|
|
1321
|
+
<bucket>` creates the bucket scaffolding plus `X/` on first use,
|
|
1322
|
+
and a follow-up call with `-w Y --out <same-bucket>` appends a
|
|
1323
|
+
sibling `Y/` subdirectory without touching `X/`. Re-running with
|
|
1324
|
+
the same `-w` is sha256-idempotent: only changed task files are
|
|
1325
|
+
rewritten (mtime preserved on identical files); tasks added since
|
|
1326
|
+
the previous export get fresh files; tasks deleted from the DB
|
|
1327
|
+
STAY on disk with a `> **Deleted from DB on <ts>**` banner so you
|
|
1328
|
+
never lose context that may already be git-blamed.
|
|
1329
|
+
|
|
1330
|
+
```bash
|
|
1331
|
+
# One-shot dump (bucket happens to contain just one source-ws)
|
|
1332
|
+
mu workstream export -w auth-refactor # → ./auth-refactor/
|
|
1333
|
+
mu workstream export -w auth-refactor --out ~/notes/auth/ # explicit dir
|
|
1334
|
+
|
|
1335
|
+
# Additive accumulation across multiple workstreams in one bucket
|
|
1336
|
+
mu workstream export -w mufeedback --out exports/mu # creates exports/mu/mufeedback/
|
|
1337
|
+
mu workstream export -w roadmap-v0-2 --out exports/mu # adds exports/mu/roadmap-v0-2/
|
|
1338
|
+
mu workstream export -w mufeedback-v03 --out exports/mu # adds exports/mu/mufeedback-v03/
|
|
1339
|
+
```
|
|
1340
|
+
|
|
1341
|
+
The same renderer powers `mu archive export <label> --out <bucket>`,
|
|
1342
|
+
which (re)builds every source-ws subdirectory from the named
|
|
1343
|
+
archive in one shot — see `Archives` below.
|
|
1344
|
+
|
|
1345
|
+
`mu workstream destroy --yes` auto-runs an export to
|
|
1346
|
+
`<state-dir>/exports/<workstream>-<timestamp>/` BEFORE killing the
|
|
1347
|
+
tmux session and dropping the rows, so the conversation survives
|
|
1348
|
+
even if you forgot. Pass `--no-export` to opt out.
|
|
1349
|
+
|
|
1350
|
+
```bash
|
|
1351
|
+
(cd ~/notes/auth && git init && git add . && git commit -m 'auth-refactor snapshot')
|
|
1352
|
+
```
|
|
1353
|
+
|
|
1354
|
+
**Pre-0.3 export layouts are not migrated in place.** If `--out`
|
|
1355
|
+
points at a directory whose `manifest.json` was written by an
|
|
1356
|
+
older mu (no `bucketVersion`, top-level `workstream` field), the
|
|
1357
|
+
export refuses with a helpful error: `rm -rf <dir>` and re-run, or
|
|
1358
|
+
pick a different `--out`.
|
|
1359
|
+
|
|
1360
|
+
Markdown only by design — no HTML/PDF, no embedded VCS, no
|
|
1361
|
+
cross-workstream merge. Operators can pandoc / `git init`
|
|
1362
|
+
themselves.
|
|
1363
|
+
|
|
1364
|
+
### Cross-machine + collab — `mu workstream import`
|
|
1365
|
+
|
|
1366
|
+
The export above plus `mu workstream import <bucket-dir>` is the
|
|
1367
|
+
cross-machine + collaboration story. Push the bucket directory to
|
|
1368
|
+
git on machine A; pull it on machine B (or share it with a
|
|
1369
|
+
teammate); `mu workstream import` rebuilds the workstream + every
|
|
1370
|
+
task + edge + note locally.
|
|
1371
|
+
|
|
1372
|
+
```bash
|
|
1373
|
+
# Machine A — author
|
|
1374
|
+
mu workstream export -w auth-refactor --out exports/auth
|
|
1375
|
+
(cd exports/auth && git init && git add . && git commit -m 'auth snapshot')
|
|
1376
|
+
git push origin main
|
|
1377
|
+
|
|
1378
|
+
# Machine B — pull + rehydrate
|
|
1379
|
+
git pull
|
|
1380
|
+
mu workstream import exports/auth # → workstream `auth-refactor`
|
|
1381
|
+
mu workstream import exports/auth --workstream auth-v2 # rename on import
|
|
1382
|
+
mu workstream import exports/auth --dry-run # walk + parse + report; no DB writes
|
|
1383
|
+
mu workstream import exports/auth --json # machine-readable per-source-ws result
|
|
1384
|
+
|
|
1385
|
+
# Partial bucket import — multi-source bucket, but you only want
|
|
1386
|
+
# one (or a subset) restored. Two equivalent forms:
|
|
1387
|
+
mu workstream import exports/mu/roadmap-v0-2 # Form 1 — per-source-ws subdir path
|
|
1388
|
+
mu workstream import exports/mu --source-ws roadmap-v0-2 # Form 2 — bucket + filter
|
|
1389
|
+
mu workstream import exports/mu --source-ws auth,ui # Form 2 — X+Y, leave Z behind
|
|
1390
|
+
mu workstream import exports/mu --source-ws auth --source-ws ui # repeat OR comma-separate; or both
|
|
1391
|
+
```
|
|
1392
|
+
|
|
1393
|
+
Key properties:
|
|
1394
|
+
|
|
1395
|
+
- **Markdown-only.** `.db` files are never imported (binary +
|
|
1396
|
+
machine-specific). `mu undo` + snapshot files cover the
|
|
1397
|
+
same-machine case; this verb covers cross-machine + collab.
|
|
1398
|
+
- **Per-source-ws transactional.** Each source-ws subdirectory is
|
|
1399
|
+
imported in its own SQLite transaction. A failure in source A
|
|
1400
|
+
rolls back A; sibling source B is unaffected.
|
|
1401
|
+
- **Refuses silent merges.** If the target workstream already
|
|
1402
|
+
exists in the DB with tasks, the import errors with
|
|
1403
|
+
`WorkstreamAlreadyExistsError`. Recourse:
|
|
1404
|
+
`--workstream <new-name>` (single-source buckets only) or
|
|
1405
|
+
destroy the existing workstream first.
|
|
1406
|
+
- **Owners reset.** Agents aren't exported, so the imported tasks
|
|
1407
|
+
are unowned. The original owner name survives in the markdown
|
|
1408
|
+
frontmatter — that's the audit trail.
|
|
1409
|
+
- **Tombstones skipped.** Files starting with the
|
|
1410
|
+
`> **Deleted from DB on …**` banner (preserved by re-export of
|
|
1411
|
+
a deleted task) are counted as `tombstones_skipped` and not
|
|
1412
|
+
re-inserted.
|
|
1413
|
+
- **Forward edge refs are deferred.** `blocked_by` / `blocks`
|
|
1414
|
+
arrays are validated against the bucket's id-set up front, then
|
|
1415
|
+
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
|
+
- **Partial import.** Multi-source buckets accept either a
|
|
1419
|
+
per-source-ws subdir path (auto-detected via
|
|
1420
|
+
`README.md` + `INDEX.md` + `tasks/` + a parent
|
|
1421
|
+
`manifest.json` listing the subdir as a source) OR a
|
|
1422
|
+
`--source-ws <names...>` filter on the bucket root
|
|
1423
|
+
(variadic per `cli_audit_plurality_uniformity`: repeat,
|
|
1424
|
+
comma-separate, or both). The two forms are equivalent for
|
|
1425
|
+
single-source restores. `--workstream <new-name>` is allowed
|
|
1426
|
+
whenever the resolved source-ws list has exactly one entry
|
|
1427
|
+
(Form 1; or Form 2 with a single name); rejected for
|
|
1428
|
+
multi-source filters. Passing `--source-ws` against a Form 1
|
|
1429
|
+
per-source-ws subdir is refused (the subdir already implies one
|
|
1430
|
+
source). A `--source-ws` name not in the bucket manifest raises
|
|
1431
|
+
`ImportSourceNotInBucketError` (exit 4) and lists the valid
|
|
1432
|
+
names. `--source-ws ',,'` (canonicalises to zero names) is a
|
|
1433
|
+
`UsageError` (exit 2) so a typo doesn't silently fall back to
|
|
1434
|
+
importing the entire bucket.
|
|
1435
|
+
|
|
1436
|
+
---
|
|
1437
|
+
|
|
1438
|
+
## 15.5 Archives — cross-workstream preservation of task graphs
|
|
1439
|
+
|
|
1440
|
+
A `mu workstream destroy` blows away the live task graph (a
|
|
1441
|
+
snapshot is taken, but it's a binary `.db` only readable through
|
|
1442
|
+
`mu undo`). The markdown export above keeps the conversation
|
|
1443
|
+
human-readable on disk, but it's not queryable in-DB. The
|
|
1444
|
+
**archive** verb is the third option: a structured, queryable
|
|
1445
|
+
snapshot of a workstream's task graph (tasks + edges + notes +
|
|
1446
|
+
events) that lives in the same `mu.db` indefinitely and can
|
|
1447
|
+
accumulate snapshots from MANY workstreams under the same
|
|
1448
|
+
operator-named label.
|
|
1449
|
+
|
|
1450
|
+
```bash
|
|
1451
|
+
mu archive create v0-3-wave --description "v0.3 release wave"
|
|
1452
|
+
mu archive add v0-3-wave -w mufeedback-v03
|
|
1453
|
+
mu archive add v0-3-wave -w roadmap-v0-3 --destroy # cascade: archive THEN destroy
|
|
1454
|
+
mu archive list # label | tasks | sources | created | last_added
|
|
1455
|
+
mu archive show v0-3-wave # detail card + per-source-workstream summary
|
|
1456
|
+
mu archive search 'oauth' [--label v0-3-wave] # LIKE-search archived titles + note content (--limit N, --json)
|
|
1457
|
+
mu archive export v0-3-wave --out exports/v0-3-wave # render every source-ws to a bucket directory (markdown)
|
|
1458
|
+
```
|
|
1459
|
+
|
|
1460
|
+
Key properties:
|
|
1461
|
+
|
|
1462
|
+
- **Globally-unique labels.** Archive labels live in their own
|
|
1463
|
+
namespace (separate from workstream names). Pick once, reuse
|
|
1464
|
+
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.
|
|
1471
|
+
- **Outlives the source.** `archived_tasks.source_workstream` is
|
|
1472
|
+
TEXT (not an FK), so the source workstream can be destroyed and
|
|
1473
|
+
the archive's snapshot of it stays queryable forever.
|
|
1474
|
+
- **Reversible.** `mu archive delete <label> --yes` captures a
|
|
1475
|
+
snapshot first; `mu undo --yes` brings the whole archive back.
|
|
1476
|
+
`mu archive remove <label> -w <ws>` is the surgical version
|
|
1477
|
+
(one source workstream's contribution, without touching
|
|
1478
|
+
siblings).
|
|
1479
|
+
|
|
1480
|
+
### Three lifecycle patterns
|
|
1481
|
+
|
|
1482
|
+
The verb shape supports all three; pick per-call.
|
|
1483
|
+
|
|
1484
|
+
**Pattern A — single bucket per project family** (single growing
|
|
1485
|
+
archive, easy cross-time queries):
|
|
1486
|
+
|
|
1487
|
+
```bash
|
|
1488
|
+
mu archive create mu --description "every mu-self-development workstream"
|
|
1489
|
+
mu archive add mu -w mufeedback --destroy # initial v0.2 wave
|
|
1490
|
+
mu archive add mu -w roadmap-v0-2 --destroy
|
|
1491
|
+
# weeks later, after v0.3 ships:
|
|
1492
|
+
mu archive add mu -w mufeedback-v03 --destroy
|
|
1493
|
+
mu archive add mu -w roadmap-v0-3 --destroy
|
|
1494
|
+
# months later: same single 'mu' bucket grows.
|
|
1495
|
+
```
|
|
1496
|
+
|
|
1497
|
+
**Pattern B — per-release buckets** (easier to compare "what
|
|
1498
|
+
shipped in v0.2 vs v0.3"):
|
|
1499
|
+
|
|
1500
|
+
```bash
|
|
1501
|
+
mu archive create mu-v0-2 ; mu archive add mu-v0-2 -w mufeedback --destroy
|
|
1502
|
+
mu archive create mu-v0-3 ; mu archive add mu-v0-3 -w mufeedback-v03 --destroy
|
|
1503
|
+
```
|
|
1504
|
+
|
|
1505
|
+
**Pattern C — hybrid** (a workstream lives in BOTH archives;
|
|
1506
|
+
independent rows under each label):
|
|
1507
|
+
|
|
1508
|
+
```bash
|
|
1509
|
+
mu archive add mu -w mufeedback-v03
|
|
1510
|
+
mu archive add mu-v0-3 -w mufeedback-v03 --destroy
|
|
1511
|
+
```
|
|
1512
|
+
|
|
1513
|
+
### Anti-features (intentional)
|
|
1514
|
+
|
|
1515
|
+
- **No "default" / auto-archive.** `mu workstream destroy` does
|
|
1516
|
+
NOT auto-add to a fallback bucket. Either you picked a label
|
|
1517
|
+
deliberately or you didn't want one.
|
|
1518
|
+
- **No re-import.** The archive IS the workstream's afterlife.
|
|
1519
|
+
If you need an archived task back as live work, copy it via
|
|
1520
|
+
`mu sql` into a fresh workstream + `mu task add`.
|
|
1521
|
+
- **No archive→archive merge / rename.** Operator-managed via
|
|
1522
|
+
`mu sql` if it ever matters.
|
|
1523
|
+
- **Snapshots vs archives are separate concerns.** Snapshots are
|
|
1524
|
+
whole-DB binary backups for one-shot recovery (`mu undo`).
|
|
1525
|
+
Archives are first-class queryable structured data with their
|
|
1526
|
+
own lifecycle. Don't confuse them.
|
|
1527
|
+
|
|
1528
|
+
---
|
|
1529
|
+
|
|
1530
|
+
## 16. One-shot demo script
|
|
1531
|
+
|
|
1532
|
+
Copy-pasteable, end-to-end. Wipes any prior `~/.local/state/mu/mu.db`.
|
|
1533
|
+
|
|
1534
|
+
```bash
|
|
1535
|
+
# Clean start
|
|
1536
|
+
tmux kill-session -t mu-demo 2>/dev/null
|
|
1537
|
+
rm -f ~/.local/state/mu/mu.db
|
|
1538
|
+
|
|
1539
|
+
# Plan
|
|
1540
|
+
mu workstream init demo
|
|
1541
|
+
mu task add design --title "Design" --impact 80 --effort-days 2
|
|
1542
|
+
mu task add build --title "Build" --impact 80 --effort-days 5 --blocked-by design
|
|
1543
|
+
mu task add ship --title "Ship" --impact 90 --effort-days 1 --blocked-by build
|
|
1544
|
+
|
|
1545
|
+
# Crew
|
|
1546
|
+
mu agent spawn worker-1 --workstream demo --cli sh
|
|
1547
|
+
mu agent spawn worker-2 --workstream demo --cli sh
|
|
1548
|
+
|
|
1549
|
+
# Assign + observe
|
|
1550
|
+
mu sql "UPDATE tasks SET owner='worker-1', status='IN_PROGRESS' WHERE local_id='design'"
|
|
1551
|
+
mu --workstream demo
|
|
1552
|
+
|
|
1553
|
+
# Watch live (Ctrl+b d to detach)
|
|
1554
|
+
tmux attach -t mu-demo
|
|
1555
|
+
|
|
1556
|
+
# Cleanup
|
|
1557
|
+
mu workstream destroy --workstream demo --yes
|
|
1558
|
+
rm -f ~/.local/state/mu/mu.db
|
|
1559
|
+
```
|
|
1560
|
+
|
|
1561
|
+
---
|
|
1562
|
+
|
|
1563
|
+
## Mental model in three sentences
|
|
1564
|
+
|
|
1565
|
+
1. **One workstream is one tmux session full of agent panes.** Mu
|
|
1566
|
+
manages the lifecycle; tmux is the substrate. Workstreams on the
|
|
1567
|
+
same machine are isolated by `session_id` in the SQLite registry.
|
|
1568
|
+
|
|
1569
|
+
2. **The task DAG decides what's actionable; the LLM doesn't gamble.**
|
|
1570
|
+
Mission control + the `Ready` table + parallel-tracks union-find
|
|
1571
|
+
give deterministic answers to "what's next?" and "what can I
|
|
1572
|
+
parallelize?" Diamond patterns (two goals sharing a prerequisite)
|
|
1573
|
+
collapse into one merged track so two agents never collide on
|
|
1574
|
+
shared deps.
|
|
1575
|
+
|
|
1576
|
+
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
|
|
1579
|
+
`tmux display-message -t $TMUX_PANE -p '#{pane_title}'`, set on
|
|
1580
|
+
spawn. Two agents cannot claim the same task.
|
|
1581
|
+
|
|
1582
|
+
Everything else (`mu sql`, send/read, the bracketed-paste protocol,
|
|
1583
|
+
ghost reconciliation) is plumbing in
|
|
1584
|
+
service of those three.
|
|
1585
|
+
|
|
1586
|
+
---
|
|
1587
|
+
|
|
1588
|
+
## What's NOT in 0.3.0 (and how to work around it)
|
|
1589
|
+
|
|
1590
|
+
<a id="whats-not-in-030-and-how-to-work-around-it"></a>
|
|
1591
|
+
|
|
1592
|
+
The full roadmap with promotion criteria lives in
|
|
1593
|
+
[ROADMAP.md](ROADMAP.md). The short list of gaps you might hit
|
|
1594
|
+
in real use:
|
|
1595
|
+
|
|
1596
|
+
| Want | Workaround | Status |
|
|
1597
|
+
| --------------------------------------------- | ----------------------------------------------------------------------- | ------------- |
|
|
1598
|
+
| 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 |
|
|
1600
|
+
| Markdown agent-definition discovery | Spawn accepts `--cli` and `--command` directly; no template registry | dropped |
|
|
1601
|
+
| `mu run script.ts` (JS DSL) | Use `--json` + bash + jq | rejected |
|
|
1602
|
+
| 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 |
|
|
1606
|
+
|
|
1607
|
+
Anything in this table that bites you in real use is a candidate
|
|
1608
|
+
for **promotion**. Criteria: proven friction in ≥2 real workflows +
|
|
1609
|
+
fits in <300 LOC + no major refactor of the load-bearing pillars.
|
|
1610
|
+
The most useful feedback is "I tried to do X and had to fall back
|
|
1611
|
+
to `mu sql`, twice in one session" — that's exactly the signal we
|
|
1612
|
+
want. File it in [ROADMAP.md](ROADMAP.md).
|
|
1613
|
+
|
|
1614
|
+
---
|
|
1615
|
+
|
|
1616
|
+
## Where to go from here
|
|
1617
|
+
|
|
1618
|
+
| Doc | What's in it |
|
|
1619
|
+
| -------------------------------------------- | ------------------------------------------------------- |
|
|
1620
|
+
| [README.md](../README.md) | Project overview, install, comparison vs `pi-subagents` |
|
|
1621
|
+
| [CHANGELOG.md](../CHANGELOG.md) | Release notes |
|
|
1622
|
+
| [ROADMAP.md](ROADMAP.md) | What's next, with promotion criteria + rejected ideas |
|
|
1623
|
+
| [VOCABULARY.md](VOCABULARY.md) | Canonical terms — source of truth for every word |
|
|
1624
|
+
| [VISION.md](VISION.md) | The eight load-bearing pillars + design principles |
|
|
1625
|
+
| [ARCHITECTURE.md](ARCHITECTURE.md) | Module map, reconciliation algorithm, layered design |
|
|
1626
|
+
| [skills/mu/SKILL.md](../skills/mu/SKILL.md) | What an LLM running inside an agent pane sees |
|
|
1627
|
+
|
|
1628
|
+
If you're trying mu and something doesn't work as documented, file an
|
|
1629
|
+
issue with: the exact `mu` command, the full output (set
|
|
1630
|
+
`MU_DB_PATH=/tmp/mu-debug.db` to isolate from your real registry),
|
|
1631
|
+
your tmux version (`tmux -V`), and your platform.
|