Until now a landing ran only when the chief of staff or merge-gate
followed row 149's manual steps. The host now does it.
Broker. landHolders(business) lists the approved merge decisions each
instance holds, sharing the #held generator with landHeld.
landRelease(record) releases a run's claim with by='land-relaunch'.
Delivered and unread messages keep the run, and a run that hasn't had
its first message.receive answers {released:false, landing:true}
(#received, round 2), so a landing turn in progress is never cut. A
run that never reaches its first receive, a hung landing turn or a
broker restart, keeps the release closed, and the decision falls back
to row 149's path.
usage.begin accepts a decision id that landHolders lists for the run.
The bus process answers both ops from their own launchOp lines and
refuses any op it doesn't name.
Launcher. start() reads landHolders once for a non-thread instance and
takes land_work and the session's landing list from that one answer.
landPass() runs after threadPass() on each tick.
Runner. One turn per landing entry before the message loop, with a
usage bracket on the decision id.
Docs. A "Landing runs" section in the cli README and docs/TOOLS.md.
roles/merge-gate.md, cto.md and chief-of-staff.md say the host starts
the run, and row 149's steps are the fallback.
Owner Dewey, brief docs/plans/2026-10-18_landing-run-launch.md, design
DESIGN.md in the packet (sage-0808). Round 2 answers Filbert's F1-F3
(29033, rulings sage-0942). Reviewer on candidate manifest
72a5c1b6...b700: Filbert approve 29039. Seat run 4: 290/290 singles,
36/36 mutants killed. Sage gate on a618528b2 (candidate 524bfcc80): GATE_PASSED 25/25. Package tests 2258 plus queue 156, script selftests 422, 23:30:07Z to 23:38:04Z. Run 1 stopped at pkg-webui on board-held.test.mjs:163, the P19 366 timeout, 220/1. Run 2 resumed at pkg-webui on the same tree: webui 221/0 and every scripts/test-*.sh green.
Nothing live.
749 lines
44 KiB
Markdown
749 lines
44 KiB
Markdown
# 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` | Create `~/.config/mosaic-dev/config.json` if absent | Idempotent; existing config validated, never rewritten |
|
||
| `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 <task.json>` | Execute a task | Immutable run record under `<dataRoot>/runs/` |
|
||
| `scripts/run-task.sh validate <task.json>` | Strict validation | Writes nothing |
|
||
| `node scripts/mosaic-task.mjs show <runId>` | 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 <runId>` | 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 <roleFile>` | 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.
|
||
|
||
## Agent (interactive TUI)
|
||
|
||
```bash
|
||
scripts/agent.sh <name> [--mission <file>] [--workspace <ws>] [--session <s>] [--tools <list>]
|
||
```
|
||
|
||
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`.
|
||
|
||
A seat role (`agent.json` `role`) binds to `roles/<role>.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 <account>` injects
|
||
`auth.<account>.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/<name>/`. 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: `<dataRoot>/auth/<account>.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 <account>` injects `<dataRoot>/auth/<account>.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:<pi>-r<release>` |
|
||
| `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 <runId> [--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 <decision-id> [--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 `<dataRoot>/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-<p>-<t>`, 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 <METHOD> <path> [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 <seat|seat-dir> [--task TEXT] [--project NAME] [--workspace PATH] [--harness NAME] [--repo PATH] [--config PATH] [-- args...]
|
||
scripts/mosaic seat task <seat> <text> [--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 `<dataRoot>/seats/<layout>/<seat>/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 <op> 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`.
|
||
|
||
## Business files and role instances (`scripts/mosaic business`)
|
||
|
||
```bash
|
||
scripts/mosaic business validate <business>
|
||
scripts/mosaic business resolve <business> <instance> [--project <id>]
|
||
```
|
||
|
||
Reads `businesses/<business>.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/business/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 <id>] [--json]
|
||
scripts/mosaic inbox --read <id>... | --read-all [--business <id>]
|
||
scripts/mosaic decide <decision> <option> [--note <text>] [--yes]
|
||
scripts/mosaic trail <task|decision> [--json]
|
||
scripts/mosaic bus start <business> [--pm] | stop | status [--json]
|
||
scripts/mosaic talk <role> <text> [--wait SECONDS] [--business <id>]
|
||
scripts/mosaic project create <name> [--repo <repo>] | list [--json]
|
||
scripts/mosaic threads [--project <project>] [--json]
|
||
scripts/mosaic thread create <project> <name> | open <thread> | read <thread> [--json]
|
||
scripts/mosaic talk --thread <thread> <text> [--wait SECONDS | --follow]
|
||
scripts/mosaic follow --thread <thread>
|
||
scripts/mosaic stop <run>
|
||
scripts/mosaic launches off | on [--business <id>]
|
||
scripts/mosaic launches list [--json]
|
||
scripts/bus-service.sh render [--copy ID [--copies DIR]] [--pm] [--claude PATH]
|
||
scripts/bus-service.sh install [--copy ID [--copies DIR]] [--pm] [--dir DIR] [--no-reload] [--claude PATH]
|
||
scripts/bus-service.sh uninstall | status <business>
|
||
```
|
||
|
||
The human commands read and resolve through the broker's human transport
|
||
(`packages/bus/src/human-cli.mjs`), never the SQLite file. They refuse inside
|
||
an agent run. `bus start` is the host the systemd user unit
|
||
`mosaic-bus@<business>` runs. It boots the broker, starts the Discord
|
||
notifier, and needs `<dataRoot>/notify/<business>/notify.json`. That
|
||
directory must be 0700: it also holds the notifier's journal `sent.jsonl`
|
||
(0600, never a symlink). On open the notifier copies a torn final line to
|
||
`torn-<UTC stamp>.bin` and truncates the journal to its last newline; a
|
||
malformed complete line refuses with exit 3.
|
||
|
||
`bus-service.sh` pins the Claude Code binary managed sessions run as the
|
||
unit's `MOSAIC_CLAUDE_BIN`, from `--claude PATH` or else `command -v claude`
|
||
(`packages/cli/README.md`, "Unit").
|
||
|
||
`bus start` also serves the session launcher (slice 1 S6): a PM session's
|
||
`launch` tool starts role sessions through it, each a runner under its own
|
||
PID namespace with a generated bundle, and `--pm` launches the business's
|
||
`launch.by` instance at start. A host that starts ends the launches a
|
||
crashed host left as `host-lost` and refuses with exit 3 on a
|
||
`sessions.json` it can't read. Every 2 s it also starts each launch-block
|
||
instance that holds an approved, unused merge decision, once per decision,
|
||
with a landing turn that calls `land_work`; a holder run that started
|
||
before the approval is stopped between turns first (`land-relaunch` in the
|
||
launch log; row 156, cli README "Landing runs"). `talk` sends a REQUEST and prints only the
|
||
reply to it, marking only that read (polls every 2 s, `--wait` default 900;
|
||
`0` only sends); other messages stay unread for `mosaic inbox`, and
|
||
`--follow` and `follow` print and read everything. `inbox --read <id>...`
|
||
marks unread messages read by the ids `inbox` prints, and `--read-all` marks
|
||
every one it lists (row 150); the cli README, "Human commands", says how
|
||
refusals print. When no run holds the
|
||
role, a line after `sent` says whether `launch.by` was told, or that the role
|
||
isn't in the launch block and `launch.by` can't launch it (row 152); the cli
|
||
README, "Human commands", lists the lines. `stop` sends SIGTERM to
|
||
a run's runner after checking it in
|
||
`sessions.json`. `launches off|on` revokes and restores `role.launch` for
|
||
the business; running sessions keep running. `launches list` reads
|
||
`sessions.json` and needs no broker. `talk`, `stop` and `launches off|on`
|
||
refuse inside an agent run.
|
||
With no argument on a terminal, `scripts/mosaic` opens the full-screen app:
|
||
Inbox, Tasks, Agents, Trail and Help, deciding through the same flow and
|
||
strings as `decide`. In an agent run it exits 3; without a terminal on
|
||
stdin and stdout it exits 4 with the usage.
|
||
Exit codes: `0` ok · `1` failed or outcome unknown · `2` invalid input ·
|
||
`3` refused or config problem · `4` usage. Details: `packages/cli/README.md`.
|
||
|
||
## Stack copies (`scripts/stack-copy.sh`)
|
||
|
||
```
|
||
scripts/stack-copy.sh copy COMMIT [--copies DIR] [--allow-worktree]
|
||
scripts/stack-copy.sh wrapper ID [--copies DIR] [--bin DIR]
|
||
scripts/stack-copy.sh check [--copies DIR] [--bin DIR] [--business ID]
|
||
```
|
||
|
||
One read-only tree supplies the bus, Console and human CLI (row 85,
|
||
#1579). Copies default to `${XDG_DATA_HOME:-$HOME/.local/share}/mosaic-stack/copies/<full id>`.
|
||
`copy` requires a canonical checkout (.git directory); --allow-worktree
|
||
is a test override. COMMIT must be reachable from both local refactor and
|
||
origin/refactor. It archives the runtime/import closure, adapters, roles,
|
||
RELEASE, package.json and package-lock.json; refuses missing manifests;
|
||
runs npm ci inside the temporary copy; removes write permissions; and
|
||
renames beside the target. A published existing copy stays unchanged.
|
||
Dependencies use the exact lock and carry Pi's installed lock record.
|
||
Failure publishes nothing and removes the temporary tree.
|
||
|
||
`wrapper` is a live switching step, reserved for Jason or Mos. It accepts
|
||
only an existing copy and a regular non-symlink mosaic wrapper with the
|
||
Q81 marker on line 2 and its exact lease line to mosaic-old. It preserves
|
||
both lines, adds comments naming the copy root and Q81's mosaic-old lease
|
||
probe, and atomically replaces the wrapper's final exec with the
|
||
copy's scripts/mosaic. --bin is a fixture override; the default is
|
||
~/.npm-global/bin. Neither the Q81 worktree nor mosaic-old is removed.
|
||
`check` changes nothing: it reads effective systemd WorkingDirectory and
|
||
ExecStart (including drop-ins) for the bus and Console, plus the wrapper
|
||
exec. It prints all three paths and succeeds only on the same existing
|
||
copy; absent/failed reads and a mismatched ExecStart fail. Business defaults
|
||
to mosaic-stack. Queue work continues on the canonical scripts/mosaic.
|
||
|
||
Bus render/install accept --copy ID, --copies DIR, --pm and the unchanged
|
||
--claude pin. Without --copy the rendered unit stays the checkout unit.
|
||
--pm adds the PM startup; a refusal exits 3, and RestartPreventExitStatus
|
||
prevents a retry. Install refuses any busy bus instance, even with
|
||
--no-reload; a failed state/drop-in read also refuses. Any template or
|
||
instance drop-in, or inherited mosaic-.service.d/ or service.d/ drop-in,
|
||
assigning ExecStart or WorkingDirectory refuses,
|
||
naming the file. Status reads effective roots, --pm and DropInPaths,
|
||
reports checkout or copy identity and refactor drift, and writes nothing.
|
||
|
||
### Pinned stack update
|
||
|
||
```
|
||
scripts/stack-release.sh adopt BUSINESS
|
||
scripts/stack-release.sh activate ID BUSINESS [--pm] [--migrate] [--timeout S] [--plan]
|
||
scripts/stack-release.sh rollback BUSINESS [--restore ACTIVATION [--events N]] [--timeout S] [--plan]
|
||
scripts/stack-release.sh status BUSINESS
|
||
common options: --copies DIR, --bin DIR
|
||
```
|
||
|
||
This is an operator plan, not an action agents run. Both reviews and the
|
||
landing come before it. Mos grants each live run first. A bus restart ends
|
||
running sessions, so run it only when no PM turn is in flight. `--plan` and
|
||
`status` change nothing and need no go.
|
||
|
||
The script keeps `<dataRoot>/state/stack-active.json` (the live copy, business,
|
||
PM flag, claude pin, Console flags, schema and activation) and appends every
|
||
run to `<dataRoot>/state/stack-activation-log.jsonl`: a `started` line, then
|
||
one final line with `result` `ok`, `refused`, `gate-failed` or
|
||
`rollback-failed`. A refusal exits 3, a gate failure that rolled back exits 1,
|
||
a failed rollback exits 2, usage exits 4. Only one run at a time holds
|
||
`state/stack-release.lock` (`run-locked`).
|
||
|
||
1. Once per host, `adopt BUSINESS` records the running stack as the pointer:
|
||
the copy `stack-copy.sh check` reports, the bus unit's `--pm` and
|
||
`MOSAIC_CLAUDE_BIN`, the Console's flags without `--checkout`, and the
|
||
copy's schema version. It refuses when the check fails, the bus unit has no
|
||
`MOSAIC_CLAUDE_BIN` or a pointer exists.
|
||
2. Make the pushed target copy with `stack-copy.sh copy COMMIT` and note its
|
||
full id.
|
||
3. Run `activate ID BUSINESS --plan` and take the output to Mos. It names the
|
||
store's schema and the target's, any migration with `--migrate` and the
|
||
decision 96 condition 2 line (Jason present), the commits the run adds,
|
||
and the three install commands. It refuses `copies-unrelated` when neither
|
||
copy is an ancestor of the other.
|
||
4. Mos removes any bus drop-in that overrides ExecStart or WorkingDirectory;
|
||
`activate` refuses `drop-in-override` and names the file.
|
||
5. With Mos's go, `activate ID BUSINESS`, plus `--migrate` when the plan
|
||
says so. Before it stops anything, it refuses a missing, unpushed or
|
||
unreadable copy, a busy `mosaic-bus@` instance, and unit flags that differ
|
||
from the pointer. Then it saves the unit files, drop-ins and wrapper under
|
||
`state/stack-rollback/<activation>/`, stops the Console then the bus, and
|
||
reads the stopped store. A `-wal` or `-shm` beside it refuses
|
||
`store-unclean` and leaves the store alone. A migration takes an
|
||
owner-only backup under `<dataRoot>/backups/stack/<activation>/` first.
|
||
It installs the bus, the Console and the wrapper from the pointer's flags,
|
||
starts the bus then the Console, and runs the gate: both units active, a
|
||
live bus host for this start, Console `/healthz`, `stack-copy.sh check`
|
||
and, with `--pm`, exactly one `launched` journal line for a live run of
|
||
this start. A pass writes the pointer.
|
||
6. A gate failure puts back the saved units and wrapper, restores the backup
|
||
when a migration ran, keeps the store it replaced as
|
||
`bus.post-attempt-<stamp>`, starts the old copy and gates it. If that gate
|
||
fails too, both units stay stopped, the log says `rollback-failed`, and
|
||
Jason or Mos settles it from the rollback set the message names.
|
||
7. `rollback BUSINESS` returns to the copy before the pointer's, taken from
|
||
the log. When the schemas differ it needs `--restore ACTIVATION`; events
|
||
written after that backup need `--events N` with the count it prints, and
|
||
Jason decides first.
|
||
8. Jason's plain `mosaic talk pm "..."` must print sent and a reply. Keep the
|
||
`status` output with the receipts.
|
||
|
||
A `started` line with no final line is an interrupted run. `status` shows
|
||
it, and `adopt`, `activate` and `rollback` refuse until Jason or Mos settles
|
||
it. Nothing prunes copies, rollback sets, backups or post-attempt stores.
|
||
|
||
After a switch, the human commands and `--tester` work only through `mosaic`
|
||
on PATH; the checkout's `scripts/mosaic` exits 3 with `human-required` or
|
||
`tester-required`, and queue writes stay on it ([packages/cli/README.md, One
|
||
pinned copy](../packages/cli/README.md#one-pinned-copy-row-85-1579)).
|
||
|
||
## Console service (`scripts/webui-service.sh`)
|
||
|
||
```bash
|
||
scripts/webui-service.sh copy COMMIT [--copies DIR] # make a pinned copy
|
||
scripts/webui-service.sh render [--copy ID [--copies DIR]] [FLAGS] # print the unit
|
||
scripts/webui-service.sh install --copy ID [--copies DIR] [--dir DIR] [--no-reload] [FLAGS]
|
||
scripts/webui-service.sh uninstall [--dir DIR] [--no-reload]
|
||
scripts/webui-service.sh status [--dir DIR]
|
||
# FLAGS: [--port N] [--business ID] [--board URL]
|
||
# [--public-host NAME --auth-header HEADER --allow-user ID [--send]]
|
||
```
|
||
|
||
The systemd user unit `mosaic-webui.service` runs the Console,
|
||
`node packages/webui/src/cli.mjs serve`, with serve's flags as given:
|
||
`--port` defaults to 7332 (7330 is the operator's own Console), and the
|
||
preview set goes all three or none. `--send` (row 83) goes only with the
|
||
preview set and is refused otherwise (exit 1, nothing written); ExecStart
|
||
then ends with `--send`, and the Console needs the system config at start
|
||
(`--send needs the bus configured`, exit 2). Without `--send` the unit is
|
||
read-only. The template is
|
||
`packages/webui/systemd/mosaic-webui.service.in`.
|
||
|
||
Under `--send` the Console's Docs page (row 148) can also save an existing
|
||
spec/north-star.md or docs/NAME.md of a project: one local commit authored by the
|
||
business's human, refused `stale-base` if the file changed since the editor opened it.
|
||
It is the human bus verb `project.file.save` through the human transport; an agent
|
||
refuses it. Nothing is pushed. Without `--send` the page reads and previews only. See
|
||
`packages/webui/README.md` ("Spec and docs editor"). The page opens in a visual editor
|
||
built from reviewable source (row 154): `third_party/editor/` is unmodified upstream
|
||
source with its licenses and `PROVENANCE.json`, and `npm run build:editor` in
|
||
`packages/webui` rebuilds `src/public/editor.js` and `editor.css` with esbuild 0.28.2;
|
||
the save, the verb and the routes are unchanged. See "Visual editor" there. A save of spec/north-star.md also
|
||
sends the business's pm and cto one notice naming the commit (row 143); a BLOCK from
|
||
either goes to the chief of staff and on to the assistant. See `packages/bus/README.md`
|
||
("Spec review notices").
|
||
|
||
The unit runs a pinned copy, not the working checkout (row 77, #1571).
|
||
`copy COMMIT` resolves COMMIT to its full id, refuses one not reachable
|
||
from both `refs/heads/refactor` and `refs/remotes/origin/refactor`, and
|
||
delegates to stack-copy.sh for the archive and pinned npm install into `DIR/<id>/`, by default
|
||
`${XDG_DATA_HOME:-$HOME/.local/share}/mosaic-stack/copies/<id>/`. It writes a
|
||
temp directory beside the target, makes every file and directory read-only
|
||
(so a writer that replaces by rename can't change it either), then renames
|
||
it, so the copy's path is always whole. A copy that exists is left alone
|
||
(`exists:`). `install --copy ID` and `render --copy ID` point
|
||
`WorkingDirectory` and ExecStart at the copy and pass serve
|
||
`--checkout <this checkout>`, where the queue read still runs; `RELEASE`,
|
||
the roles, the config check and the bus child come from the copy. The bus
|
||
child is the human transport, `packages/bus/src/human-cli.mjs`, and the bus
|
||
checks that path against its own, so the bus must run from the same copy
|
||
(below). `install` refuses without `--copy`, or when the copy is missing.
|
||
`render` without `--copy` prints the unit for the checkout, as before.
|
||
|
||
`install --copy ID` also refuses unless the bus runs from the same copy
|
||
(round 2, #225). The bus accepts a human proof only from its own
|
||
`packages/bus/src/human-cli.mjs`, so a Console on a copy whose bus runs
|
||
from the checkout would fail every bus call with `human-required`. With
|
||
`--business ID`, install reads `[email protected]`; without it, every
|
||
loaded `mosaic-bus@` unit. It reads effective WorkingDirectory and ExecStart from
|
||
`systemctl --user show -p LoadState -p WorkingDirectory -p ExecStart`, so a drop-in counts,
|
||
and every ExecStart path must lie inside the copy. It refuses, naming both
|
||
paths, when one doesn't; when the unit is not installed or none is loaded;
|
||
when ExecStart has no path; and when the systemctl call fails. The check
|
||
runs before the unit state read and changes nothing.
|
||
|
||
`install` writes a temp file, renames it and runs
|
||
`systemctl --user daemon-reload`. `install` and `uninstall` refuse while the
|
||
unit is in any state but inactive or failed (`systemctl --user list-units`),
|
||
and say Jason or Mos stops it first; `--no-reload` skips only the
|
||
daemon-reload, never that check. If the state read itself fails they refuse
|
||
with one line naming the call, and change nothing. `copy` and `install`
|
||
refuse outside the canonical checkout (a `.git` directory, not a worktree's
|
||
`.git` file); `--allow-worktree` overrides that for the tests only. `status`
|
||
prints the unit state, the copy id it runs, whether `refactor` points at
|
||
that commit or another, whether the installed unit sends, and
|
||
`GET /healthz` on the installed port. It changes nothing.
|
||
|
||
Each value is held to a plain character set the unit can carry (no space,
|
||
`%`, `$`, quote, backslash, `&` or `|`), and the preview values must start
|
||
with a letter or digit; anything else is refused. With `--auth-header
|
||
X-authentik-uid`, in any letter case, `--allow-user` must be the Authentik
|
||
uid, `^[0-9a-f]{64}$` (lead decision 101). The unit sets no `MOSAIC_*`,
|
||
`CLAUDE*`, `CODEX*` or `PI_*` variable. Serve's exit 2 (usage, config, or a
|
||
port already in use) is not restarted.
|
||
|
||
The update/rollback sequence is [Pinned stack update](#pinned-stack-update)
|
||
above. The queue view reads the checkout and stays current. Copies remain
|
||
until an operator removes them; nothing here prunes or migrates copies.
|
||
|
||
There is no start, stop, enable or restart verb. The Console reads the bus
|
||
through the human transport, so whoever starts it holds the human's
|
||
capability. Jason starts, stops and restarts it
|
||
(`systemctl --user enable --now mosaic-webui`), and so may Mos under lead
|
||
decision 102, which accepts that sends from a Console Mos started enter the
|
||
trail as Jason (`human-cli`). Any other agent starting it, even through
|
||
systemd, works around the human proof (lead decisions 62 and 94).
|
||
Exit codes: `0` ok · `1` failed or refused · `4` usage.
|
||
|
||
## Discord connector (`scripts/discord.sh`)
|
||
|
||
```bash
|
||
scripts/discord.sh check <binding>
|
||
scripts/discord.sh run <binding> [--supervised]
|
||
scripts/discord.sh stop <binding>
|
||
scripts/discord.sh unlock <binding>
|
||
scripts/discord.sh recover <binding>
|
||
scripts/discord.sh reload <binding>
|
||
scripts/discord-service.sh render | install [--dir DIR] [--no-reload] | uninstall [--dir DIR] [--no-reload] | status <binding>
|
||
```
|
||
|
||
One seat's conversation reachable from listed channels of one Discord
|
||
server, chat only. `<binding>` names `<dataRoot>/discord/<binding>.json`
|
||
(0600, never committed; shape in `packages/discord/fixtures/binding.example.json`).
|
||
`check` validates the binding, token file mode, context files and pi, reads
|
||
the bot, guild and channels over REST, opens and closes one gateway
|
||
connection; it sends nothing. `run` serves turns until `stop`, SIGTERM or
|
||
SIGINT and refuses to start when `STOP` exists or a delivery cannot be
|
||
reconciled. `stop` writes `STOP` and signals the running process. `unlock`
|
||
writes `STOP` and removes a `run.lock` left by a dead process; it refuses
|
||
while the owner is alive with an unverifiable identity or its record cannot be
|
||
parsed. `run` refuses a stale lock rather than reclaiming it, and releases a
|
||
claim that meets `STOP`. `recover` is the supervised pre-start, and
|
||
`run --supervised` does it first in the same process: it refuses (exit 3)
|
||
while `STOP` is present or the binding is held, clears a lock whose owner
|
||
is gone and removes only the `STOP` it wrote for that. `reload` validates
|
||
the binding file and sends SIGHUP to the running connector, which applies
|
||
`guildName`, `channels`, `users` and `limits` in place and refuses anything
|
||
else, keeping the old binding; attempts are journaled in `reloads.jsonl`. A
|
||
user entry may carry `channels`, an allowlist of listed channel ids.
|
||
`scripts/discord-service.sh install` renders and writes the systemd user
|
||
unit `[email protected]` (one instance per binding, restart on
|
||
failure, exit 3 never retried, SIGTERM on `systemctl --user stop`, SIGHUP on
|
||
`systemctl --user reload`). With
|
||
`install --copy ID [--copies DIR]`, a full id selects an existing shared
|
||
copy holding the Discord CLI and pi; ExecStart runs Node on that CLI with
|
||
--repo and WorkingDirectory in the copy. Without --copy the checkout unit
|
||
is unchanged. Status names its effective cwd/copy id, including drop-ins.
|
||
`check --offline` validates local binding/context/tool roots/pi/token mode
|
||
and reports journal/STOP without REST or gateway calls; run refuses this
|
||
flag. For a Mos-approved re-pin after the SetSpark freeze: make the copy,
|
||
run `node <copy>/packages/discord/src/cli.mjs check <binding> --repo <copy> --offline`,
|
||
save the installed unit, install --copy, then restart on Mos's go. Context
|
||
files resolve inside the copy and missing files refuse. Rollback restores
|
||
the saved unit and daemon-reloads/restarts on Mos's go; keep both copies.
|
||
The connector's pin is independent of stack-copy.sh check. An optional `tools` key in the binding declares
|
||
file roots; pi then runs with its own tools off and the package's
|
||
extension providing `list_dir`, `read_file` and `search` confined to those
|
||
roots, plus `write_file` and `edit_file` for roots marked `"write": true`
|
||
(temp file and rename, no folder creation, no dot-prefixed path, no
|
||
credential shapes), and `web_fetch` and `web_search` when `tools.web`
|
||
names a SearXNG instance (https only, public addresses only, three
|
||
redirects, capped body, html to text), with each call in the turn record.
|
||
A writable root that is a git work tree may carry `git` (`branch`,
|
||
`identity`, `tokenFile`, `author`, optional `protocol: "vault"`) and
|
||
gains `git_status`, `git_commit` (explicit paths, seat author,
|
||
`Requested-by:` trailer, push at once), `git_pull` (ff-only) and
|
||
`git_push` (one branch, never force), with `reserve_id` under the vault
|
||
protocol; git runs with no host config and the package's own credential
|
||
helper reading a 0600 token file, never printed. `tools.setspark` names the
|
||
SetSpark record service and adds `record_list`, `record_get`,
|
||
`record_create`, `record_update`, `resolve_id`, `open_approval_request`,
|
||
`get_approval_request` and `create_document`, one fixed path each, writes
|
||
keyed per turn and call; approvals of its proposals are an Approve button
|
||
or an exact `approve` reply, checked by the connector against the
|
||
request's approvers, never by the model, with one Discord message per
|
||
approval as evidence. `tools` is a fixed key: changing
|
||
it needs a stop and start. Records
|
||
under `<dataRoot>/discord/<binding>/`: `inbox.jsonl`, `outbox.jsonl`,
|
||
`drops.jsonl`, `admissions.jsonl`, `notices.jsonl`, `reloads.jsonl`, `approvals.jsonl`, write-once `turns/<id>.json`. Suite:
|
||
`scripts/test-discord.sh`.
|
||
Exit codes: 0 ok, 1 operation failed, 2 invalid data or configuration, 3
|
||
refused by a brake (a supervisor must not retry), 4 usage. Details:
|
||
`packages/discord/README.md`.
|
||
|
||
## Tools (host-side)
|
||
|
||
Host-side helpers under `tools/`, outside the `scripts/` command surface.
|
||
Per-tool READMEs: `tools/tmux/README.md` and `tools/unslop-hook/README.md`.
|
||
|
||
| Command | Purpose | Notes |
|
||
|---|---|---|
|
||
| `tools/tmux/agent-send.sh` | Inter-agent tmux message with addressing preamble | Reliable submit (bracketed paste, Enter flush, draft detection); ships `send-message.sh` over ssh for remote panes (remote needs only bash + tmux + base64) |
|
||
| `tools/agent-watch/agent-watch.sh` | Condition watcher per agent seat | One transient systemd `--user` timer + service per watch; fires `agent-send.sh` when the condition command exits 0 |
|
||
| `node tools/unslop-hook/unslop-check.js <file>` | Mechanical AI-tell prose check | Dependency-free node CLI + module driven by `lists.json`; `extension.ts` is the pi extension wrapper |
|
||
|
||
`agent-send.sh` prepends the preamble
|
||
`[<src_host>:<src_session> -> <dst_host>:<dst_session>]`; `-C`/`--class` adds
|
||
a ` class=<CLASS>` token (`terminal-log`, `actionable`, `human`, `reaction`,
|
||
`digest`; consumers treat an absent class as `actionable`). Flags: `-s` dst
|
||
session (required) · `-H` ssh target for a remote pane · `-L` named tmux
|
||
socket · `-n` dst hostname for the preamble · `-m`/`-f`/stdin message body ·
|
||
`-S` source-label override · `-r N` Enter-flush attempts (default 2) · `-v`
|
||
verbose · `-h` help. Exit codes: `0` delivered/queued · `1` target not found ·
|
||
`2` still draft · `3` usage error · `4` ambiguous socket (the session exists
|
||
on more than one tmux server; disambiguate with `-L` or `MOSAIC_TMUX_SOCKET`).
|
||
|
||
`agent-watch.sh` subcommands: `start --name <id> --session <session> --when
|
||
'<shell command; exit 0 = met>' --message <text>` with `--class`,
|
||
`--interval` (default 30), `--timeout` (default 3600), `--repeat`,
|
||
`--quiet-timeout`, `--socket` · `list` · `status [--json]` · `stop <name>` ·
|
||
`log <name>` · `meta-install [--interval 300] [--unit-name <unit>]` ·
|
||
`meta-remove [--unit-name <unit>]`. Interval floor is 10s (a watcher is a
|
||
fallback cadence, never a tight poll); hidden `_tick`/`_scan` subcommands run
|
||
inside the systemd services. `status` exit codes: `0` clean · `3` any stale
|
||
watch or dead meta-watch · `6` systemd user bus unreachable. Delivery goes
|
||
through `agent-send.sh`; rc `2` means the text reached the pane as an
|
||
unsubmitted draft, which counts as delivered and is not retried (other
|
||
failures retry twice, then the watch gives up). Watches are one-shot by
|
||
default; `--repeat` re-arms. Notices carry a `[watch:<name>]` prefix.
|
||
|
||
`unslop-check.js` checks a file (or stdin) against the word, phrase,
|
||
punctuation-density, and pattern lists in `lists.json`, stripping fenced and
|
||
inline code first so a quoted mention never flags. Invocation:
|
||
`node tools/unslop-hook/unslop-check.js <file>`; `UNSLOP_LISTS=<path>`
|
||
overrides the lists location. Exit codes: `0` clean · `1` violations (findings
|
||
printed as JSON on stdout) · `2` gate broken (invalid lists or unreadable
|
||
input; error on stderr, never a clean verdict).
|
||
|
||
## Exit-code convention
|
||
|
||
`0` success · `1` operation failed · `2` invalid data/configuration ·
|
||
`3` configuration missing for a read operation · `4` usage/file/environment
|
||
problem. Scripts print diagnostics on stderr; model responses (and only
|
||
model responses) on stdout.
|
||
|
||
## Agent limits (`scripts/mosaic limits`)
|
||
|
||
View: `scripts/mosaic limits --business ID --json`. Set: `scripts/mosaic limits
|
||
set --business ID --file PATCH`, with the current revision and replacement
|
||
`limits` and/or `models`, or `instances` mapping role names to model-table keys,
|
||
or `assistants` mapping an assistant instance to any of its `vars`, `tracker`
|
||
(`null` removes it) and `definition` (row 155, #1649, Jason's ruling Q169). The
|
||
definition stays an assistant one; `holder` and `credentials` stay hand edits.
|
||
Both use the human bus proof. Settings offers the same edits in signed-in send
|
||
mode. Unset means unlimited, zero blocks new launches, and running sessions
|
||
remain alive when a limit is lowered. Total, provider and exact model limits
|
||
are checked for each launch; counts have no hard-coded policy ceiling.
|
||
|
||
Token limits (row 125) are `limits.tokens.instances.NAME`, `providers.NAME`,
|
||
`models.PROVIDER.MODEL` and `keys.NAME`, each `{ "limit": N }`. The writer adds
|
||
and keeps `since`. `scripts/mosaic limits reset WORD... [--business ID]` (for
|
||
example `reset tokens providers anthropic`) moves one limit's `since` to now,
|
||
logged with its actor. A token limit is enforced only by a switched-on
|
||
`limit-check` plug-in (`token-limit` ships): it refuses a launch at the limit
|
||
and stops the session at the end of the turn that reaches it. Otherwise the
|
||
read shows it as `not enforced`; a failing check refuses and stops (fail
|
||
closed). Without `--json` the read prints JSON without `tokens` followed by
|
||
`token limits:` text lines, so scripts must pass `--json`.
|
||
|
||
The Console's Assistant section (signed-in send mode) also sets an instance's
|
||
seat name, as `scripts/mosaic name` does, and stops the assistant's runs in the
|
||
human's threads. The chief of staff makes the same changes with its
|
||
`change_organization`, `set_name` and `end_run` tools. Until row 134 gives it a
|
||
revision read, the revision comes from the human.
|
||
|
||
Convert an old organization file with `scripts/mosaic limits migrate --file
|
||
ORGANIZATION --models MAPPING --roles-dir roles > CONVERTED`. It needs no bus,
|
||
never edits the input or system config and has no write flag. Inherited models
|
||
require a valid system config and an exact matching provider/model mapping.
|
||
Mapping JSON contains `models`,
|
||
`families` and optional per-role `instances` selections. Jason checks and installs
|
||
the output in Mos's window with the PM run idle; re-pin in that same window.
|
||
Never migrate the live file from an agent session.
|
||
|
||
|
||
### Local project records (`mosaic project`)
|
||
|
||
`mosaic project create` and `mosaic project list` work on the bus record of a
|
||
business's projects; `mosaic project init` and `mosaic project check` work on
|
||
the repository on disk. Threads (`mosaic threads`, `mosaic thread create|open|read`,
|
||
`mosaic talk --thread`, `mosaic follow --thread`) are described in
|
||
`packages/cli/README.md`; a plain `mosaic talk` to the threaded instance exits 2
|
||
and asks for `--thread`.
|
||
|
||
`mosaic project init <business> <project>` creates the declared project's
|
||
separate local record repository, with layout version 1, package-validated JSON
|
||
templates, Markdown spec/docs and its first commit authored as the business's
|
||
human. Init refuses an agent environment, an undeclared project or a non-empty
|
||
directory. Failed init cleans up its own layout and allows retry, preserving a
|
||
pre-existing empty target and unrelated files. A changed owned path refuses
|
||
`project-init-cleanup` for inspection. Records may not overlap any declared
|
||
project's code root. No bus or remote is involved.
|
||
|
||
`mosaic project check <business> <project>` validates project metadata, every
|
||
record, filename/ID agreement and the fixed layout, naming each problem. It
|
||
refuses `project-no-commit` if the layout has no readable HEAD commit. It
|
||
exits 0 when valid, 2 for project/record problems (including an undeclared
|
||
project), 3 for configuration loading problems and 4 for usage. The optional business project `repo` is a normalized absolute
|
||
record-repository path; the default is
|
||
`<dataRoot>/projects/<business>/<project>/`. `root` remains the code checkout.
|
||
The package README describes schemas and the shared agent record writer; agent
|
||
writes never enter `spec/`, never absorb pending edits and refuse remotes.
|
||
|
||
### Token usage (row 124)
|
||
|
||
`mosaic usage [--business ID] [--json]` reads token accounting from the bus.
|
||
Each total, run, instance, model, provider and account group shows input,
|
||
output, cache write, cache read and unknown turns. A malformed or unavailable
|
||
transcript contributes an unknown turn, never a zero-token reading. Accounts
|
||
are unassigned until account tracking ships. Exact totals larger than the
|
||
safe integer range are decimal strings in JSON. Settings has the same
|
||
read-only Token usage section. Counts are a best reading, not proof: the
|
||
session can edit its own transcript. This command changes no setting and
|
||
performs no token-limit enforcement.
|
||
|
||
### Console Chat (row 129, #1623)
|
||
|
||
The Console's **Chat** section uses `#/chat`, `#/chat/projects/UUID` and
|
||
`#/chat/threads/UUID`. Create and name a project and its threads, then send a
|
||
message in that thread's conversation. Names are creation-only. The project
|
||
catalog is business-wide; thread lists and histories belong to the current
|
||
human. This is separate from the Board's seat reply route.
|
||
|
||
`GET/POST /api/chat/projects` lists/creates projects; `GET/POST /api/chat/threads`
|
||
lists/creates threads (`project=UUID` optionally filters the GET).
|
||
`GET /api/chat/threads/UUID/messages?limit=100&after=UUID` pages history forward;
|
||
`POST` on that messages route sends `{body}` as a REQUEST.
|
||
`POST /api/chat/threads/UUID/open` takes `{}` and explicitly resumes the assistant.
|
||
GET never writes, opens a thread or marks messages delivered/read. No browser
|
||
owner, tenant, proof or arbitrary bus verb is accepted. A same-business thread
|
||
owned by someone else is 403 `thread-not-owned`; foreign/missing ids are 404.
|
||
|
||
The four chat POSTs use the existing signed-in `--send` boundary: named Host,
|
||
one exact proxy user header, exact HTTPS Origin, same-origin Fetch Metadata,
|
||
strict JSON and 16 KiB. Read-only mode disables them. Bodies are inert text;
|
||
drafts are memory-only per thread and cleared on sign-out. History starts
|
||
oldest-first in pages of 100, with a 200-message display window. Sending does
|
||
not claim an assistant answer; an uncertain receipt is never retried. Read the
|
||
current list/history before trying again. Activation still needs Mos's separate
|
||
window; changing these files does not authorize a live Console action.
|