# Surface contract

The contract mu 4.0 is built to: what every result, wait, delegate and
CLOSE block promises an agent caller, and the budgets on what it reads.
Terms are in [VOCABULARY](../VOCABULARY.md), the module map in
[ARCHITECTURE](../ARCHITECTURE.md), the reasons in the
[design history](../history/mu-next-design/README.md). Where this page
and the source disagree, the source is right and this page is a bug.

A change that alters a contract here updates this page, ARCHITECTURE
(module map) and VOCABULARY (term) in the same commit as the code. No
module-map row, runnable guide snippet or `next` hint exists for code
that has not shipped.

## What ships

Schema 13, `CAPTURE_VERSION` 6, 29 classified tables and 4 views
(`PORTABLE_TABLES`: `workstreams`, `tasks`, `task_edges`, `task_notes`,
`checks`;
every other table is machine-local, see `src/db.ts`). The verbs and
their guarantees:

- **Output.** `{ok,data,next}` on stdout for every `--json` verb and
  every failure; `next` is computed once per result (`computeNext`).
  Task, log, state and notes reads are capped records.
- **Wait.** `mu wait` (typed refs, first event, one JSON result,
  `resume`, `--all`, `--until-text`, `--wave`, `--blockers-of`,
  `--on-stall exit|warn|steer|release`); `mu task wait` is its alias over
  task refs; `mu agent wait` is the agent-keyed form over the same engine. Producers
  (`spawn`, `send`, `claim --for`) arm a registered wait from real state.
- **Tasks and agents.** Batch `task add`, `--blocks`, note kinds,
  `--if <rev>` fences on membership changes and destructive verbs,
  task-bound spawn with receipts and replay, `mu agent check`, soft
  teardown with a preview revision, `mu human-needed`, recorded checks
  (`mu task check`/`verdict`, listed by `mu state` until answered, awaited by
  `close --if-ready`), `mu orchestrate`
  (an orchestrator of any harness as a managed pane, `MU_ORCHESTRATOR=1`),
  `--as <caller>` on `wait`, `state` and `next`. On the `hooks` channel
  (a local claude, codex, cursor-agent or muse pane on tmux), `agent send`
  pastes after clearing the input line, gated on murmur's report and
  confirmed by `run_count` (`ack`), never resent; `--steer` is refused;
  `mu agent answer` types a pending dialog's answer; `--interrupt` and
  `mu agent abort` press Escape (confirmed by the harness's interrupt event,
  else `idle?`), `--fresh` sends the harness's fresh command first, and a
  wait settles on its `turn_stopped` with that turn's text. A task-bound
  spawn of a `hooks` worker writes the brief file and pastes a one-line
  pointer after `session_start`, and its replay promotes an `unknown`
  receipt from the inbox. Spawn presets: `--model` and each harness's
  auto-approve and trust flags. `mu agent list` shows harness and channel.
- **Executor.** `mu task auto` and `mu next` (`--dry`, `--hold`,
  `--workers`, `--refute`), `dispatch_host`.
- **Delegates.** A `mu_delegate` call is a `kind=delegate` task with a
  durable `call_id` (`mu delegates`, wave tally, `mu_delegate_reissue`),
  and `mu delegates run <call-id>` runs one with no multiplexer.
- **CLOSE block.** A pi worker's final `CLOSE:` lines are applied once
  per settled run by the wait engine (`src/close-block.ts`), and so are a
  `hooks` worker's, from its `turn_stopped` text.
- **Remote.** Host profiles, remote spawn, `agent collect`, the close
  rule, remote command gates, recovery records. A remote claude, codex,
  cursor-agent or muse worker is on `hooks` with a remote key: mu reads its
  node through `--exec` on a send gate, a wait, a collect and `spawn --task`
  readiness (one read every 5 s until its session starts, up to
  `MU_SPAWN_ACK_MS`), never an ack poll, so its send is `ack: sent`,
  `--fresh` is refused, and `collect` applies its last turn's `CLOSE:` block
  (`close`). Its task brief file is written on the host through `--exec` (at
  most 96 KB with `\` and `'` counted escaped, and a filled template under
  128 KiB). `host check` records
  which harnesses the host has (`host_profiles.capabilities`), runs the pi
  steps only on a host with pi, and a spawn for a harness it lacks is refused.
