Skip to content

mu 3.13.0

Choose a tag to compare

@github-actions github-actions released this 20 Oct 17:10
· 9 commits to main since this release

Faster everyday verbs. tmux-touching verbs no longer load execa (about
50-60 ms each), pure-JS dependencies are bundled, and a bare verb in a
mu-spawned pane skips the tmux workstream lookup. mu doctor counts ops by
key range (~250 ms less), peer sync resumes from where it stopped, and the
TUI launches faster and stops re-detecting the VCS every 10 s.

Upgrade with npm i -g @mu-crew/mu@3.13.0, then /reload in running pi
sessions to pick up the extension. No schema change. Task, track and peer
lists now sort names by code unit (a-b, a0, a_b; uppercase first).

Changed

  • Spawn warns on Claude's folder-trust dialog. The dialog defaults to
    "No, exit", so the Enter that ends --send quit claude. The tmux
    startup scan now spots it (non-fatal): the pane and agent stay, the
    initial --send is skipped (send.error TrustDialogPending in
    --json), and a warning plus next step says to answer it in the pane
    and then mu agent send. It does not guard a later agent send or the
    herdr spawn path, and mu never presses a key for you.

  • TUI dashboard fills its space. A card's maxRows now caps only
    the first pass of the row allocator: leftover rows go to cards whose
    data still overflows, in proportion to the rows each hides, so a
    40-item Ready list at 80 rows shows all 40 instead of 15 above 60
    blank rows. Cards also move between columns: arrangeColumns
    (replacing balanceColumns, which only ran after a cull) searches
    single moves and swaps for the fewest hidden rows, even with all ten
    cards visible, and keeps the previous layout unless the new one hides
    max(3, 10%) fewer rows. The search also descends from the previous
    layout, so a retained layout never has a single move or swap that
    beats it by more than that margin. Among layouts that hide the same rows, the search
    prefers fewer cards away from their default column, then slot order. Footers and mouse hit regions read the same
    budgets (columnRowBudgets).

  • Skill: recipe wording from a prior runtime. An unknown owner
    is not a dead one: orchestrator-loop says to read the pane or run
    mu doctor before releasing its task. Reviews with only
    UNVERIFIED items go to the human, not into a REJECT round. findings § Record
    grades severity strictly (critical needs a concrete scenario).
    ultrathink and loop-until-done refute the umbrella's criteria on the
    merged tree before close --if-ready.
    watcher caps attempts per item and parks it with every attempt listed.
    long-run records DEAD-END: notes on the umbrella.
    backlog-triage re-verifies an item against the code before starting.
    dag.md states that every edge comes from a verb the caller ran (block, add/reparent --blocked-by) and no write adds edges on its own.
    VISION cites a prior runtime's transport and sentinel-marker lessons.

  • A changed peer segment is ingested from where the last read stopped.
    Every append used to make each other machine hash the whole segment,
    read it a second time as text and split it (~120 ms for this fleet's
    64 MB segment, ~9 ms of it needed). A clean read now saves the byte
    offset and running sha256 state next to the caught-up fingerprint; the
    next ambient ingest reads only the appended bytes, and accepts them
    only when their hash is the manifest's sha256 (~20 ms). A shrunk
    file, a stale manifest, a defect (which saves no offset) and mu sync
    take the full read and whole-file verify as before. The resumed hash
    trusts the prefix it already read: a local edit to those earlier bytes
    that keeps the file growing is caught only by mu sync, not by the
    ambient ingest. No
    schema change: the offset is extra fields in the existing
    sync_fingerprints value, which an older mu never matches and
    ignores.

  • mu workstream list reads mux liveness from one list-sessions.
    It also ran has-session once per workstream (6 spawns for 6
    workstreams). The listing now reuses the session names it already
    fetched, and falls back to per-workstream has-session when
    list-sessions fails, so a transient failure still never marks live
    sessions dead. ~22 ms CLI CPU saved; output is identical.

  • mu_delegate lists agents at most every 30 s while a failed delegate
    lingers.
    Each spawn ran mu agent list (~150 ms of CPU) only to prune
    the footer's failed count; the refresh is now throttled to once per
    30 s. Spawning and safety checks are unchanged; the footer may show a
    closed failure for up to 30 s longer.

  • mu agent spawn sets the tmux pane border in one tmux call. The four
    set-option -w calls are ;-chained into one invocation and target the
    pane directly, which drops the display-message #{window_id} lookup
    (the border helper's 5 tmux processes become 1, ~33-80 ms per spawn). Options, -w scope,
    MU_BANNER_QUIET, and the swallow-all-errors behaviour are unchanged.

  • The TUI detects the VCS backend once, not every 10 s. The slow tick
    ran jj root, sl root (twice) and git rev-parse before every
    git log just to learn the same backend again. It now caches the
    backend per project root for the TUI's lifetime (~4 of 5 processes per
    tick; ticks after the first dropped from ~690 to ~175 ms best case on
    a loaded machine). A none result is not cached, nor is a detection
    during which a root probe failed (spawn error, signal) rather than
    cleanly answering "not this VCS": a transient jj root failure in a
    colocated jj/git repo no longer pins git. mu state is unchanged (it
    never probes the VCS).

  • The TUI bundles ink and react into its lazy chunk. Importing the TUI
    resolved and compiled ~560 ESM files from node_modules per launch. tsup
    now inlines ink, react and their pure-JS deps into dist/tui-*.js (loaded
    only by bare mu and mu state --tui; other verbs are unchanged), using
    the createRequire banner from the commander/cli-table3 bundling. NODE_ENV
    is not defined at build time, so src/cli/tui-load.ts still picks react's
    production build at runtime and an explicit NODE_ENV still wins. TUI
    import ~470 -> ~67 ms, first Agents card ~890 -> ~440 ms (PTY, 20
    alternating launches on a copied DB). react-devtools-core stays external
    (ink loads it only under DEV=true).

  • Task, track and sync-peer sorts use code-unit order, not
    localeCompare.
    The first localeCompare call initialised ICU
    (~4 ms on nearly every list verb). Ties by name now sort by code unit,
    so a-b, a0, a_b replaces the locale order a_b, a-b, a0; uppercase
    sorts before lowercase. Sort keys, ROI order and everything else are
    unchanged.

  • mu doctor and mu db forget count a workstream's ops by key range.
    The per-workstream substr(key, …) test scanned all of ops once per
    torn-down workstream (27 scans of 61k rows, ~320 ms of SQL in
    mu doctor's size hint). It now seeks idx_ops_entity_key with a key
    range per entity (~28 ms); counts and deleted rows are identical.

  • A bare verb in a mu-spawned pane skips the tmux lookup. With no
    -w and no $MU_SESSION, resolveWorkstream ran tmux display-message -p '#S' (and loaded execa) even though spawn already
    put the workstream in $MU_WORKSTREAM. It now uses $MU_WORKSTREAM
    when $MU_MANAGED_AGENT=1 is also set: bare mu task list ~133 -> ~67
    ms from an agent pane. -w and $MU_SESSION still win, and a
    $MU_WORKSTREAM without $MU_MANAGED_AGENT (a stale export in a user
    shell) is ignored. User-started orchestrator panes are unchanged.

  • Spawn sees a pi agent's control socket within ~50 ms. The spawn
    handshake polled every 250 ms, so a socket pi bound just after a poll
    waited up to 250 ms to be noticed. It now stats the socket path every
    50 ms (no fork) and probes as soon as it exists: readiness overshoot
    132 -> 24 ms, ~100 ms less per pi spawn. The pane-alive check and
    scrollback capture keep their 250 ms cadence, plus one final check at
    the budget's deadline, so a pane dying in the last 250 ms keeps its
    crash scrollback.

  • mu_delegate closes the pane and writes the record note in
    parallel.
    A finished delegate with record waited for
    mu agent close before starting mu task note, though neither needs
    the other. They now overlap: ~70 ms less before the answer arrives
    (median of 25 pairs), on clean finishes that close and record. Failure
    reporting is unchanged: a refused close and a failed note are each
    named in the follow-up.

  • The keep-driving nudge reads mu task list, not mu state. At
    each armed settle the pi extension ran mu state --json only to read
    IN_PROGRESS task names, paying for the tmux pane listing and the
    orphan and workspace views it never used. It now runs
    mu task list --status IN_PROGRESS --json per dispatched workstream
    (--sort recency, newest first as state listed them), in parallel,
    deduplicated by task: ~56-69 ms less per armed settle (one
    workstream). A task still owned by a dead agent shows in the nudge until the next
    mu state or mu task wait reaps it.

  • Faster tmux-touching verbs. The tmux, herdr, kick and murmur-status
    executors run through node:child_process instead of lazily importing
    execa (~110 modules, 25-40 ms). mu state, mu agent list, and bare
    verbs that resolve the workstream via tmux drop by ~50-60 ms per call
    (state ~140 -> ~85 ms). Output and exit codes are unchanged; capture
    keeps execa's 100 MiB buffer cap.
    In-flight children are killed when mu exits or gets SIGTERM, SIGINT or
    SIGHUP, as execa's cleanup did, so no tmux or murmur process outlives mu.

  • Faster startup: pure-JS dependencies are bundled. Every verb
    resolved, read and compiled about 35 node_modules files (commander,
    cli-table3, string-width and their dependencies). tsup now bundles
    commander, cli-table3, string-width and picocolors into dist/
    (native better-sqlite3, execa, ink and react stay external; the TUI
    chunk is still lazy). Measured on interleaved warm runs: 5-15 ms per
    invocation (--version, task list, state).

  • Skill: orchestrators clean up scratch files. Review panels and
    other delegate jobs left briefs, diffs and outputs in /tmp or ad
    hoc dirs under $HOME. SKILL.md's orchestrator rules now say to keep
    briefs and answers in task notes and mu log (stdin for pure
    delegate calls), keep any files in one mktemp -d dir per job,
    and remove it before the turn ends.

  • Skill: worker checkouts come from mu. Orchestrators made their
    own git worktrees for workers, which mu could not refresh, diff or
    free, so they leaked. SKILL.md now says to use --workspace /
    workspace: true for every local agent; remote agents keep the
    manual recipe.

Fixed

  • mu log names who sent, spawned or aborted. agent.send,
    agent.spawn and a new agent.abort op (written only when a busy agent
    was actually aborted) carry the caller ($MU_AGENT_NAME, pane title,
    $USER) as their actor instead of system. The extension's abort reply
    now carries wasBusy, so a run that settles between the status probe and
    the abort is not logged.

  • A rolled-back spawn leaves an agent.spawn_failed log entry with the
    phase reached and the error class; agent.spawn now carries ctl=.

  • Spawned panes get the caller's SSH_AUTH_SOCK. mu sessions are
    created detached and can stay so, so tmux never refreshed the env and a
    pane could inherit a stale socket. Spawn now forwards it when it is set
    and a socket (file-type check only, no liveness probe).

  • Detached tmux sessions start at 200x50, not 80x24. mu agent read
    captured wrapped lines at 80 columns on unattached sessions.
    new-session -d now passes -x 200 -y 50 (attached sessions are
    unchanged; no resize-window). herdr is unaffected.

  • Nudges yield to a delegate answer on its way. The keep-driving and
    close nudges continued a settling run even when a mu_delegate answer
    sat in pi's follow-up queue (pi's hasPendingMessages() does not count
    custom follow-ups), so the agent took an extra turn without the
    answer. All three nudges now skip while an answer is being delivered or
    queued, re-checking after their mu read and again once the slowest
    nudge's read finishes (the nudges run in parallel); keep-driving and close also
    skip while an extension dialog is open. A skipped settle does not use
    up the prompt's one nudge.

  • Spawned agents use the spawner's database. Panes inherit the tmux
    server's env, so under MU_DB_PATH=/tmp/x.db a worker's mu hit the
    real DB. Local spawns now pin MU_DB_PATH (absolute, skipping
    :memory:) and MU_STATE_DIR in the pane env.

  • mu doctor reports dependency cycles made by sync. Two machines can
    each add a locally valid edge that merge into a cycle, silently hiding its
    tasks from ready/next. A dag section (and dagCycles in --json) lists
    each cycle with a mu task unblock … --by … -w … command that breaks it.
    Report-only; the task tree header no longer claims the schema forbids cycles.

  • mu agent send <agent> "" no longer fails with tmux "no buffer".
    On the mux path an empty message now presses a bare Enter (dismisses a
    "Press Enter" prompt). Over the pi control socket it is refused with
    AgentEmptySendError, whose next step is --via mux. herdr refuses an
    empty send (no verified bare-Enter primitive).

  • paneExists no longer reads a failed tmux probe as a dead pane.
    Any nonzero display-message exit returned false, so a permission or
    socket error looked like "gone" to liveness gates such as
    workspace free. Only "can't find pane" / no-server answers mean
    false now; other failures throw, and callers fail closed.

  • Cycle errors name the full path. CycleError now reads
    adding edge c -> a would create a cycle: c -> a -> b -> c and its
    next steps give a concrete mu task unblock <b> --by <a> -w <ws> for
    an edge on the path. findCyclePath (shortest path, safe on existing
    cycles) is exported from src/tasks/edges.ts.

  • Reconcile no longer deletes a respawned agent. The ghost prune
    deleted by name, so an agent closed and respawned under the same name
    during a pass lost its new row and had its tasks reopened. The delete
    now checks the snapshotted pane id inside the reaper transaction and
    skips the row (and the ctl socket unlink) on a mismatch.

  • mu agent send no longer calls an ambiguous failure "refused".
    When a send, fresh, interrupt or command request was already written
    to the control socket and the reply then timed out or the connection
    closed, pi may have queued the prompt. mu now raises
    AgentSendIndeterminateError (exit 1, "delivery unknown") whose next
    step is mu agent read <agent> -w <ws> -n 20 before resending.
    Failures before the write, dead panes and status/wait/hello keep their
    old errors.

  • Send waits for Codex's "model: loading" screen. The boot screen
    has no spinner, so awaitPaneQuiescence saw it as calm and pasted
    into a booting Codex, concatenating text. "model: loading" is now a
    busy marker (visible tail only).

  • mu workspace free refuses under a working agent. It ran
    worktree remove --force beneath a running worker and lost its
    pending changes. It now exits 4 when the owner is busy, needs
    permission, or is unreadable with a live pane. Idle (needs_input),
    a dead pane and a pane-less row are allowed. --force skips only
    this check. agent close and spawn rollback are unchanged.

  • A pi agent stuck on an extension dialog reads needs_permission.
    The control socket only knew busy / idle, so a pi waiting on a
    confirm or question mid-run read busy, and mu agent wait and
    mu_delegate held until their timeout (600 s / 3600 s). The mu pi
    extension now tracks pi's ui_prompt_start / ui_prompt_end and adds
    an optional blocked: {kind, title, since} to the status reply; mu agent list, mu state and mu agent show show needs_permission: dialog <kind>: <title>. A held mu agent wait on a pi agent ends
    early with outcome blocked (not fired, run counter unchanged) and
    exit 7. The mu_delegate follow-up says the delegate is blocked and
    has not finished, keeps the pane, and copies no partial answer. Older
    extensions send no field and ignore the new returnOnBlocked wait
    flag, so they behave as before. A held wait whose connection drops
    falls back to polling, and a dialog seen by that poll also ends the
    wait blocked (exit 7) rather than counting busy → needs_permission
    as a finished run. The blocked JSON row carries afterRuns, the run
    baseline the wait counted from, and the re-wait hint (CLI next steps
    and the mu_delegate follow-up, blocked or timed out) passes it as
    --after-runs, so a run that finishes after the dialog is answered
    but before the re-wait starts is returned rather than missed.

  • tmux panes keep their mu title. pi sets an OSC title
    π - <cwd>, which overwrote the pane title mu uses as its identity
    fallback. Spawn and adopt (including idempotent re-adopt) now set
    allow-set-title off on the pane. herdr is unchanged.