Skip to content

Latest commit

 

History

History
1341 lines (1122 loc) · 139 KB

commands.md

File metadata and controls

1341 lines (1122 loc) · 139 KB

CLI Command Reference (relavium)

Last updated: 2026-10-18

The relavium CLI is the terminal surface of the platform and the fastest way to run a workflow non-interactively (scripts, CI/CD). It embeds the same @relavium/core engine as the desktop app and VS Code extension — there is no separate "CLI engine," so behavior is identical across surfaces (see ../../architecture/shared-core-engine.md). The CLI is built second (right after the engine) and serves as the engine's canonical integration-test harness.

Install & distribution

  • Package: published to npm as relavium, installed globally. The artifact is an engine-inlined ESM bundle — the proprietary @relavium/* engine is bundled in; every third-party dependency (including the prebuilt native addons) installs normally (ADR-0051). A global install needs no compiler toolchain.
  • Build: TypeScript bundled with tsup to one executable ESM entry and finite lazy chunks. Wave 3 Steps 7–8, including actual same-tarball Linux/macOS/Windows and minimum Node 22 consumers, are independently accepted on development; Step 9 maps, release custody and later whole-wave gates remain staged in the packaging contract. The published release retains its historical single bundle; see release-a-surface.md.
  • Stack: commander.js for argument parsing, ink (React for terminals) for the interactive TUI, @clack/prompts for setup wizards.
  • API keys: stored in the OS keychain via @napi-rs/keyring (macOS Keychain / Windows Credential Manager / Linux libsecret) — never plaintext, and never the archived keytar (see ADR-0019 and add-a-provider-key.md).
  • Workflow discovery: reads workflows from the .relavium/ directory in the project root, or from an explicit path argument.
npm install -g relavium
relavium run ./workflows/code-review.relavium.yaml --input file=./src/index.ts

Output modes

The CLI auto-detects its environment and switches presentation accordingly:

Mode When Behavior
Interactive TUI TTY attached, no --json ink-rendered live view: animated per-node status, separate bounded live reasoning and answer output for the active node, final cost/duration summary
Plain No TTY or CI=true (and no --json) The TUI is disabled; a terse line-per-lifecycle-event human renderer writes to stdout
NDJSON --json (anywhere on the command line) The machine contract: stdout is a pure NDJSON stream — RunEvents for run / gate / budget resume, or SessionEvents for chat / agent run — and all diagnostics go to stderr. See The --json machine-output contract

NDJSON is engaged only by --json (the explicit machine opt-in); a non-TTY or CI=true environment disables the interactive TUI but does not by itself switch stdout to NDJSON (ADR-0049). Exit codes are CI-friendly (see Exit codes).

--no-color (and --color) do not change the mode — the interactive TUI stays active and only ANSI color/dim are suppressed (plain output without a renderer swap). A swap to the Plain renderer happens only on no-TTY / CI=true, and to NDJSON only on --json.

Color precedence. ANSI styling is orthogonal to the output mode above, resolved with this precedence: an explicit --color / --no-color flag > the NO_COLOR env var (any non-empty value ⇒ off, the no-color.org accessibility contract) > the FORCE_COLOR env var (0/false ⇒ off, the supports-color convention) > on by default. NO_COLOR intentionally beats FORCE_COLOR — a user who opts out of color wins over a tool/CI that opts in. (A --json/CI/no-TTY stream carries no ANSI regardless — that is the separate output-mode selection above.)

The --json machine-output contract

Every record is serialized with stringifyJsonLine, not a bare JSON.stringify. The wire contract is unchanged — JSON.parse round-trips to the identical string, which is exactly why the escape was chosen over a strip — but what reaches a terminal differs: JSON.stringify escapes ESC and leaves DEL, the C1 block (including U+009B, a working escape-sequence introducer) and the Trojan-Source bidi family RAW in content the model, a tool, or an imported artifact controls. Enforced by lint rather than review; the reasoning and the one allowlisted exception live in security-review.md.

Under relavium run --json, the CLI emits a stable machine contract a CI job can pipe and assert on (ADR-0049). The contract covers a workflow run; --help, --version, and a bare no-command invocation are exit-0 meta-operations that print their human text (usage / version) to stdout as usual, --json notwithstanding. (On a genuine interactive TTY a bare relavium instead opens the interactive Home — see home.md for the gate; the help + exit-0 meta-op is preserved byte-for-byte on every non-interactive path: --json, a pipe/redirect, or CI.)

  • stdout is a pure NDJSON stream of RunEvents — one event serialized verbatim per line, in sequenceNumber order. The line is the stable envelope (type / runId / timestamp / sequenceNumber per the schema); there is no wrapper, stream header, or version line. Every event the run emits appears, unfiltered.
  • The terminal run:completed event is the final result line — it already carries outputs + totalTokensUsed + totalCostMicrocents + durationMs, so there is no separate summary line. A run:failed terminal carries error + partialOutputs; failed and cancelled terminals also carry the engine's current cumulativeCostMicrocents. The canonical envelope qualifies this known sum with usage and price coverage. These additive fields remain optional when reading historical events.
  • All diagnostics go to stderr, never stdout. A pre-run CLI fault (bad arguments, workflow not found, missing key) is written to stderr as a structured { "type": "error", "code", "message" } envelope (distinct from the run stream's run:failed/node:failed) and exits 2; stdout stays empty. A pipe consumer reads stdout for events and uses the exit code + stderr for faults.
  • Declared secret input slots stay masked — the engine emits them as { "secret": true, "ref": … }. This does not certify arbitrary or historical event/output content; see historical output/error data.

Exit codes are CI-friendly (see Exit codes).

Agent-readable command surface — now realized by the command manifest. Because Relavium's own thesis is that work starts in an agent, the CLI is a natural tool surface for other agents. The two affordances once proposed here — a machine-readable help mode (--help emitting the command/flag surface as JSON, so an agent can discover the CLI without scraping prose) and a per-command effect annotation (read / write / destructive) so an agent's tool policy can gate destructive commands behind approval — are delivered by the command manifest (ADR-0056, 2.5.C). Approval enforcement of a destructive entry is owned by ADR-0057 (2.5.E).

Global options

These flags are position-independent — they may appear anywhere on the command line, so relavium run wf --json and relavium --json run wf are equivalent (the CLI extracts them before parsing the subcommand).

Flag Effect
--json Emit machine-readable NDJSON output (disables the TUI) — see Output modes.
--color Force colored (ANSI) output on.
--no-color Disable colored (ANSI) output.
--cwd <dir> Run as if started in <dir> (project discovery and relative paths resolve from here). On chat resume this cannot replace the original workspace.
--config <path> Use an explicit global config file instead of ~/.relavium/config.toml — the project .relavium/ layers still apply (config-spec.md). Chat resume uses this current global override while discovering current project layers at its original workspace.
--allow-fs-root <absolute-directory> Repeatable invocation-only filesystem grant for bare Home, chat, chat-resume and agent run; at most 16 arguments, at most 4096 characters each, naming existing accessible directories. Other commands, including workflow run, refuse it (exit 2). Project/workspace selectors can narrow these grants.
--no-alt-screen Keep the byte-identical inline renderer for the bare Home + relavium chat (no full-screen alternate screen) — the screen-reader fallback. Overrides [preferences].alt_screen; a non-TTY / --json / CI path is always inline regardless (ADR-0068).
--no-mouse Disable terminal mouse reporting (DECSET 1002+1006) inside the full-screen renderer. The wheel stops scrolling the transcript (PgUp/PgDn + Ctrl+Home/Ctrl+End still page), in-app selection and copy-on-select go with it, and the emulator's own click-drag selection works again without a modifier. Overrides [preferences].mouse, and forces [preferences].copy_on_select off. Ignored outside the alt screen (ADR-0068 §e, accessibility.md).
-v, --verbose Print verbose diagnostics to stderr.
-q, --quiet Suppress non-essential output. (--verbose and --quiet cannot be combined → exit 2.)
-V, --version Print the version and exit 0.
-h, --help Print help for the program or a subcommand and exit 0.

Filesystem grants undergo native admission before dispatcher history/provider/key/MCP composition. Invalid or unavailable roots refuse with value-free diagnostics. No environment/config/session/model output grants a root. The flag works without selector config; fresh resumes require fresh grants under the session authority contract.

Commands

The command set below is the confirmed surface. Commands ship per workstream: run (2.D), gate + gate list (2.G/2.I), provider (2.C), the read commands list / logs / status (2.I), the whole agent-first chat family — chat (2.M), chat-resume (2.N), chat-list (2.O), chat-export (2.P), and chat --json + agent run (2.Q) — and the YAML-lifecycle authoring commands create / import / export (2.J) are all live; budget resume is live under W7 (ADR-0097). Invoking a not-yet-shipped command exits with a clean "not available yet (lands in …)" message. Subcommands marked (planned) are intended but not yet locked.

Command Purpose
relavium run <workflow> [--input k=v] [--secret-inputs-stdin] [--allow-mcp-stdio <digest>] Execute a workflow. Streams progress; resolves with the workflow output.
relavium chat [--agent <ref>] Start an interactive agent session (the agent-first REPL). See chat-session.md.
relavium chat-resume <sessionId> Reload a persisted session from history.db and continue the conversation.
relavium chat-list List past agent sessions (id, agent, last activity), the way relavium list lists workflows.
relavium chat-export <sessionId> Export a session to a .relavium.yaml scaffold for review (ADR-0026).
relavium agent run <agent> [--fixture <path>] [--json] [--allow-mcp-stdio <digest>] Run a single agent one-shot (non-interactive) on the same AgentSession infra — the prompt is read from stdin, one turn, then exit. See relavium agent run and agent-run-fixture.md.
relavium list List discovered workflows (and, with a flag, agents) in the current project.
relavium create [--force] Scaffold a new agent or a minimal single-agent workflow YAML via an interactive wizard (schema-validated before write).
relavium import <path> [--force] Import an external .relavium.yaml / .agent.yaml into the project, validated + slug-deduplicated.
relavium export <id> [--out <path>] [--force] Export a workflow/agent to a portable, canonical YAML copy. Serialization drops comments and preserves authored values, including sensitive literals; review before sharing. Configured provider keys use keychain references; author MCP env credentials as {{secrets.*}}.
relavium logs <runId> Print the persisted event/log stream for a past run.
relavium status Show active runs and their per-node status.
relavium gate <runId> Resolve a pending human gate (approve / reject / provide input).
relavium gate list [<runId>] List pending human gates (all active runs, or one run) — the multi-gate subcommand for resolving one of several concurrently-pending gates.
relavium budget resume <runId> [--gate <gateId>] --approve-amount <microcents> | --abort Resolve a recorded budget gate with its exact frozen amount, or reject it. See budget resume.
relavium init (planned) Initialize a .relavium/ directory in the current project.
relavium agent <subcommand> (planned) Manage agents (list / create / test).
relavium models List the cached model catalog (refreshes on first run if the cache is empty). See relavium models.
relavium models refresh Re-fetch both axes: each connected provider's live model list (availability) and the models.dev catalog (price, ceilings, reasoning tiers). Reports per-source outcomes.
relavium models refresh --providers Availability only — the provider lists. ADR-0064's original behaviour.
relavium models refresh --catalog Metadata only — the models.dev catalog. Needs no provider key.
relavium models pricing <model> Hand-enter a user price for a model the registry does not know, so the cost cap enforces it. See relavium models.
relavium provider <subcommand> Manage providers and API keys in the OS keychain (list / add / set-key / remove-key / test).

Command manifest

Canonical home for the command manifest (ADR-0056, 2.5.C). The runtime form lives in apps/cli/src/commands/manifest.ts (a CLI-only contract — no other surface consumes a CLI command list).

The command manifest is a derived, alias-free projection of the CLI descriptor registry. That registry owns the shell grammar and metadata used by commander, discovery and the finite executor mapping; apps/cli/src/commands/manifest.ts is not a second editing authority. Drift guards compare the actual parser and executor set with the descriptors and derived manifest. (The in-REPL / palette + slash commands are a separate, curated registry — see In-REPL slash commands below.) Each manifest entry is:

{
  id          // stable id; a subcommand is dotted — `provider.set-key`, `agent.run`, `gate.list`
  label       // a short human label — "Run workflow", "Set provider key"
  description // the one-line help; MUST match commander's .description() (the --help --json text)
  args?       // [{ name, type: 'string'|'number'|'boolean', required?, description? }]; name is the camelCase CommandInput key
  effect      // 'read' | 'write' | 'destructive'  (see below)
  modeScope?  // chat modes a command is available in; omit ⇒ all modes
}
  • effect is a forward-looking annotation: read never mutates, write creates/modifies, destructive irreversibly removes (provider.remove-key and media.prune-temporaries). It is marked for agent discoverability now; approval enforcement of a destructive entry is owned by ADR-0057 (workstream 2.5.E), not here.
  • modeScope lists the chat modes a command appears in (omit ⇒ all). The mode values (ask / plan / accept-edits / auto) are defined in ADR-0057 (2.5.E); 2.5.C ships the field with omit = all.
  • A manifest ↔ commander drift guard (a unit test) asserts every real commander command has an entry with the same description (command + each option), so commander, the executeCommand table, and --help --json stay byte-consistent.
Example entry effect
run write
list read
provider.set-key write
provider.remove-key destructive

In-REPL slash commands

The interactive / palette + slash commands inside the Home and chat are a SEPARATE, curated surface (ADR-0056 amendment, 2.5.C) — the runtime registry is apps/cli/src/commands/repl-commands.ts (REPL_COMMANDS), the single source for the palette, the /help list, and the unknown-slash hint. It surfaces only the commands that make sense in a live REPL: lifecycle (/exit, /cancel, /export, /clear), info/discovery (/help, /workflows, /cost, /doctor), and — in a chat — /mode, /effort, /thinking (also Ctrl+T), the ADR-0062 context commands (/compact, /trim), /models, and the ADR-0068 §e copy-and-search hatches /scrollback, /edit + /copy. Their in-chat behavior is spec'd in chat-session.md and its ADRs — the /models live-reseat vs bare-Home write-default and its reasoning-effort sub-step + picker UX (ADR-0059 reseat, ADR-0064/ADR-0063 catalog+write), /effort's per-turn override (ADR-0066), and /thinking's panel toggle (2.5.H) — this reference does not restate them. The heavy, session-starting shell commands (run, chat, provider, …) are never in-REPL slashes — they stay shell-only (relavium <cmd> …). A bare / at an empty prompt opens the filterable palette (the footer hint-bar surfaces / for commands there, 2.5.C S6); an unknown slash — or an undeclared argument on a known command (/exit now) — prints a sanitized, secret-free hint. A command may declare flags (/doctor --deep) or a single positional value (/mode plan); the palette runs the bare form, so a flag/value is opt-in by typing it. There is no separate /shortcuts command — the palette's own nav hints (↑/↓ · Enter · Esc) + the footer keep keys discoverable in context.

relavium run

Runs a workflow end-to-end. The argument is a path to a .relavium.yaml file (or a workflow id/slug resolvable inside .relavium/).

# interactive
relavium run ./workflows/code-review.relavium.yaml --input file=./src/index.ts

# CI: machine-readable event stream
relavium run ./workflows/code-review.relavium.yaml --input file=./src/index.ts --json

# A workflow with a declared secret input: supply it through a non-TTY pipe
printf 'api_key=%s\n' "$API_KEY" | relavium run ./workflows/code-review.relavium.yaml --secret-inputs-stdin
  • --input k=v (repeatable, one distinct key per input) supplies ordinary typed workflow inputs (see workflow inputs). Values are coerced to their declared types. An unknown or repeated key, a missing required input, or an invalid value is an invocation fault (exit 2). Every explicit --input for a declared secret is refused, including name=: argv can appear in process lists, shell history and CI logs. Raw malformed-input and run-parser diagnostics do not echo pasted argument values.
  • --secret-inputs-stdin reads declared secret inputs once from non-TTY stdin; TTY stdin is refused even if stdout is redirected. Supply one name=value line per secret. Blank lines are ignored, the name is trimmed, the value after the first = is preserved verbatim (including spaces and further =), and a carriage return at the end of a line is removed. Repeated names after trimming, malformed lines, unknown or non-secret names, and empty or whitespace-only values are refused with payload-free exit-2 diagnostics. Names such as __proto__, constructor and toString work only when declared; accumulation uses own properties in a null-prototype map. Required secrets must be supplied; omitted optional secrets remain absent. The common reader limit applies before line parsing. A workflow with no declared secrets refuses the flag without reading stdin.
  • Run admission rejects forbidden ordinary secret arguments before reading the secret pipe, constructing credential/provider resolvers, connecting MCP or opening run history. The CLI then coerces ordinary values, merges stdin secrets, and runs the pure core input contract (including length, format, pattern, enum and numeric bounds) before those effects. A rejected invocation creates no run. After unknown CLI keys are refused, core contract refusals identify a bounded set of already-declared inputs with sanitized labels and value-free reasons. Unknown, duplicate and malformed ordinary argument diagnostics keep raw argv names and values opaque. The engine validates again at start; admitted secret input slots and input-node results are masked in run events/history, while the authored workflow snapshot is kept faithful. This does not redact arbitrary authored literals, tool output or other content.
  • Consuming the secret pipe removes interactive prompt authority for this invocation. A later unresolved human gate pauses with exit 3 for a fresh relavium gate call; an unapproved local MCP program refuses instead of prompting. An existing exact consent grant or explicit --allow-mcp-stdio <digest> remains the authorization path. The flag itself grants no tool or transport authority.
  • --json switches to NDJSON RunEvent output.
  • Ctrl-C (SIGINT) requests a cooperative cancel; the run drains to run:cancelled and exits non-zero (1). This is unchanged when the workflow declares mcp_servers: the spawned servers are torn down alongside the drain, and the MCP teardown deliberately does not take the exit code — a signalled run still records its terminal and releases its lease (ADR-0088 §1.3).
  • A missing API key for an inline agent's primary provider is caught pre-flight as an invalid invocation (exit 2) naming the RELAVIUM_<PROVIDER>_API_KEY to set, before the run starts. The pre-flight is a strict subset of the keys a run may touch, so it never blocks a valid run: a fallback_chain provider's key (read only if the chain fails over to it) and a $ref-resolved external agent's key (until $ref resolution lands, 2.M–2.Q) are conditional and instead surface mid-run as a run failure (exit 1).
  • --allow-mcp-stdio <digest> (repeatable) authorizes a local MCP program for this invocation only, when there is no one to prompt. A workflow declaring a stdio MCP server that has not been approved on this machine exits 2 before anything spawns — see Local MCP servers need consent.
  • On a human_gate node the run pauses: in interactive mode it prompts inline; in CI mode it exits with the gate-paused code (3, see Exit codes) and can be resumed with relavium gate. The emitted human_gate:paused event carries the runId + gateId needed for the resume (relavium gate <runId> --gate <gateId>); with --json they are on the NDJSON event line, otherwise the plain/TUI renderer prints them inline (paused at gate <gateId> (<type>), also echoed in the final summary). (relavium status, relavium logs <runId>, and relavium gate list also surface pending gateIds, 2.I.)

Implementation status (as of workstream 2.G). run is wired to the @relavium/core engine: path/id resolution, --input coercion, the full lifecycle event stream, exit codes 0/1/2/3, SIGINT→cancel, and the stable --json NDJSON machine contract (stdout = pure RunEvent stream, diagnostics → stderr; see above) are live. The interactive ink TUI (2.E) renders the live run on a TTY — per-node status + spinners, the active node's streaming tokens, a running cost/duration footer, and a persistent final summary. Under --no-color it keeps the TUI but suppresses ANSI color; it falls back to the plain line renderer when no TTY is attached or CI=true, and to NDJSON under --json (the three renderers are one onEvent seam over one bus). Provider keys resolve from the OS keychain → RELAVIUM_<PROVIDER>_API_KEY env var → error (2.C; manage them with relavium provider), and runs persist to durable history (2.H). The interactive human-gate prompt + out-of-band relavium gate resume are live (2.G): on a TTY a human_gate node renders a @clack/prompts card inline (approve / reject + comment / input) and the run continues; under --json/CI/no-TTY there is no prompt and the run exits 3, resumable later by relavium gate <runId>. The CLI supplies reviewed filesystem, process and egress host capabilities; tool availability remains subject to invocation authority, per-tool policy and wired delegates. The built-in tool reference owns their current host behavior and security floors.

relavium list

Lists the workflows discovered under the project .relavium/workflows/, grouped by tag, each annotated with its last-run status from durable history (the latest run per workflow — a SQLite ROW_NUMBER() OVER (PARTITION BY workflow_id …) pick, since SQLite has no DISTINCT ON). --agents lists the agents under .relavium/agents/ instead (agents carry no tags or run history, so they list flat). Disk is the catalog source of truth, distinct from run history — a discovered file that fails to parse is listed and flagged (invalid: <reason>) rather than hidden. Outside a .relavium/ project this is reported clearly and exits 0 (an empty catalog is not a fault).

Under --json, each entry is one NDJSON record — { kind, slug, name, tags, path, valid, error?, lastRun }, where name is null when the file declares none, error is a short, secret-free parse-failure reason present only when valid is false, and lastRun is { runId, status, completedAt } or null for a never-run workflow (and is omitted entirely for agents). See Read-command --json output.

relavium create

Interactive scaffolder (@clack/prompts) that asks kind (an agent → .agent.yaml, or a minimal single-agent workflow → .relavium.yaml, i.e. an input → agent → output scaffold wrapping one inline agent), then name → provider → model → system prompt → tools (comma-separated, optional). The id is the slugified name; the file lands at .relavium/agents/<id>.agent.yaml or .relavium/workflows/<id>.relavium.yaml.

The answers are assembled into a typed definition and validated against the appropriate @relavium/shared schema before any write — the agent schema for an .agent.yaml, the workflow schema for a .relavium.yaml — so a bad model/provider/name is the same clean exit-2 fault a run would raise (a name with no usable id characters is rejected up front). The id must be unique across both catalogs (see import/export below): a same-kind clash needs --force, a cross-kind clash is always rejected. The wizard needs an interactive terminal on both ends (a TTY stdout to draw the prompt and a TTY stdin to read keys), so it fails loud (exit 2) under --json or a non-TTY pipe; a cancel (Ctrl-C / ESC) writes nothing and exits 0. The result is a plain YAML file ready to commit — pure file I/O, no keychain, no run state.

relavium import / relavium export

Both are surface-agnostic git-native YAML operations — they read/write .relavium/ files only, never the keychain or run state. A document's kind is detected from its filename suffix (.agent.yaml / .relavium.yaml), falling back to a content sniff; a file that is neither a valid workflow nor a valid agent is a clean exit-2 fault naming both parse failures.

  • import <path> [--force] copies an external .relavium.yaml / .agent.yaml into the project after validating it against the schema. It writes the re-serialized canonical form to .relavium/<workflows|agents>/<slug>.<suffix>. Ids are unique across both catalogs (a bare id resolves project-globally, so export <id> stays unambiguous): a same-kind slug collision is exit 2 unless --force overwrites it; a cross-kind collision (the id already names the other kind) is exit 2 always — rename one, since --force would leave both files in place.
  • export <id> [--out <path>] [--force] resolves <id> across both catalogs (an id naming neither is exit 2; an id that is both a workflow and an agent is exit 2, "rename one"), re-validates it, and writes a portable copy. The default destination is ./<id>.<suffix> in the cwd (a copy to share, outside the catalog); --out overrides it. Under --json it emits a single { id, kind, path } record, where path is cwd-relative (the same shape import --json emits — no absolute filesystem path is printed in either mode).

Review before sharing. Relavium does not insert a resolved provider key into authored YAML: its provider-key configuration uses OS-keychain references. Arbitrary authored strings, including prompts, metadata and MCP env, can still contain a pasted credential or other sensitive content. export/import re-serialize faithfully and drop comments; they preserve authored values and do not scrub them. Author MCP credentials as {{secrets.*}} placeholders, never inline a literal. See key custody and content retention.

create, import and run apply a bounded, best-effort review of the parsed authored definition using the existing finite diagnostic secret-shape detector. A suspicious shape produces a value-free notice on stderr; an incomplete scan is also disclosed. These notices neither reject nor redact the document and add no JSON stdout record. The original parsed definition and the run's admitted augmented snapshot remain faithful. Absence of a notice does not certify safe sharing or absence of credentials. Supplied runtime secret-input values are outside this authored-content scan; their admission and recorded-slot masking follow the input contract.

create and import emit these notices only after their YAML file is written. A refused write emits no authored-content notice or success output.

The review visits at most 4,096 nodes through depth 64, checks strings of at most 16,384 UTF-16 code units and at most 262,144 checked code units in total, including property names. It reports only a count of strings matching the finite detector: prefixed OpenAI/Anthropic or Google keys, HTTP(S)/WS(S)/FTP(S) URL userinfo, exact api-key/x-api-key/x-goog-api-key headers, Bearer/Basic credentials and literal secret query names. It does not classify every credential format or distinguish arbitrary opaque credential fragments from following diagnostic prose. Over-budget strings or structure, repeated objects and unsupported object shapes produce a separate fixed incomplete-review notice. Object-key enumeration relies on the existing parser source ceiling; the node limit is not an independent bound on that enumeration. Neither notice includes values or arbitrary property names.

For this detector probe only, a recognised bare template reference without a path or filter is projected to an already masked span, provided its raw reference contains no recognised credential shape. This keeps Bearer {{secrets.token}} and equivalent query placeholders quiet. Adjacent literals, credential-shaped identifiers, paths, filter arguments and unknown or malformed references retain their evidence. This projection neither resolves references nor changes authored content.

relavium logs <runId>

Replays a past run's persisted run_events in seq order (the same data the desktop run-detail drawer replays) — a terse line per event in human mode. Under --json it emits each raw RunEvent as one NDJSON line — the same RunEvent data relavium run --json streamed (this is the "raw RunEvent JSON" the run-detail replay consumes — no separate --raw flag). For a run paused at a gate, the human_gate:paused event surfaces the gateId to copy into relavium gate <runId> --gate <gateId>. An unknown runId is an invalid invocation (exit 2).

Replay retains the stored content; current declared-secret context protection does not scrub or certify older outputs. Apply the historical output/error guidance before sharing replay output or recovering the run.

Raw historical events preserve absent fields: the reader does not synthesize usageCoverage, cumulativeUnpricedCalls or invocation correlation; absence cannot certify complete or free coverage.

Two rows the replay may not be able to read, and it says which is which (ADR-0074 §5):

  • a row written by a newer Relavium — an event type this binary does not know — is skipped, and a bounded note: naming the affected seq numbers goes to stderr (in both modes, so --json's stdout stays a pure RunEvent stream). Without the note the event count and the [seq] column would present a short log with a gap as a complete one, which is exactly what the event contract tells consumers to read as data loss. Upgrade to read them.
  • a damaged row — unreadable JSON, or a type this binary knows whose body does not parse — fails the command (exit 1), naming the run and the row's seq. It is never silently dropped. Other runs stay listable: logs/status/gate list isolate a damaged run so one bad row cannot blank the whole listing.

relavium status

Shows the currently active/paused runs (from runs + step_executions) and each one's per-node status. Pending human and budget gates are reconstructed from the strict ordered event log even when only the authoritative budget pause survived a crash; discovery never claims ownership or supplies a decision. Useful while a long workflow runs in another terminal or was launched detached. For any run paused at a human gate it also prints the pending gateId(s) (with gate type and node id), so a CI author can pass the right one to relavium gate <runId> --gate <gateId> — required when a run has more than one gate pending at once. It takes no argument (it lists every active run; a terminal run is not shown — inspect one with relavium logs <runId>). A run whose event log is damaged is still listed, without its gate detail, rather than aborting the listing (ADR-0074 §5). A run whose TERMINAL is held in the terminal outbox is named as such — it reads running in the derived projection because its terminal never became durable, so without the marker it is indistinguishable from a run still working (exit code 5). The listing READS the outbox and never drains it: draining claims a run lease, and a status read must not take ownership of a run another process may be finishing. Under --json each active run is one NDJSON record — { runId, workflowId, status, totalCostMicrocents, usageCoverage, unpricedCalls, terminalHeld, startedAt, steps, pendingGates, pendingBudgetGates }, where each steps entry is { nodeId, nodeType, status, attemptNumber, startedAt, completedAt, durationMs, costMicrocents, usageCoverage } and each pendingGates entry is { gateId, nodeId, gateType, message, expiresAt? } (the same pending-gate shape gate list emits). Each pendingBudgetGates entry is { gateId, nodeId, allowance, expiresAt? }: allowance is { kind: "amount", microcents }, { kind: "legacy" }, { kind: "reject_only", reason: "unpriced" } or { kind: "reject_only", reason: "unrepresentable" }. Each allowance variant may also carry excludedEntries?: string[]: recorded excluded-candidate notices with redacted, quoted, truncated and escaped model identifiers and fixed exclusion reasons. These display strings grant no allowance. The JSON projection exposes no full quote, priced-model identity, endpoint or price provenance. Plain output adds the exact approval/abort commands for an amount-bearing gate. Terminal controls and secret-shaped identifiers are redacted and bounded in the budget display; a modified or shell-active identifier gets generic instructions rather than an executable hint. A damaged log emits empty gate arrays plus gatesUnavailable: true and gatesUnavailableReason: "corrupt_event_log", so absence of readable authority is distinguishable from no pending gate. Run amounts and per-step amounts/lifecycle are qualified by usage coverage from one SQLite snapshot. A concurrent completion cannot attach an older complete-usage fold to a newer step amount. If strict usage replay fails, the record carries usageUnavailable: true, usageUnavailableReason: "unreadable_event_log" and explicitly unverified run/step coverage; independently readable step detail remains visible.

relavium models

The live model catalog (2.5.G, ADR-0064). The catalog is a local cache in history.db (model_catalog) that records which model ids each connected provider key can currently reach; the static registry (pricing.ts) stays the pricing authority, so the cache holds no price and no API key.

relavium models              # list the cached catalog
relavium models refresh      # force a live re-fetch of every connected provider
relavium models pricing my-custom-model --provider openai --input 3 --output 9   # hand-enter a price
  • relavium models (no subcommand) lists the cached catalog (read-only). On the very first run — when the cache is empty — it does one minimal blocking refresh, then lists; an empty result stays a clean exit 0 (an empty catalog is not a fault, like relavium list). Human output is one line per model (<modelId> <provider> ctx=<n> [<source>]).

  • relavium models refresh re-fetches two axes, because "refresh what I know about models" is one intent, not two (ADR-0071 §4a): the provider lists say which models a key can reach, and the catalog says what they cost and which reasoning tiers they take. --providers and --catalog address one axis each; neither flag (or both) means both.

    The provider half is per-provider isolated: one provider's failure (bad key, network, endpoint drift) or a provider without a list endpoint never fails the whole command — that provider is reported failed / skipped and the others still refresh. A per-provider failure is therefore not a command failure (exit 0 with the report). An explicit --providers refresh with zero providers connected (no key at all) is a clean exit 2 naming how to add a key, because nothing could be fetched.

    The catalog half needs no key at all — which is what makes --catalog the form a user with no provider configured yet can still run to see what things cost. A failed catalog fetch is a note on stderr, never an error: the shipped snapshot still answers, so a refresh can never leave Relavium knowing less than it shipped knowing. Under --json, each source emits its own record ({ provider, … } per provider, then { source: 'catalog', status, models, added }).

  • The catalog is EMBEDDED and answers offline. Relavium ships a generated models.dev snapshot — every price, ceiling and reasoning control — so a fresh install with no network prices every model it ships. [catalog] auto_refresh (default false) governs whether a bare relavium models refreshes it first; an explicit models refresh fetches regardless, because a command the user typed is consent. A local-first tool does not contact a third party unless asked.

  • relavium models pricing <model> --provider <slug> [--input <usd> --output <usd>] [--cached <usd>] [--image <usd-per-image>] [--audio <usd-per-second>] [--video <usd-per-second>] hand-enters the price of a model. It MAY be a model the generated catalog (packages/llm/src/catalog/) already prices — the USER outranks the catalog (ADR-0071 §5): a negotiated rate, an enterprise discount, or a price the snapshot has not caught up with wins over the shipped one (2.5.G S10, ADR-0065 §1–2). Token prices are USD per million tokens (--input prompt, --output completion, --cached cache-read); media prices are per BILLED UNIT — --image per image, --audio and --video per second (ADR-0044 §3's canonical units; ADR-0089 §4). All are stored as integer micro-cents (usd × 1e8, never a float), and the two denominators carry separate implausibility ceilings — $1000 per image is already absurd where $1000/Mtok is merely expensive. --input/--output are given together or not at all, and a media-only invocation is legitimate: it is how you satisfy a strict_cost_cap refusal on a model the catalog already prices for tokens, without writing a user row that would then outrank the catalog for tokens too. An omitted flag leaves its column alone (the store preserves it); a stated 0 is believed and recorded as free, which for the media columns is distinct from unpriced. The row is written as source='user' and a live models refresh never clobbers it. Overriding a catalog price is not silent: the command echoes the catalog price it replaces, so the divergence is stated at set-time (ADR-0071 §5). This also closes the cost-cap gap (ADR-0064 §6) for a model the catalog does NOT price: before, an unknown model had no price, so budget.max_cost_microcents / [chat].max_cost_microcents degraded to allow for it; once user-priced, the cap is enforced (pre-egress and realized) on run, a run resumed via relavium gate, chat / chat-resume (incl. a /clear rebuild, re-read fresh), the Home chat, and one-shot agent run. --clear retires the override; the model falls back to the catalog price, or to unpriced if the catalog has none. Guards (each a clean exit 2, nothing written): an unregistered provider is refused (register it first with relavium provider add); a negative / non-finite / implausibly-large price is refused (per-unit and per-Mtok have their own ceilings); an invocation with no price at all, or a half-stated --input/--output pair, is refused; --clear beside any price flag — media included — is refused rather than half-applied; a price under a provider the catalog does not anchor the model to is refused (it would never apply); and the same model id already user-priced under a different provider is refused (the overlay keys by model id, so a second provider's price could not be distinguished — use a distinct id or re-price under that provider).

  • Security. A provider key is read only to make the live request (over the bounded, abortable, secret-free listModels seam) and is never logged, persisted (the cache holds no key), or placed in the report / --json payload / any error message. A failing provider surfaces only the seam's already-redacted message (or a generic refresh failed), never a raw cause. models pricing writes only a model id + provider + integer prices — no key, ever.

  • --json (ADR-0049) emits one NDJSON record per line, stdout-pure, key-free:

    • relavium models --json — one record per model: { provider, modelId, displayName, contextWindowTokens, maxOutputTokens, source, lastRefreshedAt, deprecationDate } (null for an absent optional; source ∈ static | live | user; lastRefreshedAt is epoch-ms).
    • relavium models refresh --json — the two axes each emit their own record (§4a). A per-provider record: { provider, status, added, updated, deactivated, error }, where status ∈ refreshed | skipped-no-key | skipped-unsupported | failed, the three counts are the model ids added / refreshed-in-place / soft-deactivated (null unless status is refreshed), and error is a short, secret-free reason (null unless failed). And, when the catalog axis ran, exactly one catalog record: { source, status, models, added, reason? } — source is always the literal 'catalog'; status ∈ refreshed | failed; models is the total the refreshed catalog describes and added the count the shipped snapshot did not carry (both 0 on a failure — the snapshot still answers); reason is present only on failed and is a short, secret-free sentence (never a raw fetch error, which can carry a URL).
    • relavium models pricing --json — one record: { model, provider, source, inputCostPerMtokMicrocents, outputCostPerMtokMicrocents, cachedInputCostPerMtokMicrocents, mediaImageCostMicrocents, mediaAudioCostMicrocents, mediaVideoCostMicrocents, overriddenCatalogPrice } (the stored integer micro-cents; source is always user). A field is omitted when that flag was not given — the columns were preserved, not written, and reporting a 0 would claim a price nobody set. overriddenCatalogPrice is null when the catalog does not price the model.

Read-command --json output

The non-streaming read commands (list / status / gate list / chat-list / models / provider list, and logs) keep the CLI to one machine-output idiom: --json emits one result record per line (NDJSON, jq-friendly, stdout-pure with diagnostics on stderr) — the same line-oriented shape relavium run --json uses for its RunEvent stream (ADR-0049). For logs --json the records ARE raw RunEvents — the same RunEvent data the run streamed (re-serialized from the persisted log, so the field order may differ from the live run --json bytes); for the others they are the per-command result records documented above. An unknown runId (logs / gate list) is the structured pre-run fault on stderr with exit 2, stdout empty — exactly as for run. (chat-export --json is not a read command — it emits a single session:exported event, not a result record, since the export is a session-lifecycle action.)

relavium gate

Resolves a pending human gate from the terminal — the surface-agnostic resume path for human_gate:paused:

relavium gate <runId> --approve
relavium gate <runId> --reject --comment "Too risky"
relavium gate <runId> --input '{"region": "us-east-1"}'      # for gate_type=input
relavium gate <runId> --gate <gateId> --approve            # disambiguate when >1 gate is pending

# a run with `secret`-typed inputs: re-supply them on stdin, never in argv
printf 'api_key=%s\n' "$API_KEY" | relavium gate <runId> --approve --secret-stdin
  • Exactly one of --approve / --reject / --input is required and they are mutually exclusive; --comment <text> annotates an approve/reject rationale and is invalid with --input (which carries the payload). A bad combination is an invalid invocation (exit 2).
  • --input <value> is parsed as JSON when it parses ('{"k":1}' / '42' / 'true' → a structured payload), else kept as the raw string (--input some-token → "some-token"); the result becomes the gate node's output. (The interactive prompt for a gate_type=input gate takes the typed value as a raw string.)
  • Do not pass secrets via --input. The value reaches the durable event log (human_gate:resumed.payload) and the --json stream, and argv itself leaks into ps / shell history / CI logs — exactly the exposure relavium provider set-key's stdin-only rule avoids. Use a non-secret gate input. Re-supply declared workflow secret inputs with --secret-stdin; provider API keys have their separate OS-keychain / RELAVIUM_<PROVIDER>_API_KEY resolution path.
  • --gate <gateId> selects which pending gate to resolve. The resume contract is engine.resume(runId, gateId, decision) — gateId is mandatory on the resume path (it is carried on the human_gate:paused event; see sse-event-schema.md and resume_run in ipc-contract.md). --gate is optional on the CLI: when exactly one gate is pending the CLI fills it in automatically; when more than one gate is pending it is required, and omitting it is an invalid invocation (exit 2) listing the pending gateIds.
  • Read the pending runId + gateId from the run's own output: the human_gate:paused event line under --json, or the paused at gate <gateId> (<type>) line the plain/TUI renderer prints. relavium gate list, relavium status, and relavium logs <runId> (2.I) also surface them out-of-band.
  • --secret-stdin re-supplies the run's secret-typed inputs (ADR-0083 §6). The raw value is not persisted in the declared input slot — the durable record holds only a masked { secret: true, ref } marker there — so a secret-bearing run cannot resume without the value being handed back. Each is one name=value line on stdin; blank lines are ignored, and anything else is an invalid invocation (exit 2). The shared reader limit applies.
    • Values never travel through argv, which is why this is a boolean flag and not --secret <name=value>: a credential on a command line leaks to ps, shell history and CI logs — the same rule relavium provider set-key follows. The name in the line is not a secret, and carrying it there rather than in a repeated flag removes the ordering dependency that would silently swap two credentials.
    • Every masked slot must be supplied and nothing else may be. A missing slot, a name the run has no slot for, a repeated name, and an empty or all-whitespace value are each exit 2. A value may contain = (the split is on the first one); it may not contain a newline. The name is trimmed; the value is taken verbatim after the first =, so leading and trailing spaces are part of the credential — only a trailing line carriage return is removed. (An earlier version trimmed the whole line, which silently stripped a trailing space from a pasted key and surfaced hours later as an opaque 401.)
    • --secret-stdin makes the invocation non-interactive. Reading the credential drains stdin, so a resumed run that reaches a second human gate cannot prompt: it exits 3 and is resolvable by another relavium gate, exactly as a --json or CI resume does.
    • Without the flag, a secret-bearing resume is refused (exit 2) and the message names the slots and this remedy. With the flag on a run that has no secrets, the command refuses rather than blocking on a pipe nobody attached.
    • What this proves is the slot, not the credential: the engine verifies that the same named secret input was re-supplied and cannot tell whether the value is the same key or a rotated one. ADR-0083 §6 states that limit rather than implying more.
  • Idempotent. A doubled decision — the run already finished, or the named gate was already resolved — is a clean exit-0 no-op, never a double-advance (it leans on the engine's checkpoint/gate-state idempotency). An unknown runId is exit 2. Idempotency is per gate, though: on a sequential multi-gate workflow a blind repeat without --gate (after the first decision advanced the run and it re-paused at the next gate) auto-fills and resolves that gate — so an automated retry-until-exit-0 loop should pin --gate <gateId> to avoid resolving later gates unattended.

Implementation status (2.G). relavium gate runs in a fresh process from the original run: it reloads the run's frozen WorkflowDefinition + inputs from the durable history snapshot (2.H), reconstructs the paused checkpoint from the persisted event log, and calls engine.resumeFromCheckpoint over the same store — then drives the resumed run to its terminal (exit 0 complete / 1 failed / 3 paused again at a later gate). The recorded decidedBy is the constant cli (a deterministic, non-PII marker; the desktop/portal supply a real user id). Budget-cap pauses (budget:paused, ADR-0028) are not resolved here — that is the separate relavium budget resume surface. A run that declares a secret-typed input is resumed by re-supplying it on stdin with --secret-stdin (above); without the flag relavium gate still fails closed (exit 2), because the declared input slot retains only a masked placeholder (ADR-0006/0036) and has no raw value to restore. The engine enforces the same rule independently — it refuses a resume whose masked slot was not filled, and refuses the placeholder itself as a value. The relavium gate list multi-gate listing is live (2.I).

Current context qualification on resume does not rewrite completed outputs restored from history; the historical output/error guidance also applies to human and budget recovery.

relavium budget resume

Resolve a budget gate from another process or a non-interactive shell. The engine validates and activates the recorded allowance; the CLI transports the decision without recomputing the quote.

relavium budget resume <runId> [--gate <gateId>] --approve-amount <microcents>
relavium budget resume <runId> [--gate <gateId>] --abort
  • Exactly one decision flag is required. --approve-amount accepts decimal digits representing a non-negative safe integer in microcents, including 0; signs, fractions, exponents and overflow are invalid invocations (exit 2). It must equal the frozen amount exactly. A wrong amount is refused before secret input, key resolver construction, MCP connection, engine construction or ownership.
  • With one pending budget gate, --gate is filled automatically. With multiple budget gates it is required; ordinary human gates are separate. Pin the gate id in an automated retry so a later gate is never selected accidentally. Human relavium status and ambiguous resume discovery report budget ids, scalar allowance amounts and the recorded excluded candidates. Candidate model identifiers are redacted, quoted, truncated and escaped; they cannot supply an approval amount. Priced model/rate provenance stays out of this display. relavium gate list and relavium gate continue to cover ordinary gates only.
  • A legacy gate with no frozen allowance, an unpriced quote or an unrepresentable amount offers --abort only on this surface. There is no input payload, comment or generic --approve flag.
  • Approval preserves the governor and its dispatch-owned allowance. It does not lift the workflow cap. The CLI first checks recorded priced quantities against current prices without seeding database rows, before secret input, credential-resolver construction or MCP connection. That check grants no approval. The engine independently prepares the actual request and rechecks the complete current quote before claiming the gate; a stale quote remains pending and is refused (exit 2). Human and budget resume build their provider resolvers over the command's existing database connection. See the preparation/resume contract.
  • --abort rejects the selected budget gate, dispatches no agent and fails the run with budget_exceeded (exit 1). It calls no credential factory and creates no MCP client. A doubled resolved budget gate or terminal-run decision is a clean exit-0 no-op before resource construction. For a nonterminal run, naming a resolved ordinary human gate is an invalid invocation (exit 2), not a budget no-op.
  • --secret-stdin follows the human-resume stdin contract. A secret-bearing frozen run still needs its masked slots re-supplied, including on abort. The advice preserves the selected run, gate and decision; values stay out of argv. Drained stdin disables any later interactive prompt.
  • --allow-mcp-stdio <digest> follows the existing declaration consent gate. Both human and budget resume reconnect against the original recorded workspace, with the frozen tool grant: removed, replaced or newly discovered MCP ids refuse rather than changing the authorized request. A budget rejection does not connect MCP. Signal teardown is armed before connect and removed after cleanup. Ctrl-C during connect/build returns exit 1, leaves the decision unrecorded and the gate pending, and reaps any connected child. The interruption guard also covers resumes without MCP. Once engine resume starts, cancellation reaches both passive preparation and the active execution. A decision append may already be in flight: its successful acknowledgement stays durable, while cancellation prevents later dispatch and follows the engine's cancelled outcome; it never rolls back a durable approval. Signal subscriptions remain armed through database/client cleanup.
  • A successful native decision write yields to queued OS signal handling before its acknowledgement admits the approved dispatch. Terminal persistence uncertainty returns exit 5 with the delivered terminal retained in the outbox. Nonterminal ownership uncertainty refuses with exit 6; its diagnostic does not claim that an earlier durable approval is absent. See the exit codes.
  • Under --json, stdout remains a pure run-event stream, with diagnostics and no-op/interruption notices on stderr. There is no prompt under JSON, CI or no TTY. On a TTY the inline budget card shows the frozen scalar amount and requires that exact amount or rejection; unpriced/unrepresentable gates offer rejection only. A legacy inline card may explicitly continue without an allowance under current budget checks; the amount-bearing shell command cannot invent an amount for it. The card also shows the safely projected excluded candidates. An engine-refused stale or invalid inline budget approval stops further prompts and drains the actual ordered event stream until the aggregate run:paused is durably acknowledged, retaining the command's database and cancel handler through that barrier. It then returns exit 3 with the gate pending. Once cooperative cancellation is requested, an already queued pause cannot trigger that exit: the command retains its resources through the acknowledged run:cancelled terminal and returns exit 1. Other competing terminals retain their own outcomes while the primary stream is being consumed. Cancellation from a dismissed card also stops all queued inline prompts. The primary subscription stays open through renderer teardown: cancellation accepted during Ink unmount is durably acknowledged before SQLite closes and before the single persistent summary is printed. A custom renderer that omits the summary barrier still cannot release the command resources before the cancellation terminal. Voluntary paused finalization releases input without printing an irreversible summary, then joins engine-owned local departure. The one primary reader remains active through both acknowledgements: a gate deadline or decision that advances the run keeps its real outcome. A stable pause returns exit 3 only after local detachment; it makes no claim about subsequent progress by another host. Terminal and fenced outcomes also join admitted work before SQLite or MCP resources close. See the canonical engine lifecycle. A join pending for 250 ms emits a fixed cleanup_pending diagnostic to stderr and continues waiting. A failed cleanup acknowledgement emits cleanup_failed and retains the owner rather than treating failure as completion. JSON diagnostics are nondurable objects with type, code and message; stdout remains the run-event stream. SIGINT stays owned through input and engine acknowledgement. On receiving an actual terminal, Ink immediately writes a fixed provisional human outcome to stderr (Run completed/failed/cancelled; run cleanup is pending.), even if its input mount has already exited or its last frame is throttled. This notice carries no final receipt verdict or provider error text. Ink prints one persistent summary for the actual final local outcome; plain/NDJSON add no synthetic terminal or summary. The operator can inspect or reject the pending gate through budget resume. Ordinary human-gate prompts retain their existing decisions.
  • An emitted budget rejection stops later queued cards and drains its real failed terminal. A cancellation or terminal arriving during live-view suspension suppresses the stale card; one arriving while a card is open dismisses it without requiring another answer. Late answers cannot replace the winning decision or terminal. A losing idempotent rejection does not stop subsequent genuine gates, and ordinary human-gate rejection retains its existing continue behaviour.
  • Authority and matching companions produce one budget notice per (runId, nodeId, gateId) in plain and TUI views. They never fall through to an ordinary human approval prompt. The durable protocol is defined once in the event contract.

relavium gate list

Lists the pending human gates so an operator can pick the gateId to resolve — the multi-gate discovery surface the gate command's --gate requirement points at.

relavium gate list             # every active run's pending human gates
relavium gate list <runId>     # just one run's
  • With no argument it scans every active run; with a <runId> it lists just that run's pending gates (an unknown runId exits 2). Budget-cap pauses (budget:paused) are excluded — those are the separate relavium budget resume surface (ADR-0028).
  • It rests on the same persisted-event reconstruction the gate resume path uses, so the listing and the resume can never disagree on what is pending.
  • Human output is one line per gate (<runId> <gateId> <gateType> node=<nodeId> "<message>"); under --json each pending gate is one NDJSON record — { runId, gateId, nodeId, gateType, message, expiresAt? } (see Read-command --json output).

relavium chat-list

Lists past agent sessions from durable history.db, most-recently-updated first — the session counterpart of relavium list. Human output is one line per session (<id> <agentSlug> [<status>] <updatedAt> "<title>"); an empty history is reported clearly (exit 0). Under --json each session is one NDJSON record — { sessionId, agentSlug, title, status, modelId, createdAt, updatedAt, totalCostMicrocents, usageCoverage, unpricedCalls }, where title / modelId are null when absent (see Read-command --json output). The known actual amount, usage coverage and independent missing-price count come from one durable accounting snapshot. Human output remains identity/lifecycle metadata only. Soft-deleted sessions are excluded.

JSON discovery exhausts all live sessions in indexed 256-row pages within one read snapshot; page size is a working-memory bound, not a result limit. Identity, known money, usage coverage and missing-price counts therefore remain consistent across pages while another process writes. No per-session attribution or transcript is loaded for this listing.

relavium chat-export

Exports a persisted session to a .relavium.yaml scaffold for review before commit (ADR-0026) — the same contract the in-REPL /export drives. Writes <sessionId>.relavium.yaml in cwd by default (the file name is keyed on the unique session id, so two sessions never collide); --out <path> overrides, --force overwrites an existing target. The session row is marked exported with the written path. Under --json it emits a single session:exported event ({ type, sessionId, timestamp, sequenceNumber, workflowPath }). An unknown sessionId or an existing target without --force exits 2; success is exit 0.

Successful chat-export writes a fixed review/continuity notice on stderr, even in JSON mode. Live /export uses the rendered transcript in interactive chat and Home, and stderr on plain/JSON drivers. The scaffold cannot reproduce the session's summarised, trimmed or omitted working context; retained user/assistant text, attachments, shell output and authored agent literals may be sensitive. Review before running or committing. Strict structural tool-content exclusion remains in force; there is no raw-tool-result opt-in. Deterministic ASCII title normalisation and bounded session-identity fallback follow the canonical mapping. The notice adds no machine stdout record and is not emitted for a refused export.

If chat-resume or chat-export encounters a session message that fails persistence-format validation, it refuses with exit 2 and a fixed, content-free diagnostic. Restore a safe history backup or start a new session. A refused export writes no scaffold; JSON mode keeps stdout empty and puts one structured error on stderr.

relavium agent run

Committed effect metadata is swept at one-shot teardown; unresolved effects remain audit evidence. No transcript is persisted and no earlier invocation is replayed. A failed one-shot identity reservation cannot clean another session's rows; see effect-journal.md §9.

Runs a single agent one-shot (non-interactive) on the same AgentSession infra as relavium chat — a session with one turn, then exit. The agent-first headline as a scriptable, CI-friendly primitive.

echo "summarize ./README.md" | relavium agent run code-reviewer
echo "review it" | relavium agent run ./agents/coder.agent.yaml --json
echo "review it" | relavium agent run code-reviewer --fixture ./fixtures/review.cassette.json --json
  • The <agent> argument is required — a .agent.yaml path or a .relavium/-discoverable agent id (resolved by the same strict parser relavium chat --agent uses). An unknown agent is an invalid invocation (exit 2).
  • The prompt is read from stdin (the echo … | relavium agent run idiom); an empty stdin is an invalid invocation (exit 2).
  • --input k=v is reserved — currently rejected (exit 2): a session does not yet interpolate {{ctx.*}} into the agent's prompt (the engine passes system_prompt verbatim), so the flag is failed loud rather than exposed as an inert no-op. It re-opens when session prompt interpolation lands (a tracked engine follow-up, deferred-tasks.md).
  • --fixture <path> replays a recorded LLM cassette so the run is deterministic and fully offline (no key, no network, no keychain) — the format is documented in agent-run-fixture.md. A malformed cassette exits 2.
  • --json emits the SessionEvent NDJSON stream on stdout (the same shape chat --json produces); otherwise the assistant reply streams in human form.
  • --allow-mcp-stdio <digest> (repeatable) authorizes a local MCP program for this invocation only. agent run is the one-shot, pipeline-facing member of the chat family — its stdin carries the prompt, so it can never prompt for consent, and an agent declaring an unapproved stdio MCP server exits 2 before anything spawns. See Local MCP servers need consent.
  • --allow-fs-root uses the global invocation contract, including when no selector config exists.
  • The transcript is not persisted — a stateless invoke (no transcript or resumable session; only an already-tombstoned durable effect-identity bookkeeping row), unlike the REPL. It does still open history.db to attach the effect journal (effect-journal.md): an external effect is carried forward by the target, not by the run, so an unattached journal would refuse every effectful tool on this surface. Its effect metadata is read at teardown for the owned committed-row sweep, never for replay. A history.db that cannot be opened fails the invocation.
  • Ordinary exits reflect the turn's outcome: 0 on success, 1 on a classified turn error; an invocation fault is 2. Unexpected infrastructure or output failure uses 9. It is never 4 (the interactive REPL's session-ended code). A termination signal retains 128+signo (130 on Ctrl-C) unless a stronger observed failure applies; see failure classification and remedies. This one-shot surface has no workflow receipt/effect health authority for exits 5–8.
  • Teardown stays with the original invocation. Cancellation requests do not acknowledge MCP or database closure, including on a second Ctrl-C. The finite backstop preserves cleanup uncertainty; it cannot guarantee that every descendant is reaped. See native crash teardown scope for actual-close, process-ownership and containment limits.

relavium provider

Registers LLM providers and manages their API keys in the OS keychain (workstream 2.C; @napi-rs/keyring, ADR-0019). The llm_providers row stores only the keychain account ref; provider displays expose a hint, not the key value. The host resolves the credential into process memory for provider requests, including verification/model discovery; key custody does not mean the adapter can call a provider without reading its credential. See the keychain/secret contract. Known providers: anthropic, openai, gemini, deepseek.

relavium provider list                                  # registered providers + whether a key is set
relavium provider add anthropic                         # register a provider (its default base URL + pricing page)
relavium provider add openai --pricing-url https://example.com/prices   # override the pricing reference page
echo "$ANTHROPIC_API_KEY" | relavium provider set-key anthropic   # store a key (read from STDIN, never argv)
relavium provider test anthropic                        # verify the key with a minimal live request
relavium provider remove-key anthropic                  # delete the key from the keychain
relavium provider list --verify                         # + a live key-verification probe per provider
  • set-key reads the key from stdin, never a CLI argument (argv leaks into ps, shell history, and CI logs); pipe it or use a heredoc. The key is stored in the OS keychain under the canonical entry-naming scheme (keychain-and-secrets.md). The common secret-stdin limit applies before key storage.
  • list shows each registered provider, its base URL, and whether a key is set — a fast, offline read (no key is read; the status is derived from the stored keychain ref). --verify (2.5.G S11, ADR-0065 §6) additionally runs a bounded, key-redacted live probe per provider — the SAME validateProviderKey seam provider test + /doctor --deep use — and reports verified / failed — <redacted reason> / no key in the status column. A provider with no resolvable key (keychain → env both empty) is reported no key and never probed (so --verify never hangs on a keyless provider); the key is never echoed. list honors --json (ADR-0049): one key-free NDJSON record per provider — { name, baseUrl, keySet, verified, verifyDetail }, where verified is null without --verify (else true/false) and verifyDetail is a short reason — a redacted failure message, or "no key" when a probed provider has no resolvable key, else null (so a verified: null record with verifyDetail: "no key" is a probed-keyless provider, distinct from the un-probed verifyDetail: null). The --verify probes run concurrently (each timeout-bounded), so verifying N providers costs one timeout, not N.
  • add / set-key auto-register the provider row. --base-url <url> on add records a custom endpoint that is now actually used at request routing (2.5.G S9, ADR-0065 §3–4 — the earlier "dead-config" gap is closed): the resolver rebinds that provider's adapter to the custom endpoint and routes all its egress (streaming generate/stream + the models.list refresh) through the shared SSRF-validated hop (connectValidated — HTTPS-only, no embedded credentials, every resolved IP range-blocked, connect pinned to the validated IP for DNS-rebinding safety). Custom endpoints are OpenAI-compatible only this round (openai / deepseek); a --base-url on anthropic / gemini is refused with a clear message (exit 2), as is a non-HTTPS / private-loopback / credential-bearing URL, or one carrying terminal-control / bidirectional characters (fail-fast at add, so the stored value is terminal-safe on every surface). The provider-id set stays closed — a custom endpoint reuses the openai / deepseek id (ADR-0065 §6).
  • --pricing-url <url> on add overrides the seeded pricing_reference_url — the public pricing page where you find a model's price to hand-enter via relavium models pricing (2.5.G S10, ADR-0065 §1). Each known provider is seeded with its default pricing page; the add confirmation echoes it. It is a display-only pointer, never fetched (not an egress target), so — unlike --base-url — it needs no SSRF gate; it is validated as an HTTPS URL with no embedded credentials and stored normalized (control bytes percent-encoded, so it is terminal-safe). Omitting the flag on a re-add preserves a previously-set custom pointer.
  • test does a 1-token generate through @relavium/llm; --model <id> overrides the cheap default. A bad key fails cleanly (exit 2) without echoing the key.
  • Key resolution (used by run + test): OS keychain → RELAVIUM_<PROVIDER>_API_KEY env var → error. The env var is the headless/CI source; the secrets.enc encrypted-file fallback is deferred past v1.0 (keychain-and-secrets.md). Keychain writes and deletion surface a fixed operation-specific error when unavailable (locked / no Linux Secret Service). Runtime resolution may still use the documented environment source; it never creates a plaintext fallback file.

Secret stdin limits

The common reader used by provider set-key, run --secret-inputs-stdin, gate --secret-stdin and budget resume accepts at most 65,536 input bytes (64 KiB) across all chunks, including whitespace. It refuses an oversized pipe rather than truncating it. String chunks count their UTF-8 encoding; byte chunks are assembled before decoding, preserving split Unicode characters. This compatibility limit applies to the incoming pipe, before caller-specific trimming or line parsing.

The reader refuses TTY stdin before reading; redirecting stdout does not enable terminal secret entry. It preserves admitted whitespace: provider set-key trims the key, while run/gate line parsing preserves values. Empty/whitespace-only input, over-limit input and read failures produce payload-free exit-2 diagnostics. Early refusal stops reading. This byte bound does not impose a deadline on a stalled pipe.

Local MCP servers need consent

Every command that can open an MCP-bearing artifact — relavium run, relavium chat, relavium chat-resume, relavium agent run, relavium gate, relavium budget resume, and the bare-invocation Home — passes through the same gate before any local program starts (ADR-0084). An agent or workflow with a stdio MCP server declares a program on your machine; the artifact chooses it, so the decision is yours. Network transports (http, sse, websocket) need no consent — there is no local process — and a server already approved on this machine is not asked about again.

The prompt asks once per server, showing the resolved executable, each argument on its own line, the environment (a secret shows as a <secret:NAME> marker, never its value), the working directory, the artifact that declared it, and the fingerprint. It defaults to No, and Ctrl-C is a refusal. A refused server means the invocation stops before anything spawns.

Approval is recorded per machine in ~/.relavium/mcp-consent.ndjson. It covers the exact declaration — a changed executable, argument, environment value or working directory is a different program and is asked about again.

Asking requires all four of: a TTY stdout, a TTY stdin, no --json, and no CI environment. Missing any of them — a pipeline, a piped stdin, a machine-readable stream — the invocation refuses with exit 2 rather than prompting into a stream nobody reads or hanging a job on a question nobody answers. The refusal prints each unapproved server and its digest on stderr:

  fs: /usr/local/bin/npx  v1:9f2c…

The digest is a hash of the declaration, not a secret. To authorize those servers for a non-interactive invocation, review each one and pass it back:

relavium run pipeline.relavium.yaml --allow-mcp-stdio v1:9f2c… --allow-mcp-stdio v1:41ab…

--allow-mcp-stdio is repeatable, authorizes only that invocation, and writes no grant — a CI runner does not accumulate standing trust. It exists on relavium run, relavium agent run, relavium gate and relavium budget resume, including their non-interactive execution-resume paths. relavium budget resume <runId> --abort does not start MCP servers. Human-gate decisions resume workflow execution and use the same MCP consent rules; the interactive chat family answers at the prompt instead.

relavium media prune-temporaries

relavium media prune-temporaries --legacy-offline --force [--json]

Explicit offline cleanup of the two historical ownerless media-temp layouts under the canonical global home CAS. Both flags are required strict booleans; absence or a string-valued equivalent is an invocation refusal before CAS/service construction. Stop old producer binaries first: their ownerless files cannot prove quiescence. A live or uncertain new-protocol producer refuses the entire operation before any legacy deletion. An absent store reports zero and is not created. The command opens no project config, history database, keychain, provider or network service.

Human output reports removed/retained counts and fixed retention reasons. --json emits one { removed, retained, reasons } object; retained reason counts sum to retained. Neither form contains paths, native diagnostics or file bytes. Successful maintenance, including conservatively retained candidates, exits 0; missing flags/exclusive-offline refusal exits 2; an operational filesystem failure exits 1; unexpected and failed-output faults exit 9. This command never changes published CAS objects or read grants. Exact temp grammars, ownership/recovery rules and reason vocabulary belong to the physical media lifecycle contract, under ADR-0106.

Exit codes

CI relies on deterministic exit codes. The table covers ordinary run and gate outcomes. A first workflow SIGINT requests cooperative cancellation, normally exiting 1 unless a stronger observation applies. A second SIGINT or a terminating SIGHUP, SIGQUIT or SIGTERM uses 128+signo (130, 129, 131 or 143), subject to the observed health and infrastructure precedence. Home and agent run also retain their owned signal dispositions.

Code Meaning
0 Workflow completed successfully
1 Workflow failed, or a classified operational/history/storage refusal; see the surface dispositions
2 Invalid invocation (bad arguments, workflow not found, schema validation error)
3 Run paused at a human gate (CI/non-interactive mode) — resume with relavium gate
4 A chat session ended — via /exit, /cancel (or Ctrl-C in TTY mode), or an input-stream EOF — a user-initiated end of a relavium chat REPL — see chat-session.md
5 The run produced a terminal, but whether it reached the durable log is not known (ADR-0078 §5). The terminal is held in the outbox and retried on the next start
6 Ownership was refused before starting, or this host was fenced out and stopped without claiming an outcome (ADR-0079 §5, §7). Inspect current history; the successor may already have finished
7 An external effect needs attention, including a prior unresolved attempt or an admitted effect whose final receipt remained unresolved after host joining (effect-journal.md §4, §8). Do not retry: inspect the target, then resolve the row out of band
8 Required money receipts were not durably acknowledged. Do not repeat paid work: inspect relavium logs <runId> and the ledger before recovery. This is separate from terminal-outbox uncertainty
9 CLI infrastructure failed, including output delivery. Inspect existing work before repeating paid/effectful operations; see the classification and remedy contract

Wave 3 Step 8 implements runtime origin classification, fixed boundary projections, fatal coordination and actual workflow-health precedence on escaping faults. Step 8d and whole Step 8 are independently accepted on development; the surface and ownership contracts below describe that implementation.

Final required-money uncertainty takes priority (8), then effect attention (7), then the actual terminal/ownership disposition (5/6) and ordinary outcome. A combined 8 warning also directs the operator to inspect the effect target and resolve its row. Final health never replaces the actual terminal, its error code or durable history; it selects the command's disposition after joining admitted work. The fixed final-health stderr diagnostic has type: diagnostic, code, message, terminalDurability (durable, uncertain or none) and effectNeedsAttention. It reports one highest-priority warning; a combined money/effect case includes both remedies. These fields are nondurable and never added to stdout RunEvents.

Codes 5 and 6 have different remedies: 5 means a terminal exists and the CLI will retry writing it, so re-check after the next invocation; 6 means this invocation cannot claim an outcome — read relavium logs <runId> for current truth. A terminal-free fenced close uses 6 when neither final-money nor effect attention takes priority; it never promises an outbox retry.

Exit codes 7 and 8 must be surfaced to a person, never automatically re-invoked. An external effect or paid call may already have happened without its required receipt. Repeating it can duplicate the effect or charge.

A pre-start live-ownership refusal (6) can be retried after backoff. A fenced host's 6 instead requires inspecting current history before resuming: local departure does not certify a successor's present status. An invalid invocation (2) requires correcting the call.

Exit code 3 lets CI distinguish a pause-for-approval (a run:paused event — the run's aggregate suspension, a human/approval/budget gate — in non-interactive mode) from a hard failure. This is the canonical home for the gate-paused code; other docs reference it as 3.

Under --json, a pre-run fault (exit 2) writes its structured { "type": "error", … } detail to stderr while stdout stays empty (ADR-0049) — the exit code is the primary fault signal; read stderr for the detail.

Exit code 4 is the canonical chat-session-ended code: it marks a deliberate /exit (or its --json equivalent, a final session:cancelled/end event) from the relavium chat REPL, kept distinct from a successful workflow run (0) and a hard failure (1) so a wrapper script can tell "the user quit the chat" apart from either. Other docs reference it as 4.

Exit code 5 is durability-uncertain, and it is deliberately neither 0 nor 1. The run may well have COMPLETED — the outputs are in the delivered terminal — and only its durable record is missing, so reporting success would be as wrong as reporting failure. The terminal is held in the host's terminal outbox (~/.relavium/terminal-outbox.ndjson, beside history.db and deliberately not inside it) and retried by the next relavium run, relavium gate or relavium budget resume — these three commands construct a WorkflowEngine for their run/resume path. chat, agent run and the bare-invocation Home run on AgentSession and have no engine to drain with. A script seeing 5 should treat the run as done-but-unrecorded; relavium status NAMES such a run (terminalHeld in --json) so the state is visible, but it does not itself drain — draining claims a run lease, and a status read must not take ownership of a run another process may be finishing. Other docs reference it as 5.

The bare-invocation interactive Home (2.5.B, home.md) is a long-lived mode whose clean exit is 0 (Ctrl-C / Ctrl-D on an empty prompt). A chat launched from inside the Home has its own exit code 4, which the Home loop consumes — a chat ending returns to the Home, never leaked. An external signal to the Home runs teardown then exits the conventional 128+signo (130 SIGINT / 143 SIGTERM) so a pipeline still detects the interruption.

CI/CD usage

The CLI is designed to run inside pipelines. A typical pattern: install globally, provide the API key via the OS keychain or an environment variable, and run with --json for parseable output.

# illustrative CI step
- run: npm install -g relavium
- run: relavium run .relavium/code-review.relavium.yaml --input file=src/index.ts --json

A workflow whose agents declare a stdio MCP server will not spawn one in a pipeline: there is no one to ask, so it exits 2 and prints each server's digest. Review each program, then authorize it per invocation — see Local MCP servers need consent.

- run: >
    relavium run .relavium/code-review.relavium.yaml --input file=src/index.ts --json
    --allow-mcp-stdio v1:9f2c…

For a complete walkthrough (key handling, gates, artifacts, exit-code checks), see run-a-workflow-in-ci.md.

Phase 2 note

In Phase 2, relavium gains cloud-mode commands (e.g. relavium auth login via OAuth Device Flow, and switching execution to the cloud). The engine interface is identical in both modes — the CLI requires no code changes to target cloud execution. See ../portal/api-reference.md and ../../architecture/cloud-phase-2.md.

Qualified usage and cost totals

Numeric cost totals are known realized sums. Human totals qualify unverified history, pending invocations, unknown usage and missing prices; machine records expose usageCoverage and independent price information. Unknown usage at a numeric zero is never free. The /cost panel includes models with uncertainty but no realized-money row; estimate release leaves usage uncertainty intact. Resumed/reseated views seed the existing durable coverage instead of a fresh zero. Compaction notices distinguish known observed tokens from complete summary usage. See the seam, event contract, and database contract.

Proposed Wave 3 CLI compatibility

Approved design, partially implemented on development — 2026-10-17. ADR-0110 was approved by the maintainer. Step 6 implements the pure flag/error/observation and ownership substrate, descriptor-derived current Commander grammar and the compatible model projection. Its discovery DTO is available as a pure function; production JSON help, aliases, pricing spellings, output categories and consent remain Step 10. Step 7's lazy entry and installed package are independently accepted. Step 8a implements minimal storage-origin adapters and defensive runtime projections, and adds an inert native invocation foundation. Product fatal/native/terminal activation is independently accepted in Step 8d and whole Step 8 on development. The remaining Step 10 behaviour described in this proposed section is still unshipped. Earlier sections describe current behaviour unless stated here.

Diagnostic policy

Resolve one policy before runtime imports. --quiet plus --verbose is exit 2. Either explicit flag wins over the environment; absent flags use verbose only for exact RELAVIUM_DEBUG=1, otherwise normal. The older any-defined predicate is intentionally narrowed. JSON selects representation independently; CI/TTY select interactivity, never implicitly JSON.

Output category Quiet human mode JSON mode
Requested result, assistant answer, help/version, log/list/output data Preserve Preserve every contractual record
Final outcome/cost/usage/estimate/unpriced qualifications; approval, warning, denial, receipt/effect/ownership disclosure and remedy Preserve Preserve existing data/structured stderr diagnostic
Decorative banner/spinner/progress and optional informational notice Suppress at owner Never remove actual events; optional noncontractual notices may be suppressed
Deprecated alias One bounded compatibility warning, preserved One structured stderr notice, no extra stdout record
Verbose detail Quiet disables it Bounded safe structured detail, never a human stack dump

Every production write/render owner needs an explicit category. Do not globally filter IO or implicitly approve a prompt. Reasoning follows its existing content control. Verbose exposes only owned fixed classifications and bounded validated frame locations, with no stack first-line message, arbitrary cause/detail, argv, request or credential text. Unknown throws, projection/getter faults and broken sinks fall back to a fixed message; do not coerce arbitrary objects. Infrastructure and unclassifiable faults use the same fixed diagnostic at every verbosity; their message does not suggest that --verbose exposes additional detail. The bounded safe-detail policy still applies to diagnostic categories that provide owned metadata.

Frame syntax alone is not provenance: Error.stack is writable. Emit only an exact owned shipped-artifact/source-file identity from a pure static/build-generated allowlist, as its canonical relative ID plus bounded positive line/column. Drop function labels, absolute install/user paths, URL queries/fragments, eval/anonymous/sourceURL and unknown/external frames. Do not open maps/config or inspect causes to validate frames. Early failures with no owned match get fixed classification only. Bound descriptor-safe stack acquisition and parsing; hostile accessor/proxy/coercion faults produce no detail. Include arbitrary canaries in syntactically valid frame paths as negative controls.

Failure classification and remedies

These are CLI dispositions, separate from engine ErrorCode. One closed CliErrorCode/classification authority in the pure process layer owns the new cli_internal, output_unavailable, storage_unavailable and config_write_refused codes; ADR-0112 does not create a parallel error union.

Surface / origin Proposed disposition
Workflow run/gate/budget resume: actual terminal or pause Existing 0/1/3; actual RunHandle health has the precedence below
Chat/chat resume: classified failed turn / deliberate end Failed turn stays in the REPL; deliberate end is 4. No invented session 5–8 health channel
Home: clean shell end / nested chat end / terminating external signal Existing 0; consumes nested 4; actual signal 128+signo absent stronger observed disposition
Standalone agent: actual turn outcome / terminating external signal Existing 0/1; actual signal 128+signo absent stronger observed disposition
Management/meta success / known operational failure Existing 0 or classified 1; invalid invocation/schema remains 2
Typed corrupt/unreadable history or known departure-unacknowledged Existing known operational 1/remedy, unless actual workflow health overrides
Owned storage boundary: a DbOpenError with owned storage provenance, or filesystem EACCES/EPERM/ENOSPC/EROFS Fixed storage_unavailable, operational 1; correct permissions/capacity before retry. Native import/ABI failures retain infrastructure 9, including constructor-time failures wrapped as open_failed. No arbitrary cause text
Failed-output/EPIPE/write acknowledgement on any surface Fixed output_unavailable, exit 9 unless actual stronger workflow health overrides
Unexpected CLI throw, invariant, native import/ABI or wiring failure on any surface Fixed cli_internal, exit 9 unless actual stronger workflow health overrides; never chat-end 4/Home success
Recoverable unhandled background rejection Keepalive; upgrade only otherwise clean 0 to 9, preserve established nonzero outcomes
Fatal exception / rejected invocation Stop admission and enter one bounded fatal shutdown; compute disposition from actual observations

Do not remap broad CliErrorCode.internal wholesale. Inventory each constructor, catch and direct forced-exit site before integration: workflow/gate departure, agent-run catch, chat slash/export/workflows/cost/doctor/compact and rebuild catches, Home initial/submit/clear/reseat arms, persister backstop and signal/force-quit paths. Known operational refusal stays inline where supported; unexpected faults must reach the invocation coordinator even if a balanced internal turn event was emitted first. That engine event alone cannot convert an escaping CLI fault to an ordinary turn outcome. Programmer-invalid DB URI is not a normal CLI storage-open failure; preserve its API validation boundary. Operational 1 after admitted work (including session publication/join failure) does not prove absence of paid/effectful work: retain inspect-before-repeat advice before correcting the storage issue. No healthy session receipt is inferred from its numeric exit.

The Step 6 source inventory distinguishes the twelve existing literal CliError('internal', ...) constructors before runtime integration:

Existing owner Count Step 8 disposition
Acknowledged stderr writer 1 Output 9; a failed sink cannot acknowledge itself
Run departure and gate departure 2 Known operational 1, subject to actual workflow health
Media maintenance 1 Split actual storage 1, output 9 and unexpected 9 at their originating boundaries
Session export writer 1 Owned storage 1; preserve target-shape/existence refusal 2; unexpected 9
Missing MCP consent wiring 1 Infrastructure 9 before spawn
Job-control setup/disposal 2 Infrastructure 9, with attempt-all cleanup and primary/health priority
Terminal suspension invariants 4 Infrastructure 9; existing busy/no-renderer recoveries remain separate

The same inventory tracks workflow/gate/driver catches, every AgentSession/Home construction, submit and replacement catch, history/forced-exit and storage owners. The nine current CLI native-DB open invocations are four dispatch sites, three history helpers, gate resume and model pricing; the shared opener definition is not a tenth invocation. Minimal origin adapters land in Step 8 and the sole opener refactor remains last in Step 18. Best-effort catalog/overlay/outbox reads, ordinary doctor results and explicit benign engine races retain their recovery policies. Caught unknown faults and forced-success fallbacks require explicit coordinator routing; a blanket catch or broad internal-code rewrite is insufficient.

Classify native storage errno only at the actual owned I/O boundary, not by examining arbitrary provider/plugin objects or private cause trees. Recognize actual typed DbOpenError without importing DB through the lightweight entry. The lazy runtime adapter supplies its classification. DbOpenError.open_failed alone cannot distinguish storage failure from an addon/ABI load inside the native database constructor; the actual creation boundary must supply that provenance. Existing CLI open wrappers that label runtime permission/capacity failures invalid invocation/2 deliberately move to operational 1. Step 6 defines this mapping and Step 8 installs minimal boundary adapters; Step 18 centralizes them after all callers have moved.

Step 8a origin adapters

The runtime classifier queries the read-only @relavium/db storage-origin fact. A public DbOpenError('open_failed', …), a copied error, an errno-shaped object or a convincing cause does not establish storage origin. The CLI's concrete directory-creation, permission-hardening, export-write and native editor temporary-directory/private-transcript-write operations classify EACCES, EPERM, ENOSPC and EROFS at those owned native calls. Shared local opening, run/gate opening and history helpers preserve these dispositions; centralizing all openers remains Step 18.

Recognized storage failures project the fixed storage_unavailable message and operational exit 1. Unexpected native constructor/import/ABI failures and unknown throws project fixed cli_internal and exit 9. Runtime projection uses own data descriptors; reflection/accessor faults fall back to the same fixed infrastructure diagnostic without incoming message, cause or coercion. Exit classification comes from the CLI policy, not a mutable error exit field. Typed history corruption/unreadability, including the shared checkpoint suspension-corruption family, and known run/gate departure failures retain operational 1; URI/API validation, authoring and known maintenance refusals retain their existing invocation disposition.

Session export preserves target existence/shape refusal 2. Media maintenance classifies maintenance failures before rendering its result; a failed result write is output_unavailable/9, not a storage or maintenance refusal. Failed acknowledged diagnostic writes also use output_unavailable/9. These boundary changes do not yet connect every command's escaping fault or forced exit to the invocation coordinator.

A sticky observation record computes this total priority on return and throw: actual workflow health 8 > 7 > 5 > 6, then fatal/output infrastructure 9, then an actually terminating external signal 128+signo, then ordinary outcome. 5 and 6 remain the existing terminal-uncertainty versus fencing dispositions, not new session statuses. First workflow SIGINT remains cooperative run cancellation (normally 1); raw-mode chat Ctrl-C remains deliberate end 4. The recoverable background net is the separate keepalive rule in the table.

The first termination request latches admission-stop and one cleanup operation. Later observed faults may upgrade the final disposition, never duplicate cleanup, renew a deadline or downgrade 9 to a signal/session-end result. A fault first observed after synchronous process exit cannot be included retrospectively.

Exit 9 means CLI infrastructure could not complete reliably, including output delivery. It does not say the workflow failed. cli_internal and output_unavailable distinguish unexpected failure from classified broken output. After admitted paid/effectful work, do not automatically repeat it: inspect logs/ledger and external targets before recovery. It certifies neither absence of work nor receipt durability. Session surfaces expose stop/join and retained publication failures, not RunDeparture health; their 9 remedy carries the same uncertainty. If stderr fails, no diagnostic delivery is claimed; stdout is never a fallback diagnostic sink. Node may itself exit 9 for invalid runtime arguments before this entry runs; a CLI diagnostic distinguishes the handled case, not its numeric value alone (Node exit codes).

Invocation owners and fatal ordering

Step 8d activation independently accepted on development. The acceptance register records exact-source reviews, security and platform evidence. The registry exposes read-only termination and remaining-budget observations so an explicitly installed native adapter can react to autonomous owner failure. The adapter observes both real output streams and uses the same absolute three-second infrastructure budget through owner joins, registered terminal restoration and final diagnostic acknowledgement. Later observations can upgrade disposition or shorten that deadline; they cannot renew it. Fixed human/JSON diagnostic formatting does not inspect a thrown value, stack or cause, and stderr failure has no stdout diagnostic fallback.

The successor background observer keeps the process alive, emits at most five fixed failure records plus one suppression record, and upgrades only an otherwise clean exit to 9. Existing nonzero outcomes remain intact. Importing these modules installs no listeners. Product bootstrap explicitly installs the coordinator and retains its actual loader/run root before entering deferred modules. The root retires before finalization so cleanup cannot join itself. An invocation-owned signal-custody lease leaves each actual surface's existing first-Ctrl-C and suspended-terminal behaviour authoritative; the bootstrap fallback covers loading, admission, metadata and post-surface gaps. Known runtime errors are projected before returning to the pure entry. Escaping infrastructure faults are observed before joins and diagnosed only after restored-terminal ACK. Cumulative review, security and platform acceptance remain separate obligations.

One invocation-owned lightweight registry is installed before dynamic imports. Owners register before admitting work and retire only after actual settlement. Callbacks expose facts/capabilities specific to their owner, not a generic false health object:

Owner Available observations / actions
Workflow RunHandle departure/durability/terminal error; actual host joins and owned producer teardown
Chat/Home/standalone AgentSession Cancel/stop and existing whenHostSafeToClose join; retain rejection/publication failure. No invented RunDeparture, 5–8 or healthy receipt inference
Management/maintenance command Seal command admission, cancel captured input/probes and join actual raw work before releasing its database or maintenance resources. No AgentSession identity or workflow receipt health
Terminal renderer/input Stop redraw/input and SIGCONT reclaim; restore raw/paste/mouse/cursor/alternate-screen custody
Native tool/MCP Exact creation-specific registration, stop/join and the finite reap authority below

Stop every owner even if one callback fails, before awaiting one of them. Release terminal custody before the final fixed stderr diagnostic and its acknowledgement; otherwise alternate-screen restoration can erase it. Restoration control bytes use the existing terminal control sink, not diagnostic stdout. Keep host/DB close behind actual joins; fatal process termination does not grant safe store close.

Model discovery preserves the distinction between its bounded public report and actual SDK/transport completion. The management refresh and shared Home/chat catalog port transfer their original work authority through the provider seam, as does the key-validation probe. A completed or timed-out report cannot release the database or signal custody while an admitted descendant remains pending. Normal cleanup may still wait after reporting a timeout; no new deadline or safe-close acknowledgement is invented.

The fatal deadline is three seconds from the first infrastructure fault, including join, native helper/reap, restore and flush. Every callback/helper receives only the remaining common monotonic budget; no serial per-owner renewal. An already earlier bounded signal teardown can shorten it, never extend it. It is a responsive-event- loop backstop, not preemption of blocked JS/native work; native tests require an external watchdog. Normal receipt joins retain their actual protocol. A timer never acknowledges an owner. After expiry no unbounded async helper starts; finite nonawaited exit cleanup cannot certify native closure.

Step 6's registry observes a creation-owned retirement promise separately from stop/join/reap attempts. Rejected or malformed retirement retains the owner; attempt completion and budget expiry never remove it. Successful retirement may carry a fixed failure classification, releases retained owner closures and keeps only bounded sticky classifications. Registering an idle session does not call its host-safe-close barrier: the runtime adapter first seals future admission, then joins actual lifetime work. Reentrant close requests share one operation; all stops enter before any join. Terminal restoration can progress while another owner's join is pending. Failure classification consumes the same absolute fatal budget. These are pure protocol controls, not native-close or store-close proof.

A stop adapter synchronously seals its owner and makes a finite cancellation request even if an earlier owner consumed the budget. At zero remaining budget it cannot launch a new async helper; a returned promise may only observe work already started. Joins, restoration and reap helpers do not start after expiry. This attempt-all cancellation rule grants no retirement or safe-close ACK.

Session ownership implementation checkpoint

Step 8d independently accepted on development. See the acceptance register. Standalone agent run now reserves its surface and host owners before asynchronous input, consent or session construction. The original invocation owns captured stdin cancellation, late builder adoption, producer/accounting joins, MCP cleanup and the shared database tail. A rejected durability barrier, callback-reported MCP close fault or database-close throw retains cleanup uncertainty; cancellation and the common fatal deadline do not acknowledge those obligations. Classified turn failures retain exit 1, invocation refusals retain exit 2, and unexpected infrastructure failures use exit 9. This surface has no workflow health authority to invent exits 5–8.

Interactive session MCP consent uses captured prompt streams and the host cancellation signal. A late answer cannot append a grant. Prompt custody joins actual completion and acknowledged cursor delivery; a fulfilled Clack prompt is not aborted again during normal cleanup. Best-effort retention warnings retain their actual delivery tail and output-failure classification. The optional builder ownership port preserves accounting/publication failures across repeated host-safe-close checks.

One-shot agent run first consumes and closes its mandatory prompt input. Its default MCP gate therefore has no remaining stdin prompt authority, even if captured TTY metadata still says interactive. An existing persistent grant or exact --allow-mcp-stdio digest can authorize the declared server; otherwise the gate refuses with exit 2 before starting a client. An explicitly supplied host consent gate retains its own authority; the default never reopens ambient input.

Fresh, resumed, cleared and reseated standalone chats, and every actual pending, late or superseded Home chat, now retain their construction and post-build persistence/view setup independently of the UI's selected session. Equal durable session IDs do not identify an instance's cleanup. The shared database closes in the surface's final cleanup stage, after host roots and transferred producer, accounting, prompt, catalog and output work. Signal custody remains until that cleanup succeeds; a failed database close cannot remove the original signal path.

Each actual Ink mount reserves custody before render, synchronously seals key, paste, mouse and submit dispatch, and joins complete entered suspension and job-control continuations before unmount and captured-input release. Native exit and final control delivery are separate acknowledgements. The persistent chat buffer retains all actual mounts and job bindings until their positive retirement; no-Ink job-control custody covers old-host cleanup as well as rebuild. Control delivery precedes native handoff and final buffer restoration precedes summary publication. Every new final write checks the original remaining budget.

Home onboarding binds informational output and each select/password prompt to captured streams. The installed spinner's ambient-input path is excluded; a fixed verification notice supplies progress. Actual provider validation remains retained after its UX timeout or cancellation result. Every continuation rechecks admission, including the provider command's internal post-secret-read boundary before keychain and database mutation. Catalog discovery is joined before database cleanup and checks admission before publication; unexpected local clock/key/publication faults are observed before sibling discovery joins. Optional cache reads keep their existing recoverable UI policy.

These contracts are covered by Step 8d acceptance, including complete cumulative reviews, separate security and current required platform evidence. Home post-chat snapshot failures reach the actual lifetime; ordinary slash notices are suppressed after owned stop, including delayed doctor/compaction results and export fault catches. Standalone shell rejection notices and directory-completion results also recheck the live input seal before publication; an already-entered native suspension still drains completely before unmount. Live classified refusals retain their inline warning, and clearing local busy state does not authorize a new notice. Home applies its permanent exit seal to deferred directory/file results, shell results and model refresh banners before changing business state. Local busy cleanup and already-entered producer/native joins still complete after that seal. The session substrate checkpoint and chat/Home checkpoint record their historical implementation scopes; the final acceptance above closes whole Step 8.

Management ownership implementation checkpoint

Step 8d independently accepted on development. See the acceptance register. Management dispatch and the separate media-maintenance route reserve a command owner before entering work. Provider verification and both model-refresh axes retain actual operations beyond early report/failure results. Their original signal and admission checks prevent credential, catalog and output publication after stop. Captured secret input reserves close observation before iterator acquisition and joins actual native close on success, early refusal and failure, separately from iterator.return() or a cancellation/destruction request. A partially acquired iterator cannot retire the command or release its database while the consumed pipe remains live. A public closed flag does not establish close delivery; an unobserved preclosed source retains cleanup uncertainty. TTY refusal remains before consumption; non-native iterable sources retain their separate awaited-return contract. Headless plain/JSON chat likewise owns partial native readline construction and retains entered driver continuations through final callback removal and input pause. Deep-doctor probes belong to their actual chat/Home host; an ordinary failed probe remains a report, while its still-running producer remains owed.

Command database cleanup follows roots and transferred work, with independent releases entered before any join. A failed close retains uncertainty and signal custody. Short resolver/pricing reads keep synchronous close-before-next-open ordering and retain their exact close capability if release fails; classified operational pricing refusals may still degrade. This is lifecycle integration, not the later sole-opener refactor.

The original command, workflow or session surface captures a database's actual close capability before native setup, including partial acquisition through injected production openers. Returned wrappers cannot replace that capability. Release follows the owner's existing host/root/tail joins and precedes removal of its signal custody. A failed or unobservable release retains the same original finite-exit obligation. SQLite construction details have their canonical home in the database contract. Optional pricing reads may degrade a classified operational storage refusal; unknown acquisition faults and failed native cleanup remain infrastructure 9. A later successful release permits retirement without erasing an earlier infrastructure observation.

The standalone create wizard binds installed Clack prompts to captured streams and checks admission between answers and before writing. Its already-entered terminal restoration uses the original captured control capability, separately from guarded business output. No deadline certifies input, terminal or database release. Bootstrap/caught-fatal activation and management ownership are covered by the final Step 8d acceptance; earlier implementation checkpoints retain their historical scopes.

Workflow and terminal ownership

Optional terminal effect retention and media GC check the original invocation remaining budget immediately before new maintenance. At zero they leave rows and bytes for a later eligible pass. An already-entered media sweep remains joined, including every entered grace/orphan deletion and CAS-shard enumeration branch when a sibling rejects. A synchronous deletion-entry refusal stops new entries but still joins previously entered deletions. Only then may the original failure reach the best-effort caller; an aggregate rejection alone never acknowledges sibling completion. This skips no engine, receipt, input, MCP or native database-close acknowledgement and never resets the absolute termination deadline.

Step 8c independently accepted on development. The acceptance register records exact reviewed-source, causal, CI and platform evidence. Workflow run, human-gate resume and budget resume reserve their workflow and terminal owners before asynchronous preparation. The command explicitly activates the existing single fallback observer before native creation; an autonomous native fault needs no later business rejection or signal to start physical termination. Activation alone starts no timer, and ordinary retirement grants no physical-exit authority. One signal subscription covers secret input, consent, MCP discovery, engine construction, execution and actual cleanup. First SIGINT requests cooperative cancellation; a second SIGINT or TERM/HUP/QUIT uses the invocation's single finite-exit capability and unchanged absolute budget. A late engine/handle is adopted before further work and drains without acquiring new input after stop. A late consent answer cannot append a grant or enter secrets, MCP or business work.

A successfully constructed MCP client returned after stop, and the finalizers of run, human-gate resume and budget resume, share the invocation's original remaining budget. At zero they retain the unacknowledged client and database obligations without entering another asynchronous SDK close. A close entered before expiry still needs its actual acknowledgement before history can close and signal custody can be removed; neither expiry nor a cancellation request is that acknowledgement.

The driver keeps one primary reader across prompts. Actual departure health can progress while input is held; a rejected reader is never positive primary done. Publish actual money/effect/terminal health before rendering, finalization and maintenance can throw. Typed ownership-loss diagnostics retain their existing remedy. Stronger actual health wins over an opaque CLI fault; a competing infrastructure fault wins over ordinary success or invocation refusal. No timer, cancel request or iterator rejection grants permission to close history.

Native prompt and Ink acquisition uses the invocation's captured input and stdout. A partially allocating renderer or job-control constructor transfers cleanup custody before native entry. Permanent stop prevents remount, redraw and SIGCONT reclaim, then independently attempts actual prompt/input/listener release and acknowledged cursor restoration. Prompt rejection or a missing Ink handle after a failed mount is not a release ACK. A remount already entered before stop must settle and release its final native state. JSON/CI/plain output acquires no TTY job-control or cursor custody merely because stdout has a TTY flag.

Run/gate self-stop failures reach the original workflow owner before reversible listener recovery consumes them. An unexpected fault selects infrastructure 9 and seals business rendering and reattachment; an already classified self-stop refusal retains its recoverable outcome. Ownerless bindings keep their existing recovery behaviour. A failed self-signal supplies no native-release or cursor delivery acknowledgement.

MCP manager close failures can arrive through its callback even when close() fulfills. They retain the MCP/workflow obligation and keep DB close behind actual cleanup. An opaque database-close throw also supplies no release ACK; already durable workflow results remain intact while the CLI records its lifetime fault. Secondary warnings remain fixed and value-free. Known operational errors retain their classifier; unknown cleanup failures do not silently become success.

The first owned infrastructure fault starts the common three-second backstop immediately, including when cleanup holds the rejection from the outer catch. Retained terminal cleanup starts that bound even when the original operation has a known operational classification. The terminal retains its own release ACK; this does not mark an already-safe workflow as unacknowledged. A positively native-free constructor refusal keeps its ordinary classification. The native bootstrap takes the original observer, deadline and physical-exit latch before runtime entry, invalidating any queued fallback completion. Actual terminal restoration and the sole fixed final diagnostic require native ACK within the unchanged budget. A background-only selected exit 9 does not become a fatal shutdown merely because a completed surface returns it. Whole Step 8 is independently accepted with its cumulative review, security and actual-platform qualifications.

Native crash teardown scope

Step 8b independently accepted on development. Actual process, OS-helper, editor and stdio MCP creation paths now receive the original invocation's native controller through run/resume, standalone chat, one-shot agent and Home builders, including pending/replaced builds. Construction is inert. Each actual creation registers before asynchronous facility verification and rechecks admission before native entry. Actual target and cleanup-helper close remain independent retirement obligations; command rejection and successful cancellation requests supply neither acknowledgement. Standalone clear/reseat also preserve the already-captured MCP consent gate.

One removable invocation-bound exit callback iterates the existing registry's active native capabilities. There is no completed PID/group history or independent product MCP reaper collection. Workflow routing follows the ownership section above; Step 8d implements the corresponding session/management lifetimes and activates the product coordinator. Source fixtures retain their separately attributed explicit foundation tests; those fixtures alone do not certify product activation. Workflow/session/terminal retirement and safe DB close have separate acknowledgement obligations, covered by the accepted 8c/8d integration above.

Creation owner / platform Permitted finite teardown
POSIX run_command Its explicitly detached owned process group; retain the group token through bounded final reap even if the leader closes
POSIX SDK-backed MCP Its owned live PID only; SDK spawn is not detached. Never cast PID to negative PGID or kill the CLI's group; descendants are unclaimed
Other POSIX child Only the creation-specific PID/group authority actually registered; no ambient scan
Windows supported child/MCP Verified absolute taskkill with fixed /PID <owned-live-root> /T /F, shell=false; no cached closed root or image/remote/wildcard selectors

Resolve taskkill from the trusted OS installation root, canonicalize/verify that fixed regular executable before tool/MCP spawn; never use workspace/PATH or model-supplied roots. Missing/invalid facility refuses Windows subprocess admission, with a fixed remedy. The OS root is trusted launching environment, not protection against an already hostile launching user/process. The helper is itself owned and its runtime fits the same remaining deadline; a successful request is not target close ACK. Its ordinary operational request limit is fixed at entry, independently of a later fatal deadline. Owner-scoped budget notifications only shorten/cancel that already-entered helper; expiry without a numeric deadline is zero remaining time. They cannot create another helper or renew either bound. Helper failure retains the fixed infrastructure diagnosis and permits only finite cancellation of the already-owned root/helper while their actual close remains owed. If the root finished cooperatively before a nonzero taskkill result, that closed/now-absent root does not create a new failure. Finite process-exit cleanup launches no helper or normal asynchronous shutdown, including when its own cancellation attempt fails.

Use actual owner close observations. MCP's latched live PID list alone is not a ChildProcess handle or race-free liveness proof: a narrow owner adapter must expose its known-close/lifetime state, gate fallback on current ownership and perform a best-effort immediate liveness check. Preserve PID-reuse uncertainty. A closed Windows root cannot be used for /T; an explicitly created POSIX group may still need final reap after leader close, but bounded PGID retention is not reuse-proof. Escaped/reparented/detached descendants and PID/PGID races are uncontained; do not claim universal no-orphan behavior. Existing M/N isolation remains its own work.

Required native controls distinguish POSIX group versus MCP PID, leader/root exit, actual close versus request, competing faults/signals, hostile Windows PATH, invalid facility, timeout and both failed sinks. Full Windows job-object/native containment would require a separate approved decision; none is added here.

Discovery DTO and one descriptor authority

The new CLI-only DTO is { type: 'command_manifest', version: 1, path: string[], globalOptions: OptionDescriptor[], commands: CommandDescriptor[] }. Root uses an empty path; command help resolves aliases then returns the canonical selected subtree including its executable/group root and descendants. Authored descriptor order is stable. No internal executor loader paths or functions are serialized.

Descriptor Required contract
Command Canonical path: string[], description, existing effect/label metadata, availability: 'implemented' | 'not_implemented', aliases, ordered positionals/options, allowExcessArguments: boolean, and optional stable executorId/existing modeScope
Alias Explicit token path, deprecated: boolean; canonical and flat paths register separately
Positional Name/binding key, scalar value type, required, variadic, description
Option Binding key, canonical flag spelling(s), deprecated spellings, value arity (none/required/optional), mandatory-presence required: boolean (distinct from value arity), placeholder/type where applicable, repetition (single/repeatable/variadic), negation, description, existing default where applicable and conflict bindings
Global option Same grammar plus position-independent scope; include help/version and extraction-owned flags

A container can also execute, as chat/gate/models do. Optional executor binding does not force a leaf/group exclusive union. One enriched descriptor registry derives Commander grammar, this DTO, help and legacy COMMAND_MANIFEST; the legacy strict alias-free model projection and stable IDs remain compatible. Finite lazy executor bindings are checked both ways against descriptors. No second authored list of commands/arguments is permitted.

Canonical paths are chat resume, chat list, chat export; flat aliases keep working. Canonical pricing flags are --input-price and --output-price, with old --input/--output deprecated. Preserve separate source-spelling presence until conflict checks; both spellings for one quantity refuse even when equal. Only then normalize to stable executor/model binding keys input and output and existing decimal parsing. Model DTO calls are not shell alias usage. Workflow input, reserved standalone-agent input and gate payload meanings stay command-scoped. All product-generated remedies/examples use canonical pricing/chat spellings; retained aliases do not cause the product to recommend its own deprecated syntax.

Explicit --help --json (including -h) prints one DTO and exits 0, without services. Resolve known command/subcommand/alias grammar and -- terminator; help may omit required values/positionals but cannot excuse unknown grammar. --version --json and bare --json remain human meta output. Unknown commands/options/excess args/value errors use fixed stderr hints, no arbitrary token echo or Commander suggestions. JSON last-resort errors are structured too.

Key removal and durable log preservation

provider remove-key prompts only when stdin and stdout are TTY, JSON is off and CI is off. Prompt defaults no; --force skips it. Headless without force refuses with exit 2 before DB/keychain construction, and direct command-core calls enforce consent as well. Decline/Esc/prompt cancellation exits 0 without deletion; actual process signals retain their contract. Check abort/admission again after awaited consent immediately before synchronous delete. Preserve keychain-first/reference-clear order and truthful absent-key result, without claiming a cross-store transaction. Never read the credential for confirmation.

G15 is refuted as a current shipping defect. Preserve human durable-event replay and actual JSON stored sequence/gaps. The engine/store/log regression must prove live fragments are emitted live but absent from durable history; no token coalescer or new persistence is authorized by that historical finding.

Step 8d passive consumers and editor preparation

Surface-owned session view/printer/JSON callbacks observe an unexpected failure through their original invocation before passive bus isolation can consume it. The callback seals before cancellation reentry; the core bus keeps its listener isolation. Headless business writes use the captured output origin, while entered terminal restoration retains its independent original capabilities. Actual producer, accounting, native close and output delivery remain independently owed.

The native editor temp helper observes unexpected creation/write failures before partial removal can hold. Its existing classified EditorOutcome compatibility contract remains: missing editor and known operational refusals stay inline; unexpected native faults select infrastructure 9 through the original owner. Actual spawn ENOENT/EACCES remain inline only at concrete spawn origins. Rejected admission after ordinary stop is separate from a native spawn fault. Partial-removal failure reports cleanup uncertainty while retaining the exit net and original primary failure; observation does not acknowledge cleanup. Ownerless compatibility callers retain the existing outcome contract.

A completed export's auxiliary provenance-row write remains best effort. Timestamp construction is outside that recovery scope, so an unexpected clock/format fault cannot become successful provenance recovery or cause a second scaffold write. Checkpoint loading likewise preserves original causes: typed inconsistent history remains operational 1 with inspection advice; an unexpected driver/wiring failure selects infrastructure 9 before original-owner cleanup.

Budget, unpriced-model, withheld-effort and passive-error notification callbacks are bound at shared session-runtime creation to that actual host's owner. A failure in notification output or view publication is observed before advisory recovery; it stops further business admission without disabling captured terminal restoration. Successful passive-error reporting keeps its existing recoverable policy: the reported error argument alone does not become an infrastructure fault. Ownerless embeddings keep their callback compatibility. Missing local-MCP consent wiring uses the existing fixed infrastructure-9 classification before any native start; ordinary consent refusal and known cancellation retain their distinct outcomes.

Cooperative session finalization and passive notifications

Normal owned JSON chat finalization (EOF, /exit, /cancel and first cooperative SIGINT) publishes the real synchronous session:cancelled while its original subscriber/output capability is still admitted, then seals further work in all cases. It observes the original persister refusal before waiting for producer tails. This terminal is not a provider/accounting/native cleanup acknowledgement: those actual joins still precede writer and database release. Fatal registry stopping seals admission before cancellation; cooperative finalization does not reopen an already-stopped callback or permit postfatal business output. A terminal-output failure retains the existing output_unavailable disposition and its private cause.

Workflow effort, unpriced-model/modality and legacy-media-hold notifications bind to the original command lifetime before entering passive core recovery. The notification's own sink or formatter failure is observed before that recovery, seals reentrant delivery, and selects existing infrastructure disposition 9 unless actual workflow health 8 > 7 > 5 > 6 takes priority. Healthy warnings and successful reporting of an existing passive error retain their established behaviour. This does not turn the reported argument into a new fatal fault or replace captured terminal-restoration capabilities. Actual producer, receipt, input and native cleanup joins remain required.

Deferred-checkpoint privacy notices use the original invocation's captured stderr and retain the actual delivery operation independently of the opened database. The successful open still transfers its connection to the caller; notice failure neither closes that connection nor repeats maintenance. A synchronous write fault seals invocation admission before the opener returns, and an asynchronous fault is observed when its delivery rejects. Both retain output_unavailable and exit 9, subject to the same actual workflow-health priority. Database closure, stop requests and deadline expiry never acknowledge a pending notice delivery. Ownerless library calls retain their existing best-effort behaviour. A custom opener delegating to the default factories forwards the explicit notice context, alongside its existing native-close handoff.

Resumed chat and Home effect disclosures bind publication, render acknowledgement and fallback stderr delivery to the original session owner before advisory recovery. Failed publication retains the audit rows and selects output_unavailable; an entered delivery remains owned until its actual settlement. Run/gate and one-shot retention warnings use their original command or session owner in the same way. A maintenance refusal with successful warning delivery remains best effort, and stopped one-shot cleanup still sweeps only its committed rows. Neither database closure nor an outer recovered helper promise acknowledges a pending notification. Ownerless library calls retain their existing best-effort behaviour.

Home controller notifications observe their own unexpected listener failures through the original Home invocation before palette or other advisory recovery. Bare-Home /doctor retains its complete entered report operation through formatting and final publication; exiting Home does not acknowledge a still-pending report. Unexpected formatter, report or listener faults select infrastructure 9 with private original causes, while known diagnostic-check refusals remain an inline report. An obsolete report still cannot overwrite a newer prompt or session. Ownerless controller embeddings retain their existing compatibility behaviour.

Compaction disclosures bind their complete returned publication or render-flush operation to the original host before core starts waiting for acknowledgement. Cancelling compaction may release its interactive wait and unused admission; the entered delivery still precedes host retirement and shared database closure. A late publication or flush fault reaches that original owner even after the compaction result is consumed. Unexpected faults select infrastructure 9, and native sink refusal retains output_unavailable. An operation abort permits a subsequent turn but cannot restart the cancelled summary request. Full owner stop refuses notices that have not entered. Fresh, resumed, cleared, reseated and Home hosts share this boundary; absent callbacks and ownerless embeddings retain their existing behaviour, and one-shot execution gains no notice.

Home onboarding observes an unexpected raw-input release or reclaim failure through the original invocation before reversible job-control recovery consumes it. The original private cause selects infrastructure 9 and stops further business entry; a classified operational refusal retains its existing inline behaviour. Failed reversible mode changes supply no final terminal-release acknowledgement. The original prompt and full cursor delivery must still settle before terminal and shared database release. Ownerless embeddings retain their existing best-effort recovery, and the finite process-exit restoration net grants no cleanup ACK.