# TOOLS.md — command and tool reference On-demand reference for agent sessions (conductors, bootstrapping agents, reviewers). `AGENTS.md` routes here; this file carries the depth: usage, inputs/outputs, exit codes, and safety notes for every entry point. Reading guide: system entry points are `scripts/*.sh` (bash) or invoked via `node scripts/mosaic-task.mjs` (node). Host-side helpers under `tools/` (tmux messaging, watchers, prose checker) are covered under Tools (host-side) below. Every script fails closed — missing or invalid configuration/policy refuses the operation with a nonzero exit and changes nothing. ## Lifecycle | Command | Purpose | Notes | |---|---|---| | `scripts/bootstrap.sh [--instance NAME]` | Create `~/.config/mosaic-dev/config.json` if absent; with `--instance NAME`, `~/.config/mosaic-NAME/config.json` and the claimed root `~/.mosaic-NAME` | Idempotent; existing config validated, never rewritten. See [Stack instances](#stack-instances---instance-name-row-190) | | `scripts/build.sh` | Build the release image | Tag derived from `RELEASE` + pinned pi version | | `scripts/hello.sh` | One-shot startup request | Prints model response on stdout | | `scripts/verify.sh` | Full gated test | Exit 0 only on exact `MOSAIC_HELLO_OK`; `EXPECTED_MARKER` overrides for negative drills | ## Tasks (missions, runs, evidence) | Command | Purpose | Notes | |---|---|---| | `scripts/run-task.sh run ` | Execute a task | Immutable run record under `/runs/` | | `scripts/run-task.sh validate ` | Strict validation | Writes nothing | | `node scripts/mosaic-task.mjs show ` | Inspect a run | Full record + snapshots + artifacts | | `node scripts/mosaic-task.mjs list` | List runs | task/workspace/session columns | | `node scripts/mosaic-task.mjs retry ` | Re-execute a run's snapshot | New run dir; `retriedFrom` lineage recorded | | `node scripts/mosaic-task.mjs prune [--keep=N] [--yes]` | Retention | Dry-run default; receipt in `runs/.pruned.log` | | `node scripts/mosaic-task.mjs resolve-role ` | Validate a role file, version 1 or 2 | Prints `MOSAIC_ROLE_TOOLS` / `MOSAIC_ROLE_NETWORK`, and `MOSAIC_ROLE_CONTRACT` for version 2; config-free | Task fields: `prompt` (required), `mission` (path), `expectExact`, `timeoutSeconds` (5–600), `workspace` (`:run` or named), `capabilities.tools` (allowlist: read write edit bash grep find ls), `session`, `sessionForkFrom` (requires `session`). Mission fields: `objective`, `directives[]`, optional governing `capabilities.tools`. Policy: a task may narrow a mission's tools, never widen; empty intersection = tool-free run. Legacy worker containers use a typed per-launch mount contract. Task, mission, user and session-fork snapshots are read-only. A fork source is opened without following its final symlink, limited to 16 MiB, checked for owner/type and concurrent changes, then copied write-once into the current run before launch. The host-owned `control/` parent is read-only so atomic processing-state replacement remains visible; it contains only processing and runtime/stop metadata, not a socket. Only the selected workspace and target session are read-write. The host data-root root, evidence outside the current run, unselected run/workspace/session trees and broker/control sockets are absent, and the generated prompt exists only in the disposable container filesystem. Legacy selected names still share namespaces with managed workers: a workspace name can select a managed business parent and `discord-*` sessions are not reserved. A dedicated legacy prefix or explicit collision refusal is a separate migration follow-up. Every launch kind, including `hello.sh` and `verify.sh` probes, refuses a symlinked `user/`, a symlinked or non-file `user/*.md`, or any mount source path containing `:`. Missing or unsupported launch inputs also refuse before Docker dispatch. These source properties do not by themselves establish native container confinement. The admitted #660 compatibility gate must run this exact legacy-worker list in addition to its full repository gate; it was not run by this source repair: ```bash node --test --test-concurrency=1 packages/processing-control/tests/containers.test.mjs packages/processing-control/tests/coordinator.test.mjs packages/processing-control/tests/effects.test.mjs packages/processing-control/tests/mount.test.mjs packages/processing-control/tests/runtimes.test.mjs packages/processing-control/tests/state.test.mjs packages/processing-control/tests/stop-run.test.mjs packages/processing-control/tests/task.test.mjs node --test scripts/test-darkwing-launch.mjs scripts/test-task.sh scripts/build.sh ``` ## Agent (interactive TUI) ```bash scripts/agent.sh [--mission ] [--workspace ] [--session ] [--tools ] ``` Launches an interactive pi TUI inside the container with the four immutable contracts + optional mission + agent identity as its system prompt, persistent named session, optional workspace. Exit with `/quit`. The interactive container uses the same narrow launcher: its selected SOUL, mission, user context and enabled skills are read-only mounts, while only its workspace and named session are writable. Role authority is resolved on the host from the reviewed `roles/*.json`; the data-root root is not mounted. A seat role (`agent.json` `role`) binds to `roles/.json` (M18): the contract's tools are a ceiling the seat definition or `--tools` may narrow, never escalate past. Missing/invalid contract refuses the launch; empty intersection = loud tool-free seat. An explicit `MOSAIC_AGENTS_DIR` override that cannot resolve the named seat also refuses (#46) — unset the override for the M13 plain governed TUI. `--auth ` injects `auth..json` (beside the active credential file) as the launch's `PI_AUTH_FILE`; a missing/invalid account refuses (M19). For native repository development, opt in with a **leading** `--host-dev`: ```bash scripts/agent.sh --host-dev darkwing [--fresh] [--check] [--soul FILE] [--constitution FILE] [--user FILE] [--provider NAME] [--model ID] [--thinking LEVEL] ``` This mode delegates to `scripts/agent-host-dev.sh`, uses host Pi and repository tools/skills plus the development goal extension, and keeps its own sessions under `.pi/state//`. It uses native Pi authentication and does not run container release alignment or apply managed seat role ceilings. It is a host development session, not a sandboxed worker. Container-only flags such as `--auth`, `--mission`, and `--tools` are rejected in this mode. Omitting `--host-dev` retains the existing container lifecycle and policy checks; container failures never trigger a host fallback. Darkwing's agent-local shim selects host development explicitly. See `agents/darkwing/README.md`. ## Auth (credentials) Credential checkpoint over pi's auth model (one `auth.json` keyed by provider; resolution order `--api-key` → `auth.json` → env → models.json). No credential material is ever printed — provider names, credential types, and env var NAMES only. Ownership rule (#48): `~/.pi` is read-only to the stack, permanently. The only interaction is the existing read-only container mount of the default credential (`PI_AUTH_FILE`, default `~/.pi/agent/auth.json`). Mosaic-managed accounts live under the data root: `/auth/.json`, perms 0600 (mirroring `scripts/gitea-api.sh` hygiene — loose perms are flagged in listings and refused by `--auth`). | Command | Purpose | Notes | |---|---|---| | `scripts/auth.sh status` | Report both credential sources | Default harness credential (read-only) + mosaic-managed accounts; never prints material | | `scripts/auth.sh accounts` | List mosaic-managed accounts | Under the data root; marks the active one; flags non-0600 | `agent.sh --auth ` injects `/auth/.json` as the launch's `PI_AUTH_FILE`; missing/symlinked/non-0600 accounts refuse. Headless task runs keep the default credential. ## Release | Command | Purpose | Notes | |---|---|---| | `scripts/release.sh package` | Build + tag the release image | Tag: `mosaic-poc-agent:-r` | | `scripts/release.sh activate` | Health gate → atomic pointer swap | `--fault-injection` proves the refusal path | | `scripts/release.sh rollback` | Health-gated return to previous | Refuses if image missing | | `scripts/release.sh status` | Release, tag, active pointer, log | Safe on empty state | | `scripts/release.sh ensure` | Self-determination: align active pointer to `RELEASE` | Fast path restores a missing/mismatched pointer without a gate; slow path packages + health-gates first. Invoked automatically at launch | ## Conductor (worker patches) ```bash scripts/conductor-apply.sh [--dry-run] ``` Auto-applies a worker's patch under `roles/conductor-policy.json`: succeeded run → clean target tree → path allowlist → syntax gates → apply → policy suites → attribution commit. Any failure reverts. Push is never automatic. ## Conductor (landing approved work) ```bash scripts/land-work.sh [--base refactor] [--dry-run] [--subject TEXT] [--attribution LINE] [--business ID] [--project ID] ``` Turns a merge decision the human resolved into one gated local commit on `refactor` (#1610). It reads `/bus/bus.sqlite` read-only through `scripts/land-work/read.mjs` and checks that the human picked the raise's approval option, that the question names one `work/vikunja-

