context extract, design tests, and maintain reconcile — are interactive by default. This page is the contract for running them headless: from CI, from a script, or from an AI agent driving kane-cli.
The ask policy: --mode
On a terminal, extract and design open a chat. Headless is an explicit opt-in — a bare non-TTY invocation exits 2 and mutates nothing:
--mode decides both what happens when the agent has a question, and what the command writes to stdout:
Rule of thumb:
agent when something can read the pause and answer (an AI agent, a human on the next shift); ci when a pipeline must fail rather than guess on anything high-risk; override when you accept the recommended defaults wholesale and want one unattended pass.
The same matrix drives maintain reconcile, with two reconcile-specific rules: no headless mode ever archives anything — ARCHIVE decisions wait for an interactive session — and a ci-mode run that hits a decision needing a human stores the plan and exits 2 (the work isn’t lost; walk the stored plan interactively or apply it in agent mode).
context ingest follows the matrix with one extra rule (0.7.1): it lands the files and then runs the extraction under the given mode — except --mode ci, or piped stdin without any --mode, which lands only (exit 0, with a stderr guidance line naming the next command). Two extraction dials also matter headless: --trust hold holds everything new for review instead of committing it (headless-only; ci refuses the flag entirely with exit 2), and --trust auto is the default everywhere.
Exit codes
Consistent across extract, design, and the maintain commands that embed them:The NDJSON stream (--mode agent)
With --mode agent, stdout speaks a versioned NDJSON vocabulary — envelope {"type": "<name>", "v": 1, "verb": "extract"|"design", ...}, one object per line. The vocabulary is open: new event types may appear, so tolerate unknown types.
(0.7.2) The stream is strict: stdout carries only NDJSON — the first line is an event, done is the last — and stderr stays silent (crash traces excepted). No version banner, no receipts, no progress lines; everything user-relevant arrives as a typed event. On 0.7.1, prose diagnostics could ride stderr and a merged ingest printed receipt lines before the stream — a consumer that skips non-JSON prefix lines works on both releases.
The
done guarantee: every --mode agent invocation ends its stream with exactly one done event — including refusals and graceful interrupts. (0.7.2) The stream starts at the first line: a merged ingest’s landing failures (a bad path, an unsupported or oversized file, a refused URL, a misused --mode or --as) also arrive as error + done — codes MODE_USAGE, AS_SINGLE_SOURCE, UNSUPPORTED_URL, INGEST_FAILED — and sources already landed stay safe, with the run saying so. (On 0.7.1 these landing failures ended with a prose error line and exit 1/2 before any NDJSON began — no stream, no done.) The one exception is operator force: a second Ctrl+C can hard-kill the process (exit 130) without a done. Any other stream that ends without done should be treated as a crash. One more parsing note: the agent may also repair a draft mid-turn on its own — that surfaces only as agent_activity lines (labels like validation failed, refining the draft); treat activity labels as display text, never script against them.
Two more parsing rules:
- Receipts and prefixes. (0.7.2) Nothing precedes the stream — the landing receipts are the
ingestedevents, and every stdout line parses as JSON. On 0.7.1, a merged ingest printed a few prose receipt lines per file before the NDJSON began, so strict per-lineJSON.parseconsumers had to skip non-JSON prefix lines. That skip is harmless on 0.7.2 — a version-tolerant consumer can keep it. next[]carries follow-up commands (0.7.1). Pauses, gate refusals, anddonecan carry anextlist of ready-to-run follow-ups. The common shape is objects ({cmd, why, title}); a few refusal sites emit plain strings — handle both, and treat every entry as a command to offer, not to auto-run.
Reconcile’s stream
maintain reconcile --mode agent speaks the same envelope with verb: "reconcile" and its own event set. (0.7.2) The stream opens with a minimal run_start (it carries session only — no trace), and the re-extract child rides the same stream: its extract-vocabulary events (source_start, agent_activity, plan, commit, …) interleave between the reconcile_* events, all stamped verb: "reconcile" — one command, one stream. The engine itself is unchanged: ADD/MODIFY auto-apply, ARCHIVE pauses, --apply resumes.
Validation failures (bad inputs, unknown source, the fork guard) ride the stream as
error + done with exit 2 — never stderr alone.
The pause → answer → resume loop
This is the heart of driving assurance from an agent. A real exchange (events abridged, payloads shortened):run_start, corpus, and source_start (with "resumed": true) events, the stream continues:
--message (0.7.1):
-
Answer by id —
--answer <question-id>=<option number | free text>(repeatable,--resume --mode agentonly). Each landed answer echoes as apanel_resolvedevent before the run continues: -
Land a source instead of answering —
--resume <sid> --with-source <path|url>lands the file or URL first and sets the pending batch aside (ask_deferredon the stream); the agent reads the new source and re-asks only what it didn’t settle. Only refs you provide can land — anything else refuses withINGEST_UNAUTHORIZED_REF.
kane-cli context sessions clean garbage-collects them.
Headless review
Trust promotion deliberately has no auto-approve — but it does have a non-interactive path. Prepare verdicts as JSON and land them atomically:resolution is one of approved | edited | rejected | skipped | supersede (optional reason, edit, supersede_target). One unresolvable ref fails the whole file (exit 2, nothing committed). With --json, each landed verdict echoes as one NDJSON row.
Two 0.7.1 additions:
- Structured verdict flags — for scripted single decisions without a file:
--approve <refs...>lands approvals;--skip <refs...>and--defer <refs...>record nothing and leave the items queued. Mutually exclusive with--verdicts. - Archives require explicit consent. A headless rejection no longer destroys anything: rejected entries are held as non-destructive
pending_archivefacts (exit0, loudly summarized). Destroying them takes--allow-archiveplus--because "<reason>"— and under--mode ci, archives are refused under any flag (exit2).
The sync verbs on the stream
kane-cli context sync, kane-cli context push, kane-cli context pull, kane-cli context clone and the subcommands kane-cli context sync add, kane-cli context sync list, kane-cli context sync remove, kane-cli context sync status and kane-cli context sync doctor all take --mode agent and speak the same strict envelope with verb: "sync": stdout is NDJSON only, stderr stays empty, and done is last. None of them calls the agent or spends credits. kane-cli context sync setup is the one command that needs a terminal, and under --mode agent it answers error{code: TTY_REQUIRED} naming kane-cli context sync add and kane-cli context clone as the alternatives. What these commands do is in Sharing the context graph with your team.
Exit codes keep their meanings, with one addition: exit
3 is also a person has to decide something, which covers a kane-cli context push or kane-cli context pull refused because you are behind or diverged (the remedy names the command), and a rebase that stopped on open decisions (answer them with --answer, or on a terminal). Exit 2 is a precondition (a location that cannot be reached, a rebase still open, missing keys), and exit 1 a record that cannot be used. A refusal says what stopped, not that nothing happened.
Coverage on the stream (0.7.1)
cover --mode agent and cover gaps --mode agent speak the same envelope (verb: "cover" / "gaps"): the full --json payload arrives as one coverage (or gaps) event — (0.8.2) cover gaps <uc-id> emits the document closed over that use-case — and done closes the stream carrying the worklist’s ready-to-paste commands in next[]. --mode ci speaks the identical stream. Any refusal is an error event + done with exit 2.
When releases don’t match
Sessions bind to the kane-cli release that created them, and the refusals are loud with the remedy in the message:PAIR_MISMATCH at startup (exit 2 — reinstall so the installed pieces match), BINDING_MISMATCH on resume (exit 2 — the session belongs to another release: start fresh, committed work is kept, or resume on the release that created it), and a mid-run “this version of kane-cli is no longer supported — update kane-cli and retry” (a message-only runtime failure, exit 1). Hitting BINDING_MISMATCH on a paused session right after upgrading is expected, not corruption.
Machine-readable reads
These read commands have structured forms:context list --json and context sessions --json (one JSON object per line), context explain --json, context view --json (the full computed graph payload), context view --no-open --out graph.html (render without a browser), cover --json, and cover gaps --json (the nested coverage document — see Coverage).
Headless maintain
maintain reconcile --from <file> --source-id <id> --plan— safe preview: records the source change, stages every proposed row into a stored plan, touches nothing else. Exit0; when the source actually changed, the plan path is the last stdout line (an unchanged source is a no-op that stores nothing).maintain reconcile … --mode override(or--mode ci) — unattended application: ADD and MODIFY rows apply, archiving never happens headless, andcifail-closes the moment human judgement is needed (the plan is stored; exit2).- Re-running the same reconcile command is idempotent — it resumes a pending plan, reports an applied one, and recomputes a superseded one (details).
- Bare headless runs without an explicit
--moderefuse with exit2— by design.
A CI shape that works
Next steps
- The assurance overview — where each command sits.
- Building the context graph · Designing tests · Maintaining the suite.