Last updated: 2026-10-18
- Status: Reference (partial — surface defined, exact flags to be finalized as the CLI is built)
- Surface: CLI (
relavium) - Scope: Phase 1, local-first. Same
@relavium/coreengine as every other surface. - Related: home.md, chat-session.md, ../vscode/extension-api.md, ../desktop/routes-and-screens.md, ../contracts/workflow-yaml-spec.md, ../contracts/agent-yaml-spec.md, ../contracts/sse-event-schema.md, ../shared-core/built-in-tools.md, ../../tutorials/cli/run-a-workflow-in-ci.md, ../../runbooks/add-a-provider-key.md
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.
- 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
tsupto 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 ondevelopment; 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.jsfor argument parsing,ink(React for terminals) for the interactive TUI,@clack/promptsfor 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 archivedkeytar(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.tsThe 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.)
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
sequenceNumberorder. The line is the stable envelope (type/runId/timestamp/sequenceNumberper the schema); there is no wrapper, stream header, or version line. Every event the run emits appears, unfiltered. - The terminal
run:completedevent is the final result line — it already carriesoutputs+totalTokensUsed+totalCostMicrocents+durationMs, so there is no separate summary line. Arun:failedterminal carrieserror+partialOutputs; failed and cancelled terminals also carry the engine's currentcumulativeCostMicrocents. 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'srun:failed/node:failed) and exits2; 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 (
--helpemitting the command/flag surface as JSON, so an agent can discover the CLI without scraping prose) and a per-commandeffectannotation (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 adestructiveentry is owned by ADR-0057 (2.5.E).
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.
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). |
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
}
effectis a forward-looking annotation:readnever mutates,writecreates/modifies,destructiveirreversibly removes (provider.remove-keyandmedia.prune-temporaries). It is marked for agent discoverability now; approval enforcement of adestructiveentry is owned by ADR-0057 (workstream 2.5.E), not here.modeScopelists 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 withomit = all.- A
manifest ↔ commanderdrift guard (a unit test) asserts every realcommandercommand has an entry with the same description (command + each option), socommander, theexecuteCommandtable, and--help --jsonstay byte-consistent.
| Example entry | effect |
|---|---|
run |
write |
list |
read |
provider.set-key |
write |
provider.remove-key |
destructive |
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.
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 (exit2). Every explicit--inputfor a declaredsecretis refused, includingname=: 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-stdinreads declaredsecretinputs once from non-TTY stdin; TTY stdin is refused even if stdout is redirected. Supply onename=valueline 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-2diagnostics. Names such as__proto__,constructorandtoStringwork 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
3for a freshrelavium gatecall; 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. --jsonswitches to NDJSON RunEvent output.Ctrl-C(SIGINT) requests a cooperative cancel; the run drains torun:cancelledand exits non-zero (1). This is unchanged when the workflow declaresmcp_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 theRELAVIUM_<PROVIDER>_API_KEYto 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: afallback_chainprovider's key (read only if the chain fails over to it) and a$ref-resolved external agent's key (until$refresolution lands, 2.M–2.Q) are conditional and instead surface mid-run as a run failure (exit1). --allow-mcp-stdio <digest>(repeatable) authorizes a local MCP program for this invocation only, when there is no one to prompt. A workflow declaring astdioMCP server that has not been approved on this machine exits2before anything spawns — see Local MCP servers need consent.- On a
human_gatenode 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 withrelavium gate. The emittedhuman_gate:pausedevent carries therunId+gateIdneeded for the resume (relavium gate <runId> --gate <gateId>); with--jsonthey 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>, andrelavium gate listalso surface pendinggateIds, 2.I.)
Implementation status (as of workstream 2.G).
runis wired to the@relavium/coreengine: path/id resolution,--inputcoercion, the full lifecycle event stream, exit codes0/1/2/3, SIGINT→cancel, and the stable--jsonNDJSON machine contract (stdout = pure RunEvent stream, diagnostics → stderr; see above) are live. The interactiveinkTUI (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-colorit keeps the TUI but suppresses ANSI color; it falls back to the plain line renderer when no TTY is attached orCI=true, and to NDJSON under--json(the three renderers are oneonEventseam over one bus). Provider keys resolve from the OS keychain →RELAVIUM_<PROVIDER>_API_KEYenv var → error (2.C; manage them withrelavium provider), and runs persist to durable history (2.H). The interactive human-gate prompt + out-of-bandrelavium gateresume are live (2.G): on a TTY ahuman_gatenode renders a@clack/promptscard inline (approve / reject + comment / input) and the run continues; under--json/CI/no-TTY there is no prompt and the run exits3, resumable later byrelavium 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.
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.
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.
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.yamlinto 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, soexport <id>stays unambiguous): a same-kind slug collision is exit2unless--forceoverwrites it; a cross-kind collision (the id already names the other kind) is exit2always — rename one, since--forcewould leave both files in place.export <id> [--out <path>] [--force]resolves<id>across both catalogs (an id naming neither is exit2; an id that is both a workflow and an agent is exit2, "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);--outoverrides it. Under--jsonit emits a single{ id, kind, path }record, wherepathis cwd-relative (the same shapeimport --jsonemits — 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.
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
typethis binary does not know — is skipped, and a boundednote:naming the affectedseqnumbers goes to stderr (in both modes, so--json's stdout stays a pureRunEventstream). 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
typethis binary knows whose body does not parse — fails the command (exit1), naming the run and the row'sseq. It is never silently dropped. Other runs stay listable:logs/status/gate listisolate a damaged run so one bad row cannot blank the whole listing.
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.
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 exit0(an empty catalog is not a fault, likerelavium list). Human output is one line per model (<modelId> <provider> ctx=<n> [<source>]). -
relavium models refreshre-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.--providersand--catalogaddress 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/skippedand the others still refresh. A per-provider failure is therefore not a command failure (exit0with the report). An explicit--providersrefresh with zero providers connected (no key at all) is a clean exit2naming how to add a key, because nothing could be fetched.The catalog half needs no key at all — which is what makes
--catalogthe 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(defaultfalse) governs whether a barerelavium modelsrefreshes it first; an explicitmodels refreshfetches 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 (--inputprompt,--outputcompletion,--cachedcache-read); media prices are per BILLED UNIT —--imageper image,--audioand--videoper 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/--outputare given together or not at all, and a media-only invocation is legitimate: it is how you satisfy astrict_cost_caprefusal 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 stated0is believed and recorded as free, which for the media columns is distinct from unpriced. The row is written assource='user'and a livemodels refreshnever 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, sobudget.max_cost_microcents/[chat].max_cost_microcentsdegraded to allow for it; once user-priced, the cap is enforced (pre-egress and realized) onrun, arunresumed viarelavium gate,chat/chat-resume(incl. a/clearrebuild, re-read fresh), the Home chat, and one-shotagent run.--clearretires the override; the model falls back to the catalog price, or to unpriced if the catalog has none. Guards (each a clean exit2, nothing written): an unregistered provider is refused (register it first withrelavium 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/--outputpair, is refused;--clearbeside 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
listModelsseam) and is never logged, persisted (the cache holds no key), or placed in the report /--jsonpayload / any error message. A failing provider surfaces only the seam's already-redacted message (or a genericrefresh failed), never a raw cause.models pricingwrites 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 }(nullfor an absent optional;source∈static | live | user;lastRefreshedAtis 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 }, wherestatus∈refreshed | skipped-no-key | skipped-unsupported | failed, the three counts are the model ids added / refreshed-in-place / soft-deactivated (nullunlessstatusisrefreshed), anderroris a short, secret-free reason (nullunlessfailed). And, when the catalog axis ran, exactly one catalog record:{ source, status, models, added, reason? }—sourceis always the literal'catalog';status∈refreshed | failed;modelsis the total the refreshed catalog describes andaddedthe count the shipped snapshot did not carry (both0on a failure — the snapshot still answers);reasonis present only onfailedand 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;sourceis alwaysuser). A field is omitted when that flag was not given — the columns were preserved, not written, and reporting a0would claim a price nobody set.overriddenCatalogPriceisnullwhen the catalog does not price the model.
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.)
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/--inputis 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 (exit2). --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 agate_type=inputgate 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--jsonstream, and argv itself leaks intops/ shell history / CI logs — exactly the exposurerelavium 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_KEYresolution path. --gate <gateId>selects which pending gate to resolve. The resume contract isengine.resume(runId, gateId, decision)—gateIdis mandatory on the resume path (it is carried on thehuman_gate:pausedevent; see sse-event-schema.md andresume_runin ipc-contract.md).--gateis 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 (exit2) listing the pendinggateIds.- Read the pending
runId+gateIdfrom the run's own output: thehuman_gate:pausedevent line under--json, or thepaused at gate <gateId> (<type>)line the plain/TUI renderer prints.relavium gate list,relavium status, andrelavium logs <runId>(2.I) also surface them out-of-band. --secret-stdinre-supplies the run'ssecret-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 onename=valueline on stdin; blank lines are ignored, and anything else is an invalid invocation (exit2). 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 tops, shell history and CI logs — the same rulerelavium provider set-keyfollows. 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 opaque401.) --secret-stdinmakes the invocation non-interactive. Reading the credential drains stdin, so a resumed run that reaches a second human gate cannot prompt: it exits3and is resolvable by anotherrelavium gate, exactly as a--jsonor 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
secretinput 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.
- Values never travel through argv, which is why this is a boolean flag and not
- Idempotent. A doubled decision — the run already finished, or the named gate was already resolved — is a clean exit-
0no-op, never a double-advance (it leans on the engine's checkpoint/gate-state idempotency). An unknownrunIdis exit2. 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-0loop should pin--gate <gateId>to avoid resolving later gates unattended.
Implementation status (2.G).
relavium gateruns in a fresh process from the originalrun: it reloads the run's frozenWorkflowDefinition+ inputs from the durable history snapshot (2.H), reconstructs the paused checkpoint from the persisted event log, and callsengine.resumeFromCheckpointover the same store — then drives the resumed run to its terminal (exit0complete /1failed /3paused again at a later gate). The recordeddecidedByis the constantcli(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 separaterelavium budget resumesurface. A run that declares asecret-typed input is resumed by re-supplying it on stdin with--secret-stdin(above); without the flagrelavium gatestill fails closed (exit2), 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. Therelavium gate listmulti-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.
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-amountaccepts decimal digits representing a non-negative safe integer in microcents, including0; signs, fractions, exponents and overflow are invalid invocations (exit2). 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,
--gateis 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. Humanrelavium statusand 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 listandrelavium gatecontinue to cover ordinary gates only. - A legacy gate with no frozen allowance, an unpriced quote or an unrepresentable amount offers
--abortonly on this surface. There is no input payload, comment or generic--approveflag. - 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. --abortrejects the selected budget gate, dispatches no agent and fails the run withbudget_exceeded(exit1). It calls no credential factory and creates no MCP client. A doubled resolved budget gate or terminal-run decision is a clean exit-0no-op before resource construction. For a nonterminal run, naming a resolved ordinary human gate is an invalid invocation (exit2), not a budget no-op.--secret-stdinfollows 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 exit1, 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
5with the delivered terminal retained in the outbox. Nonterminal ownership uncertainty refuses with exit6; 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 aggregaterun:pausedis durably acknowledged, retaining the command's database and cancel handler through that barrier. It then returns exit3with the gate pending. Once cooperative cancellation is requested, an already queued pause cannot trigger that exit: the command retains its resources through the acknowledgedrun:cancelledterminal and returns exit1. 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 exit3only 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 fixedcleanup_pendingdiagnostic to stderr and continues waiting. A failed cleanup acknowledgement emitscleanup_failedand retains the owner rather than treating failure as completion. JSON diagnostics are nondurable objects withtype,codeandmessage; 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 throughbudget 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.
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 unknownrunIdexits2). Budget-cap pauses (budget:paused) are excluded — those are the separaterelavium budget resumesurface (ADR-0028). - It rests on the same persisted-event reconstruction the
gateresume 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--jsoneach pending gate is one NDJSON record —{ runId, gateId, nodeId, gateType, message, expiresAt? }(see Read-command--jsonoutput).
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.
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.
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.yamlpath or a.relavium/-discoverable agent id (resolved by the same strict parserrelavium chat --agentuses). An unknown agent is an invalid invocation (exit2). - The prompt is read from stdin (the
echo … | relavium agent runidiom); an empty stdin is an invalid invocation (exit2). --input k=vis reserved — currently rejected (exit2): a session does not yet interpolate{{ctx.*}}into the agent's prompt (the engine passessystem_promptverbatim), 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 exits2.--jsonemits theSessionEventNDJSON stream on stdout (the same shapechat --jsonproduces); otherwise the assistant reply streams in human form.--allow-mcp-stdio <digest>(repeatable) authorizes a local MCP program for this invocation only.agent runis 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 unapprovedstdioMCP server exits2before anything spawns. See Local MCP servers need consent.--allow-fs-rootuses 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.dbto 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. Ahistory.dbthat cannot be opened fails the invocation. - Ordinary exits reflect the turn's outcome:
0on success,1on a classified turn error; an invocation fault is2. Unexpected infrastructure or output failure uses9. It is never4(the interactive REPL's session-ended code). A termination signal retains128+signo(130on 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 exits5–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.
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 providerset-keyreads the key from stdin, never a CLI argument (argv leaks intops, 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.listshows 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 SAMEvalidateProviderKeyseamprovider test+/doctor --deepuse — and reportsverified/failed — <redacted reason>/no keyin the status column. A provider with no resolvable key (keychain → env both empty) is reportedno keyand never probed (so--verifynever hangs on a keyless provider); the key is never echoed.listhonors--json(ADR-0049): one key-free NDJSON record per provider —{ name, baseUrl, keySet, verified, verifyDetail }, whereverifiedisnullwithout--verify(elsetrue/false) andverifyDetailis a short reason — a redacted failure message, or"no key"when a probed provider has no resolvable key, elsenull(so averified: nullrecord withverifyDetail: "no key"is a probed-keyless provider, distinct from the un-probedverifyDetail: null). The--verifyprobes run concurrently (each timeout-bounded), so verifying N providers costs one timeout, not N.add/set-keyauto-register the provider row.--base-url <url>onaddrecords 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 (streaminggenerate/stream+ themodels.listrefresh) 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-urlonanthropic/geminiis refused with a clear message (exit2), as is a non-HTTPS / private-loopback / credential-bearing URL, or one carrying terminal-control / bidirectional characters (fail-fast atadd, so the stored value is terminal-safe on every surface). The provider-id set stays closed — a custom endpoint reuses theopenai/deepseekid (ADR-0065 §6).--pricing-url <url>onaddoverrides the seededpricing_reference_url— the public pricing page where you find a model's price to hand-enter viarelavium models pricing(2.5.G S10, ADR-0065 §1). Each known provider is seeded with its default pricing page; theaddconfirmation 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-addpreserves a previously-set custom pointer.testdoes a 1-tokengeneratethrough@relavium/llm;--model <id>overrides the cheap default. A bad key fails cleanly (exit2) without echoing the key.- Key resolution (used by
run+test): OS keychain →RELAVIUM_<PROVIDER>_API_KEYenv var → error. The env var is the headless/CI source; thesecrets.encencrypted-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.
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.
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 --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.
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 combined8warning 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 hastype: diagnostic,code,message,terminalDurability(durable,uncertainornone) andeffectNeedsAttention. 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
5and6have different remedies:5means a terminal exists and the CLI will retry writing it, so re-check after the next invocation;6means this invocation cannot claim an outcome — readrelavium logs <runId>for current truth. A terminal-free fenced close uses6when neither final-money nor effect attention takes priority; it never promises an outbox retry.Exit codes
7and8must 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's6instead 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
3lets CI distinguish a pause-for-approval (arun:pausedevent — 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 as3.Under
--json, a pre-run fault (exit2) 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
4is the canonical chat-session-ended code: it marks a deliberate/exit(or its--jsonequivalent, a finalsession:cancelled/end event) from therelavium chatREPL, 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 as4.Exit code
5is durability-uncertain, and it is deliberately neither0nor1. 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, besidehistory.dband deliberately not inside it) and retried by the nextrelavium run,relavium gateorrelavium budget resume— these three commands construct aWorkflowEnginefor their run/resume path.chat,agent runand the bare-invocation Home run onAgentSessionand have no engine to drain with. A script seeing5should treat the run as done-but-unrecorded;relavium statusNAMES such a run (terminalHeldin--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 as5.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 code4, 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 conventional128+signo(130SIGINT /143SIGTERM) so a pipeline still detects the interruption.
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 --jsonA 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.
In Phase 2,
relaviumgains cloud-mode commands (e.g.relavium auth loginvia 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.
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.
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.
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.
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.
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).
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.
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.
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.
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.
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.
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.
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.
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.
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.