-`, and that the task's latest `review.verdict` approves a commit C that the work ref in the working repository still points at. It then applies `git diff $(merge-base refactor C) C` in a detached worktree, checks the patch-id, runs every package suite except `queue` and every `scripts/test-*.sh`, and commits. The subject is the approval option's text unless `--subject` overrides it, and `--attribution` replaces the last line, `Applied-under: scripts/land-work.sh (conductor)`. `--business` and `--project` are needed only when the config has more than one business file or the business more than one project. The tool prints the line for the PM and doesn't send it. `--dry-run` runs every check and the gate and writes no ref. A refusal names its reason and exits 2; the reasons are listed at the top of the script. The worktree, its `node_modules` link and its Docker network are removed on every exit. The gate log directory stays and the tool names it. Keep `TMPDIR` short: the gate inherits it, the cli suite puts unix sockets under it, and a 46-byte `TMPDIR` failed 8 of those tests. Push is a separate act. Since #1631 the gate, its steps and the root-file rule live in `packages/seat/src/landing.mjs`, and the decision checks in `packages/bus/src/landing.mjs`. The bus host's `land` op uses both to land an agent's task on its working repository's `main`, never on `refactor` (`packages/cli/README.md`, "Git socket"). Since #1651 the host starts the holder's landing run itself ("Landing runs"). A decision raised for one target can't land the other. land-work still accepts only a decision the human resolved through the CLI. ## Maintenance | Command | Purpose | Notes | |---|---|---| | `scripts/reset.sh` | Delete the data root | Triple-safety-checked (path, symlink, ownership marker) | | `scripts/test-config.sh` | Config selftests (no Docker) | 24 cases | | `scripts/test-task.sh` | Task selftests + live cases | 90 cases | | `scripts/test-release.sh` | Release selftests | 14 cases | | `scripts/test-conductor.sh` | Auto-apply selftests (sandboxed) | 17 cases | | `scripts/test-auth.sh` | Auth checkpoint selftests (no Docker) | 13 cases | | `scripts/test-land-work.sh` | Land-work selftests (sandboxed, fake Docker) | 84 cases | | `scripts/gitea-api.sh [body]` | Gitea API helper; reads the JSON credential file or a raw per-seat token file | Token never on argv/stdout | ## Seat launch with registration (`scripts/mosaic`) ```bash scripts/mosaic launch [--task TEXT] [--project NAME] [--workspace PATH] [--harness NAME] [--repo PATH] [--config PATH] [-- args...] scripts/mosaic seat task [--by NAME] [--layout repo|fleet|unknown] [--config PATH] ``` Runs a seat's `launch.sh` unchanged (arguments after `--` pass through) and leaves one registration record at `/seats///registration.json` that the control board reads: seat, project, task, workspace, tmux session, harness, start time, pid. Rewritten on every launch. `seat task` changes the task field only. The four repository seats (`agents/*/launch.sh`) register themselves through this path unless called with `--check`. Run it by path from the checkout. On a pinned host, `mosaic` on PATH is a wrapper around a pinned copy's `scripts/mosaic` ([Stack copies](#stack-copies-scriptsstack-copysh)) and carries the human commands; on Jason's host the estate CLI it replaced is `mosaic-old`. Queue commands stay on `scripts/mosaic queue` in the canonical checkout, because a copy refuses them ([One pinned copy](../packages/cli/README.md#one-pinned-copy-row-85-1579)). Exit codes: the launch script's own once it runs; before that 1 could not start, 2 invalid config or seat, 4 usage. Details and the record's fields: `packages/seat/README.md`. ## Work queue (`scripts/mosaic queue`) ```bash scripts/mosaic queue list | show ID | next [SEAT] scripts/mosaic queue add|move|release|assign|note|set ... --op ID [--by NAME] scripts/mosaic queue verify [--current] | render [--check] | sync | unlock [--check-gate] scripts/mosaic queue review request|resolve|abandon|record ... --op ID [--by NAME] scripts/mosaic queue review verify-commit ID REF scripts/queue-commit.sh -m MSG ``` `docs/plans/queue.json` holds the rows and an append-only log; the table in `docs/plans/QUEUE.md` between the `mosaic-queue` markers is rendered from it. Canonical checkout only. Every change needs an `--op ID` chosen before the first attempt and reused on retry; only an op whose `ok rev N` receipt printed is done. The lead commits queue changes with `scripts/queue-commit.sh`; the pre-commit guard refuses any other commit that stages the two queue files. Moving a row with reviewers to in-review posts one review request comment on its issue as the acting seat, with the seat's own token file in `MOSAIC_GITEA_CREDENTIAL_FILE`; reviewers then run `review record`. Exit codes: `0` ok · `1` failed · `2` invalid or refused · `3` uncertain, retry the same op (for a review request, the retry sends nothing; resolve or abandon it) · `4` usage. Details: `packages/queue/README.md`. ## Organization files and role instances (`scripts/mosaic org config`, `scripts/mosaic org instances`) ```bash scripts/mosaic org config validate scripts/mosaic org config resolve [--project ] scripts/mosaic org instances [--json] scripts/mosaic org instance add --definition [--vars ] [--launch] --revision scripts/mosaic org instance set [--definition ] [--vars ] [--launch | --no-launch] --revision ``` `scripts/mosaic business validate|resolve|instances|instance|veto` is the old spelling of these commands, with the same arguments, and it still works. Both spellings run the same code and give the same status, stdout and stderr. Only a validate/resolve usage error differs: it prints the usage of the spelling you used. Every other validate/resolve stderr line, refusals and warnings included, starts `mosaic business: ` in both spellings. `scripts/mosaic org validate FILE` is a different command: it checks a draft file by path and prints `valid`. Reads `businesses/.json` next to the system config (`$MOSAIC_CONFIG` or `~/.config/mosaic-dev/`), the version 2 role files in `roles/` (or `$MOSAIC_ROLES_DIR`) and each project's `.mosaic/project.json`. Writes nothing. `validate` checks the whole business, stat-checks every credential reference without opening it and prints one digest per role instance. `resolve` prints one instance's merged variables, their sources, its narrowed tools, network and authority, and its credential references. The business file must belong to you and not be group or other writable. Exit codes: `0` ok · `2` invalid file or credential reference · `3` system config problem · `4` usage or a required file missing. Details: `packages/organization/README.md`. `mosaic credentials status|issue|rotate|revoke|delete` (row 199) goes to `packages/cli` and the bus's human-only credential verbs: an agent's own token in `/secrets/agents///`, read on stdin, never printed. The tester may run `status` and `revoke`. Details: `packages/cli/README.md`. `instances` and `instance add|set` (row 165) go to `packages/cli` and the bus's human-only `instances` and `instances.set` verbs, so they need a live bus host and refuse inside an agent session. `instances` prints the revision, each instance and the definitions `add` and `set` accept. `add` and `set` write the business file under that revision and log the change with you as the actor. They bind no credentials: a definition that declares credentials, or a definition change on an instance that holds them, is refused with `organization-credentials-out-of-scope` and stays a hand edit. After an add or a definition change the output says a bus restart is needed; the restart needs Mos's go or Jason's hand, and the command doesn't run it. Exit codes: `0` ok · `2` refused, nothing written · `3` human proof refused · `4` usage. The Console's Settings page has the same two forms. Details: `packages/cli/README.md`. ## Inbox, decisions and the bus host (`scripts/mosaic`) ```bash scripts/mosaic # no argument: the full-screen app scripts/mosaic inbox | tasks | agents [--business ] [--json] scripts/mosaic inbox --read ... | --read-all [--business ] scripts/mosaic decide