@mu-crew/mu 1.6.0 → 2.0.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/README.md +11 -15
- package/dist/cli.js +2603 -2283
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +97 -121
- package/dist/index.js +2677 -2425
- package/dist/index.js.map +1 -1
- package/docs/ARCHITECTURE.md +36 -40
- package/docs/ROADMAP.md +20 -105
- package/docs/USAGE_GUIDE.md +23 -24
- package/docs/VISION.md +11 -21
- package/docs/VOCABULARY.md +22 -35
- package/docs/specs/2026-09-28-agent-state-from-murmur.md +333 -0
- package/package.json +1 -1
- package/skills/mu/REMOTE_WORKERS.md +31 -38
- package/skills/mu/SKILL.md +16 -21
- package/docs/.m-reflow-5f7exa_b.md +0 -509
- package/docs/.m-reflow-5v_034rw.md +0 -545
- package/docs/.m-reflow-7yaw8ubc.md +0 -513
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
# Agent state from murmur (mu 2.0.0, murmur 1.0.0)
|
|
2
|
+
|
|
3
|
+
Status: approved design, 2026-09-28. Task `crew/mu_status_from_murmur`.
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
mu works out what an agent is doing by reading the last 20 lines of its
|
|
8
|
+
pane (`src/detect.ts`), and stores the answer in `agents.status`. This fails
|
|
9
|
+
in three ways:
|
|
10
|
+
|
|
11
|
+
- **Remote workers.** The local pane holds an ssh client that renders a
|
|
12
|
+
nested tmux. `skills/mu/REMOTE_WORKERS.md` records wrong answers in both
|
|
13
|
+
directions in one session: a stall fired at 300 s on a worker that was
|
|
14
|
+
mid-turn, and `mu agent list` showed `needs_input` for a worker that was
|
|
15
|
+
working.
|
|
16
|
+
- **Wrappers.** Each pi wrapper with different chrome has needed its own
|
|
17
|
+
patch (the Braille-spinner fallback).
|
|
18
|
+
- **Stale copies.** The stored status is only as recent as the last
|
|
19
|
+
reconcile, and `mu agent show` needed a fix to re-detect before it printed.
|
|
20
|
+
|
|
21
|
+
murmur's pi extension reports state from inside the agent on its own host,
|
|
22
|
+
and it publishes the state on each pane (`@murmur_pane_state`) and across
|
|
23
|
+
machines (`murmur status --json`). mu already exports the variables that
|
|
24
|
+
murmur uses to recognise mu's workers (`MU_MANAGED_AGENT`, `MU_AGENT_NAME`,
|
|
25
|
+
`MU_WORKSTREAM`).
|
|
26
|
+
|
|
27
|
+
## Decision
|
|
28
|
+
|
|
29
|
+
**murmur is the answer for agent state.** mu owns the work: the DAG, task
|
|
30
|
+
owners, workspaces and panes. murmur owns what an agent is doing. mu does
|
|
31
|
+
not read pane text to decide agent state, and it does not store agent state.
|
|
32
|
+
|
|
33
|
+
Sources of runtime state, in order:
|
|
34
|
+
|
|
35
|
+
1. herdr's `paneStatus()` on the herdr backend. This is herdr's own report,
|
|
36
|
+
not scraping.
|
|
37
|
+
2. On tmux, murmur. The local read is the pane's `@murmur_pane_state` and
|
|
38
|
+
`@murmur_pane_since`. The remote read is `murmur status --json`, matched
|
|
39
|
+
on `agent_name` and `workstream`.
|
|
40
|
+
3. Otherwise `unknown`, and mu says why.
|
|
41
|
+
|
|
42
|
+
This follows ZEN rules 2 (report state, don't scrape it), 3 (absence means
|
|
43
|
+
absence) and 5 (standalone tools that compose). mu's core needs none of this
|
|
44
|
+
to work: the DAG, `claim`/`close`, `task wait`, workspaces, spawn, send and
|
|
45
|
+
read.
|
|
46
|
+
|
|
47
|
+
## Scope
|
|
48
|
+
|
|
49
|
+
In:
|
|
50
|
+
|
|
51
|
+
- A state resolver module that replaces every state read from
|
|
52
|
+
`detectPiStatus`:
|
|
53
|
+
- reconcile, including `mu agent list` and `mu state`
|
|
54
|
+
- `mu agent show`
|
|
55
|
+
- `mu agent wait`
|
|
56
|
+
- stall detection in `mu task wait`
|
|
57
|
+
- spawn readiness
|
|
58
|
+
- `computeAgentIdle`
|
|
59
|
+
- the TUI cards and popups
|
|
60
|
+
- Stop writing `agents.status` after the initial insert, and stop reading it
|
|
61
|
+
everywhere.
|
|
62
|
+
- A new `--json` shape for agents: `state` and `source`, with no `status`.
|
|
63
|
+
- `mu doctor` checks for murmur and its pi extension.
|
|
64
|
+
- Updates to the README, the skill, `REMOTE_WORKERS.md`, ARCHITECTURE,
|
|
65
|
+
DECISIONS and ROADMAP.
|
|
66
|
+
- murmur 1.0.0: a contract section in its ARCHITECTURE, and the release.
|
|
67
|
+
|
|
68
|
+
Out:
|
|
69
|
+
|
|
70
|
+
- The two input-timing checks in `send`. `awaitPaneQuiescence` waits out
|
|
71
|
+
pi's re-init modal, which eats the Enter. `isTextStranded` detects a
|
|
72
|
+
prompt that was typed but never submitted. Both are about typing into a
|
|
73
|
+
terminal, not about agent state, and murmur cannot see either. They keep a
|
|
74
|
+
private helper for spinner and work-marker text. It is used only by
|
|
75
|
+
`send`, returns no status, and is not exported.
|
|
76
|
+
- `detectSpawnStartupError` in `src/agents/spawn.ts`. It scans a new pane
|
|
77
|
+
for provider and auth errors (a dud spawn), which is a spawn failure, not
|
|
78
|
+
agent state. It stays for the same reason as the `send` checks.
|
|
79
|
+
- Dropping the `agents.status` column. mu has no migration path: v10 is both
|
|
80
|
+
the minimum and the current schema. The drop waits for the first change
|
|
81
|
+
that needs a v11 anyway, and ROADMAP records it.
|
|
82
|
+
- Reading murmur's SQLite files. Reading them would couple mu to murmur's
|
|
83
|
+
private schema.
|
|
84
|
+
- Any feature added to murmur for mu. murmur only documents what it already
|
|
85
|
+
publishes.
|
|
86
|
+
|
|
87
|
+
## Design
|
|
88
|
+
|
|
89
|
+
### Caller's view
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
const states = await readAgentStates(db, agents); // one tmux read, ≤1 murmur call
|
|
93
|
+
const s = states.get(agent); // { state: "busy", source: "murmur", since: 1790581553851 }
|
|
94
|
+
if (s.state === "unknown") hint(s.reason); // "murmur not installed" | ...
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`mu agent list --json`:
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
{ "name": "worker-1", "pane": "%4", "state": "busy", "source": "murmur",
|
|
101
|
+
"since": "2026-09-28T07:45:53Z" }
|
|
102
|
+
{ "name": "worker-9", "pane": "%9", "state": "unknown", "source": "none",
|
|
103
|
+
"reason": "murmur not installed" }
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### State resolver (`src/agent-state.ts`, new)
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
type RuntimeState = "busy" | "needs_input" | "needs_permission" | "unknown";
|
|
110
|
+
type StateSource = "murmur" | "herdr" | "none";
|
|
111
|
+
interface StateReading {
|
|
112
|
+
state: RuntimeState;
|
|
113
|
+
source: StateSource;
|
|
114
|
+
since: number | null; // ms; when the state last changed, from the source
|
|
115
|
+
reason?: string; // set when state is unknown
|
|
116
|
+
}
|
|
117
|
+
function readAgentStates(db: Db, agents: AgentRow[]): Promise<Map<AgentRow, StateReading>>;
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
- It takes a batch of agents, so a whole display costs one read.
|
|
121
|
+
- **herdr backend:** calls `paneStatus()` per pane, as today.
|
|
122
|
+
- **tmux, local:**
|
|
123
|
+
- One `list-panes -a -F '#{pane_id}\t#{@murmur_pane_state}\t#{@murmur_pane_since}'`
|
|
124
|
+
call.
|
|
125
|
+
- A pane with a value is read from murmur. A pane with no value is a
|
|
126
|
+
candidate for the remote read.
|
|
127
|
+
- murmur publishes `@murmur_pane_state` only for a pane its extension has
|
|
128
|
+
claimed (fixed in murmur `3c8c1a7`).
|
|
129
|
+
- **tmux, remote:** the agents with no local value.
|
|
130
|
+
- One `murmur status --json` call, cached per mu process for
|
|
131
|
+
`MURMUR_CACHE_MS` (10 s), so the 1 s poll in `mu agent wait` does not
|
|
132
|
+
start Node every tick. murmur limits its own ssh collect to one per
|
|
133
|
+
30 s ± 10 s.
|
|
134
|
+
- A row matches when `agent_name` and `workstream` are equal and `local`
|
|
135
|
+
is false.
|
|
136
|
+
- Zero matches gives `unknown`, with reason "murmur has no row". More
|
|
137
|
+
than one match gives `unknown`, with reason "ambiguous: N hosts". A
|
|
138
|
+
peer that murmur reports as stale gives `unknown`, with reason "remote
|
|
139
|
+
snapshot stale".
|
|
140
|
+
- **`since`:**
|
|
141
|
+
- For a local pane, it is `@murmur_pane_since`: the time the pane entered
|
|
142
|
+
its current state (see murmur 1.0.0 below).
|
|
143
|
+
- For a remote row, it is the row's `updated_at`. That is an
|
|
144
|
+
approximation, because it also moves on runtime updates. It is used
|
|
145
|
+
only for remote idle and stall timing, where scraping had no answer at
|
|
146
|
+
all.
|
|
147
|
+
- Only the remote path starts `murmur`. A machine with no remote workers
|
|
148
|
+
never runs it.
|
|
149
|
+
- **When murmur is missing:** no `murmur` on PATH, or the pi extension not
|
|
150
|
+
linked (`~/.pi/agent/extensions/murmur.ts` missing).
|
|
151
|
+
- Every tmux agent gets `unknown`, with reason "murmur not installed" or
|
|
152
|
+
"murmur pi extension not linked".
|
|
153
|
+
- The resolver checks this once per process.
|
|
154
|
+
|
|
155
|
+
### Mapping
|
|
156
|
+
|
|
157
|
+
| murmur | mu |
|
|
158
|
+
| --- | --- |
|
|
159
|
+
| `working` | `busy` |
|
|
160
|
+
| `idle` | `needs_input` |
|
|
161
|
+
| `done` | `needs_input` |
|
|
162
|
+
| `blocked` | `needs_permission` |
|
|
163
|
+
| `crashed` | `needs_input` |
|
|
164
|
+
|
|
165
|
+
- `done` and `idle` both mean "waiting for a prompt".
|
|
166
|
+
- `blocked` means waiting on a human answer, which is what a permission
|
|
167
|
+
dialog means.
|
|
168
|
+
- For `crashed`, pi is gone but the pane may still be alive. The mapping
|
|
169
|
+
makes waits and stall detection fire. Pane death stays with the reaper.
|
|
170
|
+
|
|
171
|
+
herdr's mapping does not change: `working` gives `busy`; `blocked` gives
|
|
172
|
+
`needs_permission`; `idle`, `done` and `unknown` give `needs_input`.
|
|
173
|
+
|
|
174
|
+
### Consumers
|
|
175
|
+
|
|
176
|
+
- **reconcile.** It keeps pruning dead panes and refreshing titles. It
|
|
177
|
+
stops detecting and writing status. `report-only` mode stays read-only.
|
|
178
|
+
- **`mu agent list`, `mu state`, `mu agent show`, TUI.** These show
|
|
179
|
+
`state` from the resolver. An `unknown` state shows `?` and a one-line
|
|
180
|
+
hint, printed once per invocation: "agent state needs murmur (see mu
|
|
181
|
+
doctor)".
|
|
182
|
+
- **`mu agent wait`.** It fires on busy → not busy, as today. `unknown`
|
|
183
|
+
never fires. If every watched agent is `unknown` at the first tick, it
|
|
184
|
+
prints the reason once and keeps waiting until `--timeout`.
|
|
185
|
+
- **`mu task wait` stall detection.** The stall timer measures time in
|
|
186
|
+
`needs_input` using `since`. `unknown` never counts as a stall. The rest
|
|
187
|
+
of `task wait` is unchanged: it polls the DB, which is exact.
|
|
188
|
+
- **Spawn readiness.**
|
|
189
|
+
- With murmur present, spawn waits until the pane's `@murmur_pane_state`
|
|
190
|
+
is set, within the existing budget. murmur publishes it right after the
|
|
191
|
+
extension claims the pane.
|
|
192
|
+
- Without murmur, spawn skips the wait and keeps the "pane died on spawn"
|
|
193
|
+
check. The first `send` waits for pi through its input-timing checks.
|
|
194
|
+
- **`computeAgentIdle`.** An agent is idle when it is `needs_input` and
|
|
195
|
+
`since` is older than the threshold, taken from the resolver. It was
|
|
196
|
+
`agents.updated_at`.
|
|
197
|
+
- **`mu doctor`.** It reports murmur on PATH, the version (≥ 1.0.0), the pi
|
|
198
|
+
extension linked, and whether the identity exists. A failure is a
|
|
199
|
+
warning, not an error.
|
|
200
|
+
|
|
201
|
+
### `agents.status` in 2.0
|
|
202
|
+
|
|
203
|
+
- It is deprecated in place.
|
|
204
|
+
- Inserts write the fixed value `'spawning'` to satisfy `NOT NULL` and the
|
|
205
|
+
CHECK. Nothing updates it and nothing reads it.
|
|
206
|
+
- Mid-spawn is already marked by the `pending:` pane-id placeholder.
|
|
207
|
+
- `free` (from `adopt`) and the "sticky until activity" overwrite rule
|
|
208
|
+
disappear, because nothing overwrites anything.
|
|
209
|
+
- `db.ts` marks the column deprecated.
|
|
210
|
+
- The SDK drops `AgentStatus`, `shouldOverwriteAgentStatus`,
|
|
211
|
+
`updateAgentStatus` and the `detectPiStatus` export. It exports
|
|
212
|
+
`RuntimeState`, `StateReading` and `readAgentStates`.
|
|
213
|
+
|
|
214
|
+
### murmur 1.0.0
|
|
215
|
+
|
|
216
|
+
1.0.0 means the interfaces mu reads are stable. A breaking change to them
|
|
217
|
+
needs a major version.
|
|
218
|
+
|
|
219
|
+
- **New: `@murmur_pane_since`.** This is the time (ms since the epoch)
|
|
220
|
+
the pane entered its current `@murmur_pane_state`.
|
|
221
|
+
- It is set in the same tmux command as the state, and only when the
|
|
222
|
+
state changes: `if -F '#{!=:#{@murmur_pane_state},<new>}' 'set -p
|
|
223
|
+
@murmur_pane_since <now>' ; set -p @murmur_pane_state <new>`.
|
|
224
|
+
- The compare-and-set runs inside tmux, so every writer (the extension,
|
|
225
|
+
the collector, `clear`) agrees, and a repeated write of the same state
|
|
226
|
+
leaves the value alone. This was checked on a scratch server:
|
|
227
|
+
`working → working → idle` kept the first time and then moved.
|
|
228
|
+
- It is unset together with the state. It needs no store schema change.
|
|
229
|
+
- **The contract**, listed in the "Contract" section of its ARCHITECTURE:
|
|
230
|
+
- `@murmur_pane_state` and its five values, and `@murmur_pane_since`
|
|
231
|
+
- the `status --json` fields `panes[].{pane, local, agent_name,
|
|
232
|
+
workstream, driver, activity, attention, freshness, updated_at}`
|
|
233
|
+
- the `MU_*` variables murmur reads
|
|
234
|
+
- **Release:** it ships main (the idle-label fix) plus
|
|
235
|
+
`@murmur_pane_since`, with a CHANGELOG entry that says what 1.0 means.
|
|
236
|
+
There is no 0.6.2.
|
|
237
|
+
- **Order:** murmur 1.0.0 first, then mu 2.0.0.
|
|
238
|
+
|
|
239
|
+
## Cost
|
|
240
|
+
|
|
241
|
+
Measured on the dev machine with 5 local agents:
|
|
242
|
+
|
|
243
|
+
| Read | Before (scrape) | After |
|
|
244
|
+
| --- | --- | --- |
|
|
245
|
+
| local state, 5 agents | 5 × `capture-pane`, 13 ms | 1 × `list-panes`, 2 ms |
|
|
246
|
+
| `agent wait`, per 1 s tick | 5 captures, plus detection in JS | 1 × `list-panes` |
|
|
247
|
+
| local idle and stall timing | a DB timestamp that only moved on reconcile | `@murmur_pane_since`, read in the same call |
|
|
248
|
+
| remote agents | captures that gave wrong answers | `murmur status --json`, 90 ms, cached for 10 s |
|
|
249
|
+
|
|
250
|
+
`mu agent list` and `mu state` take about 0.8 s, and most of that is mu's
|
|
251
|
+
startup (`mu --version` takes 0.30 s). This change removes work from both
|
|
252
|
+
commands. No local path starts a second Node process.
|
|
253
|
+
|
|
254
|
+
## Testing
|
|
255
|
+
|
|
256
|
+
- **Unit tests** for the resolver, using a fake tmux and a fake `murmur`
|
|
257
|
+
runner:
|
|
258
|
+
- the local value
|
|
259
|
+
- a remote match
|
|
260
|
+
- no match, an ambiguous match and a stale peer
|
|
261
|
+
- murmur missing and the extension not linked
|
|
262
|
+
- the herdr path
|
|
263
|
+
- cache reuse within `MURMUR_CACHE_MS`
|
|
264
|
+
- no `murmur` process started when every agent has a local option
|
|
265
|
+
- **Consumer tests:**
|
|
266
|
+
- `agent wait` never fires on `unknown`
|
|
267
|
+
- stall detection uses `since` and ignores `unknown`
|
|
268
|
+
- spawn readiness with murmur and without it
|
|
269
|
+
- `computeAgentIdle`
|
|
270
|
+
- **Removal checks:**
|
|
271
|
+
- No state path imports the send helper. A grep test in the style of
|
|
272
|
+
murmur's cheap-tick check enforces this.
|
|
273
|
+
- `src/detect.ts` exports no status.
|
|
274
|
+
- **Integration:**
|
|
275
|
+
- With real tmux and a fake `@murmur_pane_state` set with `tmux set -p`:
|
|
276
|
+
`mu agent list --json` shows `source: murmur`.
|
|
277
|
+
- Without the option, it shows `unknown` with its reason.
|
|
278
|
+
- **Live check** on this machine: spawn a worker, send it work, and check
|
|
279
|
+
`mu agent list` for busy and then needs_input, plus `mu agent wait` and
|
|
280
|
+
`mu task wait --on-stall exit`.
|
|
281
|
+
- **Gates:**
|
|
282
|
+
- mu: `npm run typecheck && npm run lint && npm run test`
|
|
283
|
+
- murmur: `npm run check`
|
|
284
|
+
|
|
285
|
+
## Implementation checklist
|
|
286
|
+
|
|
287
|
+
1. murmur: publish `@murmur_pane_since` with a compare-and-set in
|
|
288
|
+
`setPaneState`, with tests (set on change, kept on a repeat, unset with
|
|
289
|
+
the state). Add the Contract section to ARCHITECTURE and the 1.0.0
|
|
290
|
+
CHANGELOG entry. Live check, then release 1.0.0 through CI.
|
|
291
|
+
2. mu: add `src/agent-state.ts` (the resolver, mapping and cache) and its
|
|
292
|
+
unit tests.
|
|
293
|
+
3. mu: switch reconcile, `agent list/show`, `mu state` and the TUI to the
|
|
294
|
+
resolver. Stop writing status.
|
|
295
|
+
4. mu: switch `agent wait`, `task wait` stall detection and
|
|
296
|
+
`computeAgentIdle` to the resolver.
|
|
297
|
+
5. mu: switch spawn readiness to the murmur option, or skip it when murmur
|
|
298
|
+
is missing.
|
|
299
|
+
6. mu: move the send input-timing checks to a private helper, remove the
|
|
300
|
+
state exports from `src/detect.ts`, and add the grep test.
|
|
301
|
+
7. mu: add the `--json` shape (`state`, `source`, `since`, `reason`), and
|
|
302
|
+
make the SDK export changes.
|
|
303
|
+
8. mu: add the `mu doctor` murmur check.
|
|
304
|
+
9. mu docs:
|
|
305
|
+
- README: "agent state needs murmur"
|
|
306
|
+
- the skill and `REMOTE_WORKERS.md`: drop the "ask murmur by hand" step
|
|
307
|
+
- ARCHITECTURE: rewrite § "Status detection"
|
|
308
|
+
- DECISIONS: this decision and what it rejects
|
|
309
|
+
- ROADMAP: the column drop, and update § murmur
|
|
310
|
+
- VOCABULARY
|
|
311
|
+
10. mu: CHANGELOG 2.0.0 (breaking: `status` is gone, murmur is needed for
|
|
312
|
+
state); do the live check, then release through CI.
|
|
313
|
+
|
|
314
|
+
## Rejected
|
|
315
|
+
|
|
316
|
+
- **Scraping as a fallback.** It keeps two answers, one for each source. It
|
|
317
|
+
also keeps the wrapper-patch cycle, and the misreads return whenever
|
|
318
|
+
murmur is absent. `unknown` is honest.
|
|
319
|
+
- **Scraping only for remote workers.** Remote is exactly where scraping is
|
|
320
|
+
wrong.
|
|
321
|
+
- **Storing runtime state in `mu.db`.** The copy goes stale. The data
|
|
322
|
+
belongs in murmur.
|
|
323
|
+
- **Dropping the column now.** It would make this release mu's first
|
|
324
|
+
migration, and the only gain would be deleting an unused column.
|
|
325
|
+
- **Reading murmur's `state.db`.** It couples mu to a private schema.
|
|
326
|
+
- **Making murmur a hard dependency.** It pulls murmur's native
|
|
327
|
+
`better-sqlite3` build into installs that want only the task graph.
|
|
328
|
+
- **Removing the send checks.** They fixed measured failures, such as a
|
|
329
|
+
prompt lost to pi's session-naming modal, and murmur cannot replace them.
|
|
330
|
+
|
|
331
|
+
## Open questions
|
|
332
|
+
|
|
333
|
+
None.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mu-crew/mu",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.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"
|
|
@@ -10,7 +10,8 @@ the traps below it are the part that costs time.
|
|
|
10
10
|
**If you read one thing:** on a session-capped host, never leave an
|
|
11
11
|
attach pane open — it holds the only ssh channel and silently breaks
|
|
12
12
|
`git fetch`, `murmur collect` and every other ssh. Attach to look, then
|
|
13
|
-
`mu agent close`. See § Never leave an attach pane open.
|
|
13
|
+
`mu agent close`. See § Never leave an attach pane open. This guidance
|
|
14
|
+
concerns interactive attachment, not mu's automatic agent-state reads.
|
|
14
15
|
|
|
15
16
|
---
|
|
16
17
|
|
|
@@ -19,8 +20,8 @@ attach pane open — it holds the only ssh channel and silently breaks
|
|
|
19
20
|
**The pane is local, the process is remote.** `mu agent spawn
|
|
20
21
|
--command 'ssh <host> -t "..."'` starts an ordinary tmux pane whose
|
|
21
22
|
foreground process happens to be ssh. Everything mu does is
|
|
22
|
-
pane-shaped, so `mu agent send`, `mu agent read`,
|
|
23
|
-
|
|
23
|
+
pane-shaped, so `mu agent send`, `mu agent read`, and the reaper work
|
|
24
|
+
unchanged across the network. murmur reports the remote process's agent state.
|
|
24
25
|
|
|
25
26
|
**One orchestrator DB; panes may be remote.** All state stays on your
|
|
26
27
|
machine. Do not run a second mu on the host to "coordinate": ownership
|
|
@@ -65,8 +66,8 @@ the only ssh channel**. While it is open, `git fetch`, `git push`,
|
|
|
65
66
|
`Permission denied (keyboard-interactive)` error.
|
|
66
67
|
|
|
67
68
|
**Attach to look, then close immediately.** `mu agent close <name>`
|
|
68
|
-
detaches without stopping a detached-tmux agent.
|
|
69
|
-
|
|
69
|
+
detaches without stopping a detached-tmux agent. mu reads agent state
|
|
70
|
+
through murmur automatically; use mule for orchestrator commands because
|
|
70
71
|
its separate ControlPath cannot contend with the attach pane. See
|
|
71
72
|
§ When the host limits concurrent sessions for diagnosis and the
|
|
72
73
|
remote-agent setup.
|
|
@@ -140,39 +141,30 @@ Local and remote agents mix freely in one workstream. The DAG, tracks,
|
|
|
140
141
|
— task status is a row in YOUR database, written by you, so a wait on
|
|
141
142
|
it is exact.
|
|
142
143
|
|
|
143
|
-
**Agent
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
144
|
+
**Agent state comes from murmur.** Its extension runs inside pi on the
|
|
145
|
+
host and reports state without reading the nested tmux screen. mu reads
|
|
146
|
+
murmur's remote rows and caches them for 10 seconds. Remote rows can lag
|
|
147
|
+
by murmur's collect floor (30 seconds ± 10 seconds) plus that cache;
|
|
148
|
+
their `since` value is the row's `updated_at`, so idle and stall age is
|
|
149
|
+
approximate.
|
|
148
150
|
|
|
149
151
|
| waiting on | remote? |
|
|
150
152
|
| --- | --- |
|
|
151
153
|
| `mu task wait` (task status) | exact — a DB poll, once something closes the task |
|
|
152
154
|
| the reaper | fires, but see below |
|
|
153
|
-
| stall detection |
|
|
154
|
-
| `mu agent wait --first` |
|
|
155
|
+
| stall detection | exact state from murmur; approximate age |
|
|
156
|
+
| `mu agent wait --first` | exact state from murmur |
|
|
155
157
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
`
|
|
159
|
-
`--timeout`; see `mu task wait --help` for stall controls.
|
|
158
|
+
Without murmur, remote agent state is `unknown`. `mu agent wait` does not
|
|
159
|
+
fire on `unknown`, and `unknown` never counts as a task stall. Run
|
|
160
|
+
`mu doctor` for the missing-source reason.
|
|
160
161
|
|
|
161
|
-
The reaper is right for a
|
|
162
|
+
The reaper is right for a direct spawn — the connection dying really
|
|
162
163
|
does kill that agent — and wrong for a detached-tmux one, where the
|
|
163
164
|
agent outlives the ssh but mu reaps the task anyway. See § A dropped
|
|
164
165
|
connection reaps the task but NOT the commit.
|
|
165
166
|
|
|
166
|
-
|
|
167
|
-
extension runs inside pi ON THE HOST and pushes state, so nothing is
|
|
168
|
-
scraped and no ssh hop distorts it:
|
|
169
|
-
|
|
170
|
-
```bash
|
|
171
|
-
murmur collect && murmur status --json # activity is host-reported
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
That is the division worth remembering: mu owns the work, murmur owns
|
|
175
|
-
what the agent is doing.
|
|
167
|
+
The division is: mu owns the work; murmur reports what the agent is doing.
|
|
176
168
|
|
|
177
169
|
### On step 2 — the note is load-bearing
|
|
178
170
|
|
|
@@ -361,17 +353,18 @@ respawn.
|
|
|
361
353
|
|
|
362
354
|
## mu and murmur, and what you lose without it
|
|
363
355
|
|
|
364
|
-
[murmur](https://github.com/mu-crew/murmur)
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
remote recipe and `git fetch` collection
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
356
|
+
[murmur](https://github.com/mu-crew/murmur) provides agent state for
|
|
357
|
+
mu's tmux backend. Without it, mu reports agent state as `unknown`.
|
|
358
|
+
The DAG, claims, task completion waits, workspaces, spawn, send, read,
|
|
359
|
+
the reaper, this remote recipe, and `git fetch` collection still work.
|
|
360
|
+
Agent-state consumers such as `mu agent wait`, idle flags, and task-stall
|
|
361
|
+
detection wait rather than treating `unknown` as completion or a stall.
|
|
362
|
+
|
|
363
|
+
murmur also provides the tmux status pills, `prefix+a`, attachment hints,
|
|
364
|
+
and `murmur peer list` for host reachability. The division is: **mu owns
|
|
365
|
+
the work; murmur reports what an agent is doing.** murmur never places
|
|
366
|
+
work. Its interfaces used by mu are in murmur's
|
|
367
|
+
[stable contract](https://github.com/mu-crew/murmur/blob/main/ARCHITECTURE.md#contract).
|
|
375
368
|
|
|
376
369
|
| question | ask |
|
|
377
370
|
| --- | --- |
|
package/skills/mu/SKILL.md
CHANGED
|
@@ -32,8 +32,8 @@ both. `--json` exists on every verb:
|
|
|
32
32
|
- **agent** — named worker in a pane (you may be one).
|
|
33
33
|
- **mux** — the multiplexer mu drives: tmux, or herdr. One per
|
|
34
34
|
invocation. `mu doctor` names the active one; `MU_MUX` forces it.
|
|
35
|
-
Spawn, send,
|
|
36
|
-
|
|
35
|
+
Spawn, send, and read work on both. Agent state comes from murmur
|
|
36
|
+
on tmux and from herdr on herdr; `mu agent kick` is Linux-only on herdr.
|
|
37
37
|
- **task** — DAG node with mandatory `impact` (1–100) and
|
|
38
38
|
`effort_days`. Status: `OPEN`, `IN_PROGRESS`, `CLOSED`
|
|
39
39
|
(satisfies `--blocked-by`). Record postponed/wont-do rationale as
|
|
@@ -98,8 +98,9 @@ that a refusal with `--strict-staleness`.
|
|
|
98
98
|
### Remote agents
|
|
99
99
|
|
|
100
100
|
Agents can run on another machine: the PANE is local, the PROCESS is
|
|
101
|
-
remote (`--command 'ssh <host> -t "..."'`), so `send`, `read`,
|
|
102
|
-
|
|
101
|
+
remote (`--command 'ssh <host> -t "..."'`), so `send`, `read`, and the
|
|
102
|
+
reaper keep working unchanged. murmur reports the remote agent's state.
|
|
103
|
+
**One orchestrator
|
|
103
104
|
DB; panes may be remote** — never run a second mu on the host, since
|
|
104
105
|
`tasks.owner_id` is an FK into the machine-local `agents` table and a
|
|
105
106
|
remote mu could not claim your tasks anyway. You create the remote
|
|
@@ -158,7 +159,7 @@ Every turn:
|
|
|
158
159
|
`mu task claim <id> -w <ws> --for <agent> --evidence "..."`.
|
|
159
160
|
If no task exists, `mu task add` first; include initial context with
|
|
160
161
|
`--note 'REPRO: ...\nSCOPE: ...'` when the title alone is not
|
|
161
|
-
enough. Agent
|
|
162
|
+
enough. Agent state is runtime observation; task ownership is durable and
|
|
162
163
|
waitable.
|
|
163
164
|
4. Send terse instructions: task id, files/notes to read, workspace
|
|
164
165
|
path, validation command, scope guards, task note contract.
|
|
@@ -210,8 +211,8 @@ Every turn:
|
|
|
210
211
|
conflict. Refresh workspaces between waves.
|
|
211
212
|
- Cross-workstream wait/claim uses qualified refs. The owner stays in its own
|
|
212
213
|
workstream; only task ownership crosses.
|
|
213
|
-
- For an idle worker,
|
|
214
|
-
`MU_IDLE_THRESHOLD_MS` defaults to 5m.
|
|
214
|
+
- For an idle worker, read the pane to learn why it stopped, then answer,
|
|
215
|
+
retry, or release its task. `MU_IDLE_THRESHOLD_MS` defaults to 5m.
|
|
215
216
|
- `mu agent kick` targets a wedged foreground subprocess. It refuses to signal
|
|
216
217
|
the wrapping CLI; close the agent if that is what must stop.
|
|
217
218
|
- Use `mu agent send`, not raw mux input: mu preserves literal text and confirms
|
|
@@ -291,24 +292,18 @@ Convention: `pi_mini` / `pi` / `pi_big`. Use mini for probing,
|
|
|
291
292
|
modest for build/edit/refactor, big for design/review/incidents.
|
|
292
293
|
Discover model strings with `pi --list-models [fuzzy-search]`.
|
|
293
294
|
|
|
294
|
-
## Reaper and
|
|
295
|
+
## Reaper and agent state
|
|
295
296
|
|
|
296
297
|
If an agent pane dies, or `mu agent close` kills it mid-task, owned
|
|
297
298
|
IN_PROGRESS tasks revert to OPEN with a `[reaper]` note and `task
|
|
298
299
|
reap` op. No manual release after crashes.
|
|
299
300
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
**
|
|
305
|
-
|
|
306
|
-
on the host rather than scraping a pane, and it answers across machines.
|
|
307
|
-
Optional and strictly additive: nothing here needs it, and the seam is
|
|
308
|
-
the env vars `mu agent spawn` already injects (`MU_MANAGED_AGENT`,
|
|
309
|
-
`MU_AGENT_NAME`, `MU_WORKSTREAM`), which murmur reads to mark a pane as
|
|
310
|
-
crew. **mu owns the work; murmur owns what an agent is doing.** See
|
|
311
|
-
[REMOTE_WORKERS.md](REMOTE_WORKERS.md) § mu and murmur.
|
|
301
|
+
Agent state comes from [murmur](https://github.com/mu-crew/murmur) on
|
|
302
|
+
tmux and from herdr on herdr. `unknown` means no state source; run
|
|
303
|
+
`mu doctor` for the reason. mu needs murmur only for agent state:
|
|
304
|
+
tasks, claims, workspaces, spawn, send, read, and task completion waits
|
|
305
|
+
work without it. **mu owns the work; murmur reports what an agent is
|
|
306
|
+
doing.** See [REMOTE_WORKERS.md](REMOTE_WORKERS.md) § mu and murmur.
|
|
312
307
|
|
|
313
308
|
For high-stakes decisions:
|
|
314
309
|
|
|
@@ -359,7 +354,7 @@ Sending to a BUSY agent is not delayed — that input queues normally.
|
|
|
359
354
|
|
|
360
355
|
## Guardrails
|
|
361
356
|
|
|
362
|
-
Task ownership outranks
|
|
357
|
+
Task ownership outranks runtime agent state. Coordinate through task notes and the
|
|
363
358
|
activity log. Keep edges within one workstream, role-name agents, and reserve
|
|
364
359
|
the `mu_` task-id prefix. Give workers bounded paths and commands; use `mu
|
|
365
360
|
agent kick` if a subprocess wedges.
|