context ingest/context extract, design tests, and maintain reconcile) are interactive by default. This page describes how to run them headless, that is, without a person at a terminal: from CI, from a script, or from an AI agent that drives kane-cli. The coverage reads (cover, cover gaps) use the same stream.
The ask policy: --mode
In a terminal, extract and design open a chat. Running headless is something you opt into explicitly. A bare run without a terminal (no TTY) exits 2 and changes nothing:
--mode controls two things: what happens when the agent has a question, and what the command writes to stdout.
How to choose:
- Use
agentwhen something can read the pause and answer it, such as an AI agent or a person on the next shift. - Use
ciwhen a pipeline must fail rather than guess on anything high-risk. - Use
overridewhen you accept all the recommended defaults and want one unattended pass.
maintain reconcile follows the same table, with two rules of its own:
- No headless mode ever archives anything. ARCHIVE decisions wait for an interactive session.
- When a
cirun reaches a decision that needs a person, it stores the plan and exits2. The work is not lost: walk the stored plan interactively, or apply it inagentmode.
context ingest also follows the table, with one extra rule. It lands the files and then runs the extraction under the given mode. The exception is --mode ci, or piped stdin with no --mode: then it only lands the files (exit 0) and prints a guidance line on stderr naming the next command.
Two extraction settings also matter when you run headless:
--trust holdholds everything new for your review instead of committing it. It works only headless, andcimode refuses the flag entirely with exit2.--trust autois the default everywhere.
Exit codes
Extract, design, and the maintain commands that run them all use the same exit codes:
The sync commands use the same three meanings, and exit
3 also means a person has to decide something. That covers a kane-cli context push or kane-cli context pull refused because you are behind or diverged (the remedy names the command to run), and a rebase that stopped on open decisions (answer them with --answer, or in a terminal). For these commands, exit 2 means a precondition failed (a location that cannot be reached, a rebase still open, missing keys), and exit 1 means a record that cannot be used. A refusal tells you what stopped; it does not mean nothing happened. See the sync verbs on the stream.
The NDJSON stream (--mode agent)
With --mode agent, stdout carries a versioned NDJSON vocabulary, one object per line, in the envelope {"type": "<name>", "v": 1, "verb": "extract"|"design", ...}. New event types can appear, so your consumer should ignore types it does not recognise.
The stream is strict:
- stdout carries only NDJSON. The first line is an event and
doneis the last. - stderr stays silent, except for crash traces.
- There is no version banner, no receipt line, and no progress line. Everything you need arrives as a typed event.
The
done guarantee. Every --mode agent run that starts the stream ends it with exactly one done event, including refusals and graceful interrupts. The stream starts at the first line, so 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). Sources that already landed stay safe, and the run says so. The one exception is operator force: a second Ctrl+C can kill the process (exit 130) without a done.
One command is shaped differently on purpose. kane-cli context review --mode agent keeps its --json output: its verdict and drift listings are one JSON object per line, with no done at the end (an empty listing prints nothing and exits 0). Its early refusals (no store, an open rebase, or a verdict queue that needs a terminal: TTY_REQUIRED) are typed events followed by done; its later refusals exit without done, so read its exit code. Treat any other stream that ends without done as a crash.
The agent can also repair a draft mid-turn on its own. That shows up only as agent_activity lines (with labels like validation failed or refining the draft). Activity labels are display text: never script against them.
Two more parsing rules:
- Every line is JSON. Nothing comes before the stream: the landing receipts are the
ingestedevents, and every stdout line parses as JSON. A consumer that skips non-JSON lines still works. next[]carries follow-up commands. Pauses, gate refusals, anddonecan carry anextlist of follow-up commands that are ready to run. Most entries are objects ({cmd, why, title}), but a few refusals send plain strings, so handle both. Treat every entry as a command to offer, not one to run automatically.
Reconcile’s stream
maintain reconcile --mode agent uses the same envelope with verb: "reconcile" and its own set of events. The stream opens with a minimal run_start that carries session only (no trace). The re-extract that reconcile runs shares the same stream: its extract events (source_start, agent_activity, plan, commit, …) appear between the reconcile_* events, all stamped verb: "reconcile". One command gives one stream. ADD and MODIFY rows apply automatically, ARCHIVE rows pause, and --apply resumes.
Validation failures (bad inputs, an unknown source, the fork guard) arrive on the stream as
error + done with exit 2, never on stderr alone.
Evolve’s stream
maintain evolve --mode agent uses the same envelope with verb: "evolve". Requires Kane CLI 0.8.21 or later.
The first call states the blast radius (the scenario and test pairs the re-design would affect) and pauses on a question. The next call answers it with --answer evolve:<use-case>=1 (proceed) or --answer evolve:<use-case>=2 (decline); the words proceed and decline also work. See Maintaining the suite for what evolve does.
A
--from-stale batch runs every use-case, whatever happened to the earlier ones. Its exit code is the first design-run code that is neither 0 nor 3; otherwise 3 if any run paused; otherwise 0.
The pause → answer → resume loop
This loop is the core of driving assurance from an agent. Here is a real exchange (events abridged, payloads shortened):run_start, corpus, and source_start (with "resumed": true) events, and then the stream continues:
--message:
-
Answer by id. Use
--answer <question-id>=<option number | free text>. You can repeat it, and it works with--resume --mode agentonly. Each answer that lands is echoed 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 asks again only what it did not settle. Only refs that you provide can land; anything else refuses withINGEST_UNAUTHORIZED_REF.
kane-cli context sessions clean removes them.
Headless review
Promoting items to trusted has no auto-approve on purpose, but it does have a non-interactive path. Prepare your verdicts as JSON and land them in one atomic step:resolution is one of approved | edited | rejected | skipped | supersede (with optional reason, edit, supersede_target). If one ref cannot be resolved, the whole file fails (exit 2, nothing committed). With --json, each verdict that lands is echoed as one NDJSON row.
Two more options:
- Structured verdict flags for single scripted decisions without a file:
--approve <refs...>lands approvals;--skip <refs...>and--defer <refs...>record nothing and leave the items in the queue. These flags cannot be combined with--verdicts. - Archives need explicit consent. A headless rejection destroys nothing: rejected entries are held as non-destructive
pending_archivefacts (exit0, clearly summarized). To destroy them, pass--allow-archiveand--because "<reason>". Under--mode ci, archives are refused whatever the flags (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 use 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. Under --mode agent it answers error{code: TTY_REQUIRED} and names kane-cli context sync add and kane-cli context clone as the alternatives. What these commands do is described in Sharing the context graph with your team.
The
done guarantee holds here too: every one of these commands ends its stream with exactly one done, refusals included. Ignore event types you do not recognise. A refusal tells you what stopped; it does not mean nothing happened. kane-cli context sync finishes its pull before its push can be refused, and a push can land on the server before its confirmation is lost (SYNC_PUBLICATION_UNKNOWN). The events before the sync_error show what completed. The exit codes are in Exit codes.
Answering decisions from an agent. When a rebase meets a disagreement, it sends every open sync_rebase_decision and then stops with done{paused, 3}.
- Answer a decision by id with
--answer <decision_id>=<answer>onkane-cli context syncorkane-cli context pull. You can repeat the flag. Use only ananswerthat the event listed. - Each run applies the answers it was given and stops again while decisions remain. The last answer lets the rebase finish:
kane-cli context syncthen pulls and pushes in the same run, whilekane-cli context pullonly pulls. - The two sides are always
localand the location’s name.minedescribes the local change andtheirsdescribes the location’s. - Never guess an answer.
keep-theirswrites nothing and is always offered, but it is still a choice: the local change stays in the backup, and any later saved record built on it is looked at again. - An unknown id, or an answer the decision does not offer, returns
error{USAGE}and writes nothing. If your answer was checked against decisions that have since moved, the command refuses withSYNC_REBASE_PENDING, so read the decisions again before answering. - Between runs,
kane-cli context sync status --jsonlists the open decisions again when the location can be reached, and--show <n>prints one saved record in full without touching the location.
kane-cli context extract, kane-cli design tests, kane-cli maintain reconcile, kane-cli context ingest, kane-cli context review, kane-cli context name, kane-cli context retire, kane-cli context revert and kane-cli context push) refuses with sync_error{code: SYNC_REBASE_PENDING} on its own stream and exit 2. A test run is the exception: it does not refuse. The results it would record are set aside and land on the first run after the rebase closes, and the run logs a warning. To unblock the store, finish the rebase (kane-cli context sync or kane-cli context pull, with answers) or close it (kane-cli context sync doctor --abort). Never delete files under .context/.
kane-cli context name, kane-cli context retire and kane-cli context revert also take --mode agent. On success the stream ends with done, preceded by a warning{message} event for each note the command has (kane-cli context revert sends one per note). Every refusal is typed.
In CI: clone once, pull before the run, push after it, and stop the job on exit 3. The maintained recipe is in CI/CD recipes: A shared context store in CI.
Coverage on the stream
cover --mode agent and cover gaps --mode agent use the same envelope (verb: "cover" / "gaps"). The full --json payload arrives as one coverage (or gaps) event; cover gaps <uc-id> sends the document scoped to that one use-case. done closes the stream and carries the worklist’s ready-to-paste commands in next[]. --mode ci produces the identical stream. Any refusal is an error event plus done, with exit 2.
When releases don’t match
A session is tied to the kane-cli release that created it. Mismatches refuse loudly, with the remedy in the message:PAIR_MISMATCHat startup (exit2): reinstall so the installed pieces match.BINDING_MISMATCHon resume (exit2): the session belongs to another release. Start fresh (committed work is kept), or resume on the release that created it.- A mid-run “this version of kane-cli is no longer supported — update kane-cli and retry” (a runtime failure with a message only, exit
1).
BINDING_MISMATCH on a paused session right after you upgrade is expected. It does not mean anything is corrupted.
Machine-readable reads
These read commands have structured forms:context list --jsonandcontext sessions --json(one JSON object per line)context explain --jsoncontext view --json(the full computed graph payload)context view --no-open --out graph.html(renders without opening a browser)cover --jsonandcover gaps --json(the nested coverage document; see Coverage)kane-cli context sync status --json(the relation, the open rebase id and its decisions),kane-cli context sync list --jsonandkane-cli context sync doctor --json
Headless maintain
maintain reconcile --from <file> --source-id <id> --planis a safe preview. It records the source change, stages every proposed row into a stored plan, and touches nothing else. It exits0. When the source actually changed, the plan path is the last stdout line; an unchanged source stores nothing.maintain reconcile … --mode override(or--mode ci) applies the plan unattended. ADD and MODIFY rows apply, archiving never happens headless, andcistops the moment a person’s 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).
- A bare headless run without an explicit
--moderefuses with exit2. This is by design.
A CI shape that works
Next steps
- The assurance overview: where each command fits.
- Building the context graph · Designing tests · Maintaining the suite.