- **Surfaces.** In-process `mu db query` (alias `mu sql`), five-tab
  read-only dashboard, pi package (3.x's link verb is gone), `mu pi migrate`,
  `mu init` (mu's Stop hook entry and skill copy per hook harness, prompt
  skills for claude and cursor-agent, touching only what it wrote, never a
  skill directory pi reads).
- **Skill and prompts.** One `mu` skill for every harness, with a capability
  section per channel (`ctl`, `hooks`, `mux`); its recipes give the
  native-subagent path (`mu task check`/`verdict`) beside `mu_delegate`, and
  the `hooks` caveats for orchestrators and workers. The prompts ship as pi
  commands (`/mu-review`) and, through `mu init`, as skills of the same names
  for claude and cursor-agent, which only the user starts
  (`disable-model-invocation`); codex gets none (no verified opt-out), and
  Muse Code installs skills itself.

- **Settle wait.** The pi extension's keep-driving nudge is the settle
  wait (`extension/nudge.ts`): every completed settle of an orchestrator
  runs `mu wait --first --on-stall exit --json` as its callers (no shell
  line parsed, no once-per-prompt latch) and continues the turn with the
  event. It stops when nothing is owed, the user cancels, or the wait
  fails. Under a `mu human-needed` stop it still collects owned work, led
  by a drain rule (start nothing new) instead of the keep-driving rule. A cancelled wait drains through
  `MuRunOpts.drainMs`. Skipped under `MU_REMOTE=1` and in a nested pi.
  Delegate answers arrive through it (`v_settle_delivery`): a `delegate`
  event's continuation carries that call's report (capped, with the
  `mu task notes` pointer); a wave's waits for its siblings and shows ONE
  summary. The pi message is only a **wake** (a pointer, no answer text)
  when no settle wait will show an answer.

- **Stop hook.** `mu hook stop <harness>` is the settle wait's twin for
  claude, codex and cursor-agent: for an orchestrator pane mu started (an
  anchor in its process chain, never a nested or remote agent), a consuming
  `mu wait --as <caller>` whose event blocks the stop, a block while work is
  owed (`MU_KEEP_DRIVING_MAX` in a row) and an allow when nothing is owed or a
  live wait holds the caller; for a worker, the close nudge once per session.
  stdout is the harness's block answer, empty to allow; exit is always 0,
  parse errors included (claude and codex read exit 2 as a block).
  Never for Muse Code.

- **Delegates without a multiplexer.** `mu_delegate` registers in any
  unmanaged pi. A spawn that fails `NoMultiplexerError` (only that error)
  runs the call through `mu delegates run <call-id>`: one child
  `pi --mode rpc`, no pane, no agents row, `transport: process`,
  `attach: null`. Admission, record and cancel are unchanged; `keep` is
  dropped; `/reload` leaves the run going; quit cancels it.

## Fixed contract

**Output.** Success `{ok:true,data,next}`; error `{ok:false,error,message,next,exit}`
on stdout with the real non-zero exit. `next` is `NextPlan`
`{mode:'seq'|'alt',commands:string[]}` or `null`; a `seq` is never
truncated. Op payloads keep history names; output keys equal SQL
column names, `id` is `local_id`. Ordinary result <= 4 KB, list record
<= 400 chars, a cut says `more=N`/`cut=N` and names the exact
retrieval command. A list record may carry a per-item `next` string
(`agent list`'s leftovers, doctor's `close`): data, not a `NextPlan`, and
never hinted by the envelope. A leftover's cap bounds `stop_failed`; identity
and `next` are never cut. Whole pinned brief, report and verdict are exempt
from the cap (they are the content). Text record: escaped, one record
per line; the exemption allows no multiline framing. NDJSON only for
explicitly streaming logs; `mu wait` is one object.

**Wait.** One `mu wait` over refs the caller owns. Default **first**
event (`--first-of` alias); `--all` / `--all-of` keeps the all-of
completion loop. Result `data`: `event` (alias `firing`: `kind`, `ref`,
`incarnation`, `runs`), `exit`, `snapshots`, `resume`, `timedOut`,
`cursor`. Event kinds: `closed settled input dead stall delegate
pane-exit exited pane-text heartbeat interrupted`. `exited` is a remote
command gate's own numeric status (`exit` is that code, 23 stays 23; under
`--all` the first nonzero). Timeout is `ok:true` with `timedOut`
and exit 0; dead 6, stall 7, interrupt 130. Heartbeat cap 1500 s.
`--until-idle`, `--until-text <re>` (bounded RegExp over authoritative
ctl `lastText` or the task report, never pane text for a settle; an agent
with no answering ctl socket matches its pane tail and ends the wait as the
distinct `pane-text` event, never `settled`; invalid or oversized rejects), `--status`, `--stuck-after`, `--on-stall exit|steer|release`
(steer and release only on `auto` tasks on this `dispatch_host`;
release reopens, never closes). SIGINT/SIGTERM emit one final snapshot;
**SIGKILL cannot, and no output is promised.** Unknown is never idle.
`send --wait` / `claim --for <w> --and-wait` enter the wait (one JSON document, the producer's
receipt as `data.sent` / `data.claimed`); spawn, send and claim arm a registered wait from real
state, not shell parsing (`v_wait_arming`: `src/cli/wait-arm.ts`).

