@mu-crew/mu 3.0.0 → 3.2.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/AGENTS.md +12 -1
- package/README.md +70 -47
- package/dist/cli.js +6108 -4717
- package/dist/cli.js.map +1 -1
- package/dist/extension/mu-pi.js +653 -0
- package/dist/extension/mu-pi.js.map +1 -0
- package/dist/index.d.ts +743 -540
- package/dist/index.js +6618 -5786
- package/dist/index.js.map +1 -1
- package/docs/ARCHITECTURE.md +74 -7
- package/docs/USAGE_GUIDE.md +118 -16
- package/docs/VISION.md +18 -9
- package/docs/VOCABULARY.md +26 -15
- package/docs/specs/2026-09-29-task-substates.md +1 -1
- package/package.json +4 -4
- package/skills/mu/REMOTE_WORKERS.md +63 -43
- package/skills/mu/SKILL.md +46 -52
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -115,7 +115,7 @@ it mu is just an agent runner.
|
|
|
115
115
|
decides edge satisfaction: any `CLOSED` task satisfies its blockers.
|
|
116
116
|
- **Substate** qualifies status and never touches edges:
|
|
117
117
|
`OPEN/todo|parked`, `IN_PROGRESS/active`,
|
|
118
|
-
`CLOSED/done|wontfix|duplicate|superseded`. Rule: **store intent,
|
|
118
|
+
`CLOSED/done|rejected|wontfix|duplicate|superseded`. Rule: **store intent,
|
|
119
119
|
derive graph facts** — ready and blocked stay derived from edges.
|
|
120
120
|
`OPEN/parked` is excluded from `ready` but stays in `goals`.
|
|
121
121
|
|
|
@@ -624,7 +624,9 @@ returning. Two steps, in order:
|
|
|
624
624
|
- **Reality wins**: the mux is the source of truth for what panes
|
|
625
625
|
exist. Reconciliation closes the gap on every `mu agent list`.
|
|
626
626
|
- **Agent state is reported, not scraped or persisted.** `src/agent-state.ts`
|
|
627
|
-
|
|
627
|
+
probes a pi agent's control socket ([§ Control socket](#control-socket)),
|
|
628
|
+
in parallel for all pi agents. For other CLIs it reads herdr's
|
|
629
|
+
`paneStatus()` on herdr. On tmux, one `list-panes -a`
|
|
628
630
|
reads `@murmur_pane_state` and `@murmur_pane_since` for every local
|
|
629
631
|
agent. Only agents without a local option use `murmur status --json`;
|
|
630
632
|
that result is cached for 10 seconds. See murmur's
|
|
@@ -642,6 +644,67 @@ returning. Two steps, in order:
|
|
|
642
644
|
|
|
643
645
|
---
|
|
644
646
|
|
|
647
|
+
## Control socket
|
|
648
|
+
|
|
649
|
+
mu drives a pi agent through a unix socket served by mu's own pi
|
|
650
|
+
extension inside the agent's interactive pi. The pane stays pi's TUI;
|
|
651
|
+
a human attached to it shares the same session. No `pi --mode rpc`, no
|
|
652
|
+
screen scraping, no daemon: the server lives and dies with the pi
|
|
653
|
+
process.
|
|
654
|
+
|
|
655
|
+
**Path derivation** (`src/ctl/path.ts`). `ctlSocketPath(ws, agent)` is
|
|
656
|
+
`<state dir>/sock/<ws>/<agent>.sock`, where the state dir is
|
|
657
|
+
`dirname($MU_DB_PATH)` or the default. Past 103 bytes (macOS
|
|
658
|
+
`sun_path`) it becomes `<state dir>/sock/h/<sha1(ws/agent)[:16]>.sock`.
|
|
659
|
+
Nothing is stored: spawn injects the path as `MU_CTL_SOCK`, and every
|
|
660
|
+
later verb derives it again.
|
|
661
|
+
|
|
662
|
+
**Protocol v1** (`src/ctl/protocol.ts`, imported standalone by the
|
|
663
|
+
extension). JSON lines, one request per connection. Every reply carries
|
|
664
|
+
`v: 1`; the client throws `CtlVersionError` on anything else.
|
|
665
|
+
|
|
666
|
+
| op | does |
|
|
667
|
+
| --- | --- |
|
|
668
|
+
| `hello` | identity plus `ops` (served ops) and `extVersion` (mu version the extension was built from) |
|
|
669
|
+
| `status` | `state` (`busy` / `idle` / `needs_input`), `since`, `runs`, `pending` |
|
|
670
|
+
| `send` | `pi.sendUserMessage`; when busy, `mode` `steer` or `followUp` (default) |
|
|
671
|
+
| `wait` | resolves on pi's `agent_settled` after `afterRuns`, with the run's `lastText` |
|
|
672
|
+
| `abort` | `ctx.abort()` (pi's Esc) |
|
|
673
|
+
| `fresh` | new session + prompt as one operation via the internal `/mu-fresh` command; replies once the new run starts; refused with `busy` unless `force` |
|
|
674
|
+
|
|
675
|
+
An op the extension does not serve returns `unknown op: <op>` with its
|
|
676
|
+
`ops`; mu raises `AgentExtensionOutdatedError` (exit 4, next steps
|
|
677
|
+
`/reload` via mux or respawn), and `mu doctor` flags an `extVersion`
|
|
678
|
+
older than the installed mu.
|
|
679
|
+
|
|
680
|
+
**Extension lifecycle** (`extension/mu-pi.ts`). Inert unless
|
|
681
|
+
`MU_CTL_SOCK` is set. pi re-creates extension runtimes per session, so
|
|
682
|
+
the server, run counters and an in-flight `fresh` live in a
|
|
683
|
+
process-global map (`Symbol.for("mu.pi.ctl")`). Session replacement
|
|
684
|
+
(`new` / `resume` / `fork` / `reload`) keeps the socket; quit closes it.
|
|
685
|
+
Binding guards against stealing: if the path already answers (a nested
|
|
686
|
+
`pi -p` inherited `MU_CTL_SOCK`), the extension leaves it alone. It
|
|
687
|
+
listens on a private name and hard-links it into place, records the
|
|
688
|
+
inode, and unlinks the public path only if that inode is still its own.
|
|
689
|
+
On `session_start` a server whose path was deleted or replaced closes
|
|
690
|
+
and rebinds, which is how `/reload` recovers a lost file.
|
|
691
|
+
|
|
692
|
+
**No silent fallback** (`src/agents/transport.ts`). A pi agent
|
|
693
|
+
(`expectsCtl`) whose socket does not answer is `AgentCtlUnreachableError`
|
|
694
|
+
for send and abort, and `unknown` with reason `ctl missing` /
|
|
695
|
+
`ctl refused` for state. mu never pastes into it or asks murmur instead.
|
|
696
|
+
The mux paste path serves non-pi CLIs, adopted panes without ctl, slash
|
|
697
|
+
commands (`sendUserMessage` does not run them), and explicit `--via mux`.
|
|
698
|
+
|
|
699
|
+
**Remote agents.** The user's `ssh` command carries
|
|
700
|
+
`-L <local derived path>:<remote path>` plus `MU_CTL_SOCK=<remote path>`
|
|
701
|
+
in the remote env; `mu agent remote-env` prints both and runs nothing.
|
|
702
|
+
mu always connects to the local path, so local and remote share one
|
|
703
|
+
transport. ssh leaves the local socket file behind when it dies; it
|
|
704
|
+
probes `refused`, and close or the reaper deletes it.
|
|
705
|
+
|
|
706
|
+
---
|
|
707
|
+
|
|
645
708
|
## Modules (actual src/ layout)
|
|
646
709
|
|
|
647
710
|
Mostly-flat `src/`: root `.ts` modules plus cohesive subclusters
|
|
@@ -661,6 +724,9 @@ separately below.
|
|
|
661
724
|
| `src/apply.ts` | **The apply path** — capture's counterpart: given one op, local or from a peer, make the tables reflect it. Also owns `reprojectDeferredOps` ([§ ambient sync hook](#the-ambient-sync-hook)). |
|
|
662
725
|
| `src/undo.ts` + `src/cli/undo.ts` | **Undo as inverse ops**: inverses for one `group_id`, derived from log provenance, refusing a superseded group (exit 4; `--force` overrides). Bare form lists undoable groups (`-n` widens), a prefix previews, `--yes` applies. |
|
|
663
726
|
| `src/project-root.ts` | `detectProjectRoot` — the launch-cwd ladder bare `mu` uses to guess which workstream to focus. Pure filesystem walk; no DB. |
|
|
727
|
+
| `extension/mu-pi.ts` + `extension/delegate.ts` | **The mu pi extension**, built to `dist/extension/mu-pi.js`. Child side (`serveCtl`): serves `$MU_CTL_SOCK` in a pi mu spawned. Parent side (`registerDelegate`, hidden when `MU_MANAGED_AGENT` is set or `MU_DELEGATE=0`): the `mu_delegate` / `mu_delegate_cancel` tools, which shell out to the `mu` CLI (spawn, send, `wait --json`, read, abort, close) and only format the CLI's `outcome` into a follow-up message. Imports nothing from pi; only the protocol from mu. |
|
|
728
|
+
| `src/ctl/*.ts` | **Control socket** client side ([§ Control socket](#control-socket)): `path.ts` (`ctlSocketPath`, `remoteCtlSocketPath`, `MU_CTL_SOCK`), `protocol.ts` (v1 request/reply types, `CTL_OPS`, line framing; imports nothing from mu so the extension can load it), `client.ts` (`ctlRequest`, `ctlProbe`, typed version / unknown-op errors). |
|
|
729
|
+
| `src/link.ts` + `src/cli/link.ts` | **Link** — `mu link pi`: `linkPi` writes the extension shim (or `--copy`), `linkSkill` symlinks the skill, `inspectLinks` reports `ok` / `missing` / `stale-copy` / `foreign` / `dangling` for doctor. Filesystem only: the verb runs outside `handle()` (no DB). |
|
|
664
730
|
| `src/rebuild.ts` + `src/cli/rebuild.ts` | **Rebuild** — disaster recovery: `rebuildInto` replays the whole log into a NEW DB file via `applyOp`. The verb renders the report: counts, `--json`, an `mv`-swap `Next:` step, a warning that `agents` / `vcs_workspaces` are not reconstructible. |
|
|
665
731
|
| `src/legacy-ops.ts` | Permanent compatibility classifier for historical log-only intents whose entity looks projectable. Rebuild copies them without projection; segment flush never emits them. |
|
|
666
732
|
| `src/drift.ts` | **Drift detection**, two tiers: `checkDrift` (`mu doctor --deep`) rebuilds into a temp DB and diffs field-by-field; `checkCheapDriftInvariant` (the default) asserts every live row has ≥1 op naming its key. Plus `driftRemediation`. |
|
|
@@ -678,7 +744,7 @@ separately below.
|
|
|
678
744
|
| `src/agent-state.ts` | Runtime agent-state resolver. Reads herdr's native state or murmur's local pane options and remote JSON; maps source states, returns `unknown` with a reason, and caches remote murmur reads for 10 seconds. |
|
|
679
745
|
| `src/reconcile.ts` | Ghost prune + orphan surface; "reality wins" |
|
|
680
746
|
| `src/agents.ts` | Hub: CRUD + send / read / list / close + liveness + reaper. Re-exports `src/agents/*`; pane-title composition (`composeAgentTitle`) lives here. |
|
|
681
|
-
| `src/agents/*.ts` | Agent-lifecycle internals: `spawn.ts` (spawn, CLI resolution, liveness wait *or* backend `startAgentInPane`, pane create-or-reuse, rollback), `spawn-lock.ts` (per-session lock around topology+finalize), `wait.ts`, `adopt.ts`, `kick.ts` (signal a wedged pane's pgid), `errors.ts`. |
|
|
747
|
+
| `src/agents/*.ts` | Agent-lifecycle internals: `spawn.ts` (spawn, CLI resolution, liveness wait *or* backend `startAgentInPane`, pane create-or-reuse, rollback), `spawn-lock.ts` (per-session lock around topology+finalize), `wait.ts`, `adopt.ts`, `kick.ts` (signal a wedged pane's pgid), `abort.ts` (stop a pi turn via the control socket), `transport.ts` (send routing: ctl for pi agents, mux paste otherwise, no fallback), `delegate.ts` (`delegateOutcome`: one wait result → `done` / `empty` / `died` / `timeout` / `pending`, surfaced by `mu agent wait --json`), `errors.ts`. |
|
|
682
748
|
| `src/dag.ts` | Shared DAG read/render helpers: `loadFullDag` plus pure `renderForest` / `renderTaskTree`, reused by `mu task tree` and the TUI DAG popup. |
|
|
683
749
|
| `src/tasks/*.ts` | Task-graph internals: `core.ts` (row shapes, id resolution), `id.ts`, `queries.ts` (reads), `edit.ts` (+ delete cascade preview), `edges.ts` (+ cycle check), `status.ts` (TaskStatus), `sort.ts`, `claim.ts` (atomic CAS), `lifecycle.ts` (close/open), `wait.ts`, `errors.ts`. |
|
|
684
750
|
| `src/tracks.ts` | Parallel-tracks union-find with diamond merge |
|
|
@@ -785,7 +851,7 @@ DB must never sit in `MU_SYNC_DIR`:
|
|
|
785
851
|
4. **Executes the operation** — agent ops shell out to tmux (and to
|
|
786
852
|
jj/sl/git for workspaces); task ops are pure SQL.
|
|
787
853
|
5. **Reconciles with reality** — read paths prune ghosts and surface
|
|
788
|
-
orphans; runtime agent state is read from murmur or herdr without a DB write.
|
|
854
|
+
orphans; runtime agent state is read from the control socket (pi), murmur or herdr without a DB write.
|
|
789
855
|
6. **Records ops** — automatically, per [§ The ops log](#the-ops-log-read-this-first);
|
|
790
856
|
machine-local changes go through `emitEvent`. `mu log --tail`
|
|
791
857
|
subscribers see the new ops on the next 1-second poll.
|
|
@@ -1013,9 +1079,10 @@ Single npm package `mu` (see `package.json`):
|
|
|
1013
1079
|
- `dist/index.js` + `dist/index.d.ts` — programmatic API + types for SDK callers
|
|
1014
1080
|
- `skills/mu/SKILL.md` — bundled skill (the only non-`dist` asset shipped)
|
|
1015
1081
|
|
|
1016
|
-
`tsup` bundles
|
|
1017
|
-
|
|
1018
|
-
|
|
1082
|
+
`tsup` bundles three entries: `index` and `cli` from `src/`, and
|
|
1083
|
+
`extension/mu-pi` (the pi extension `mu link pi` points at). No runtime
|
|
1084
|
+
build step on the user's machine. pi stays a peer dep — the extension
|
|
1085
|
+
types pi's API structurally — and there is no bundled `agents/*.md` or `prompts/*.md`:
|
|
1019
1086
|
per-role agent guidance lives in the user's project repo.
|
|
1020
1087
|
|
|
1021
1088
|
The dependency list lives in `package.json`; the rule for adding
|
package/docs/USAGE_GUIDE.md
CHANGED
|
@@ -389,8 +389,8 @@ table/key/field is the reproduction. See
|
|
|
389
389
|
`mu` refuses to open a pre-v11 DB (`SchemaTooOldError`, exit 4) or a DB
|
|
390
390
|
newer than it understands (`SchemaTooNewError`, exit 4) and leaves the
|
|
391
391
|
file alone. The retained `scripts/migrate.ts` sidecar auto-detects v7,
|
|
392
|
-
v8, v9, or
|
|
393
|
-
Legacy `REJECTED` tasks become `CLOSED/
|
|
392
|
+
v8, v9, v10, or v11 and writes a fresh v11 DB. It never migrates in place.
|
|
393
|
+
Legacy `REJECTED` tasks become `CLOSED/rejected` and `DEFERRED` tasks
|
|
394
394
|
become `OPEN/parked`; their original op payloads remain unchanged.
|
|
395
395
|
Pre-1.0 archives restore as live workstreams under their original
|
|
396
396
|
`source_workstream` names unless you pass `--drop-archives`.
|
|
@@ -422,7 +422,26 @@ To update the source-installed copy: pull from upstream, then
|
|
|
422
422
|
`npm install -g .` from inside the checkout. The `prepare` script
|
|
423
423
|
rebuilds before linking the new dist/.
|
|
424
424
|
|
|
425
|
-
###
|
|
425
|
+
### Link pi (`mu link pi`)
|
|
426
|
+
|
|
427
|
+
For pi agents, one verb installs both pieces mu needs:
|
|
428
|
+
|
|
429
|
+
```bash
|
|
430
|
+
mu link pi # extension shim + skill symlink; then restart pi (or /reload)
|
|
431
|
+
mu link pi --copy # inline the extension instead (pinned; relink after upgrades)
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
- `~/.pi/agent/extensions/mu.ts` — a one-line shim that re-exports the
|
|
435
|
+
installed `dist/extension/mu-pi.js`, so `npm install -g` upgrades the
|
|
436
|
+
extension with no relink.
|
|
437
|
+
- `~/.agents/skills/mu` — a symlink to the package's `skills/mu`.
|
|
438
|
+
|
|
439
|
+
mu refuses to replace a real directory at the skill path (exit 4), and
|
|
440
|
+
replaces a symlink pointing elsewhere (e.g. a dev checkout) only with
|
|
441
|
+
`--force`. `--extension-only` / `--skill-only` install one piece.
|
|
442
|
+
`MU_PI_HOME` overrides the home directory both paths are rooted at.
|
|
443
|
+
|
|
444
|
+
### Install the bundled skill (other agents)
|
|
426
445
|
|
|
427
446
|
Mu ships a skill at `skills/mu/SKILL.md` that teaches the LLM running
|
|
428
447
|
inside an agent pane how to use mu. The canonical install path is the
|
|
@@ -839,9 +858,15 @@ Next:
|
|
|
839
858
|
|
|
840
859
|
**When to use which:** `mu workstream init <name>` the moment
|
|
841
860
|
coordination *is* the work — multiple agents, dependencies, gated
|
|
842
|
-
review. `scratch` for a single driveable helper.
|
|
843
|
-
|
|
844
|
-
|
|
861
|
+
review. `scratch` for a single driveable helper. A **delegate** when
|
|
862
|
+
you only need a one-shot answer back: the `mu_delegate` tool (shipped
|
|
863
|
+
in the extension `mu link pi` installs) spawns a scratch agent, sends
|
|
864
|
+
the task, returns at once, and delivers the answer as a follow-up
|
|
865
|
+
message. From a shell it is `mu agent spawn -w scratch` + `send` +
|
|
866
|
+
`mu agent wait --json` (see [`mu agent wait`](#mu-agent-wait-the-task-less-counterpart-to-mu-task-wait)).
|
|
867
|
+
Unlike a hidden subagent, the delegate's pane stays attachable: watch
|
|
868
|
+
it, steer it, abort it, or pass `keep: true` and keep talking to it.
|
|
869
|
+
Each delegate costs one pane and one pi process.
|
|
845
870
|
|
|
846
871
|
### The log ledger: durable watcher dedupe + memory
|
|
847
872
|
|
|
@@ -1370,7 +1395,37 @@ From any shell with mu on `$PATH`:
|
|
|
1370
1395
|
mu agent send worker-1 "echo hello from outside"
|
|
1371
1396
|
```
|
|
1372
1397
|
|
|
1373
|
-
|
|
1398
|
+
**Which transport.** A pi agent (`--cli pi`, a command whose argv0
|
|
1399
|
+
is `pi` / `pi-meta`, or any agent whose control socket file exists)
|
|
1400
|
+
gets the text through its **control socket**: the
|
|
1401
|
+
mu pi extension calls `pi.sendUserMessage` inside the pane's own pi, so
|
|
1402
|
+
nothing is pasted or scraped. When pi is busy the message queues as a
|
|
1403
|
+
follow-up; `--steer` interrupts the current run instead. If the socket
|
|
1404
|
+
does not answer, the send fails with `AgentCtlUnreachableError` (exit 1)
|
|
1405
|
+
and pastes nothing; `--via mux` pastes explicitly. A pi started before
|
|
1406
|
+
you upgraded mu still runs the old extension: an op it lacks (say
|
|
1407
|
+
`--fresh`) fails with `AgentExtensionOutdatedError` (exit 4). Type
|
|
1408
|
+
`/reload` in its pane (`mu agent send <name> '/reload' --via mux`) to
|
|
1409
|
+
load the new extension and keep the session, or respawn it. `mu doctor`'s
|
|
1410
|
+
`ctl` row flags such agents before you hit the error. Text starting with
|
|
1411
|
+
`/` (`/new`, `/compact`) is a TUI command that `sendUserMessage` would
|
|
1412
|
+
hand to the model as plain text, so it always goes through the mux.
|
|
1413
|
+
Non-pi CLIs always go through the mux. `--json` reports `transport:
|
|
1414
|
+
"ctl"|"mux"` (and pi's `state` for ctl). An adopted pi pane was started
|
|
1415
|
+
without `MU_CTL_SOCK`; `mu agent adopt` warns and prints the path to
|
|
1416
|
+
restart pi with.
|
|
1417
|
+
|
|
1418
|
+
**Fresh session + prompt.** For a pi agent, `mu agent send <name>
|
|
1419
|
+
--fresh '<prompt>'` starts a new session and sends the prompt into it
|
|
1420
|
+
as one operation inside pi (the extension's internal `/mu-fresh`
|
|
1421
|
+
command runs `newSession` and sends from the new session). It returns
|
|
1422
|
+
once the prompt's run has started, so a send right after it lands in
|
|
1423
|
+
the new session. Use it instead of sending `/new` then the prompt. A
|
|
1424
|
+
busy pi refuses with exit 4 (`mu agent abort` first, or `--force` to
|
|
1425
|
+
abandon the running turn). Non-pi CLIs have no control socket: use the
|
|
1426
|
+
two-step `/new` + prompt there and verify the prompt landed.
|
|
1427
|
+
|
|
1428
|
+
For the mux path, mu uses the **canonical bracketed-paste protocol**:
|
|
1374
1429
|
|
|
1375
1430
|
0. wait for the pane to stop being mid-modal (`MU_SEND_READINESS_MS`,
|
|
1376
1431
|
default 15000; 0 disables)
|
|
@@ -1630,8 +1685,9 @@ mu task unpark polish # OPEN/parked → OPEN/todo
|
|
|
1630
1685
|
|
|
1631
1686
|
Every task carries a **substate** next to its status, rendered as a pair.
|
|
1632
1687
|
`OPEN` is `todo` or `parked`; `IN_PROGRESS` is `active`; `CLOSED` is
|
|
1633
|
-
`done`, `wontfix`, `duplicate`, or `superseded`. `--as` picks
|
|
1634
|
-
|
|
1688
|
+
`done`, `rejected`, `wontfix`, `duplicate`, or `superseded`. `--as` picks
|
|
1689
|
+
the closed substate: `rejected` when the proposal itself was declined,
|
|
1690
|
+
`wontfix` when it is valid but not worth doing. Anything but `done` requires `--why`, stored as a note in the
|
|
1635
1691
|
same transaction. Any `CLOSED/*` satisfies a blocker, so a non-`done`
|
|
1636
1692
|
close prints the dependents it unblocked.
|
|
1637
1693
|
|
|
@@ -1739,8 +1795,20 @@ Mirrors `mu task wait`'s shape: `--any`/`--first` fire on the first
|
|
|
1739
1795
|
agent (default: all must finish); `--first` prints the firing agent's
|
|
1740
1796
|
ref; `--json` carries `nextSteps`; refs may be qualified
|
|
1741
1797
|
`<workstream>/<name>`. Exit codes: `0` met, `5` timeout, `6` a watched
|
|
1742
|
-
agent's pane died
|
|
1743
|
-
|
|
1798
|
+
agent's pane died (for a pi agent: its control socket stopped
|
|
1799
|
+
answering). For pi agents the wait is one request to the mu extension,
|
|
1800
|
+
answered when pi settles, so it returns immediately rather than on the
|
|
1801
|
+
next poll, and `--json` adds each settled pi agent's `lastText`: the
|
|
1802
|
+
text of its final assistant message, verbatim (`""` when the run ended
|
|
1803
|
+
without text; capped at 64 KiB). For other CLIs murmur (tmux) or herdr
|
|
1804
|
+
supplies the state, and `lastText` is absent; read the pane instead.
|
|
1805
|
+
Every `--json` agent row also carries `outcome`: `done` (settled with
|
|
1806
|
+
text), `empty` (settled without text, or no `lastText` source), `died`,
|
|
1807
|
+
`timeout`, or `pending` (`--any` fired on another agent). The
|
|
1808
|
+
`mu_delegate` tool in the mu pi extension is this verb plus spawn and
|
|
1809
|
+
send: it formats `outcome` into a follow-up message.
|
|
1810
|
+
`unknown` never fires the wait; run `mu doctor` for the missing-source
|
|
1811
|
+
reason.
|
|
1744
1812
|
|
|
1745
1813
|
**Don't reach for this verb to catch a worker that asked a question.**
|
|
1746
1814
|
It fires on `busy → needs_input`, so it does detect the case — but it
|
|
@@ -1984,13 +2052,47 @@ Reconciliation runs on every `mu agent list` / `mu`. Two steps:
|
|
|
1984
2052
|
registry. **Not** auto-adopted; mu lists them under "Orphan panes"
|
|
1985
2053
|
with the `mu agent adopt <pane-id>` hint
|
|
1986
2054
|
|
|
2055
|
+
### A pi worker on another machine
|
|
2056
|
+
|
|
2057
|
+
`mu agent remote-env` prints the ssh pieces; mu itself runs no ssh:
|
|
2058
|
+
|
|
2059
|
+
```bash
|
|
2060
|
+
eval "$(mu agent remote-env worker-1 --shell)" # sets MU_SSH_ARGS, MU_REMOTE_ENV
|
|
2061
|
+
mu agent spawn worker-1 --command \
|
|
2062
|
+
"ssh $MU_SSH_ARGS dev -t 'cd ~/ws/worker-1 && $MU_REMOTE_ENV \$SHELL -ilc \"pi --approve\"'"
|
|
2063
|
+
```
|
|
2064
|
+
|
|
2065
|
+
`$SHELL -ilc` runs pi in an interactive login shell, so the remote
|
|
2066
|
+
`~/.zshrc` / `~/.bashrc` (PATH, provider env) loads; ssh's own
|
|
2067
|
+
non-interactive shell skips it. `MU_SSH_ARGS` also forces a direct
|
|
2068
|
+
connection (`-o ControlMaster=no -o ControlPath=none`): a multiplexed
|
|
2069
|
+
ssh never binds the local end of the forward.
|
|
2070
|
+
|
|
2071
|
+
`MU_SSH_ARGS` forwards the remote pi's control socket to the local path
|
|
2072
|
+
mu always connects to, so send, state, `mu agent wait` and
|
|
2073
|
+
`mu agent abort` behave as for a local agent. The host needs
|
|
2074
|
+
`mu link pi`, and its sshd must allow the forward
|
|
2075
|
+
(`AllowStreamLocalForwarding`). Full recipe and traps:
|
|
2076
|
+
`skills/mu/REMOTE_WORKERS.md`.
|
|
2077
|
+
|
|
1987
2078
|
### A worker is wedged on an unbounded tool subprocess
|
|
1988
2079
|
|
|
1989
2080
|
A worker ran `find / -maxdepth 6 ...` (30-60 minutes on a populated
|
|
1990
2081
|
home directory) or a busy-wait loop. `mu agent send` queues steering
|
|
1991
2082
|
messages until the tool returns; `tmux send-keys C-c` against the
|
|
1992
2083
|
pane doesn't propagate (the wrapping pi/claude/codex CLI catches it
|
|
1993
|
-
as TUI input).
|
|
2084
|
+
as TUI input). For a pi agent, abort the turn first:
|
|
2085
|
+
|
|
2086
|
+
```bash
|
|
2087
|
+
mu agent abort worker-1 # Esc through the control socket; waits for pi to settle
|
|
2088
|
+
```
|
|
2089
|
+
|
|
2090
|
+
`mu agent abort` kills the running tool (the bash child dies with it)
|
|
2091
|
+
and returns once pi settles (exit 5 after `--timeout`, default 30s).
|
|
2092
|
+
An idle agent is left alone. A follow-up queued before the abort does
|
|
2093
|
+
NOT run: pi puts it back into the pane's editor box, unsent. Re-send
|
|
2094
|
+
it if you still want it. Non-pi agents, or a pi whose socket is
|
|
2095
|
+
gone, need the signal escape hatch:
|
|
1994
2096
|
|
|
1995
2097
|
```bash
|
|
1996
2098
|
mu agent kick worker-1 # SIGINT (graceful, default)
|
|
@@ -2400,10 +2502,10 @@ mv "$DB" "${DB}.old-kept" && mv "${DB}.v11" "$DB"
|
|
|
2400
2502
|
mu doctor
|
|
2401
2503
|
```
|
|
2402
2504
|
|
|
2403
|
-
The script auto-detects v7, v8, v9, or
|
|
2505
|
+
The script auto-detects v7, v8, v9, v10, or v11, opens it read-only, compares
|
|
2404
2506
|
its SHA-256 before and after, and never overwrites a target unless
|
|
2405
2507
|
`--force` is explicit. v9/v10 history is retained unchanged; legacy
|
|
2406
|
-
statuses map onto substates (`REJECTED` → `CLOSED/
|
|
2508
|
+
statuses map onto substates (`REJECTED` → `CLOSED/rejected`, `DEFERRED` →
|
|
2407
2509
|
`OPEN/parked`) while projecting into v11, and no migration notes are
|
|
2408
2510
|
written. Where an undo or a note hid the legacy status, the script
|
|
2409
2511
|
records a `migrate.substate` op; `--recover <db>` reruns that recovery
|
|
@@ -2504,7 +2606,7 @@ to `mu sql`, twice in one session". File it in [ROADMAP.md](ROADMAP.md).
|
|
|
2504
2606
|
|
|
2505
2607
|
| Doc | What's in it |
|
|
2506
2608
|
| -------------------------------------------- | ------------------------------------------------------- |
|
|
2507
|
-
| [README.md](../README.md) | Project overview, install, comparison vs
|
|
2609
|
+
| [README.md](../README.md) | Project overview, install, comparison vs hidden subagents |
|
|
2508
2610
|
| [CHANGELOG.md](../CHANGELOG.md) | Release notes |
|
|
2509
2611
|
| [ROADMAP.md](ROADMAP.md) | What's next, with promotion criteria + rejected ideas |
|
|
2510
2612
|
| [VOCABULARY.md](VOCABULARY.md) | Canonical terms — source of truth for every word |
|
|
@@ -2575,7 +2677,7 @@ The backend is resolved once per process and cached.
|
|
|
2575
2677
|
| Attach hint | `tmux a -t mu-<ws>` | `herdr session attach mu-<ws>` |
|
|
2576
2678
|
| Pane borders | 4-side border showing agent name + status glyph | no-op — herdr owns its pane chrome; mu-managed panes carry the label instead |
|
|
2577
2679
|
| Layout | `select-layout` | no-op — herdr splits are explicit, geometry is yours |
|
|
2578
|
-
| Agent state | murmur pane options for local agents; `murmur status --json` for remote agents | herdr reports state via `paneStatus()` |
|
|
2680
|
+
| Agent state | pi agents: the control socket. Others: murmur pane options for local agents; `murmur status --json` for remote agents | pi agents: the control socket. Others: herdr reports state via `paneStatus()` |
|
|
2579
2681
|
| Focus | mu creates detached | `--no-focus` on every mutating call, always. `detached: false` still gets you a detached workspace; run `herdr workspace focus` yourself. |
|
|
2580
2682
|
| Isolation seam | `MU_TMUX_SOCKET` (`-L <name>`) | `MU_HERDR_SESSION` (`--session <name>`, its own socket) |
|
|
2581
2683
|
| `mu agent send` / `read` | six-step paste/Enter protocol | one atomic `agent prompt --wait` |
|
package/docs/VISION.md
CHANGED
|
@@ -34,9 +34,9 @@ tasks.
|
|
|
34
34
|
|
|
35
35
|
Existing tools force a choice:
|
|
36
36
|
|
|
37
|
-
- **
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
- **Hidden subagents** (pi-subagents, the Claude Code and Codex task
|
|
38
|
+
tools) delegate one-shot work to a child you cannot see, steer, or
|
|
39
|
+
keep talking to; its transcript collapses into a result.
|
|
40
40
|
- **Tmux-orchestration tools** spawn agents in panes but leave
|
|
41
41
|
coordination to chat transcripts or filesystem conventions.
|
|
42
42
|
- **Task trackers** (GitHub Issues, Linear, even tg) model the work but
|
|
@@ -65,6 +65,11 @@ The pi extension is a UX skin. Everything mu does must work from a
|
|
|
65
65
|
shell with no pi anywhere. A feature that requires the extension
|
|
66
66
|
doesn't ship.
|
|
67
67
|
|
|
68
|
+
Delegation is CLI: `mu agent spawn -w scratch` + `mu agent send` +
|
|
69
|
+
`mu agent wait --json`. The `mu_delegate` tool adds only the callback
|
|
70
|
+
that puts the answer into the parent conversation, and that is
|
|
71
|
+
presentation.
|
|
72
|
+
|
|
68
73
|
### 2. One DB is canonical
|
|
69
74
|
|
|
70
75
|
All state lives in `~/.local/state/mu/mu.db`. SQLite WAL; multiple
|
|
@@ -307,9 +312,12 @@ orchestrator stays in charge.
|
|
|
307
312
|
It runs agents that do those things.
|
|
308
313
|
- **Not a chat protocol.** Agents communicate through the work graph
|
|
309
314
|
(notes, claim, status) and the **ops log** read through `mu log`.
|
|
310
|
-
- **Not a
|
|
311
|
-
|
|
312
|
-
|
|
315
|
+
- **Not a hidden-subagent runner.** A one-shot **delegate** is an
|
|
316
|
+
ordinary agent in a visible pane: attach, steer, abort, or keep
|
|
317
|
+
talking to it. The cost is one pane and one pi process per delegate;
|
|
318
|
+
a hidden subagent is lighter for many tiny calls. mu bets that
|
|
319
|
+
visibility and steering are worth that for agent work. No agent
|
|
320
|
+
types: the brief is ad-hoc text.
|
|
313
321
|
- **Not a hosted service.** Local-first SQLite. Zero ops, no accounts.
|
|
314
322
|
Your machine is the deployment.
|
|
315
323
|
- **Not a verifier.** The verbs trust the caller. `task close
|
|
@@ -348,9 +356,10 @@ orchestrator stays in charge.
|
|
|
348
356
|
`mu log --tail` for confirmation. This is by design — the
|
|
349
357
|
alternative requires a protocol every CLI would have to speak.
|
|
350
358
|
|
|
351
|
-
5. **Recursion is opt-in.**
|
|
352
|
-
|
|
353
|
-
|
|
359
|
+
5. **Recursion is opt-in.** The `mu_delegate` tool is hidden in
|
|
360
|
+
agents mu spawned (`MU_MANAGED_AGENT`), so a delegate cannot spawn
|
|
361
|
+
delegates through it. Agents get the `mu` binary on PATH, but the
|
|
362
|
+
bundled skill says "you are not the orchestrator."
|
|
354
363
|
|
|
355
364
|
6. **Subscriptions are polling-based.** `mu log --tail` polls
|
|
356
365
|
SQLite once per second. SQLite handles the concurrency; latency
|
package/docs/VOCABULARY.md
CHANGED
|
@@ -16,21 +16,21 @@ defined here, fix the doc. If you need a new term, add it here first.
|
|
|
16
16
|
| **mux backend** | An implementation of the `MuxBackend` interface (`src/mux/tmux.ts`, `src/mux/herdr.ts`). Owns everything backend-specific: session/window/pane topology, the send protocol, scrollback capture, **pane id** validation, and **actor** identity fallback. Sibling of **VCS backend**; same `src/vcs/` shape. | "driver", "provider", "adapter" |
|
|
17
17
|
| **mux session** | The mux-level container holding one workstream's panes. On tmux it is a **tmux session** named `mu-<workstream>`; on herdr it is a herdr *workspace* labelled `mu-<workstream>`. Use the generic term in cross-backend prose and the specific term when the backend matters. | "mux workspace" (collides with mu's VCS **workspace**), "session" alone |
|
|
18
18
|
| **mux detection** | The ladder that picks the active **mux backend**: `MU_MUX` override → `HERDR_ENV=1` → `$TMUX` → whichever binary is on `PATH` (tmux wins a tie as the incumbent) → `NoMultiplexerError`. `HERDR_ENV` outranks `$TMUX` because it is the narrower signal: only a herdr-managed pane sets it, and herdr may itself be running inside tmux. | "auto-detect" (vague), "probe" |
|
|
19
|
-
| **scratch workstream**| The reserved workstream named `scratch` for off-the-cuff agent use — spin up a helper you'll keep talking to without crew/DAG ceremony. Explicit `-w scratch` (no implicit fallback); auto-created on first spawn; `mu workstream init scratch` is rejected (loud). Agents in it are still **agents
|
|
19
|
+
| **scratch workstream**| The reserved workstream named `scratch` for off-the-cuff agent use — spin up a helper you'll keep talking to without crew/DAG ceremony. Explicit `-w scratch` (no implicit fallback); auto-created on first spawn; `mu workstream init scratch` is rejected (loud). Agents in it are still **agents**; a **delegate** is a scratch agent started for one task. Task-less is fine; the DAG stays opt-in. | "throwaway ws", "temp workstream", "subagent" |
|
|
20
20
|
| **tmux session** | The literal tmux session a workstream lives in — the tmux flavour of a **mux session** | "session" alone (ambiguous) |
|
|
21
21
|
| **window** | The mid-level grouping inside a **mux session** (tmux window / herdr tab); identified by `window_name` | "tab" (except as the frontmatter field name) |
|
|
22
22
|
| **pane** | One shell view inside a **window**; identified by **pane id** | "terminal", "shell" |
|
|
23
23
|
| **pane id** | The mux's stable handle for a **pane**. Shape is backend-specific — tmux `%15`, herdr `w1:p1` — so validation lives on the **mux backend**, never as a global regex. Stable for the pane's lifetime; never reused after close. Distinct from a tmux pane *index* (`0`, `1`, …), which is volatile and must never be stored. | "pane number", "pane index" |
|
|
24
24
|
| **pane title** | The string identifying which **agent** occupies a pane. **Equals the agent's name.** Set via `select-pane -T` on tmux, and via `herdr pane rename` (the pane *label*) on herdr. A fallback for **actor** identity — `$MU_AGENT_NAME` is consulted first. | "pane name" |
|
|
25
25
|
| **window name** | The **window**'s name. **Equals the agent's `tab:` value** (groups one or more agents). | "tab name" (in code; `tab:` only in frontmatter) |
|
|
26
|
-
| **agent** | A named worker running in a pane; identity = `$MU_AGENT_NAME`, falling back to **pane title**; row in `agents` table | "subagent" (
|
|
26
|
+
| **agent** | A named worker running in a pane; identity = `$MU_AGENT_NAME`, falling back to **pane title**; row in `agents` table | "subagent" (means a hidden child in other tools), "worker" (only the specific role) |
|
|
27
27
|
| **worker** | An **agent** in its role-as-task-claimer. Synonym for the registered side of identity — a row in `agents`, owns tasks via the FK. | (when ambiguous, prefer **agent**) |
|
|
28
28
|
| **remote worker** | A worker whose process and workspace are on another machine while its pane and orchestrator DB remain local. The durable inventory record is a task-note line in the exact `REMOTE: <host>:<path>` shape; pair it with `REMOTE_BASE: <agent>:<sha>` so `mu task claim --for <agent>` can print the one-shot poll-and-close step. `mu state` lists `REMOTE:` lines and `mu state --json` exposes them as `remoteWorkers`. | "remote agent record", "host registry" |
|
|
29
29
|
| **actor** | The party that *caused* a state change. May or may not be a registered worker. Recorded in `ops.actor` for every op. The orchestrator running mu from a top-level shell is an actor but not a worker. | "caller", "author" (only on notes) |
|
|
30
30
|
| **crew** | *Informal* collective noun for the agents in a workstream | (no API surface; prose only) |
|
|
31
31
|
| **task** | A node in the DAG. Has mandatory `impact` and `effort_days`. Has a **task status** and a **substate**, shown as a pair (`OPEN/parked`, `CLOSED/wontfix`). | "issue", "ticket", "item" |
|
|
32
32
|
| **task status** | The lifecycle phase, one of 3: **OPEN** = not started; **IN_PROGRESS** = claimed and active; **CLOSED** = ended. Status alone decides edge satisfaction: any `CLOSED/*` satisfies a `--blocked-by` edge. | "state" |
|
|
33
|
-
| **substate** | What the **task status** means for this task. Never null; each status has a default. `OPEN`: `todo` (default), `parked`. `IN_PROGRESS`: `active`. `CLOSED`: `done` (default), `wontfix
|
|
33
|
+
| **substate** | What the **task status** means for this task. Never null; each status has a default. `OPEN`: `todo` (default), `parked`. `IN_PROGRESS`: `active`. `CLOSED`: `done` (default), `rejected` (the proposal was declined), `wontfix` (valid, not worth doing), `duplicate`, `superseded`. Rendered as a pair `STATUS/substate`; a default substate renders as the bare status. Valid pairs live in the `task_substates` table. Never affects edges. Refer to a non-`done` close as a *closed substate*. | "resolution", "reason", "sub-status" |
|
|
34
34
|
| **park** | Verb: set an `OPEN` task to `OPEN/parked` with a required `--why` note. A parked task leaves **ready** and `next`, `claim` refuses it without `--force`, and it stays in **goals**. Its dependents stay blocked. Refuses `IN_PROGRESS` (release first) and `CLOSED` (open first). | "defer", "snooze", "shelve" |
|
|
35
35
|
| **unpark** | Verb: return an `OPEN/parked` task to `OPEN/todo`. | "resume", "reactivate" |
|
|
36
36
|
| **task DAG** / **graph** | The directed acyclic graph of tasks. | "task list", "todo", "tree" (it's a DAG, not a tree) |
|
|
@@ -65,7 +65,7 @@ defined here, fix the doc. If you need a new term, add it here first.
|
|
|
65
65
|
| **stale workspace** | A workspace whose `parent_ref` is N commits behind the project's default branch HEAD (per the workspace's local refs cache). Rendered as a color-coded `behind` column (green ≤2, yellow 3–9, red ≥10) in `mu workspace list` and `mu state`; ≥10 triggers a one-line warn in `mu state`, `mu task claim --for`, and `mu agent send` (or refusal on the two dispatch verbs with `--strict-staleness`). Pure observation — mu never auto-fetches. | "out of date", "drifting" |
|
|
66
66
|
| **refresh** | `mu workspace refresh <agent>` — rebase the agent's workspace onto a fresh base (default = backend's tracked main; `--from <ref>` overrides) WITHOUT touching the agent or pane. Refuses on dirty WC; surfaces conflicts as exit 5 with a resolve-in-place hint. The `none` backend errors (no VCS to rebase). | "recycle", "reset" (overloaded) |
|
|
67
67
|
| **backend** | Implementation of `MuxBackend` or `VcsBackend`. Always qualify which (**mux backend** / **VCS backend**) when both are in scope. | "driver", "provider" |
|
|
68
|
-
| **state source** | The system that reports **agent state**: herdr's `paneStatus()` on herdr, or murmur on tmux. mu reads local murmur pane options in one tmux call and uses `murmur status --json` only for remote workers. No source yields `unknown` with a reason. | "scraper" |
|
|
68
|
+
| **state source** | The system that reports **agent state**: the **control socket** for pi agents; otherwise herdr's `paneStatus()` on herdr, or murmur on tmux. mu reads local murmur pane options in one tmux call and uses `murmur status --json` only for remote workers. No source yields `unknown` with a reason; a pi agent whose socket does not answer is `unknown` (`ctl missing` / `ctl refused`), never a murmur reading. | "scraper" |
|
|
69
69
|
| **op** | The atomic unit of change: one row in the `ops` table, written by a trigger inside the same transaction as the mutation it records. Carries `(hlc, machine_id, group_id, actor, intent, entity, key, op, payload)`. A **semantic partial update** — the payload holds only the columns that actually changed, which is what makes per-field merge free. | "event", "delta", "change" (all overloaded) |
|
|
70
70
|
| **ops log** | The `ops` table: the single append-only record of every change, and the substrate **sync**, **undo**, and history are all queries or replays over. Canonical and ACID because it lives in `mu.db` alongside the tables it records. | "event log", "journal", "WAL" (reserved by SQLite) |
|
|
71
71
|
| **intent** | The semantic label on an **op** (`task.close`, `task.reparent`, `agent.spawn`). Set once per public SDK function via **op context**, not per mutation. `mu log` renders prose from it through one formatter. | "verb" (overloaded by CLI verbs), "action" |
|
|
@@ -74,7 +74,7 @@ defined here, fix the doc. If you need a new term, add it here first.
|
|
|
74
74
|
| **apply** | Verb: land one **op** into the portable tables, honouring the **merge rules**. `applyOp` / `applyOps` in `src/apply.ts`. Always runs capture-suppressed (`applying=1`), so applying never mints a new op — that is the echo guard. Idempotent: applying the same op twice changes nothing, which is what makes `mu sync --repair` merely "re-read that peer's **segment** from zero". Distinct from **ingest**, which is the sync-side loop that reads a segment and calls apply per op. | "merge" (that's the rule set), "import", "replay" (replay is undo) |
|
|
75
75
|
| **merge rules** | The per-entity conflict policy the **apply** path implements. note/message = **grow-only set**; task/workstream = **per-field LWW**; edge = **LWW-element-set**; KNOWN machine-local entities (`MACHINE_LOCAL_ENTITIES`: agent, workspace, event, broadcast) are rejected as a bad peer; an UNRECOGNISED entity is a forward-compatible no-op — recorded, projected nowhere, never a defect, because a reader may legitimately be older than a writer in a mixed fleet. One rule per entity, chosen so that convergence needs no coordination between machines. | "conflict resolution" (implies prompting a human), "CRDT" (only loosely true) |
|
|
76
76
|
| **per-field LWW** | Last-writer-wins applied PER FIELD, not per row: each field of a task independently keeps the value written by the newest **HLC** that touched THAT field. Free, because ops are **semantic partial updates**. Row-level LWW is explicitly rejected — with agent crews on two machines, a crew closing a task while the operator edits its impact is concurrent by construction, and row-level would silently discard one edit. | "row LWW", "column versioning" (implies version vectors we don't need) |
|
|
77
|
-
| **grow-only set** | An entity that is only ever inserted, never updated: **notes** and log messages. Two machines therefore cannot disagree about a note's content, so there is nothing to resolve — apply is insert-if-absent. Identity is `(task, author, content)`, since a note's surrogate id is assigned per machine and cannot identify it across machines. | "append-only log" (that's the ops log), "set" |
|
|
77
|
+
| **grow-only set** | An entity that is only ever inserted, never updated: **notes** and log messages. Two machines therefore cannot disagree about a note's content, so there is nothing to resolve — apply is insert-if-absent. Identity is `(task, author, content, created_at)`, since a note's surrogate id is assigned per machine and cannot identify it across machines. `created_at` is part of it because machine-written text repeats (the reaper's "previous owner gone", dispatch's "CLAIM: dispatch"), and each repeat is a separate note. | "append-only log" (that's the ops log), "set" |
|
|
78
78
|
| **LWW-element-set** | The rule for **edges**: set membership where both the add and the remove carry an **HLC**, so a remove and a later re-add converge in either arrival order. An edge has no fields worth merging — it is present or absent. | "add-wins set", "2P-set" (a 2P-set can't re-add) |
|
|
79
79
|
| **tombstone** | An `op='del'` row. There is NO tombstone table: a tombstone is an ordinary op carrying an **HLC**, so out-of-order arrival is just "compare HLCs", the same comparison the update path makes. A late **put** older than a seen tombstone loses; a tombstone older than a seen put loses. | "deletion marker", "grave" |
|
|
80
80
|
| **teardown** | Removing a workstream: `mu workstream teardown` kills its **mux session** and deletes its rows, writing **tombstone** ops so the history survives in the **ops log**. **Reversible** — `mu undo <group>` restores every row it removed, so no backup is warranted before running it. Dry-run by default; `--yes` commits. `mu workstream list --torn-down` lists past teardowns with the **group** to undo. NOT called "destroy": that word says irreversible, which is wrong, and it drove real operators to hoard pre-flight DB copies. What teardown does not restore is what was never in the DB: killed panes and freed **workspace** dirs. | "destroy", "delete workstream", "nuke", "purge" |
|
|
@@ -104,7 +104,10 @@ defined here, fix the doc. If you need a new term, add it here first.
|
|
|
104
104
|
| **operation** | A canonical mu verb (e.g. `mu task add`). Each verb is a thin CLI wrapper over a typed function in `src/*.ts` — the SDK and the CLI share one surface. | "command" (overloaded), "action" |
|
|
105
105
|
| **reconcile** | Verb: re-derive registry rows from substrate reality (the **mux**). Always runs in `mu agent list` and `mu doctor`. | "sync", "refresh" |
|
|
106
106
|
| **adopt** | Verb (`mu agent adopt`): register an existing pane as a managed **agent**. The inverse of `mu agent list`'s 'orphan' state. Pane must be in the workstream's **mux session**. | "import", "absorb" |
|
|
107
|
-
| **
|
|
107
|
+
| **control socket** / **ctl** | The per-agent unix socket the mu pi extension serves inside a pi agent's own interactive session (`$MU_CTL_SOCK`). mu derives its path from workstream and agent name, so nothing is stored. Carries `hello`, `status`, `send`, `wait` (resolves on pi's `agent_settled`), `abort` and `fresh` (new session + prompt) as JSON lines. The pane stays pi's normal TUI. **ctl state** is whether it answers: `ok`, `missing` or `refused`. A running pi keeps the extension it loaded at start; an **outdated extension** lacks an op this mu sends (`AgentExtensionOutdatedError`, exit 4) or reports an older `extVersion` in `hello` (doctor warns). pi's `/reload` re-imports it. | "rpc" (pi's `--mode rpc` replaces the TUI; mu does not use it), "daemon" |
|
|
108
|
+
| **delegate** | A **scratch workstream** agent started for one task: spawn, send the task, return at once, and hand its answer back when its control-socket `wait` resolves. The `mu_delegate` tool in the mu pi extension does this in one call; the CLI equivalent is `mu agent spawn -w scratch` + `mu agent send` + `mu agent wait --json`. The pane stays visible: attach, steer, abort, or keep talking to it. No agent types; the brief is ad-hoc text. | "subagent", "child agent", "worker" (a worker claims DAG tasks) |
|
|
109
|
+
| **lastText** | The text of the final assistant message of a pi agent run. The mu pi extension records it on `agent_end` and returns it verbatim in the control-socket `wait` reply; `mu agent wait --json` passes it through. mu never interprets it. | "result", "answer file", "output" (alone) |
|
|
110
|
+
| **hidden subagent** | A child agent in other tools (pi-subagents, pi's example subagent extension, Claude Code and Codex task tools) that runs out of sight: no pane to attach to, no way to steer it or keep talking to it, and its transcript collapses into a result. Usually comes with built-in agent types. mu's counterpart is the **delegate**. | "subagent" alone in mu prose |
|
|
108
111
|
| **TUI** | The interactive ink-based dashboard launched by bare `mu` in a TTY or explicitly by `mu state --tui`. Lives in `src/cli/tui/`. Read-only against SQLite (yanks, never executes). | "GUI", "interactive mode" |
|
|
109
112
|
| **dashboard** | The TUI's main screen — the grid of cards above the status bar. | "home screen", "main view" |
|
|
110
113
|
| **card** | A glanceable summary tile on the dashboard, identified by its toggle digit (0-9). Wrapped in a TitledBox. | "panel", "section" (overloaded) |
|
|
@@ -180,8 +183,9 @@ Agent state is runtime data and is not persisted. The codepoints live in
|
|
|
180
183
|
| `needs_permission` | nf-fa-lock | Waiting for a human answer |
|
|
181
184
|
| `unknown` | nf-fa-question_circle | No state source or no usable reading; the reason accompanies the state |
|
|
182
185
|
|
|
183
|
-
The state source is
|
|
184
|
-
backend
|
|
186
|
+
The state source is the control socket for pi agents, else herdr on the
|
|
187
|
+
herdr backend and murmur on the tmux backend. The extension's `busy`
|
|
188
|
+
maps to `busy` and `idle` to `needs_input`. mu maps murmur `working` to `busy`, `blocked` to
|
|
185
189
|
`needs_permission`, and `idle`, `done`, and `crashed` to `needs_input`.
|
|
186
190
|
The retained `agents.status` column is deprecated and always contains
|
|
187
191
|
`spawning`; no runtime read uses it.
|
|
@@ -192,6 +196,9 @@ The retained `agents.status` column is deprecated and always contains
|
|
|
192
196
|
| --------------------- | --------------------------------------------------------------------------- |
|
|
193
197
|
| `mu task release feature_a`| Clears the task owner for `feature_a`. The agent who claimed it is unaffected. |
|
|
194
198
|
| `mu agent close alice` | Terminates alice's pane and removes from registry. Destructive. |
|
|
199
|
+
| `mu agent send alice --fresh '…'` | New pi session + prompt as one control-socket operation; returns once the prompt's run started. Refused while busy unless `--force` (abandons the turn). pi only. |
|
|
200
|
+
| `mu agent abort alice` | Stops a pi agent's current turn through its control socket (pi's Esc) and waits for it to settle. No-op when idle. Queued follow-ups return to the pane's editor unsent. Non-pi agents: use kick. |
|
|
201
|
+
| `mu agent remote-env alice` | Prints (never runs) the ssh socket-forward args and identity env for a remote pi agent's `--command`, so its control socket answers at the local derived path. |
|
|
195
202
|
| `mu agent kick alice` | Signals (default SIGINT) the foreground process group of alice's pane TTY. For wedged tool subprocesses (`find /`, busy-wait); the wrapping CLI itself is untouched. Refuses when the foreground IS the wrapping CLI. |
|
|
196
203
|
| `mu agent wait alice bob --first` | Blocks until an agent finishes (busy → any other state). The task-less counterpart to `mu task wait` for scratch/off-the-cuff helpers that own no task. `--any`/`--first` fire on the first; default all. Exit 0 met, 5 timeout, 6 a watched pane died. |
|
|
197
204
|
| *(none)* | There is no detach verb. Use tmux detach to leave a workstream attached session without killing panes. |
|
|
@@ -240,7 +247,7 @@ Don't use them in mu code or docs:
|
|
|
240
247
|
|
|
241
248
|
| Avoided word | Why | Use instead |
|
|
242
249
|
| ---------------- | ---------------------------------------------------------------- | ---------------------------------------------------- |
|
|
243
|
-
| "subagent" |
|
|
250
|
+
| "subagent" | In other tools it means a hidden, opaque child agent; mu has none | "agent" (mu's unit), "delegate" (one-task scratch agent), or "hidden subagent" when contrasting |
|
|
244
251
|
| "session" | Pi has its own "session"; tmux has "session"; herdr has a third (server-level); ambiguous alone | "workstream" (mu's unit), "mux session" (cross-backend), or "tmux session" (literal) |
|
|
245
252
|
| "workspace" (mux sense) | mu already uses "workspace" for a VCS-isolated checkout. herdr's session-level container is also called a workspace, and letting both senses in would make `mu workspace list` ambiguous | "mux session"; say "herdr workspace" only when describing herdr's own CLI |
|
|
246
253
|
| "project" | Means a `.pi/` project root; conflict with mu's organizational unit | "workstream" |
|
|
@@ -283,6 +290,7 @@ exist to keep names canonical, not to replace `--help`.
|
|
|
283
290
|
| `mu sync --repair <peer>` | Reset a **watermark** to re-read that peer's **segment** from zero. The universal repair, safe because ingest is idempotent via `UNIQUE (machine_id, hlc)`. Takes a `machine_id` or any unique prefix. |
|
|
284
291
|
| `mu undo <group>` | Emit inverse ops for one **group**. Granular (touches only that action's rows), composable, and itself an op — so it syncs and is itself undoable. |
|
|
285
292
|
| `mu rebuild <file>` | Materialize a fresh DB by replaying the **ops log** in HLC order. The disaster-recovery path; prints the swap command rather than overwriting in place. Replays EVERYTHING the log knows, including machine-local ops — a rebuild is local recovery, not **ingest**, so it does not filter to synced entities. Carries `machine_identity` (and its HLC clock) across, so the rebuilt DB is the SAME peer. `agents` / `vcs_workspaces` are NOT rebuilt — they have no capture triggers so leave no ops, and `pane_id` / absolute paths would be lies after recovery; the summary reports the loss and says to re-spawn. |
|
|
293
|
+
| `mu link pi` | Install mu into pi: the **extension shim** (`~/.pi/agent/extensions/mu.ts`, a re-export of the installed `dist/extension/mu-pi.js`, marked `// mu:shim`) and a symlink `~/.agents/skills/mu` → the package's `skills/mu`. `--copy` inlines the extension (pinned). Never replaces a real directory; replaces a foreign symlink only with `--force` (`LinkConflictError`, exit 4). Root overridable via `MU_PI_HOME`. |
|
|
286
294
|
| `mu db backup <file>` | `VACUUM INTO` copy of the whole DB. The "one file I can scp" convenience; real DR is **segments** + `mu rebuild`. |
|
|
287
295
|
|
|
288
296
|
A one-off directory needs no flag — `MU_SYNC_DIR=/mnt/usb mu state`
|
|
@@ -375,7 +383,7 @@ reading commands ("alice claims design" sounds like a person;
|
|
|
375
383
|
task). Role-based names also make `mu agent list` and tmux's window
|
|
376
384
|
list legible at a glance.
|
|
377
385
|
|
|
378
|
-
|
|
386
|
+
Common roles:
|
|
379
387
|
|
|
380
388
|
`worker` long-lived implementer; the default
|
|
381
389
|
`reviewer` reads diffs/code; usually `--role read-only`
|
|
@@ -419,9 +427,9 @@ XDG-Base-Directory-Spec compliant. The state directory resolves as:
|
|
|
419
427
|
- **Never** place `MU_DB_PATH` inside `MU_SYNC_DIR`. Syncing a live
|
|
420
428
|
SQLite file (and its `-wal`/`-shm` sidecars) corrupts it. `mu doctor`
|
|
421
429
|
checks for this.
|
|
422
|
-
- mu does NOT consult any agent-template directory
|
|
423
|
-
|
|
424
|
-
|
|
430
|
+
- mu does NOT consult any agent-template directory (such as
|
|
431
|
+
`~/.pi/agent/agents/` or `.pi/agents/`). Roles are names and
|
|
432
|
+
briefs, never templates.
|
|
425
433
|
|
|
426
434
|
### Env vars (mu state location)
|
|
427
435
|
|
|
@@ -433,6 +441,7 @@ XDG-Base-Directory-Spec compliant. The state directory resolves as:
|
|
|
433
441
|
| `MU_SESSION` | Override active workstream name (when not auto-detectable) | n/a |
|
|
434
442
|
| `MU_MUX` | Force the **mux backend**, bypassing **mux detection**. Load-bearing: every verb reaches its multiplexer through `activeMux()`, so an unknown value fails the invocation rather than silently running on tmux. | wins over all detection |
|
|
435
443
|
| `MU_SYNC_DIR` | Shared folder holding one **segment** per machine. Unset = sync off, zero cost. The WHOLE cluster configuration; there is no peer list. | n/a |
|
|
444
|
+
| `MU_DELEGATE` | `0` hides the `mu_delegate` tool in the mu pi extension (kill switch for benchmarks or a model it confuses). The `mu doctor` extension row says so. | n/a |
|
|
436
445
|
|
|
437
446
|
### Env vars passed to spawned children
|
|
438
447
|
|
|
@@ -449,9 +458,11 @@ XDG-Base-Directory-Spec compliant. The state directory resolves as:
|
|
|
449
458
|
| `MU_HERDR_SESSION` | Drive a NAMED herdr server (`--session <name>`, its own socket) instead of the user's default one. The herdr analogue of `MU_TMUX_SOCKET`, and the isolation seam any herdr integration test must use so the suite can never observe — or destroy — real panes. herdr **mux backend** only. |
|
|
450
459
|
| `MU_<UPPER_CLI>_COMMAND` | Override the executable launched for `--cli <cli>` (e.g. `MU_PI_COMMAND=pi-alt` makes `--cli pi` exec `pi-alt`; hyphens in the cli key become underscores in the env var name, so `--cli pi-meta` reads `MU_PI_META_COMMAND`). Accepts multi-word strings (`MU_PI_COMMAND="pi-alt --some-flag"`); tmux exec's via a shell. Reconcile also treats the resolved binary as agent-worthy when surfacing orphan panes. When this env var supplies the override (and `--command` did not), the spawn-success line surfaces the env-var name (`Spawned worker-1 (pi-meta via $MU_PI_META_COMMAND)`) so stale aliases are visible without `mu agent show`. |
|
|
451
460
|
| `MU_SPAWN_LIVENESS_MS` | **tmux-tier knob** (herdr's `agent start` blocks until the agent is ready, so there is nothing to poll for). After spawn, wait this many ms then verify the pane is still alive AND scan the tail of its scrollback for known startup-error patterns (provider auth failures — `No API key found for X`, `401 Unauthorized`, … — plus shell-level `command not found` / `No such file or directory` when the spawned binary vanished post-pre-flight). Default 1500. Set to 0 to disable (useful in CI). On detected death the DB row is rolled back and `AgentDiedOnSpawnError` is thrown with the captured scrollback; on a startup-error match (pane alive but parked at an error prompt) the row is rolled back and `AgentSpawnStartupError` is thrown with the matched line + remediation hints. The complementary pre-flight check (PATH lookup of `--cli`'s resolved binary BEFORE any side effect) is not env-tunable; on miss it throws `AgentSpawnCliNotFoundError` with no orphan workspace / pane / row. |
|
|
461
|
+
| `MU_CTL_SOCK` | Set by spawn in every agent pane: the agent's control socket path, derived from (state dir, workstream, agent). The mu pi extension serves it; mu connects to it. |
|
|
462
|
+
| `MU_SPAWN_CTL_MS` | Budget for spawn's control-socket handshake with a pi agent, polled every 250ms (default `30000`; `0` disables). On timeout spawn still exits 0, reports `ctl: missing\|refused`, and warns on stderr. `mu agent spawn --no-ctl` skips it per spawn. |
|
|
452
463
|
|
|
453
|
-
|
|
454
|
-
|
|
464
|
+
They use the `MU_` namespace so they never collide with env vars other
|
|
465
|
+
pi extensions set in the same session.
|
|
455
466
|
|
|
456
467
|
---
|
|
457
468
|
|
|
@@ -57,7 +57,7 @@ Consequences:
|
|
|
57
57
|
| D8 | Apply resolves the pair before writing: a status-only op, or an unknown substate from a newer peer, falls back to the status default. The original payload stays in `ops`. | The foreign key fails the whole commit. Version skew must not break sync; the foreign key is there to catch our own bugs. |
|
|
58
58
|
| D9 | Non-`done` close and `park` require `--why`, stored as an ordinary note in the same transaction. | Keeps the rationale, the one good part of the notes-only convention. |
|
|
59
59
|
| D10 | Start with `parked` as the only non-default `OPEN` substate. No `--until`. | YAGNI. mu has no daemon to run the automatic un-park. Adding `triage` later is one seeded row. |
|
|
60
|
-
| D11 | One shared legacy mapping: `REJECTED → CLOSED/wontfix
|
|
60
|
+
| D11 | One shared legacy mapping: `REJECTED → CLOSED/rejected` (3.1.0; 3.0.0 used `wontfix`), `DEFERRED → OPEN/parked`. | Replaces `normalizeTaskStatus` (currently folds both onto `OPEN`). Apply, rebuild, undo and migration all use it, so recovery happens on every path. |
|
|
61
61
|
| D12 | Migrate v10 → v11 through `scripts/migrate.ts`, not in `openDb`. | Keeps the "production never migrates in place" rule. SQLite cannot add a foreign key to an existing table, so a new target is required anyway. |
|
|
62
62
|
|
|
63
63
|
## Schema (v11)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mu-crew/mu",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.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"
|
|
@@ -55,9 +55,9 @@
|
|
|
55
55
|
"test:fast": "vitest run --config vitest.fast.config.ts",
|
|
56
56
|
"test:watch": "vitest",
|
|
57
57
|
"test:watch:fast": "vitest --config vitest.fast.config.ts",
|
|
58
|
-
"lint": "biome check src test scripts",
|
|
59
|
-
"lint:fix": "biome check --write src test scripts",
|
|
60
|
-
"format": "biome format --write src test scripts",
|
|
58
|
+
"lint": "biome check src test scripts extension",
|
|
59
|
+
"lint:fix": "biome check --write src test scripts extension",
|
|
60
|
+
"format": "biome format --write src test scripts extension",
|
|
61
61
|
"typecheck": "tsc --noEmit && tsc -p tsconfig.test.json --noEmit",
|
|
62
62
|
"prepare": "npm run build",
|
|
63
63
|
"test:stress": "tools/test-stress.sh"
|