@mu-crew/mu 3.7.0 → 3.8.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.
@@ -120,7 +120,7 @@ hub that re-exports them. `src/tasks.ts`, `src/agents.ts`, `src/mux.ts`,
120
120
  | `src/agents/spawn.ts` | spawn: CLI resolution, pane create or reuse, liveness, readiness, ctl handshake, rollback |
121
121
  | `src/agents/spawn-lock.ts` | per-session lock around topology and row insert |
122
122
  | `src/agents/transport.ts` | send routing: ctl for pi agents, mux paste otherwise, no fallback; `expectsCtl` |
123
- | `src/agents/abort.ts` | stop a pi turn through the control socket |
123
+ | `src/agents/abort.ts` | stop a pi turn through the control socket; abort + send (`--interrupt`) |
124
124
  | `src/agents/delegate.ts` | `delegateOutcome`: one wait result to `done` / `empty` / `died` / `timeout` / `pending` |
125
125
  | `src/agents/wait.ts` | block until task-less agents leave `busy` |
126
126
  | `src/agents/adopt.ts` | register an existing pane as an agent |
@@ -139,7 +139,7 @@ hub that re-exports them. `src/tasks.ts`, `src/agents.ts`, `src/mux.ts`,
139
139
  | `src/ctl/client.ts` | `ctlRequest`, `ctlProbe`, version and unknown-op errors |
140
140
  | `extension/mu-pi.ts` | pi extension, child side: serves `$MU_CTL_SOCK`; built to `dist/extension/mu-pi.js` |
141
141
  | `extension/delegate.ts` | pi extension, parent side: `mu_delegate` / `mu_delegate_cancel`, which shell out to `mu` |
142
- | `extension/nudge.ts` | pi extension: the keep-driving nudge. Arms on a `mu` dispatch, checks `mu state --json` at `agent_before_settle`, injects the SKILL.md rule once per prompt, logs `--kind nudge` |
142
+ | `extension/nudge.ts` | pi extension: the keep-driving nudge. Arms on a `mu` dispatch, checks `mu state --json` at `agent_before_settle`, injects the SKILL.md rule once per prompt, logs `--kind nudge`. Also the close nudge (worker settles owning a task) and the refute nudge (`task claim --for` on a task with no refute decision in `mu task notes --json`; one notice, no continuation) |
143
143
  | `src/link.ts` | `mu link pi`: extension shim, skill symlink, `inspectLinks` for doctor |
144
144
 
145
145
  ### Tasks, workspaces and workstreams
@@ -168,7 +168,8 @@ hub that re-exports them. `src/tasks.ts`, `src/agents.ts`, `src/mux.ts`,
168
168
  | `src/cli.ts` | Commander wiring (`buildProgram`) |
169
169
  | `src/cli/*.ts` | one file per verb namespace, thin wrappers over the SDK; `format.ts` renders tables, `handle.ts` maps errors to exit codes and runs ambient sync |
170
170
  | `src/cli/tasks/*.ts` | the `mu task` namespace, including `mu me tasks` / `mu me next` |
171
- | `src/cli/dispatch-hints.ts` | dispatch-time `Next:` hints for pi agents (`--fresh`, `--steer`, abort) |
171
+ | `src/cli/dispatch-hints.ts` | dispatch-time `Next:` hints for pi agents (`--fresh`, `--interrupt`, abort) |
172
+ | `src/cli/stdin.ts` | `-` as a text argument: reads note / send text from stdin (heredoc prose) |
172
173
  | `src/cli/agents-remote.ts` | `mu agent remote-env`: prints the ssh forward and env, runs nothing |
173
174
  | `src/cli/tui-launch-focus.ts` | initial-tab focus ladder for bare `mu` and `mu state --tui` |
174
175
  | `src/cli/tui/` | the ink TUI; the only place ink and react are imported |
@@ -32,6 +32,7 @@ reply carries `v: 1`; the client (`src/ctl/client.ts`) throws
32
32
  | `send` | `pi.sendUserMessage`; when busy, `mode` is `steer` or `followUp` (default). Replies with the `status` fields measured before the dispatch, so `runs` is the baseline for a later `wait` |
33
33
  | `wait` | resolves on pi's `agent_settled` after `afterRuns`, with the run's `lastText` |