**Delegates.** Every `mu_delegate` call is a `kind=delegate` task in the
caller's workstream or `scratch`, with durable `call_id`, optional
`wave`, and states `queued starting running recording done failed
refused cancelled`. A refusal past the cap is recorded and reissuable,
never dropped. The verdict is written to the judged task before the
delegate's report and close. The full multi-line evidence stays on the
judged task; the answer is the delegate task's report. Pi echo is
optional, one summary per wave. Admission control, arriving-answer
safety and `murmur:pending {source,count}` are unchanged.

**Next.** One rule set over current state computes `next` on every
call (`computeNext`); the dashboard asks the same function. Agents
follow state-advancing hints far more often than read hints, so `next`
holds only state-advancing moves (a send's exact wait, a preview's
apply, a claim's brief, `mu next` over authorised work, the wait over
owned refs) and is usually null; reads are never hinted. Every command
is executable as printed: POSIX-quoted, real ids, no `<…>`, `...` or
`a|b`; a move mu cannot fill is omitted. Safe moves rank before
destructive ones, and a destructive move carries `--if <rev>`. Never a
resend.

**Guards.** `--if <rev>` on membership-changing and destructive verbs;
teardown and `agent close` print a preview revision first. A wrong id
returns read-only candidates, never a fuzzy write. An ambiguous ctl
delivery returns a receipt to check (`msgId`), never a resend.

**Sync.** Every verb ingests peer ops before its body and flushes its
own after, on the error path too; `mu db query` writes flush as well. A
DB inside `MU_SYNC_DIR` is refused before anything is opened, and so is
a `MU_SYNC_DIR` holding a 3.x segment (exit 4, nothing written).

**CLOSE block.** Final lines of a settled pi run's `lastText`, `task=`
the agent's own task; applied once per (agent, incarnation, runs) as
report + close in one op group; malformed writes "no close (malformed)".
`mu task close` stays. No automatic human decisions: the loop stops
only for a caller-scoped human-needed task or when nothing it owns runs.

**Budgets (pinned by tests).** SKILL 600 words, each recipe 250, all
recipes 4,000 (`test/prose-budget.test.ts`; the remote recipe's separate
200-word target is not met and not tested); per-verb `--help` description at most 3 lines at 80 columns
(`test/help-budget.test.ts`); each `next` and error one line
(`src/messages.ts`). They do not cap canonical docs such as this page.

## Where each part lives

Every shipped unit has its modules in the
[ARCHITECTURE module map](../ARCHITECTURE.md#module-map) and its what-and-why
in the [CHANGELOG](../../CHANGELOG.md) (unit ids such as `v_wait_surface`
appear in both). Machine-local tables (`spawn_receipts`, `host_channels`,
`human_needs`, `wait_registrations`, `wait_resumes`, `wait_action_receipts`,
`remote_recovery`, and the older `delegate_calls`, `wait_cursors`,
`agent_settles`, `host_profiles`, `task_auto`, `worktrees`) are classified in
`MACHINE_LOCAL_TABLES` (`src/db.ts`); `test/entities.test.ts` fails on an
unclassified one. The build-time ownership table and reconciliation notes
are in git history.
