@mu-crew/mu 4.1.1 → 4.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-L7I3BLXP.js → chunk-4ZWBLTZK.js} +1512 -411
- package/dist/chunk-4ZWBLTZK.js.map +1 -0
- package/dist/{chunk-DEYBQDTO.js → chunk-FVRIKUBR.js} +2 -2
- package/dist/{chunk-B74N2EAS.js → chunk-RJQNDFBU.js} +8 -4
- package/dist/chunk-RJQNDFBU.js.map +1 -0
- package/dist/{chunk-LBBZAG5C.js → chunk-U2QLTREB.js} +26 -23
- package/dist/chunk-U2QLTREB.js.map +1 -0
- package/dist/{cli-H2PWJAJN.js → cli-H3VVCP7M.js} +146 -31
- package/dist/cli-H3VVCP7M.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/{errors-G5IVXSMY.js → errors-MDQZHB42.js} +8 -2
- package/dist/extension/mu-pi.js +20 -3
- package/dist/extension/mu-pi.js.map +1 -1
- package/dist/index.d.ts +2574 -2288
- package/dist/index.js +58 -4
- package/dist/index.js.map +1 -1
- package/dist/{tui-EN2CDP6G.js → tui-5ZARE7RS.js} +14 -15
- package/dist/tui-5ZARE7RS.js.map +1 -0
- package/docs/ARCHITECTURE.md +15 -8
- package/docs/VOCABULARY.md +14 -6
- package/docs/guide/remote.md +113 -4
- package/docs/guide/sql.md +1 -1
- package/docs/reference/env.md +1 -0
- package/package.json +1 -1
- package/skills/mu/recipes/remote-workers.md +7 -6
- package/dist/chunk-B74N2EAS.js.map +0 -1
- package/dist/chunk-L7I3BLXP.js.map +0 -1
- package/dist/chunk-LBBZAG5C.js.map +0 -1
- package/dist/cli-H2PWJAJN.js.map +0 -1
- package/dist/tui-EN2CDP6G.js.map +0 -1
- /package/dist/{chunk-DEYBQDTO.js.map → chunk-FVRIKUBR.js.map} +0 -0
- /package/dist/{errors-G5IVXSMY.js.map → errors-MDQZHB42.js.map} +0 -0
package/docs/VOCABULARY.md
CHANGED
|
@@ -34,7 +34,7 @@ agent names, and file paths are in [reference/naming.md](reference/naming.md).
|
|
|
34
34
|
| --- | --- | --- |
|
|
35
35
|
| **agent** | A named process in a pane with a row in `agents`. Identity is `$MU_AGENT_NAME`; mu never reads or writes pane titles. | "subagent", "worker" (a role) |
|
|
36
36
|
| **worker** | An agent in its role as task claimer. Prefer **agent** when ambiguous. | |
|
|
37
|
-
| **remote worker** | An agent spawned with `--host <profile>`: its pane is local and is the transport
|
|
37
|
+
| **remote worker** | An agent spawned with `--host <profile>`: its pane is local and is the transport, or the lease on it (a `session` profile's `shell` template runs there and forwards the remote pi's control socket; a `lease` profile's runs a **carrier** that owns a detached remote job), its process is remote, and its host, remote cwd, base and (with `--worktree`) remote repo are `agents.host/cwd/base/repo`; the repo is chosen per spawn (`--root`, else the profile's default). The pane dying is the agent dying (for a lease worker, the job's death is a separate stop: see **stop template**). | "REMOTE: note", "remote-env" | "remote agent record", "host registry" |
|
|
38
38
|
| **command gate** | A remote spawn of a non-pi command (`spawn --host dev --command 'npm test'`): a foreground pane, not a worker. Its `agents.gate_token` marks it and keys its remote completion record; `mu wait` returns the command's own numeric exit (event `exited`, `data.exit`). The pane is retained after the command ends until `agent close`. | "remote job", "gate worker" |
|
|
39
39
|
| **collect** | `mu agent collect <agent>`: run a remote worker's `--fetch` template locally and pin the fetched commit as `refs/mu/collect/<ws>/<agent>`; the commit is recorded as `agents.collected_head`, and `agent close` accepts a remote worktree at its base or that HEAD. mu prints the merge, never runs it. | "pull", "sync" (that is `mu sync`) |
|
|
40
40
|
| **delivery unknown** | A send/fresh/interrupt/command request was written to a live agent's control socket but no reply arrived (`AgentSendIndeterminateError`). pi may have acted; read the pane before resending. | "refused" (the request never went out) |
|
|
@@ -154,7 +154,7 @@ agent names, and file paths are in [reference/naming.md](reference/naming.md).
|
|
|
154
154
|
| **open frontier** | The part of the task DAG `task tree` and the dashboard read (`loadOpenTaskFrontier`): open tasks expand, CLOSED neighbours are leaves counted past a few, depth and node counts are capped, and every cap says how much it left out. Not `loadFullDag`, which is the whole workstream. | "subtree" |
|
|
155
155
|
| **note kind** | `task_notes.kind`: `brief`, `report`, `verdict`, `close` or `note`. Set once at insert; a local change raises, as does a local change to a note's author, content or created_at (append-only). | "note type", "tag" |
|
|
156
156
|
| **torn down** | `workstreams.torn_down_at` set (soft teardown). Views and reads skip it (exit 3 "is torn down"); ingest still sees the row. Teardowns an earlier mu wrote as deletes stay final and are listed from the ops log. | "archived", "deleted" |
|
|
157
|
-
| **revision** | `rev=<n>`: a number a preview prints and `--if <n>` must repeat. `workstreamRevision` hashes the newest op touching the workstream plus its agents (name, pane) and worktrees, so any change in those since the preview makes the destructive verb refuse (exit 4). `taskRevision` (src/revision.ts) is the task-side revision: a hash derived from current state (task row, owner, incoming and outgoing edges, note count; never a counter, so sync apply and cascades move it too), shown as `revision` by `task show`/`list` and as `rev` by a `task delete` preview; `--if <rev>` on `task delete`/`block`/`unblock`/`reparent`/`claim`/`release` refuses a changed task (exit 4, `expected_rev`/`current_rev` in `--json`). `agentRevision` is the agent-side one: a hash of the registry **agent incarnation**, the row's fields, every owned task (any workstream)
|
|
157
|
+
| **revision** | `rev=<n>`: a number a preview prints and `--if <n>` must repeat. `workstreamRevision` hashes the newest op touching the workstream plus its agents (name, pane) and worktrees, so any change in those since the preview makes the destructive verb refuse (exit 4). `taskRevision` (src/revision.ts) is the task-side revision: a hash derived from current state (task row, owner, incoming and outgoing edges, note count; never a counter, so sync apply and cascades move it too), shown as `revision` by `task show`/`list` and as `rev` by a `task delete` preview; `--if <rev>` on `task delete`/`block`/`unblock`/`reparent`/`claim`/`release` refuses a changed task (exit 4, `expected_rev`/`current_rev` in `--json`). `agentRevision` is the agent-side one: a hash of the registry **agent incarnation**, the row's fields, every owned task (any workstream), the registered worktree rows (an orphan worktree has its own) and the name's lease **reservation** (its handle or its absence); bare `agent close` previews it and `--if <rev>` applies. Also called **preview revision**. | "etag", "version" |
|
|
158
158
|
| **portable** | Syncable: `workstreams`, `tasks`, `task_edges`, `task_notes`. The rest is **machine-local** (`src/db.ts`). | "shared", "global" |
|
|
159
159
|
| **HLC** | Hybrid logical clock `(wall_ms, counter, machine_id)`; orders every op. Never regresses. | "timestamp", "version" |
|
|
160
160
|
| **machine_id** | Per-state-dir uuid naming this DB's **peer** and **segment**. | "device id", "host id" |
|
|
@@ -224,13 +224,21 @@ Terms the [4.0 contract](reference/surface-contract.md) added.
|
|
|
224
224
|
| **worker ceiling** | `mu next --workers <n>`: the most workers WORKING at once (an IN_PROGRESS task or an attempt in flight). An idle worker whose task is CLOSED does not count. Enforced where a pane would be created; never holds back an in-place retry. | "concurrency", "slots" |
|
|
225
225
|
| **hold** | `mu next --hold`: after dispatching, wait (bounded) for the first event on the in-flight workers; a death or an API error is recovered and the wait goes on, anything else is returned. | "watch mode", "daemon" |
|
|
226
226
|
| **brief (executor)** | `buildTaskBrief`: the prompt of one attempt, built from the task's first `brief` note and latest `report` note, stored verbatim with the reservation (a replay never rebuilds it). Never re-quotes a previous prompt. | "template" |
|
|
227
|
-
| **host profile** | A machine-local template (`shell`, `exec`, `fetch`, `max_sessions`, an optional default `repo`) for one remote machine (never one per repo: `max_sessions` caps the machine), filled by mu and run with `/bin/sh -c` (`exec`, `fetch`) or as the local pane's command (`shell`); mu never connects. Selected by `spawn --host`. Not the **dispatch host**. | "remote config" |
|
|
228
|
-
| **host template** | One field of a host profile (`shell`, `exec`, `fetch`) with `{env}`, `{cmd}`, `{local_sock}`, `{remote_sock}`, `{cwd}` placeholders. Each value is one shell-quoted word (inside your own quotes, the value itself); `{name:sh}` adds a second-shell quoting layer; `{env}` is whitelisted `KEY='value'` words; `{{` `}}` are literal braces; anything else in braces is refused. | "ssh command" |
|
|
229
|
-
| **host channel** | One connection slot of a host (an attached pane, `--exec`, `--fetch` or `host check`) counted against `max_sessions`: one `host_channels` row per holder. **Transient** rides one invocation (reclaimed only when its pid is proven gone and its template's process group, recorded before it runs, is gone too); **attached** rides an agent's registry id and pane: `spawn --host` reserves it before the pane opens, binds it to the new id + pane, and `deleteAgent` frees it (reclaimed otherwise only when the pane is proven dead). A refusal is one line naming the holder. | "session" |
|
|
227
|
+
| **host profile** | A machine-local template (`shell`, `exec`, `fetch`, a `transport` and for a lease a `stop`, `max_sessions`, an optional default `repo`) for one remote machine (never one per repo: `max_sessions` caps the machine), filled by mu and run with `/bin/sh -c` (`exec`, `fetch`) or as the local pane's command (`shell`); mu never connects. Selected by `spawn --host`. Not the **dispatch host**. | "remote config" |
|
|
228
|
+
| **host template** | One field of a host profile (`shell`, `exec`, `fetch`, `stop`) with `{env}`, `{cmd}`, `{local_sock}`, `{remote_sock}`, `{cwd}` placeholders, plus `{job_file}` (a lease `shell`, exactly once) and `{job}` (`stop`, required; `{cwd}` is `.` there, `{env}` is refused). The lexer enforces which field takes which placeholder. Each value is one shell-quoted word (inside your own quotes, the value itself); `{name:sh}` adds a second-shell quoting layer; `{env}` is whitelisted `KEY='value'` words; `{{` `}}` are literal braces; anything else in braces is refused. | "ssh command" |
|
|
229
|
+
| **host channel** | One connection slot of a host (an attached pane of a `session` profile, `--exec`, `--fetch`, `--stop` or `host check`) counted against `max_sessions`: one `host_channels` row per holder. A `lease` pane holds none. **Transient** rides one invocation (reclaimed only when its pid is proven gone and its template's process group, recorded before it runs, is gone too); **attached** rides an agent's registry id and pane: `spawn --host` reserves it before the pane opens, binds it to the new id + pane, and `deleteAgent` frees it (reclaimed otherwise only when the pane is proven dead). A refusal is one line naming the holder. | "session" |
|
|
230
|
+
| **transport** | A host profile's `session` (default) or `lease`. `session`: the pane runs the `shell` template, a held ssh, and holds a host channel. `lease`: the pane runs a **carrier**, holds no channel, and a **stop template** ends the job. `--transport`, validated in `addHost` (`host_profiles.transport`, no CHECK). | "mode", "kind" |
|
|
231
|
+
| **carrier** | The local process a lease profile's `shell` template runs in the pane (e.g. `mule run --lease`): it owns one detached remote job, polls on its own connection, binds the control-socket forward, writes the **job handle** to `{job_file}` before dispatch, and stops the job when the pane dies (best effort). mu knows no carrier by name. | "wrapper", "agent process" |
|
|
232
|
+
| **job handle** | The opaque line (`^[A-Za-z0-9._:@-]{1,128}$`) a carrier writes to its job file (`<state dir>/carrier/<ws>/<agent>.job`, hashed past the ctl-socket bound). mu fills it into the name's **reservation** with the `stop` template that was admitted (`host_stop`). Only an ENOENT means "no file": an unreadable, empty or invalid file keeps the reservation and the tree. | "pid", "job id" |
|
|
233
|
+
| **reservation** | A lease spawn's `carrier_jobs` row for (workstream, name), inserted under the agent lock before any carrier effect (every spawn holds that lock from its name check to its finalized row, so none lands in between). It holds an immutable spawn token that guards every update, a `launch` state that turns `intent` just before the mux is asked for the pane, the pane and incarnation once known, and the handle after the **job handshake**. It is name-exclusive. While it exists, every spawn of the name refuses (exit 4), lease or not, and the agent-row insert checks it in the same statement. Agent deletion never touches it. It is released only after a successful stop plus cleanup of the job file and tree, by its own rollback when `launch` is still `none` and any remote worktree it prepared is removed (a failed removal keeps it, with the reason and WHICH tree: `tree_path` + `tree_agent_id`, released only by a close of that tree), or by `agent close --abandon-job`. `agent close` stops it after the agent's liveness proof and before the tree (the name's close does the same when its agent row is gone). An empty reservation with `launch` `intent` is never released otherwise: nothing mu can observe proves such a carrier will not publish. Machine-local, no FK, trigger, op or sync; `mu rebuild` reports it lost. | "lock", "claim", "job row" |
|
|
234
|
+
| **abandon a job** | `agent close <name> --abandon-job`: drop a lease **reservation** WITHOUT running its **stop template**, for a host that is gone for good. Logs an event naming the host and the handle (or the empty reservation's facts) and leaves the job file in place. The only deletion of a reservation that launched a carrier without a successful stop; `--force` never stands in for it. | "force close", "drop job" |
|
|
235
|
+
| **job handshake** | A lease spawn's wait for the **job handle** (`MU_SPAWN_JOB_MS`, default 30 s, never 0), before and independent of the ctl handshake (`--no-ctl` does not skip it). The handle is filled into the spawn's **reservation**, guarded by its token. If the pane dies first, the error is `AgentDiedOnSpawnError`. If no handle arrives by the deadline, the spawn is rolled back. Neither case releases the reservation. | "job wait", "lease probe" |
|
|
236
|
+
| **stop template** | A lease profile's `stop` (`--stop`): the template that ends a job given its handle (`{job}`), idempotent (exit 0 for a job already ended). `runStop` runs it under a transient channel only if it is byte-identical to the `host_stop` recorded with the handle; exit 3 is a **hand-back**; any other failure keeps the handle. | "kill template", "cleanup" |
|
|
237
|
+
| **hand-back** | A host `exec`, `fetch` or `stop` template that exits 3: a human must act (a hardware key, a prompt). `runLeasedTemplate` throws `HostHandbackError` (label `human needed`, exit 5, `data.handback`), never retried and never recorded as `human-needed` by mu: that record is always a decision. `host check` reports it as a `handback` step and skips the rest; a rollback or gate cleanup swallows it. | "auth failure", "ssh failed" |
|
|
230
238
|
| **collected HEAD** | A remote worktree's HEAD as last fetched home (`agents.collected_head`, set by `mu agent collect`; a **remote recovery** record keeps it after the row is gone). A remote close needs HEAD equal to the base or this. | "synced commit" |
|
|
231
239
|
| **identity trio** | Three unrelated identities: **spawner** (who started an agent), **dispatch host** (which machine may dispatch a task), **revision** (`workstreamRevision`, a preview fence). Do not substitute one for another. | |
|
|
232
240
|
| **dashboard** | The human-only read-only five-tab view (Overview, the default card grid; Tasks, DAG, Agents, Log) over a workstream row and the attention strip (on every view, drills included), opened by bare `mu` on a TTY. Enter drills a row (task notes, agent scrollback, one log op); `y` copies the row's command; only `a` on an agent runs anything (the mux's attach steps, falling back to the scrollback with the reason; from a client of another tmux server it nests on purpose and says `C-b C-b d`). Replaces the removed card TUI. Every view is live on one poll loop (3.x's tiers: a fast SQL tick, 1 s by default and set with `+`/`-`, and a 10 s slow tick for agent state and scrollback); `r` only nudges. Colours are the 3.x palette as named theme roles (`src/cli/tui/theme.ts`; NO_COLOR turns them off). Its read model is `loadDashboard` (`src/dashboard-data.ts`); its Next line is `computeNext` for the caller, "unavailable" (never "no authorised work") only when that fails. | "home screen" |
|
|
233
|
-
| **attention item** | One line of the dashboard's trouble list (`DashboardSnapshot.attention`), read over every live workstream regardless of filters: `dead` (pane gone), `permission`, `stalled` (one tool over 15 min), `input` (the agent's `idle` flag), `delegate` (a refused **delegate call**, which stays until reissued or cancelled
|
|
241
|
+
| **attention item** | One line of the dashboard's trouble list (`DashboardSnapshot.attention`), read over every live workstream regardless of filters: `dead` (pane gone), `permission`, `stalled` (one tool over 15 min), `input` (the agent's `idle` flag), `delegate` (a refused **delegate call**, which stays until reissued or cancelled; any caller; a failed call is final and is not one), `human` (an OPEN `kind=human` task, any recorder), `next_unavailable`, `sync` (the latest ingest's warnings, until an ingest comes back clean). An item a `mu` command clears carries that command as its `fix`, which `y` copies (dead: `agent close`, refused delegate: `delegates finish --outcome cancelled --no-record`, human: `task close`). Not **needs attention**, which is the `task wait` stall. | "alert", "notification" |
|
|
234
242
|
|
|
235
243
|
<!-- doc-cli-drift:skip-end -->
|
|
236
244
|
|
package/docs/guide/remote.md
CHANGED
|
@@ -136,16 +136,125 @@ mu host show dev # templates and the holders of its channels
|
|
|
136
136
|
```
|
|
137
137
|
|
|
138
138
|
- Placeholders: `{env}` (the five `MU_*` identity variables only), `{cmd}`,
|
|
139
|
-
`{local_sock}`, `{remote_sock}`, `{cwd}
|
|
139
|
+
`{local_sock}`, `{remote_sock}`, `{cwd}`; a lease profile adds `{job_file}`
|
|
140
|
+
(its `shell`, exactly once) and `{job}` (its `stop`, required). A name valid
|
|
141
|
+
in only some fields is refused in the others. Each value is one quoted word and
|
|
140
142
|
is exactly the text you passed, also inside your own `"..."` or `'...'`.
|
|
141
143
|
A template that hands command text to a second shell (ssh's remote login
|
|
142
144
|
shell) says so per placeholder: `{cmd:sh}`, `{cwd:sh}` quote the value for
|
|
143
145
|
that shell too. Write `{{` and `}}` for literal braces. Anything else in
|
|
144
146
|
braces is refused.
|
|
145
147
|
- A command that times out, or whose caller is killed, takes its whole process
|
|
146
|
-
group with it before its channel is freed, so
|
|
147
|
-
undercounts a live transport.
|
|
148
|
+
group with it (TERM, then KILL after 5 s) before its channel is freed, so
|
|
149
|
+
`--max-sessions` never undercounts a live transport.
|
|
150
|
+
- An `exec` or `fetch` template that exits 3 is a hand-back (a hardware key, a
|
|
151
|
+
prompt): mu stops with exit 5, `human needed`, and the template's first line.
|
|
152
|
+
Never retried; `host check` reports a `handback` step.
|
|
153
|
+
- A `shell` template must not ride a shared ssh master (keep `-o
|
|
154
|
+
ControlMaster=no -o ControlPath=none`, as `host init --ssh` prints): a `-L`
|
|
155
|
+
through a master is bound by the master, outlives the pane, and is never
|
|
156
|
+
re-bound, so the next spawn of that name gets no socket. `host check` warns
|
|
157
|
+
when the template lacks either option. On a session-capped host use a
|
|
158
|
+
lease ([below](#session-capped-hosts)).
|
|
159
|
+
- `mu host show` and `list` print the transport (`session` or `lease`) and,
|
|
160
|
+
for a lease, the `stop` template.
|
|
148
161
|
- `--max-sessions N` counts every channel mu holds to the host. At the cap mu
|
|
149
162
|
refuses with one line naming the holder.
|
|
150
163
|
- `host check --agent <name> -w <ws>` also probes that agent's forwarded
|
|
151
|
-
control socket.
|
|
164
|
+
control socket, and on a lease agent reports its recorded job handle.
|
|
165
|
+
|
|
166
|
+
## Session-capped hosts
|
|
167
|
+
|
|
168
|
+
Some hosts allow one ssh session per connection (`MaxSessions 1`, the Meta
|
|
169
|
+
devvm). A `session` worker's pane holds that session for the worker's whole
|
|
170
|
+
life, so every other `ssh` to the host (the murmur collector, `git fetch`, you)
|
|
171
|
+
is refused, and it shows as a misleading `Permission denied`. A `lease` profile
|
|
172
|
+
fixes that: the pane runs a local **carrier** that owns a detached remote job on
|
|
173
|
+
its own connection, so no session is held.
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
mu host init dev --mule # prints the starter below; runs nothing
|
|
177
|
+
mu host add dev --transport lease \
|
|
178
|
+
--shell 'mule run --host dev --lease --human --cwd {cwd} --job-file {job_file} --forward-unix {local_sock}:{remote_sock} -- env {env} sh -c {cmd}' \
|
|
179
|
+
--exec 'mule exec --host dev -- sh -c {cmd}' \
|
|
180
|
+
--stop 'mule rm --host dev {job}' \
|
|
181
|
+
--fetch 'git fetch ssh://dev{cwd} HEAD'
|
|
182
|
+
mu host check dev --root '~/repo'
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
- The `shell` template must contain `{job_file}` once, `{local_sock}` and
|
|
186
|
+
`{remote_sock}`: the carrier writes the job handle to the file, and mu records
|
|
187
|
+
it in the name's `carrier_jobs` reservation (machine-local; `mu sql "SELECT *
|
|
188
|
+
FROM carrier_jobs"`). The reservation is taken before the pane opens. While
|
|
189
|
+
it exists, every spawn of that name refuses. Once a carrier may have launched,
|
|
190
|
+
a failed spawn that never got a handle keeps the reservation and the tree for
|
|
191
|
+
`mu agent close <name>`.
|
|
192
|
+
`--stop` must contain `{job}`, and must be idempotent: exit 0 for a job that
|
|
193
|
+
already ended. `{env}` is refused there, `{cwd}` is always `.`.
|
|
194
|
+
- `local_sock` determines `remote_sock`: both derive from (workstream, agent),
|
|
195
|
+
so a carrier's forward table never maps one local socket to two remote ones.
|
|
196
|
+
- A lease pane holds no host channel, so `--max-sessions` counts only what mu
|
|
197
|
+
opens itself (`exec`, `fetch`, `stop`, `check`), and `collect` works while a
|
|
198
|
+
lease worker runs.
|
|
199
|
+
- Why `--fetch` stays plain ssh: a fetch is a bulk transfer, and the carrier's
|
|
200
|
+
channel is never lent to one.
|
|
201
|
+
- Why a session template must not ride a shared master: a `-L` asked of a
|
|
202
|
+
control master is bound by the master, not by the client, and outlives the
|
|
203
|
+
pane. mu then deletes the socket file but the master keeps the listener; the
|
|
204
|
+
next spawn of that name asks for the same forward, ssh exits 0 and no file
|
|
205
|
+
appears (`ctl: missing`, for that name, for good). A lease carrier binds and
|
|
206
|
+
verifies its own forward; a session template keeps `ControlMaster=no
|
|
207
|
+
ControlPath=none`.
|
|
208
|
+
- mule jobs run in a non-login shell: a host that finds `pi` only through login
|
|
209
|
+
files edits `sh -c` to `zsh -ilc`, or fixes `PATH` in `.bashrc`. `host check`
|
|
210
|
+
runs `pi` without a login shell on a lease profile for that reason.
|
|
211
|
+
- An exit 3 from any host template (`stop` included) is a hand-back: stop and
|
|
212
|
+
ask a human. mu never retries it.
|
|
213
|
+
- `agent spawn --host` on a lease profile waits for the job handle
|
|
214
|
+
(`MU_SPAWN_JOB_MS`, default 30 s) even under `--no-ctl`. A spawn that fails
|
|
215
|
+
after the handle runs `stop` before it removes the worktree. A failed stop
|
|
216
|
+
keeps both the handle and the worktree, and a respawn of that name refuses
|
|
217
|
+
until `mu agent close <name>` stops the job. A lease profile runs no command
|
|
218
|
+
gates.
|
|
219
|
+
|
|
220
|
+
- `agent close` on a lease agent kills the pane and proves it gone as usual,
|
|
221
|
+
then runs `stop` for the job, and only then removes the worktree. A stop that
|
|
222
|
+
fails (the template's first stderr line is named), hands back (exit 3) or
|
|
223
|
+
finds the `stop` template changed keeps the agent and its handle: fix that,
|
|
224
|
+
then close again. `--force` never skips the stop. A handle the spawn had not
|
|
225
|
+
recorded yet is read from the job file; with neither, close refuses (`spawn in
|
|
226
|
+
progress`).
|
|
227
|
+
- With no agent row left (the carrier was `kill -9`ed and reconcile reaped it,
|
|
228
|
+
a teardown, a raw delete), `mu agent close <name> -w <ws>` stops the job
|
|
229
|
+
first, then closes the recovery worktree, if any. `agent collect` never
|
|
230
|
+
stops a job.
|
|
231
|
+
- A host that is gone for good: `mu agent close <name> --abandon-job` drops the
|
|
232
|
+
handle without a stop and logs the host and handle; the job may still run
|
|
233
|
+
there. It is the only way past an unreachable host.
|
|
234
|
+
|
|
235
|
+
### Smoke-test a lease end to end
|
|
236
|
+
|
|
237
|
+
`scripts/smoke-lease.sh` drives a real mule against a loopback `sshd
|
|
238
|
+
MaxSessions 1` on this machine (no other host), with a ctl-speaking fake pi
|
|
239
|
+
(`scripts/smoke-fake-pi.mjs`) as the remote job. Vitest never runs it:
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
MU=~/hacking/crew/mu MULE=~/hacking/crew/mule bash scripts/smoke-lease.sh
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
- It builds both (`cargo build --release`, `npm run build`), then checks spawn
|
|
246
|
+
(a job handle, no host channel, the forward bound by mule's master), a
|
|
247
|
+
second master with `ssh -S` during the worker, `collect` at `--max-sessions
|
|
248
|
+
1`, send and wait (the settled `lastText` must echo the sent text), and a
|
|
249
|
+
close that leaves no job, forward, socket, job file, worktree or
|
|
250
|
+
`carrier_jobs` row. It then `kill -9`s a carrier, lets reconcile reap the
|
|
251
|
+
row, and closes by name, which must stop the job.
|
|
252
|
+
- Exit 0 is a pass. A missing prerequisite (sshd, ssh, tmux, node, cargo,
|
|
253
|
+
lsof, `MULE`, `node_modules`) exits 2. It is never a pass.
|
|
254
|
+
- Everything is private: its own `/tmp/mu-smoke.*` dir, `HOME`, `TMPDIR`,
|
|
255
|
+
`XDG_*`, `MU_DB_PATH`, a blank `MU_SYNC_DIR`, a private tmux server and
|
|
256
|
+
mule `--config`. Your mule config, mule state and mu DB are never read. An
|
|
257
|
+
exit trap stops every process it started, on failure too. `SMOKE_KEEP=1`
|
|
258
|
+
keeps the dir for a post-mortem.
|
|
259
|
+
|
|
260
|
+
Spec: [lease transport](../specs/2026-10-28-lease-transport.md).
|
package/docs/guide/sql.md
CHANGED
|
@@ -50,7 +50,7 @@ SIGINT/SIGTERM during a wait. A wait that hits its cap
|
|
|
50
50
|
|
|
51
51
|
## Read the schema
|
|
52
52
|
|
|
53
|
-
The schema (version 13) has
|
|
53
|
+
The schema (version 13) has 27 tables and 4 views (`ready`, `blocked`,
|
|
54
54
|
`blockers`, `goals`). Views skip torn-down workstreams. `blockers` has one
|
|
55
55
|
row per open edge: `task_local_id` is held up by `blocker_local_id`.
|
|
56
56
|
Four tables travel between machines: `workstreams`, `tasks`,
|
package/docs/reference/env.md
CHANGED
|
@@ -33,6 +33,7 @@ milliseconds; `0` disables a wait unless noted.
|
|
|
33
33
|
| --- | --- | --- |
|
|
34
34
|
| `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. On herdr, spawn refuses both with exit 2. | the cli value |
|
|
35
35
|
| `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` |
|
|
36
|
+
| `MU_SPAWN_JOB_MS` | Lease profiles only. How long `agent spawn --host` waits for the carrier to write its job handle to `{job_file}`. Runs even with `--no-ctl` or `MU_SPAWN_CTL_MS=0`. A pane that dies first throws `AgentDiedOnSpawnError` with its scrollback. A live pane with no handle by the deadline is rolled back (`LeaseJobTimeoutError`). `0` or an invalid value means the default: a lease spawn always waits. | `30000` |
|
|
36
37
|
| `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` |
|
|
37
38
|
| `MU_SPAWN_LOCK_TIMEOUT_MS` | How long a spawn waits for the per-workstream spawn lock. | `15000` |
|
|
38
39
|
| `MU_AGENT_LOCK_TIMEOUT_MS` | How long `agent close` and `--worktree` staging wait for the per-agent lock before exit 4 (`AgentLockBusyError`, nothing done). | `15000` |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mu-crew/mu",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.1",
|
|
4
4
|
"description": "A persistent, observable crew of pi agents running in one tmux or herdr session per workstream, coordinated through a built-in task DAG.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Remote workers
|
|
2
2
|
|
|
3
|
-
Use when an agent runs on another machine. A
|
|
3
|
+
Use when an agent runs on another machine. A host profile holds your command templates; mu never connects.
|
|
4
4
|
|
|
5
|
-
The pane is the transport and forwards the remote pi's control socket: `send`, `wait`, `abort` work as locally; a dead pane is a dead agent. The host has no mu DB: you close the task
|
|
5
|
+
The pane is the transport and forwards the remote pi's control socket: `send`, `wait`, `abort` work as locally; a dead pane is a dead agent. The host has no mu DB: you close the task from the worker's `CLOSE:` block.
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
mu host init dev --ssh # a starter to edit into `mu host add`
|
|
@@ -18,8 +18,9 @@ mu agent close worker-1 -w <ws> --if <rev>
|
|
|
18
18
|
|
|
19
19
|
End the brief with: commit, then `CLOSE: task=<id> evidence="<command + result>"`. Cherry-pick the collected commits and re-run the evidence before you close.
|
|
20
20
|
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
- **Long remote gates are panes** (`--command 'npm test'`), not a held ssh.
|
|
21
|
+
- A capped host (`--max-sessions 1`): use a lease (`host init --mule`).
|
|
22
|
+
- Long remote gates are panes (`--command`).
|
|
24
23
|
- `close` refuses a dirty or uncollected tree: collect again.
|
|
25
|
-
-
|
|
24
|
+
- Exit 3 from a host command is a hand-back: stop and ask; never retry or ssh yourself.
|
|
25
|
+
- A `shell` template must not ride a shared ssh master.
|
|
26
|
+
- A dead lease pane leaves its job running: `close` stops it; only `--abandon-job` passes an unreachable host.
|