@mu-crew/mu 3.6.1 → 3.7.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.
@@ -28,8 +28,8 @@ reply carries `v: 1`; the client (`src/ctl/client.ts`) throws
28
28
  | op | does |
29
29
  | --- | --- |
30
30
  | `hello` | identity plus `ops` (served ops) and `extVersion` (the mu version the extension was built from) |
31
- | `status` | `state` (`busy` / `idle` / `needs_input`), `since`, `runs`, `pending` |
32
- | `send` | `pi.sendUserMessage`; when busy, `mode` is `steer` or `followUp` (default) |
31
+ | `status` | `state` (`busy` / `idle`), `since`, `runs`, `pending` |
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
35
  | `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` |
@@ -104,7 +104,10 @@ as a capability, not a name check:
104
104
  - **tmux.** After `MU_SPAWN_LIVENESS_MS` (default 1500, `0` skips) mu
105
105
  verifies the pane survived and scans its tail for provider or auth
106
106
  startup errors. mu does not wait for murmur to claim the pane: state
107
- reads before the first claim report `unknown`.
107
+ reads before the first claim report `unknown`. A pi agent skips the
108
+ fixed wait: mu checks the pane on every handshake tick (a dead pane
109
+ rolls back at once) and scans for startup errors when the handshake
110
+ ends.
108
111
  - **herdr.** mu creates a bare pane and calls `startAgentInPane`, which
109
112
  subsumes both checks.
110
113
  - **pi, either backend.** After the agent starts, mu polls its control
@@ -51,15 +51,19 @@ From a script or other shell, run what the tool runs:
51
51
 
52
52
  ```bash
53
53
  mu agent spawn helper-1 -w scratch
54
- mu agent send helper-1 -w scratch --fresh 'Investigate why foo.spec.ts fails. Report the cause.'
55
- mu agent wait helper-1 -w scratch --json
54
+ runs=$(mu agent send helper-1 -w scratch --fresh --json 'Investigate why foo.spec.ts fails. Report the cause.' | jq .runs)
55
+ mu agent wait helper-1 -w scratch --after-runs "$runs" --json
56
56
  ```
57
57
 
58
+ `--after-runs` waits for the first run past the count the send
59
+ reported, so a run that settles before the wait starts still counts.
60
+
58
61
  Use `--fresh`: a plain send to a reused agent carries the previous
59
62
  task's context.
60
63
 
61
64
  `mu agent wait` fires when the agent leaves busy (including to
62
- `needs_input`), never on an already idle agent. For pi, the `--json`
65
+ `needs_input`), never on an already idle agent unless it finished a run
66
+ past `--after-runs`. For pi, the `--json`
63
67
  row carries:
64
68
 
65
69
  - `outcome`: `done`, `empty`, `error`, `died`, `timeout`, or `pending`.
@@ -114,12 +114,15 @@ mu task wait auth/build auth/docs --first --on-stall exit --json
114
114
  commits. If the worker closed without committing, it says so.
115
115
  - Exit codes: `0` met, `5` timeout, `6` the owner's pane died,
116
116
  `7` the owner sat in `needs_input` for `--stuck-after` seconds
117
- (default 120). Exit 7 is the default; `--on-stall warn` keeps
117
+ (default 120, or 5 for a pi worker read over its control socket). Exit 7 is the default; `--on-stall warn` keeps
118
118
  polling instead.
119
119
 
120
120
  Exit 7 has several causes: the worker finished but did not close, it
121
- asked a question, or it hit a prompt. In each case, run
122
- `mu agent read <owner>` first.
121
+ asked a question, or it hit a prompt. In each case, run the exit's
122
+ `Next:` first. For a pi owner, it starts with
123
+ `mu agent wait <owner> --after-runs <runs-1> --json`, which prints the
124
+ last answer at once. Then it shows `mu agent read <owner>`, for the
125
+ dialogs and crashes that only the pane shows.
123
126
 
124
127
  Pipeline the work: merge one task, run your tests on the merged tree,
125
128
  then dispatch the next task. Waiting for a whole wave hides partial
@@ -126,7 +126,7 @@ The exit code tells you what happened:
126
126
  | `0` | the task closed | merge the work |
127
127
  | `5` | `--timeout` expired (default 600 s) | wait again |
128
128
  | `6` | the worker's pane died and the task went back to `OPEN` | spawn and claim again |
129
- | `7` | the worker sat in `needs_input` for 2 minutes | `mu agent read worker-1 -w auth`, then answer it |
129
+ | `7` | the worker sat in `needs_input` for `--stuck-after` seconds (120, or 5 for a pi worker read over its control socket) | run its `Next:` (for a pi worker, `mu agent wait worker-1 --after-runs <runs-1> --json -w auth` returns the last answer; otherwise `mu agent read worker-1 -w auth`), then answer it |
130
130
 
