Skip to main content
The conversational assurance commands (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 agent when something can read the pause and answer it, such as an AI agent or a person on the next shift.
  • Use ci when a pipeline must fail rather than guess on anything high-risk.
  • Use override when 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 ci run reaches a decision that needs a person, it stores the plan and exits 2. The work is not lost: walk the stored plan interactively, or apply it in agent mode.
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 hold holds everything new for your review instead of committing it. It works only headless, and ci mode refuses the flag entirely with exit 2.
  • --trust auto is 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 done is 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 ingested events, and every stdout line parses as JSON. A consumer that skips non-JSON lines still works.
  • next[] carries follow-up commands. Pauses, gate refusals, and done can carry a next list 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):
The pause event carries everything you need to decide: the question, why it matters, the options, and the recommendation. Answer in plain words, with no question ids and no option numbers. The resumed run sends its usual run_start, corpus, and source_start (with "resumed": true) events, and then the stream continues:
The agent matches your statement to its own pending questions. A statement that answers nothing pending is treated as steering (for example, “also cover the coupon path”). If questions are still unanswered, the run pauses again with refreshed questions. There are two structured alternatives to --message:
  • Answer by id. Use --answer <question-id>=<option number | free text>. You can repeat it, and it works with --resume --mode agent only. Each answer that lands is echoed as a panel_resolved event 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_deferred on 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 with INGEST_UNAUTHORIZED_REF.
Between the pause and the resume, you can inspect everything without interfering with the session:
Abandoned sessions expire after 24 hours. 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_archive facts (exit 0, clearly summarized). To destroy them, pass --allow-archive and --because "<reason>". Under --mode ci, archives are refused whatever the flags (exit 2).
There is no auto-approve. These paths land your decisions faster; they never make decisions for you.

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> on kane-cli context sync or kane-cli context pull. You can repeat the flag. Use only an answer that 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 sync then pulls and pushes in the same run, while kane-cli context pull only pulls.
  • The two sides are always local and the location’s name. mine describes the local change and theirs describes the location’s.
  • Never guess an answer. keep-theirs writes 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 with SYNC_REBASE_PENDING, so read the decisions again before answering.
  • Between runs, kane-cli context sync status --json lists the open decisions again when the location can be reached, and --show <n> prints one saved record in full without touching the location.
A real exchange with one decision (the backup path is shortened):
Writes are blocked while a rebase is open. Every write command (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_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.
  • 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).
Seeing 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 --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 (renders without opening a browser)
  • cover --json and cover 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 --json and kane-cli context sync doctor --json

Headless maintain

  • maintain reconcile --from <file> --source-id <id> --plan is a safe preview. It records the source change, stages every proposed row into a stored plan, and touches nothing else. It exits 0. 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, and ci stops the moment a person’s judgement is needed (the plan is stored; exit 2).
  • 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 --mode refuses with exit 2. This is by design.

A CI shape that works

Author and batch the resulting tests with the same CI patterns as any other test. See testrun and the CI/CD recipes.

Next steps