34
34
  | `abort` | `ctx.abort()` (pi's Esc) |
35
+ | `interrupt` | busy: `ctx.abort()`, wait for `agent_settled` (up to `timeoutMs`), then `pi.sendUserMessage(text)` as a new run; idle: just the send. Replies `wasBusy`, `pending` (measured before the abort) and the `status` fields measured after the settle and before the send, so `runs` is the `wait` baseline for the new run. `timeout`: nothing sent |
35
36
  | `fresh` | new session plus prompt as one operation, through the internal `/mu-fresh` command; replies once the new run starts; refused with `busy` unless `force` |
36
37
  | `command` | `name` `new`, `reload` or `compact` (with optional `instructions`), through the internal `/mu-new`, `/mu-reload`, `/mu-compact` commands, which call `ctx.newSession()`, `ctx.reload()`, `ctx.compact()`. `new` replies once the session is replaced, `reload` after the `session_start` it causes, `compact` when compaction starts or with pi's error (`Nothing to compact ...`). Refused with `busy` unless `force` |
37
38
 
@@ -41,13 +42,14 @@ extension command, triggered with `expandPromptTemplates: true`, and
41
42
  its handler gets the command context that can replace or reload the
42
43
  session.
43
44
 
44
- These back `mu agent send` (`--fresh`, `--steer`, session commands), `mu agent wait`,
45
+ These back `mu agent send` (`--fresh`, `--steer`, `--interrupt`, session commands), `mu agent wait`,
45
46
  `mu agent abort`, the agent state reading, and `mu_delegate`.
46
47
 
47
48
  ## Version skew
48
49
 
49
50
  An op the extension does not serve returns `unknown op: <op>` with its
50
- `ops` list. mu raises `AgentExtensionOutdatedError` (exit 4), whose
51
+ `ops` list. `mu agent send --interrupt` then composes `abort`, `wait`
52
+ and `send` itself. Any other op raises `AgentExtensionOutdatedError` (exit 4), whose
51
53
  next steps are `/reload` through the mux (an extension that lacks the
52
54
  `command` op cannot run it over ctl) or a respawn. `mu doctor`
53
55
  probes every pi agent's socket (the `ctl` row) and flags an
@@ -68,7 +70,10 @@ row reports whether `mu link pi` installed the extension.
68
70
 
69
71
  1. If the path already answers, another pi serves it (a nested `pi -p`
70
72
  inherited `MU_CTL_SOCK`). The extension leaves it alone and says so
71
- on stderr.
73
+ on stderr. Identity follows the bind: that pi, and one that loses
74
+ the `link()` race, is nested and gets no keep-driving, close or
75
+ refute nudge, though it inherited the agent's `MU_AGENT_NAME`.
76
+ The decision lives in the process-global map, so `/reload` keeps it.
72
77
  2. Otherwise it listens on a private name in the same directory, sets
73
78
  mode 0600, and hard-links that name onto the public path. libuv
74
79
  unlinks the listened-on name at close, so the public path must never
@@ -23,6 +23,7 @@ Delegates called in one turn run in parallel.
23
23
  | `workspace` | Own VCS checkout, for delegates that edit files. The follow-up names its path. It isolates repository edits only. |
24
24
  | `cli` | Key for `$MU_<CLI>_COMMAND` (default `pi`), e.g. a cheaper model. |
25
25
  | `keep` | Keep the pane after it finishes, to talk to it again. |
26
+ | `record` | `{ task, workstream? }` (or `task: "<ws>/<task>"`): write the answer's verdict onto that task as a note. See [Record a verdict](#record-a-verdict). |
26
27
 
27
28
  - Watch or steer: run the attach command.
28
29
  - Stop: `mu_delegate_cancel`, or `mu agent abort <name> -w scratch`.
@@ -45,6 +46,23 @@ Delegates called in one turn run in parallel.
45
46
 
46
47
  `MU_DELEGATE=0` hides the tool ([env vars](../reference/env.md)).
47
48
 
49
+ ### Record a verdict
50
+
51
+ For a refuter or check, pass `record` so the verdict lands on the task
52
+ it judged. The task must exist (`mu task show`), else the call fails
53
+ and nothing spawns. When the answer arrives, the tool runs
54
+ `mu task note <task> -w <ws> --author <delegate>` once:
55
+
56
+ - Done: `REFUTER <label> (<delegate>, <elapsed>):`, then the last
57
+ `VERDICT:` line and every `EVIDENCE:` line after it. Without a
58
+ VERDICT line, the answer's last 1500 chars after `NO VERDICT LINE:`.
59
+ Capped at 4000 chars.
60
+ - Timeout, died, empty, error, or cancelled:
61
+ `REFUTER <label>: no verdict (<outcome>)`.
62
+
63
+ The follow-up still carries the full answer and ends with
64
+ `Recorded on <ws>/<task> as a note.`, or the reason recording failed.
65
+
48
66
  ## Delegate without pi
49
67
 
50
68
  From a script or other shell, run what the tool runs:
@@ -33,7 +33,8 @@ socket. Pick the mode by what the agent is doing:
33
33
  | Situation | Command |
34
34
  | --------- | ------- |
35
35
  | New task, any state | `mu agent send worker-1 --fresh '<brief>'` |
36
- | Busy, and you want to interrupt the current run | `mu agent send worker-1 --steer '<text>'` |
36
+ | Busy, and the current work is wasted (wrong input, changed rule): act now | `mu agent send worker-1 --interrupt '<text>'` |
37
+ | Busy, advice that can wait for the next tool boundary | `mu agent send worker-1 --steer '<text>'` |
37
38
  | Answering a question, or adding to the same task | `mu agent send worker-1 '<text>'` |
38
39
 
39
40
  - `--fresh` starts a new pi session and sends the prompt into it as
@@ -41,7 +42,14 @@ socket. Pick the mode by what the agent is doing:
41
42
  `mu agent abort` first, or pass `--force` to drop the running turn.
42
43
  - Never send `/new` and then the prompt as two sends to pi. The prompt
43
44
  can land during the reset and vanish.
44
- - A plain send to a busy agent queues as a follow-up.
45
+ - A plain send to a busy agent queues as a follow-up: pi sees it after
46
+ the run ends. `--steer` lands between tool calls, after the running
47
+ tool returns; it does not stop a long tool such as a test batch, a
48
+ wait, or a `sleep`.
49
+ - `--interrupt` aborts the run (kills the running tool), waits for pi
50
+ to settle, then sends the text as a new run. Queued messages go back
51
+ to the pane's editor unsent. Exit 5 when pi does not settle within
52
+ `--timeout` (default 30 s); the text is then not sent.
45
53
  - `/new`, `/reload` and `/compact [instructions]` run inside pi through
46
54
  the socket and report pi's answer (for example `Nothing to compact`).
47
55
  They refuse with exit 4 while pi is busy; `--force` overrides. Any
@@ -77,7 +85,9 @@ git commit -am '...' THEN
77
85
  mu task close build -w auth --evidence '...'
78
86
  ```
79
87
 
80
- Single-quote prompts that contain `$` or backticks. To see only what the worker wrote after the claim, run
88
+ Single-quote short prompts. Pass prose (apostrophes, `$`, backticks) as `-` with a
89
+ quoted heredoc: `mu agent send worker-1 - <<'EOF'`; see
90
+ [brief](../../skills/mu/recipes/brief.md#quoting). To see only what the worker wrote after the claim, run
81
91
  `mu task notes build -w auth --since-claim`.
82
92
 
83
93
  ## Give each worker a workspace
@@ -139,6 +149,10 @@ mu task release build -w auth
139
149
 
140
150
  - Closed substates other than `done` need `--why`. Every `CLOSED`
141
151
  substate satisfies a blocker.
152
+ - Deciding a finding (`accept`, or `close --as rejected|wontfix|duplicate`)
153
+ with a reason under 40 characters and no `VERDICT:` or `REFUTER` note
154
+ prints a warning. The exit code stays the same. A `REJECTED` or `SUPERSEDED`
155
+ note appends the title of each task id it names.
142
156
  - `--if-ready` closes only when every blocker is closed. Otherwise it
143
157
  lists the open blockers and exits 0.
144
158
  - A parked task leaves the ready set, and its dependents keep waiting.
@@ -179,7 +179,7 @@ mu has not freed. That protects work you have not merged.
179
179
  ## Next steps
180
180
 
181
181
  - [Dispatch work to a worker](dispatch.md) covers more than one
182
- worker and the `--fresh`, `--steer`, and plain send choices.
182
+ worker and the `--fresh`, `--interrupt`, `--steer`, and plain send choices.
183
183
  - [Use the TUI dashboard](tui.md): run bare `mu` to see every
184
184
  workstream at once.
185
185
  - [Clean up](cleanup.md) when the workstream is finished.
@@ -68,7 +68,7 @@ pi agent's `--command`. mu never reads them back.
68
68
  | --- | --- | --- |
69
69
  | `MU_DELEGATE` | `0` hides the `mu_delegate` tool. `mu doctor` reports it. | unset: shown |
70
70
  | `MU_DELEGATE_MAX` | Most delegates one pi session runs at once. Further calls queue, up to four times this; past that `mu_delegate` refuses. A positive integer. | `16` |
71
- | `MU_NUDGE` | `0` turns off both nudges: the keep-driving nudge (one reminder when an orchestrator ends a turn with dispatched work still IN_PROGRESS) and the close nudge (one reminder when a mu-spawned pi worker ends a turn still owning an IN_PROGRESS task). `mu doctor` reports it. | unset: on |
71
+ | `MU_NUDGE` | `0` turns off the three nudges: the keep-driving nudge (one reminder when an orchestrator ends a turn with dispatched work still IN_PROGRESS), the close nudge (one reminder when a mu-spawned pi worker ends a turn still owning an IN_PROGRESS task), and the refute nudge (one notice when an orchestrator dispatched a task whose notes hold no `REFUTER`, `VERDICT:` or `REFUTE-EXEMPT:` line). A pi nested in an agent's pane (it found `MU_CTL_SOCK` served by another pi) gets none of them; a pi without UI (`pi -p`, json mode) gets no close or refute nudge. `mu doctor` reports it. | unset: on |
72
72
  | `MU_PI_HOME` | Root under which `mu link pi` writes `.pi/` and `.agents/`. | `$HOME` |
73
73
  | `PI_CODING_AGENT_DIR` | Read only. pi's agent dir, checked for a linked murmur extension. | `~/.pi/agent` |
74
74
  | `MU_EXTENSION_ENTRY` | Test only. Built extension the `mu link pi` shim imports. | `dist/extension/mu-pi.js` |
@@ -140,3 +140,24 @@ claimed a task for a worker, ended its turn, received one
140
140
  `[mu-keep-driving]` message with `mu task wait nudgeprobe/slow ...`, and
141
141
  `mu log --kind nudge` held one entry. With nothing IN_PROGRESS, no
142
142
  nudge fired.
143
+
144
+ ## Refute nudge (2026-10-05)
145
+
146
+ A sibling hook in the same file (`registerRefuteNudge`), registered on
147
+ its own: it does not need the keep-driving markers and has its own
148
+ once-per-prompt flag.
149
+
150
+ - **Arm** on `mu task claim <id> --for` (`dispatchedTasks`: the id is
151
+ the first positional that is not an option value; `<ws>/<id>`
152
+ splits). `review_*` tasks never arm: their brief is the fixed
153
+ reviewer template, judging work against an already-refuted brief.
154
+ - **Check** at a completed `agent_before_settle`: `mu task notes <id>
155
+ -w <ws> --json` per armed task. This is a second read beside
156
+ `mu state --json`. A task counts as refuted when a note has a line
157
+ starting `REFUTER `, `VERDICT:` or `REFUTE-EXEMPT:`; prose that only
158
+ mentions the words mid-line does not count.
159
+ - **Notify** once per prompt: one `custom_message`
160
+ (`customType: "mu-refute-brief"`, `display: true`) listing every
161
+ unrefuted `<ws>/<id>`, and `mu log --kind nudge 'refute: ...'`. No
162
+ `continue`: it informs, and the orchestrator decides. `MU_NUDGE=0`
163
+ turns it off.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mu-crew/mu",
3
- "version": "3.7.0",
3
+ "version": "3.8.0",
4
4
  "description": "A persistent, observable crew of pi agents running in one tmux or herdr session per workstream, coordinated through a built-in task DAG.",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: mu
3
- description: Manage AI agents in terminal-multiplexer panes (tmux or herdr) — from a single off-the-cuff helper to a persistent crew coordinated through a built-in task graph. Use when the user asks to "create/spin up a subagent to X", "run X in the background", "do this in parallel", "use one subagent per X to do Y", "kick off a helper to watch/investigate/draft X", or to spawn, send work to, observe, or coordinate one or many agents — especially work you'll keep talking to, long-lived or background agents, or anything needing a dependency graph. For zero-ceremony helpers use the reserved `scratch` workstream; for one-shot "fire and get a result back" use the `mu_delegate` tool — its pane is attachable while it runs.
3
+ description: Fresh-context subagents and crews in tmux/herdr panes. Check before you commit: before acting on a claim, root cause, plan, fix or brief, have a `mu_delegate` call refute it. Also for a fresh delegate's second look at your own work or diff; parallel read-only research, investigation or fan-out over files; background helpers (watchers, soak runs, agents you keep talking to); persistent crews with a task graph, review gates and merges. Outside pi, delegate through the reserved `scratch` workstream.
4
4
  ---
5
5
 
6
6
  # mu — Multi-agent orchestration
@@ -53,9 +53,9 @@ Recipe words, the same in every recipe:
53
53
  - **wave** — the units dispatched from one main revision; refresh
54
54
  workspaces before the next.
55
55
  - **finding** — one reported problem, recorded as an `OPEN/triage` task.
56
- - **verdict** — the last line of a check's answer:
57
- `VERDICT: CONFIRMED | REFUTED | UNVERIFIED <evidence>` (or ACCEPT |
58
- REJECT for a review gate).
56
+ - **verdict** — a check's answer: `VERDICT: <id> CONFIRMED | REFUTED |
57
+ UNVERIFIED <one line>` plus `EVIDENCE:` lines (or ACCEPT | REJECT for
58
+ a review gate).
59
59
  - **stop rule** — the command and condition that end a loop, written on
60
60
  the umbrella first ([loop-until-done](recipes/loop-until-done.md)).
61
61
 
@@ -65,13 +65,18 @@ Use mu for persistent helpers, parallel work, dependencies, gated review, or
65
65
  work that must survive context compaction. Stay in one context for tiny edits
66
66
  or inspection.
67
67
 
68
- ### Off-the-cuff helpers (`scratch`)
68
+ ### Quick checks and off-the-cuff helpers (`scratch`)
69
69
 
70
- For one-shot work inside pi, call `mu_delegate` (installed by `mu link pi`).
71
- Outside pi: spawn into the reserved `scratch` workstream (no task DAG,
72
- auto-created), `send --fresh --json`, then `mu agent wait --after-runs <its runs> --json`
73
- (`lastText`); without `--after-runs` a run that ends first hangs the wait.
74
- Recipes call either form a **delegate call**.
70
+ Before you act on a claim, root cause, plan, fix or brief, or to get a second
71
+ look at your own work or diff, make a [delegate call](recipes/tasks-or-calls.md#delegate-call)
72
+ that tries to refute it (briefs: see Orchestrator rules; many claims: [refute](recipes/refute.md)).
73
+
74
+ For one-shot work inside pi, call `mu_delegate` (installed by `mu link pi`). Outside pi: spawn
75
+ into the reserved `scratch` workstream (no task DAG, auto-created), `send --fresh --json`, then
76
+ `mu agent wait --after-runs <its runs> --json` (`lastText`); without `--after-runs` a run that
77
+ ends first hangs the wait. Recipes call either form a **delegate call**. A check judging a mu
78
+ task lands its verdict there: `record: { task: "<ws>/<id>" }` on `mu_delegate`, a hand-written
79
+ `REFUTER` note on the spawn path; with no task, skip both.
75
80
 
76
81
  - `mu agent wait <names...> --first` waits for busy → idle instead of a
77
82
  `sleep` loop; exit 0 met, 5 timeout, 6 pane died.
@@ -173,11 +178,18 @@ rules hold even when you skip it:
173
178
  - **`mu task wait --first --on-stall exit`**; exit 7 means read the
174
179
  owner's pane and answer it.
175
180
  - **Stop a worker gently**: `mu agent abort`, then `kick`, then `close`.
181
+ To redirect a busy pi now: `send --interrupt` (`--steer` waits for
182
+ the running tool).
183
+ - **Send, don't note, a running worker**: it never sees new notes. The note is the durable copy
184
+ ([how to send](recipes/orchestrator-loop.md#sending)).
185
+ - **Refute a brief claiming a cause, fix, threshold or code fact** before dispatch:
186
+ one `record`ed refuter call; rewrite on AMEND. Others get a `REFUTE-EXEMPT: <why>` note.
176
187
  - **Checks are calls, not tasks.** Refuters, claim checkers, judges and
177
188
  skeptics are [delegate calls](recipes/tasks-or-calls.md#delegate-call)
178
189
  (`mu_delegate`, or a `scratch` spawn without it), all issued in one
179
- turn; their verdict lands on the task they judged. `mu_delegate` runs
180
- `MU_DELEGATE_MAX` (default 16) at once and queues the rest.
190
+ turn. Refuters and claim checkers pass `record` (or hand-write the
191
+ `REFUTER` note) to land the verdict on the task; judges report
192
+ `WINNER:`. `MU_DELEGATE_MAX` (16) run at once; the rest queue.
181
193
  - **Findings are tasks.** A reviewer's or auditor's findings become
182
194
  `mu task add --triage` tasks blocking the review, decided with
183
195
  `mu task accept` or `close --as rejected|duplicate`. A review nobody
@@ -196,6 +208,8 @@ rules hold even when you skip it:
196
208
  - **For waits use `task wait`, not `log --tail`.** `mu log --kind <k>`
197
209
  entries are your own durable state (a ledger); `--intent` filters what
198
210
  mu recorded.
211
+ - **Prose in notes, prompts, `--why` or `--evidence`**: an apostrophe
212
+ ends `'...'`; use a quoted heredoc ([brief](recipes/brief.md#quoting)).
199
213
  - **`mu sql`** does not pull synced ops; run any other mu command first.
200
214
  - **Never put `MU_DB_PATH` inside `MU_SYNC_DIR`**: it corrupts the DB.
201
215
  - Before undo, rebuild, teardown (dry-run without `--yes`), sync setup,
@@ -212,9 +226,15 @@ export MU_PI_COMMAND="pi --model sonnet:medium"
212
226
  mu agent spawn a --cli pi_big # uses $MU_PI_BIG_COMMAND
213
227
  ```
214
228
 
215
- Convention: `pi_mini` for probing, `pi` for build and refactor,
216
- `pi_big` for design, review, and incidents. List models with
217
- `pi --list-models [search]`.
229
+ `pi_mini` scouts and finds, `pi` builds and refactors, `pi_big` designs and
230
+ handles incidents. A reviewer, refuter,
231
+ judge or auditor checks level or up, never down: at least the worker's
232
+ capability, from another family if one exists (provider or model-id prefix in
233
+ `pi --list-models`). Small-model signs: a local runtime (ollama, lmstudio,
234
+ llama.cpp), a size (`4b`, `27b`), mini/haiku/flash/nano/lite, thinking `no`,
235
+ context or max-out far below the worker's. No such peer: same model, fresh
236
+ context, higher thinking unless at max. Probe an unused model first
237
+ (`pi --model <provider/model> -p "say ok"`); `ctl: ok` does not test it.
218
238
 
219
239
  ## Reaper and agent state
220
240
 
@@ -257,6 +277,7 @@ risky job that needs several of them, start with
257
277
  | [remote-workers](recipes/remote-workers.md) | an agent runs on another machine |
258
278
  | [waves](recipes/waves.md) | more than one worker edits the same repo at once |
259
279
  | [long-run](recipes/long-run.md) | a task or proof runs for hours, or must survive flakes |
280
+ | [drift-audit](recipes/drift-audit.md) | hours into a long run, after a batch, compaction or resume, before a stop rule |
260
281
  | [watcher](recipes/watcher.md) | a helper polls a PR, CI, or log for change |
261
282
  | [findings](recipes/findings.md) | any review, audit, or check reports problems: where they live, how they are triaged |
262
283
  | [tasks-or-calls](recipes/tasks-or-calls.md) | a recipe step spawns an agent: DAG task or delegate call |
@@ -28,9 +28,8 @@ PR or diff from several angles, use [review-panel](review-panel.md).
28
28
  3. **Dispatch the work** as usual. When `x` closes, `review_x` becomes
29
29
  ready.
30
30
  4. **Spawn a fresh reviewer.** A new agent, or `--fresh` on an idle one,
31
- so it has none of the author's reasoning. Use a different model from
32
- the worker where you can (`--cli pi_big` reviewing `pi`): a second
33
- model has different blind spots. Give it the worker's workspace path
31
+ so it has none of the author's reasoning, on a model chosen per
32
+ [Models](../SKILL.md#models-and-thinking-effort). Give it the worker's workspace path
34
33
  and commits read-only by instruction; it edits nothing.
35
34
  5. **Claim `review_x` for the reviewer**, send the brief (below), wait.
36
35
  6. **Act on the verdict:**
@@ -71,11 +70,20 @@ where it fails the acceptance criteria in `mu task notes <x>`.
71
70
  UNVERIFIED for it. Unverified is not a pass and not a fail.
72
71
  - Do not edit files.
73
72
 
74
- End with a note in the task note contract, then close with the verdict:
75
- ACCEPT: mu task close review_<x> --evidence '<key check>'
76
- REJECT: mu task close review_<x> --as rejected --why '<gap ids>'
73
+ Write the verdict note (FILES/COMMANDS/FINDINGS/VERIFIED), then close
74
+ (long reasons: a heredoc variable, brief.md § Quoting):
75
+ ACCEPT: mu task close review_<x> --evidence "<key command>: <result>"
76
+ REJECT: mu task close review_<x> --as rejected \
77
+ --why "<n> gaps: <id> (<why it fails, one clause>), ..."
77
78
  ```
78
79
 
80
+ mu adds each named gap's title to the REJECTED note.
81
+
82
+ **Closing a review yourself** (you fixed a finding, or accept without a
83
+ fresh reviewer): write the reviewer's note plus `COMMIT: <sha> fixes
84
+ <finding>`, and name the check in `--evidence`. Not `one finding fixed
85
+ by orchestrator`.
86
+
79
87
  ## Traps
80
88
 
81
89
  - **A reviewer that sees the author's reasoning agrees with it.** Send
@@ -32,6 +32,7 @@ not in the brief, or one pointer away, does not exist for it.
32
32
  6. **Ask for evidence, not reassurance.** The final note names the
33
33
  commands run and their exit codes (the task note contract). "Done,
34
34
  all good" is not a result.
35
+ 7. **A brief that makes claims is refuted before dispatch** ([SKILL.md](../SKILL.md#orchestrator-rules)).
35
36
 
36
37
  ## Shape
37
38
 
@@ -46,8 +47,35 @@ VERIFY: <exact command>
46
47
 
47
48
  ⚠️ FINAL ACTION
48
49
  git commit -am '<msg>' THEN
49
- mu task close <id> -w <ws> --evidence '<command + result>'
50
+ mu task close <id> -w <ws> --evidence "$v" # command + result; see Quoting
50
51
  ```
51
52
 
53
+ ## Quoting
54
+
55
+ Single quotes suit short text with no `'`. Prose (an apostrophe ends
56
+ `'...'`; `"..."` expands `$` and backticks) goes through stdin with a
57
+ quoted heredoc, stored verbatim:
58
+
59
+ ```bash
60
+ mu task note <id> -w <ws> - <<'EOF'
61
+ it's fine: $HOME and `pwd` stay literal
62
+ EOF
63
+ ```
64
+
65
+ `mu task add --note -`, `--text -` and `mu agent send <a> -` read stdin
66
+ the same way. `--why` and `--evidence` have no `-` form: capture the
67
+ heredoc in a variable and double-quote it:
68
+
69
+ ```bash
70
+ v=$(cat <<'EOF'
71
+ it's fine: $HOME stays literal
72
+ EOF
73
+ )
74
+ mu task close <id> -w <ws> --evidence "$v"
75
+ ```
76
+
77
+ Chain batched adds with `&&`, then `mu task list`, so one
78
+ quoting failure cannot half-apply the batch unseen.
79
+
52
80
  Done when a worker with no other context could finish the task from
53
81
  the brief and the notes it points at, and could tell when it is done.
@@ -62,16 +62,24 @@ the `scratch` form through `tools.bash`:
62
62
 
63
63
  ```js
64
64
  // @options: {"timeout_ms": 1800000}
65
+ const ws = "audit";
65
66
  const findings = ["f_auth_put", "f_sql_order"]; // finding task ids, at most the cap per script
66
67
  const sh = async (c) => (await tools.bash({ command: c })).output;
68
+ const json = (s) => { try { return JSON.parse(s); } catch { return {}; } };
69
+ const el = (ms) => { const t = Math.round(ms / 1000); return t < 60 ? `${t}s` : `${Math.floor(t / 60)}m ${String(t % 60).padStart(2, "0")}s`; };
67
70
  const rows = await Promise.all(findings.map(async (id, i) => {
68
- const a = `refuter-${i + 1}`;
71
+ const a = `refuter-${i + 1}`, t0 = Date.now();
69
72
  await sh(`mu agent spawn ${a} -w scratch`);
70
- const { runs } = JSON.parse(await sh(`mu agent send ${a} -w scratch --fresh --json '<brief for ${id}: ... end with VERDICT: ...>'`));
71
- const w = JSON.parse(await sh(`mu agent wait ${a} -w scratch --after-runs ${runs} --json --timeout 1200`));
72
- const text = w.agents?.[0]?.lastText ?? "";
73
- await sh(`mu agent close ${a} -w scratch`);
74
- return `${id}\t${(text.match(/VERDICT:.*$/m) ?? ["VERDICT: UNVERIFIED no verdict line"])[0]}`;
73
+ const { runs } = json(await sh(`mu agent send ${a} -w scratch --fresh --json - <<'MU_EOF'\n<brief for ${id}, ending with the VERDICT block>\nMU_EOF`));
74
+ const w = json(await sh(`mu agent wait ${a} -w scratch --after-runs ${runs} --json --timeout 1200`)).agents?.[0] ?? {};
75
+ const lines = (w.lastText ?? "").trimEnd().split("\n");
76
+ const at = lines.findLastIndex((l) => /^[\s>*_`-]*VERDICT:/.test(l));
77
+ const note = w.outcome !== "done" ? `REFUTER ${i + 1}: no verdict (${w.outcome ?? "no result"})`
78
+ : `REFUTER ${i + 1} (${a}, ${el(Date.now() - t0)}):\n` + (at < 0 ? `NO VERDICT LINE: ${(w.lastText ?? "").slice(-1500)}`
79
+ : [lines[at], ...lines.slice(at + 1).filter((l) => /^[\s>*_`-]*EVIDENCE:/.test(l))].join("\n"));
80
+ await sh(`mu task note ${id} -w ${ws} - <<'MU_EOF'\n${note}\nMU_EOF`);
81
+ if (w.outcome === "done") await sh(`mu agent close ${a} -w scratch`);
82
+ return `${id}\t${note.split("\n")[1] ?? note}`;
75
83
  }));
76
84
  return rows.join("\n");
77
85
  ```
@@ -82,9 +90,11 @@ What the tool did for you, the script now owns:
82
90
  each script's list at or under it.
83
91
  - **Cleanup.** Close each scratch pane; a timed-out one stays, readable
84
92
  with `mu agent read <a> -w scratch`.
85
- - **Recording.** The table is not the record. Record each verdict on its
86
- finding (`mu task accept`, `close --as rejected --why`), from the
87
- script or after it returns.
93
+ - **Recording.** The script writes each `REFUTER` note in `record`'s
94
+ shape, and `no verdict (<outcome>)` for a failed wait; it does not
95
+ cap long answers or special-case empty ones as `record` does. Decide
96
+ each finding after it returns ([findings § Triage](findings.md#triage)
97
+ step 3).
88
98
 
89
99
  ## Traps
90
100
 
@@ -41,12 +41,13 @@ answer rests on is an `OPEN/triage` task, decided like a finding
41
41
  gets the claim and its sources, not the searcher's reasoning, fetches
42
42
  the source itself, and tries to break the claim: is the quote there,
43
43
  does it say that, is it current, does another source disagree? It
44
- ends with `VERDICT: c4 CONFIRMED | REFUTED | UNVERIFIED <evidence>`.
44
+ ends with the VERDICT block
45
+ ([tasks-or-calls § Delegate call](tasks-or-calls.md#delegate-call)).
45
46
  For contradicted claims, one checker per side. Batch under the cap
46
- ([orchestrator-loop § Concurrency](orchestrator-loop.md#concurrency)). In workstream mode the
47
- verdict is the claim task's state: `accept` for confirmed,
48
- `close --as rejected` for refuted, an `UNVERIFIED:` note in triage
49
- otherwise.
47
+ ([orchestrator-loop § Concurrency](orchestrator-loop.md#concurrency)).
48
+ In workstream mode the checker passes `record` on the claim task, and
49
+ the verdict is decided as in [findings § Triage](findings.md#triage)
50
+ step 3.
50
51
  5. **Synthesize** once every checker has answered (in workstream mode,
51
52
  when no claim task is left undecided): answer the question
52
53
  from confirmed claims only, each with its source URL. List
@@ -0,0 +1,72 @@
1
+ # Drift audit
2
+
3
+ Use during a long run to catch a crew drifting from what you decided:
4
+ an instruction that never reached its worker, a batch run on stale
5
+ inputs, work continuing on a closed task. One read-only
6
+ [delegate call](tasks-or-calls.md#delegate-call); it edits nothing.
7
+
8
+ ## When
9
+
10
+ - every few hours of a long run;
11
+ - after each batch report of a chained task (sweep, campaign, soak);
12
+ - after a context compaction or a resume;
13
+ - before you declare a stop rule met.
14
+
15
+ ## Run
16
+
17
+ Issue one delegate call with `record: { task: "<ws>/<umbrella>" }` and
18
+ this brief, with `<ws>` filled in:
19
+
20
+ ```text
21
+ Read-only drift audit of mu workstream <ws>. Change nothing. Sources:
22
+ mu state -w <ws>; mu task list -w <ws> --json; mu task notes <id> -w <ws>;
23
+ mu sql (ops, tasks, task_notes, agents); git log in the repo and in each
24
+ owner's workspace. Never judge by a capped mu log -n window: the claim
25
+ or close you compare against is often older than it.
26
+ 1. Unsent notes: notes on IN_PROGRESS tasks, by someone other than the
27
+ owner, after the owner's last task.claim or agent.send op:
28
+ mu sql "select t.local_id task, a.name owner, n.created_at, substr(n.content,1,80) note from tasks t join workstreams w on w.id=t.workstream_id join agents a on a.id=t.owner_id join task_notes n on n.task_id=t.id where w.name='<ws>' and t.status='IN_PROGRESS' and coalesce(n.author,'')<>a.name and n.created_at > max(coalesce((select max(o.created_at) from ops o where o.intent='task.claim' and o.key='<ws>/'||t.local_id),''), coalesce((select max(o.created_at) from ops o where o.intent='agent.send' and o.key='<ws>' and o.payload like 'agent send '||a.name||' (%'),'')) order by n.created_at"
29
+ Quote each task's last claim (seq, time). For each instruction it
30
+ lists: did the owner act on it (later commits, notes, pane)? Also read
31
+ mu sql "select seq, created_at, payload from ops where intent='agent.send' and key='<ws>'"
32
+ and flag mode=plain or mode=steer with state=busy since=<t> long
33
+ before the send: it queued behind a long turn and may not have landed.
34
+ 2. Stale pins: for each build/binary sha or oracle commit named in an
35
+ IN_PROGRESS note, and each merged fix its task's findings depend on:
36
+ git merge-base --is-ancestor <fix> <pinned>. Exit 1 = pin is stale.
37
+ 3. Closed-task notes: notes added after the task's last task.close op.
38
+ Report each of the top 5 by count as its own finding with its count,
39
+ even when the notes look routine. A late note that reports the
40
+ closed bug again usually ran on a build without the closing fix:
41
+ check its sha with git merge-base --is-ancestor <fix> <sha>.
42
+ mu sql "select t.local_id name, count(*) late from tasks t join workstreams w on w.id=t.workstream_id join task_notes n on n.task_id=t.id where w.name='<ws>' and t.status='CLOSED' and n.created_at > (select max(o.created_at) from ops o where o.intent='task.close' and o.key='<ws>/'||t.local_id) group by t.local_id order by late desc"
43
+ 4. Thin decisions: an ACCEPT/REJECTED/CLOSE/SUPERSEDED note under 40
44
+ chars; a refuter tally with no REFUTER or VERDICT note; a review the
45
+ orchestrator closed with no FILES/COMMANDS note and no commit.
46
+ 5. Silent tasks: IN_PROGRESS with no note or commit for longer than
47
+ the cadence its notes state (default 2h).
48
+ 6. Missing work: a task or finding a note or log line says will be
49
+ filed that does not exist.
50
+ 7. Unrefuted briefs: IN_PROGRESS tasks with no note line starting
51
+ REFUTER , VERDICT: or REFUTE-EXEMPT:.
52
+ End with exactly this, one EVIDENCE line per finding (none when clean):
53
+ VERDICT: <ws> DRIFT-AUDIT CLEAN | FINDINGS <n>
54
+ EVIDENCE: FINDING <high|med|low> <file:line|task> <what> (check <1-7>)
55
+ ```
56
+
57
+ `record` keeps only the VERDICT line and the `EVIDENCE:` lines after
58
+ it, so each finding must be an `EVIDENCE: FINDING` line. The note
59
+ lands headed `REFUTER`, which the refute nudge and decision warnings
60
+ read as a recorded verdict; on an umbrella that is harmless.
61
+
62
+ ## Act
63
+
64
+ Each finding becomes one of:
65
+
66
+ - an `OPEN/triage` task blocking the umbrella ([findings § Record](findings.md#record));
67
+ - a send to the worker it names, per
68
+ [orchestrator-loop § Sending](orchestrator-loop.md#sending)
69
+ (`--interrupt` when its current work is wasted, as with a stale pin).
70
+
71
+ Done when the verdict is recorded on the umbrella and every finding
72
+ is a task or a delivered send.
@@ -69,24 +69,33 @@ across reviewers and creates one task per real problem.
69
69
  or several reviewers on one change: a `triage_<umbrella>` task, run
70
70
  by a fresh agent, so the orchestrator reads only its result.
71
71
  2. **Refute** each finding before accepting it with a
72
- [delegate call](tasks-or-calls.md#delegate-call): a fresh agent tries
73
- to prove it false and ends with
74
- `VERDICT: <id> CONFIRMED | REFUTED | UNVERIFIED <evidence>`. Skip this
75
- for low-severity style findings. High severity: three refuters,
76
- accept only if at least two say CONFIRMED.
77
- 3. **Decide** each one; the decision is the task's state:
78
-
79
- | Decision | Command |
80
- | --- | --- |
81
- | real work | `mu task accept <id> --evidence '<what confirmed it>'` |
82
- | false | `mu task close <id> --as rejected --why '<evidence>'` |
83
- | same as another | `mu task close <id> --as duplicate --why 'same as <id>'` |
84
- | valid, not worth it | `mu task close <id> --as wontfix --why '<reason>'` |
85
- | could not check | stays in triage, with an `UNVERIFIED: <why>` note |
72
+ [delegate call](tasks-or-calls.md#delegate-call) that passes
73
+ `record: { task: "<ws>/<id>" }`: a fresh agent tries to prove it
74
+ false, and its VERDICT block lands on the finding as a
75
+ `REFUTER <label> (<delegate>, <elapsed>):` note (VERDICT line, 3-6
76
+ EVIDENCE lines), or `REFUTER <label>: no verdict (<outcome>)` when
77
+ it times out or dies. Without `mu_delegate`, write that note yourself
78
+ before deciding
79
+ ([tasks-or-calls § Delegate call](tasks-or-calls.md#delegate-call)).
80
+ Skip this for low-severity style findings. High severity: three
81
+ refuters, accept only if at least two say CONFIRMED.
82
+ 3. **Decide** each one; the decision is the task's state. The reason
83
+ names the deciding evidence, not a tally or a judgement. mu warns
84
+ when a reason is under 40 chars and no note holds a `VERDICT:` line
85
+ or `REFUTER` note. Prose reasons go through a heredoc variable
86
+ ([brief § Quoting](brief.md#quoting)).
87
+
88
+ | Decision | Command | Bad reason |
89
+ | --- | --- | --- |
90
+ | real work | `mu task accept <id> --evidence "2/3 CONFIRMED: refuters 1 and 3 show the checklist:3 vs :33 cycle"` | `valid`, `orchestrator: valid`, `2/3 CONFIRMED` |
91
+ | false | `mu task close <id> --as rejected --why "<the guard or command that disproves it>"` | `refuter REFUTED` |
92
+ | same as another | `mu task close <id> --as duplicate --why "same as <id>"` | |
93
+ | valid, not worth it | `mu task close <id> --as wontfix --why "<cost vs impact>"` | `not worth it` |
94
+ | could not check | stays in triage, with an `UNVERIFIED: <why>` note | |
86
95
 
87
96
  4. **Group** accepted findings that touch the same files into one fix
88
97
  task when separate workers would collide; close the grouped findings
89
- `--as superseded --why 'fixed in <fix-task>'`.
98
+ `--as superseded --why "fixed in <fix-task>"`.
90
99
 
91
100
  Done when `mu task list --substate triage -w <ws>` lists only findings
92
101
  with an `UNVERIFIED` note, and each of those is reported to the human.
@@ -23,9 +23,11 @@ form independent theories; separate agents then try to kill them.
23
23
  3. **Test each hypothesis.** An experiment that changes code (force the
24
24
  race, revert the commit) needs a workspace: `mu task accept` the
25
25
  hypothesis and dispatch it as a task. One that only reads (feed the
26
- data, grep the logs) is a delegate call. A killed hypothesis closes
27
- `--as rejected --why '<evidence>'`; a survivor gets a
28
- `SURVIVED: <evidence>` note.
26
+ data, grep the logs) is a delegate call with `record` on the
27
+ hypothesis ([tasks-or-calls § Delegate call](tasks-or-calls.md#delegate-call)).
28
+ A killed hypothesis closes `--as rejected --why "<the observation
29
+ that contradicts PREDICTS>"`; a survivor gets a `SURVIVED: <evidence>`
30
+ note. Prose reasons: [brief § Quoting](brief.md#quoting).
29
31
  4. **If one survives**, add a fix task for it, then a review task
30
32
  ([adversarial-review](adversarial-review.md)). The fix is accepted
31
33
  only if the step 1 repro stops reproducing.