131
131
  `--on-stall exit` is the default; it is spelled out above so older mu
132
132
  versions behave the same. With `--on-stall warn`, a stalled worker only
@@ -41,22 +41,27 @@ back to pasting.
41
41
 
42
42
  | State | Means | Fix |
43
43
  | ----- | ----- | --- |
44
- | `ctl missing` | no socket: the extension is not loaded | run `mu link pi`, then respawn the agent |
44
+ | `ctl missing` | no socket: the extension is not loaded, or a new pi waits at its trust prompt | answer the [trust prompt](#a-new-pi-agent-shows-ctl-missing), or run `mu link pi`, then respawn the agent |
45
45
  | `ctl refused` | the socket exists but does not answer | respawn the agent; for a remote agent, check the ssh forward |
46
- | `extension X older than installed Y` | pi loaded an older mu build | type `/reload` in the pane, or respawn |
46
+ | `extension X older than installed Y` or `extension lacks ops` | pi loaded an older mu build | send `/reload`, or respawn |
47
47
 
48
48
  To send `/reload` without attaching:
49
49
 
50
50
  ```bash
51
- mu agent send worker-1 -w auth '/reload' --via mux
51
+ mu agent send worker-1 -w auth '/reload' # extension serves op command
52
+ mu agent send worker-1 -w auth '/reload' --via mux # older: type it into the pane
52
53
  ```
53
54
 
54
- `/reload` keeps the pi session. A `send --fresh` or `abort` against an
55
+ The error names the right form. `/reload` keeps the pi session. A `send --fresh` or `abort` against an
55
56
  outdated extension fails with exit 4 and prints the same two fixes.
56
57
 
57
- ## A new pi agent is stuck at `needs_input`
58
+ ## A new pi agent shows `ctl missing`
58
59
 
59
- pi asks whether to trust a project folder on first start. Run
60
+ pi asks whether to trust a project folder on first start. It asks
61
+ before it loads extensions, so the agent has no control socket yet:
62
+ mu shows `unknown (ctl missing)` and spawn warns after 30 s. A pi
63
+ launched through a wrapper whose name is not `pi` or `pi-meta` shows
64
+ `needs_input` instead. Run
60
65
  `/trust` in the pane once, or spawn with
61
66
  `--command 'pi --approve'`. `--approve` trusts every directory,
62
67
  including workspace forks, so use it only on your own code.
@@ -25,15 +25,15 @@ For agents other than pi, see [README § Install](../../README.md#install).
25
25
  ## Reload running pi agents
26
26
 
27
27
  A running pi keeps the extension it loaded at start. After an upgrade,
28
- type `/reload` in each agent's pane, or respawn the agent. From a
29
- shell:
28
+ send `/reload` to each agent, or respawn the agent. From a shell:
30
29
 
31
30
  ```bash
32
- mu agent send worker-1 -w auth '/reload' --via mux
31
+ mu agent send worker-1 -w auth '/reload' # extension serves op command
32
+ mu agent send worker-1 -w auth '/reload' --via mux # older extension
33
33
  ```
34
34
 
35
35
  Until then, the `ctl` row of `mu doctor` warns
36
- `extension X older than installed Y`. Ops the old extension lacks,
36
+ `extension X older than installed Y` or `extension lacks ops`. Ops the old extension lacks,
37
37
  such as `send --fresh`, fail with exit 4.
38
38
 
39
39
  ## Upgrade from 3.1 or earlier
@@ -29,7 +29,7 @@ milliseconds; `0` disables a wait unless noted.
29
29
  | --- | --- | --- |
30
30
  | `MU_<CLI>_COMMAND` | Executable for `--cli <cli>`; hyphens become underscores (`--cli pi-meta` reads `MU_PI_META_COMMAND`). May hold arguments. `--command` wins; the spawn line names the env var when it supplied the command. | the cli value |
31
31
  | `MU_SPAWN_CTL_MS` | Budget for the control-socket handshake with a new pi agent, polled every 250ms. On timeout spawn still exits 0, reports `ctl: missing` or `ctl: refused`, and warns. `--no-ctl` skips it per spawn. | `30000` |
32
- | `MU_SPAWN_LIVENESS_MS` | tmux only. Wait this long, then check the pane is alive and its scrollback has no startup error (auth failure, `command not found`). Failure rolls back the row and throws `AgentDiedOnSpawnError` or `AgentSpawnStartupError`. | `1500` |
32
+ | `MU_SPAWN_LIVENESS_MS` | tmux only. Wait this long, then check the pane is alive and its scrollback has no startup error (auth failure, `command not found`). Failure rolls back the row and throws `AgentDiedOnSpawnError` or `AgentSpawnStartupError`. A pi agent skips the wait: the pane is checked on each ctl handshake tick and scanned when it ends. | `1500` |
33
33
  | `MU_SPAWN_LOCK_TIMEOUT_MS` | How long a spawn waits for the per-workstream spawn lock. | `15000` |
34
34
 
35
35
  ## Send and state (tmux path)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mu-crew/mu",
3
- "version": "3.6.1",
3
+ "version": "3.7.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"
@@ -69,7 +69,8 @@ or inspection.
69
69
 
70
70
  For one-shot work inside pi, call `mu_delegate` (installed by `mu link pi`).
71
71
  Outside pi: spawn into the reserved `scratch` workstream (no task DAG,
72
- auto-created), `send --fresh`, then `mu agent wait --json` (`lastText`).
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.
73
74
  Recipes call either form a **delegate call**.
74
75
 
75
76
  - `mu agent wait <names...> --first` waits for busy → idle instead of a
@@ -78,8 +79,10 @@ Recipes call either form a **delegate call**.
78
79
  - One agent per independent unit; `--workspace` for any helper that may edit,
79
80
  build, or test the shared repo.
80
81
 
81
- A helper stuck at `needs_input` right after spawn likely hit pi's
82
- project trust prompt: add `--approve` to `MU_<CLI>_COMMAND`. Move off
82
+ A pi helper showing `unknown (ctl missing)` right after spawn (spawn
83
+ warns after 30 s) likely sits at pi's project trust prompt (through a
84
+ wrapper whose argv0 is not `pi`/`pi-meta` it shows `needs_input`): add
85
+ `--approve` to `MU_<CLI>_COMMAND`. Move off
83
86
  `scratch` when work gains dependencies or review gates.
84
87
 
85
88
  ## Mental model
@@ -67,8 +67,8 @@ const sh = async (c) => (await tools.bash({ command: c })).output;
67
67
  const rows = await Promise.all(findings.map(async (id, i) => {
68
68
  const a = `refuter-${i + 1}`;
69
69
  await sh(`mu agent spawn ${a} -w scratch`);
70
- await sh(`mu agent send ${a} -w scratch --fresh '<brief for ${id}: ... end with VERDICT: ...>'`);
71
- const w = JSON.parse(await sh(`mu agent wait ${a} -w scratch --json --timeout 1200`));
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
72
  const text = w.agents?.[0]?.lastText ?? "";
73
73
  await sh(`mu agent close ${a} -w scratch`);
74
74
  return `${id}\t${(text.match(/VERDICT:.*$/m) ?? ["VERDICT: UNVERIFIED no verdict line"])[0]}`;
@@ -54,12 +54,16 @@ keeps it.
54
54
  Use `--first --on-stall exit`: `--first` populates `.firing`, and
55
55
  `--on-stall exit` stops an unattended wait when a worker needs attention.
56
56
  Exit 6: the owner's pane died and the reaper reopened the task; exit 7:
57
- the owner sat in `needs_input`. Read that
58
- pane (`mu agent read <owner>`) and answer: the worker may be waiting on
59
- you.
60
-
61
- For an idle worker, read the pane, then answer, retry, or release its
62
- task. `MU_IDLE_THRESHOLD_MS` defaults to 5m. A remote owner: poll once
57
+ the owner sat in `needs_input`. Run the exit's `Next:` and answer: the
58
+ worker may be waiting on you. For a pi owner it leads with
59
+ `mu agent wait <owner> --after-runs <runs-1> --json`, which returns the
60
+ last answer at once. The pane read follows because dialogs and crashes
61
+ show only in the pane.
62
+
63
+ For an idle worker, read its last answer (the same `--after-runs`
64
+ wait for pi, else the pane), then answer, retry, or release its task.
65
+ `MU_IDLE_THRESHOLD_MS` defaults to 5m. A remote owner: for pi,
66
+ `mu agent wait --after-runs` first, then poll once; otherwise poll once
63
67
  per turn ([remote-workers](remote-workers.md#step-5-poll-once-per-turn)).
64
68
 
65
69
  ## Merging
@@ -44,7 +44,8 @@ mu agent spawn worker-1 -w big --command \
44
44
  mu task claim t1 -w big --for worker-1 --evidence 'remote on dev'
45
45
  mu agent send worker-1 -w big --fresh '...'
46
46
 
47
- # 5. WAIT: run the claim's one-shot Next: command once per turn
47
+ # 5. WAIT: pi: mu agent wait worker-1 -w big --after-runs <send's runs> --json,
48
+ # then the claim's Next: once. non-pi: run the claim's Next: once per turn
48
49
 
49
50
  # 6. COLLECT: fetch straight from the remote worktree
50
51
  git fetch "ssh://dev/~/ws/worker-1" HEAD && git cherry-pick FETCH_HEAD
@@ -72,13 +73,13 @@ retry next turn, not progress.
72
73
 
73
74
  ### Step 3: the control-socket forward
74
75
 
75
- Spawn reports `ctl ok` when all of these hold (`mu doctor` has a ctl
76
+ Spawn reports `ctl: ok` when all of these hold (`mu doctor` has a ctl
76
77
  row per agent):
77
78
 
78
79
  - The host has the mu extension: run `mu link pi` there.
79
80
  - The ssh is direct. `$MU_SSH_ARGS` sets `ControlMaster=no
80
81
  ControlPath=none`; a multiplexed ssh never binds the forward and spawn
81
- reports `ctl missing`.
82
+ reports `ctl: missing`.
82
83
  - sshd allows the forward. A refused forward kills the pane at spawn
83
84
  (`ExitOnForwardFailure=yes`). Check `AllowStreamLocalForwarding`.
84
85
 
@@ -88,6 +89,16 @@ plus a 10 s cache.
88
89
 
89
90
  ### Step 5: poll once per turn
90
91
 
92
+ The claim's `Next:` mule poll is mandatory. It proves the commit is on
93
+ the host and closes the task. For a pi worker, first block on
94
+ `mu agent wait <name> --after-runs <runs> --json`, with `runs` from
95
+ `mu agent send --json`. It settles exactly over the forwarded socket.
96
+ Without `--after-runs`, a worker that is already idle blocks until
97
+ `--timeout`. If the worker may already be done, pass `<runs-1>`: the
98
+ wait then returns the last answer at once. Then run `Next:` once instead
99
+ of on every turn. On exit 5 (timeout), or on a wait that returns while
100
+ the worker asks a question, run `Next:` anyway.
101
+
91
102
  - Never `sleep` in a tool call. Aborting the loop can leave remote work
92
103
  and a capped channel running.
93
104
  - Set a deadline before dispatch. At expiry, read the pane, then release
@@ -123,14 +134,20 @@ the final gate before the push that matters.
123
134
 
124
135
  ## Traps
125
136
 
126
- ### A spawn on an auth prompt looks healthy
137
+ ### A non-pi spawn on an auth prompt looks healthy
127
138
 
128
139
  With the channel busy, the attach ssh stopped at `Enter a passcode:`.
129
140
  Spawn succeeded, the agent showed `needs_input`, and the prompt went
130
- into the passcode field. Confirm a remote agent got the work
131
- (`mu agent read <name> -n 20`) before trusting it. Send nothing
141
+ into the passcode field. This applies to non-pi CLIs and to
142
+ `--via mux`, which paste into the pane. Confirm such an agent got the
143
+ work (`mu agent read <name> -n 20`) before trusting it. Send nothing
132
144
  sensitive to an unconfirmed pane.
133
145
 
146
+ A pi agent does not paste. Spawn reports `ctl: ok` only when pi
147
+ answers on the socket; otherwise it warns `ctl: missing` or
148
+ `ctl: refused`, and `mu agent send` fails instead of typing into the
149
+ pane.
150
+
134
151
  ### Use the host's real CLI command
135
152
 
136
153
  If your local `$MU_PI_COMMAND` is a wrapper such as `pi-meta
@@ -12,10 +12,14 @@ tool (another harness, a script):
12
12
 
13
13
  ```bash
14
14
  mu agent spawn refuter-1 -w scratch
15
- mu agent send refuter-1 -w scratch --fresh '<brief>'
16
- mu agent wait refuter-1 -w scratch --json # answer in lastText
15
+ runs=$(mu agent send refuter-1 -w scratch --fresh '<brief>' --json | jq .runs)
16
+ mu agent wait refuter-1 -w scratch --after-runs "$runs" --json # answer in lastText
17
17
  ```
18
18
 
19
+ `--after-runs` takes the run count the send reported, so a run that
20
+ finished before the wait started still counts. A plain wait would sit
21
+ until `--timeout`.
22
+
19
23
  - **It starts empty.** The brief carries everything: the finding or
20
24
  claim, file paths, the criteria, and the answer line to end with
21
25
  (`VERDICT: ...`, `WINNER: ...`). See [brief](brief.md).