bonsai -p "<prompt>" runs one non-interactive coding-agent turn and exits.
The TUI and headless adapters share the same permission engine, run/session
budgets, snapshot writer, cancellation token, recovery workspace, provider
failure guidance, and completion-report classifier — the differences are
explicit and listed below.
bonsai -p "summarize the repo"
bonsai --print "run cargo test and report failures" --output-format text
bonsai -p "inspect src/main.rs" --output-format json
bonsai -p "run the smoke check" --output-format stream-json
echo "summarize stdin" | bonsai -p
bonsai -c -p "continue the latest session"
Flags (both --flag value and --flag=value forms):
| Flag | Meaning |
|---|---|
-p, --print ["<text>" \| -] |
headless mode; -/no text/stdin-pipe reads the prompt from stdin; --print=<text> for prompts starting with - |
-c, --continue [id] |
resume the latest (or a specific) session |
--output-format text\|json\|stream-json |
default text |
--model <id>, --effort <level> |
one-run model/effort override, saved defaults untouched |
--autonomy <level> / --yolo |
raise autonomy (default ask; BONSAI_AUTONOMY also works) |
--max-turns <n> |
cap agent tool-call iterations |
--max-generation-seconds <s> |
cap each provider attempt (overrides BONSAI_MAX_GENERATION_SECONDS) |
--max-output-chars <n> |
cap streamed reasoning+output per attempt (overrides BONSAI_MAX_STREAMED_CHARS) |
--max-tool-seconds <s> |
cap each tool execution; a stalled call is cancelled without killing the run |
--append-system-prompt <text> |
trusted operator instructions appended before generated project context |
--timeout <s> |
cancel the whole run with exit 124 |
--isolation auto\|worktree\|off |
Git recovery boundary; auto (default) isolates mutating runs at balanced+ |
Other subcommands share the binary: bonsai eval (Eval),
bonsai recovery … (Recovery), bonsai doctor [--json]
[--online] (Troubleshooting),
bonsai completions bash|zsh|fish, and bonsai model-catalog check <path>.
Interactive-only flags: --reduced-motion, --screen-reader.
| Code | Meaning |
|---|---|
0 |
completed successfully |
1 |
agent/provider/tool/runtime failure, or a budget_exhausted result |
2 |
CLI usage error |
3 |
auth/config error |
124 |
--timeout |
130 / 143 |
interrupted by Ctrl+C / SIGTERM |
BONSAI_PROVIDER selects the provider for a new run; unset, bonsai uses the
persisted current provider, then falls back to opencode. On -c resume
the session pins its stored provider/model/reasoning and ignores
BONSAI_PROVIDER (--model still overrides). Per-provider env vars
(keys/models/base URLs — full list in
Providers) override saved settings;
web-search env vars are read at startup too.
Every JSON object and stream event carries schema_version: 1; the
compatibility contract governs changes. Human-readable
text and TUI layout are not machine-readable surfaces.
text — assistant output streams to stdout; reasoning (prefixed
[reasoning]), progress, tool activity, errors, the compact completion
report, and Resume: bonsai -c <id> go to stderr.
json — one final object on stdout:
{
"schema_version": 1,
"status": "completed | failed | budget_exhausted | interrupted | …",
"output": "…",
"provider": "anthropic", "model": "…", "session_id": 42,
"usage": { "prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0,
"cost_micros": 0, "input_cache": { "read_tokens": 0, "creation_tokens": 0,
"total_input_tokens": 0, "hit_rate_percent": 0 } },
"persistence_duration_ms": 0,
"budget_exhaustion": { "kind": "run_time", "limit_seconds": 300 },
"verification": { "…": "…" },
"completion_report": { "…": "…" }
}
budget_exhaustion appears only on status: "budget_exhausted" and is a
typed object (kind ∈ max_turns, session_turns, session_time,
run_time, generation_time, provider_output, session_output,
session_cost, provider_generation, provider_rate_limit,
provider_quota, with limit/used fields per kind).
stream-json — newline-delimited events, each versioned, each with a
type:
| Event | Payload |
|---|---|
assistant_delta, reasoning_delta |
{text} |
status |
{text} (progress/thinking) |
error |
{text} |
attempt_discarded |
marker: a failed attempt’s streamed output was retracted |
tool_started |
{id, name, arguments} |
tool_output |
{id, output} |
tool_finished |
{id, result, status, diff?} |
context |
{used_tokens, budget_tokens, last/session prompt+completion tokens, session_cost_micros, last_turn_cost_micros} |
final |
the full json-mode object, flattened |
Consumers must tolerate new optional fields, enum values, and event types within schema 1.
Derived from bonsai’s ledgers, not the model’s recollection: structured
changed_files (path, status, intent, ±lines), verification evidence,
self-review disposition, authorization counts (allowed / denied /
user-approved / policy bypasses), token/cost usage with session-budget
state, and caveats — appended for unresolved tool failures, missing or
failed verification after file changes, unfixed review findings, denied
authorizations, or an unenforceable cost budget. A nominally-complete run
with an unresolved tool failure is reclassified as failed.
The surface contract is explicit (src/runtime.rs):
| Axis | TUI | Headless |
|---|---|---|
| Prompts | interactive | denied immediately — permission asks fail with guidance toward --autonomy/rules; the question tool fails instead of blocking; sandbox escapes are always denied |
| Repository map | built in the background | built eagerly before the run |
| Background subagent wake | parking + wake loop | disabled — an unresolved wait state is an error |
| Volatile context | refreshed each turn | frozen for the one-shot run |
| Sandbox preferences | persisted interactive choices restored | read from config/env only |
| Session binding | created after startup | session id exists before runtime assembly |
| Workspace trust | can prompt | keeps restricted posture until an interactive session trusted the workspace |
Mutating headless runs execute in a detached recovery
worktree by default (autonomy balanced+); the source
checkout is untouched during the run and a recovery id prints at the end.
An automatic mutating run outside Git refuses to start unless
--isolation off opts out.
CI example:
BONSAI_PROVIDER=anthropic ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" BONSAI_AUTONOMY=balanced \
bonsai -p "run the test suite and summarize the result" \
--max-turns 20 --max-generation-seconds 300 --max-output-chars 120000 \
--max-tool-seconds 300 --timeout 900 --output-format json
bonsai -p /test and bonsai -p /build expand to the bounded
verification workflows; stale, failed,
interrupted, or incomplete verification exits non-zero.
| Concern | Location |
|---|---|
| CLI parser | src/cli.rs |
| Headless driver | src/headless/mod.rs |
| Output sink & schemas | src/headless/sink.rs |
| Surface capability contract | src/runtime.rs |
| Completion report | src/completion_report.rs |