@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.
- package/dist/cli.js +10797 -10500
- package/dist/cli.js.map +1 -1
- package/dist/extension/mu-pi.js +266 -39
- package/dist/extension/mu-pi.js.map +1 -1
- package/dist/index.d.ts +39 -9
- package/dist/index.js +1678 -1580
- package/dist/index.js.map +1 -1
- package/docs/ARCHITECTURE.md +4 -3
- package/docs/architecture/control-socket.md +8 -3
- package/docs/guide/delegate.md +18 -0
- package/docs/guide/dispatch.md +17 -3
- package/docs/guide/getting-started.md +1 -1
- package/docs/reference/env.md +1 -1
- package/docs/specs/2026-10-03-keep-driving-nudge.md +21 -0
- package/package.json +1 -1
- package/skills/mu/SKILL.md +36 -15
- package/skills/mu/recipes/adversarial-review.md +14 -6
- package/skills/mu/recipes/brief.md +29 -1
- package/skills/mu/recipes/codemode-driver.md +19 -9
- package/skills/mu/recipes/deep-research.md +6 -5
- package/skills/mu/recipes/drift-audit.md +72 -0
- package/skills/mu/recipes/findings.md +24 -15
- package/skills/mu/recipes/hypothesis-panel.md +5 -3
- package/skills/mu/recipes/long-run.md +13 -3
- package/skills/mu/recipes/loop-until-done.md +4 -3
- package/skills/mu/recipes/orchestrator-loop.md +19 -10
- package/skills/mu/recipes/plan.md +10 -6
- package/skills/mu/recipes/refute.md +3 -2
- package/skills/mu/recipes/review-panel.md +2 -2
- package/skills/mu/recipes/rules-audit.md +10 -5
- package/skills/mu/recipes/tasks-or-calls.md +39 -26
- package/skills/mu/recipes/tournament.md +4 -3
- package/skills/mu/recipes/ultrathink.md +2 -2
- package/skills/mu/recipes/worker.md +3 -2
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -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`, `--
|
|
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
|
|
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
|
package/docs/guide/delegate.md
CHANGED
|
@@ -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:
|
package/docs/guide/dispatch.md
CHANGED
|
@@ -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
|
|
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
|
|
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.
|
package/docs/reference/env.md
CHANGED
|
@@ -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
|
|
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.
|
|
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"
|
package/skills/mu/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: mu
|
|
3
|
-
description:
|
|
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** —
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
###
|
|
68
|
+
### Quick checks and off-the-cuff helpers (`scratch`)
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
|
180
|
-
`
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
|
32
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
|
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 } =
|
|
71
|
-
const w =
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
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
|
|
86
|
-
|
|
87
|
-
|
|
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
|
|
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)).
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
|
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
|
|
27
|
-
|
|
28
|
-
|
|
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.
|