Use the —agent flag to get structured NDJSON output from Kane CLI: designed for AI coding agents like Claude Code, Codex CLI, and Gemini CLI.
Agent Mode outputs structured NDJSON instead of the interactive terminal UI. It’s how AI coding agents (Claude Code, Codex CLI, Gemini CLI) consume Kane CLI results: parse events programmatically, extract the final result, and present it to the user.
kane-cli run "Verify the checkout flow completes successfully" \ --url https://myapp.com \ --agent \ --headless
When --agent is active:
The interactive TUI is fully suppressed (no boot screen, no menus)
stdout carries only NDJSON, one JSON object per line, and the progress UI goes to stderr
When stdin is not a TTY, Kane CLI turns off ask_user, so the run never stops to wait for input (see below)
Kane CLI also switches to NDJSON on its own when stdin is not a TTY, for example in CI. Pass --agent anyway, so the output format does not depend on how the shell is set up.
Add --headless wherever there is no display, such as CI runners, containers, and SSH sessions, to avoid display server errors. On a desktop, a coding agent can leave the browser visible so you can watch the run.
With --agent, Kane CLI writes one JSON object per line (NDJSON) to stdout. Every line carries v, the stream contract version (currently 1), and ts, the time the line was written. Here is a shortened kane-cli run stream. ... marks lines and values left out, and the run_end line shows only some of its fields:
{"type":"stream_start","cli_version":"0.9.0","surface":"run","pid":16664,"v":1,"ts":"2026-10-15T08:47:26.889Z"}...{"type":"bifurcation","flows":["Navigate to https://example.com then verify the page heading says Example Domain"],"count":1,"v":1,"ts":"2026-10-15T08:47:39.435Z"}{"step":2,"status":"running","remark":"Step 1","v":1,"ts":"2026-10-15T08:47:39.957Z"}{"step":2,"status":"done","remark":"navigate: Navigate to https://example.com","v":1,"ts":"2026-10-15T08:47:39.958Z"}...{"step":4,"status":"done","remark":"assert: the page heading says Example Domain","v":1,"ts":"2026-10-15T08:47:54.260Z"}{"type":"run_end","status":"passed","summary":"...","reason":"Objective completed","duration":20.5,"credits_consumed":11.9,"test_url":"https://test-manager.lambdatest.com/...","session_dir":"...","v":1,"ts":"2026-10-15T08:48:10.211Z"}
Rules for parsing the stream:
The first line is stream_start. Its surface is run, testmd, or testrun, and cli_version names the Kane CLI release that wrote the stream.
On a run stream, step lines have no type field. A step prints a running line when it starts and a done or failed line when it ends, and the stream has no run_start, step_start, step_event, or step_end events. A testmd run stream is different: every line is typed, and each test_md_step_start and test_md_step_end pair wraps typed run_start, step_start, step_event, step_end, and run_end events for that step.
Ignore fields and event types you do not recognize. New ones can appear in any release without a change to v. Skip any line that is not JSON.
For a run that started, the last line is run_end. A run refused before it starts can exit with code 2 and no run_end line, so check the exit code first.
Kane CLI also writes the same lines to events.ndjson in the session folder while the run is in progress.
The tables below describe a kane-cli run stream. A testmd run stream is typed on every line and ends with test_md_done, and a testrun run stream uses its own testrun_* events. See Command-Specific Completion.
Step index. It can run one ahead of the step a person would count, because a bifurcation takes the first slot. To report steps taken, count the done and failed lines
status
string
running when the step starts, then done or failed when it ends
Kane CLI checks every {{name}} in the objective before the run starts. If a name has no value, Kane CLI prints a warning and the run continues. The name is typed literally as written, unless a step sets its value first. With --agent, the warning is a single typed event, sent before the first progress event:
{"type":"warning","code":"unresolved_variables","message":"2 variable(s) have no value — typed as written unless a step sets them first","suggested_file":".testmuai/variables/variables.json","variables":[{"name":"checkout_url","reason":"not_declared","used_by":[{"file":"objective","step":1}]},{"name":"login_password","reason":"value_missing","file":".testmuai/variables/variables.json","used_by":[{"file":"objective","step":1}]}]}
reason is value_missing, not_declared, or not_a_dataset_column. The warning does not change the exit code. See Unresolved variables for what each reason means and which names are never checked.
run_end is the last line of a kane-cli run that started, and it carries the complete result. A run refused before it starts, for example with no start URL, no sign-in, or no Chrome, can exit with code 2 and no run_end line. Check the exit code first, then read run_end.
{ "type": "run_end", "status": "passed", "summary": "Searched for laptop and added first result to cart", "one_liner": "Searched for laptop on Amazon and added to cart", "reason": "Objective completed", "duration": 45.2, "credits_consumed": 11.9, "final_state": { "price": "$29.99", "product_name": "Wireless Headphones" }, "test_url": "https://test-manager.lambdatest.com/projects/123/test-cases/456", "session_dir": "~/.testmuai/kaneai/sessions/a1b2c3d4-e5f6-7890-abcd-ef1234567890", "v": 1, "ts": "2026-10-15T10:31:30.412Z"}
run_end can carry more fields than this example, such as context and result_code. Ignore the fields you do not need.
A short label for why the run stopped, such as "Objective completed"
duration
Run time in seconds
credits_consumed
Credits the run used, as a decimal number, when reported. Round it for display. Older releases called this field credits
final_state
Values saved with “store … as” in the objective
test_url
Link to the test case in Test Manager. Present when the upload succeeded
session_dir
The session folder. The sealed evidence pack is in its evidence/ subfolder
run_dir
Legacy. The folder it names is no longer created: run logs and screenshots are in the evidence pack
result_code
Optional machine classification of the result. See Error Codes. With --bug-detection, a confirmed product bug arrives as result_code740 with a verdict object
The run went ahead and did not pass, or the upload failed. Read the failing step’s remark and the evidence pack to tell a product bug from a test problem
2
The run did not start, and no credits were used: sign-in, configuration, no start URL, Chrome, network, or an unknown subcommand. There may be no run_end line
3
The run was canceled or hit --timeout
testmd run, testrun run, generate, and the assurance commands have their own exit codes. See CLI Reference: Exit Codes.
Every run seals an evidence pack: one .evidence file, a standard zip, that holds the test definition, a result summary, per-step screenshots (plus annotated copies that highlight the element the agent acted on), per-step browser console and network logs, run logs, and, when a step fails, a failure record for that step. The pack holds no video.
Command
Where the sealed pack is
kane-cli run
In the evidence/ subfolder of session_dir. It is also copied to .testmuai/evidence/ in your working directory when you name the run with --name
kane-cli testmd run
Copied to .testmuai/evidence/ on every run
kane-cli testrun run
Created directly in .testmuai/evidence/
run_end has no field for the pack’s path. After the run, Kane CLI prints a hint line on stderr, not on stdout:
evidence: view locally with `kane-cli evidence serve <path-to-pack>`
Save stdout to a file, read the exit code, then pick the run_end line by its type. Selecting by type keeps the script working when a run never started and has no run_end line.
kane-cli run "Go to https://example.com, store the page heading as 'heading', then verify it says Example Domain" \ --agent --headless > run.ndjsoncode=$?echo "exit code: $code" # 2: the run did not start, and there may be no run_end line# The full resultjq -cR 'fromjson? | select(.type == "run_end")' run.ndjson# Just the statusjq -rR 'fromjson? | select(.type == "run_end") | .status' run.ndjson# A value saved with "store ... as 'heading'"jq -rR 'fromjson? | select(.type == "run_end") | .final_state.heading' run.ndjson
fromjson? skips any line that is not JSON. When the run started, run_end is also the last line, so tail -n 1 run.ndjson returns it too.
Reading the last run_end event works for one-shot run only. Other commands end with their own completion event:
testmd run: read test_md_done for overall_status, duration_s, session_id, and the optional share_url. The stream can also contain run_end events; these finish individual steps, not the file.
Local testrun run suites: end with testrun_done. Suites dispatched to the remote grid then also emit remote_done; keep its status, exit, and sessions_path fields.
generate: ends with generate_done.
Assurance commands: conversational agent streams end with done. The review and read commands have their own completion contracts.
Always check the process exit code too: an early refusal, an invalid plan, or a dry run can exit without the normal completion event.Progress events are meant for live display. When you count completions, count only done and failed. Step indices can repeat, so keep track of which child agent and execution each event belongs to.testrun run prints its NDJSON stream when stdin is not a TTY, or when you pass --agent. See Batch runs (testrun).
If an objective requires user input mid-run, Kane CLI fires ask_user:
{"type": "ask_user", "question": "Which item should I select?", "options": ["Small", "Medium", "Large"]}
ask_user is turned off automatically when stdin is not a TTY. In CI/CD and AI agent contexts, stdin is never a TTY, so ask_user never fires. Write objectives that don’t require interactive prompts.
If stdin IS a TTY, respond by writing JSON to stdin: