Skip to content
qqthe agent runtime
DocsHeadless: qq ask, qq run, qq serve

Headless: qq ask, qq run, qq serve

Run agents from scripts and CI with qq ask, qq run, and qq serve; exit codes and JSONL output.

Three ways to use QQ without the TUI. All three share the runtime, tools, approval policy, and session store with it.

Terminal window
qq ask "Explain what src/parser.rs does in three sentences"
qq ask --model openai/gpt-5.4-mini "Reply with pong"

Streams one model response to stdout. No tools, no session, nothing persisted. Use it to check a credential or for quick questions from a script. Exit 0 on success, 1 otherwise.

Terminal window
qq run --approval auto "Add a --dry-run flag to the CLI and cover it with a test"

Runs one task to completion in the current directory (or --workspace PATH) through a durable session. Progress goes to stderr, the final answer to stdout.

--approvalMeaning
read-only (default)reads only; every edit, shell, MCP, and network call is denied and reported to the model
autoedits inside the workspace, safe shell (allow verdict or a grant), MCP and granted hosts run; prompt-verdict shell is denied; forbidden refused
fulleverything except forbidden shell shapes

Under read-only, when at least one held call was denied, a text-mode run ends with held calls were denied under --approval read-only; rerun with --approval auto to allow workspace edits on stderr (after the answer, before the resume hint). The exit status is unchanged; JSONL output carries no hint because each denied call is already in its tool_call_finished record.

Between auto and full, grant exactly what the task needs:

Terminal window
qq run --approval auto \
--allow-shell "cargo test" --allow-shell "cargo fmt --all" \
--allow-tool write_file \
--allow-host crates.io \
"…"

Grants use the same shapes as the TUI’s approve-for-session (Permissions); --allow-shell "cargo test" covers cargo test -p x and never cargo test | sh.

FlagEffectExit
--timeout-seconds Ncancel after N seconds of wall clock3
--max-turns Ncancel when model turn N+1 would start3
--max-cost-usd Vcancel when the estimated cost passes V; refused up front if the model has no pricing3
CodeStatusMeaning
0completedfinal answer produced (and satisfied --output-schema if given)
1task_failedthe agent reported failure, the run failed, or the answer never satisfied the schema
2invalid_configurationQQ refused to start: config, model, credential, pricing, or flag error
3timed_out / budget_exhausteda limit was reached
4harness_failureQQ itself failed (store, provider protocol, internal), or another qq process owns the session store
5needs_inputthe agent asked a question and nobody was there; the question is in the event stream
130interruptedCtrl-C
Terminal window
qq run --format jsonl --approval auto "…" > run.jsonl
qq run --trace run.jsonl "…" # text on the terminal, JSONL to the file too

Each line is one JSON object with a type:

typeWhenContents
trialonce, firstQQ version and revision, protocol_version, workspace/session/run ids, model, profile, approval, limits, correlation labels
eventevery protocol eventthe same envelope the TUI consumes: prompts, model text, tool calls and results, approvals, usage
outcomeonce, laststatus, exit_code, token usage, estimated_cost_usd_nanos, final_output when a schema was given

The shapes are versioned with PROTOCOL_VERSION and pinned by golden fixtures under crates/qq-protocol/tests/fixtures/headless/; a consumer should read trial.protocol_version first. Full contract: ../design/headless-contract.md.

Terminal window
qq run --output-schema answer.schema.json --output-repair-turns 2 "…"

The final answer must be one JSON document valid against the schema (a bounded subset: ≤ 64 KiB, no $ref). QQ gives the model up to N extra turns to repair an invalid answer; the outcome’s final_output carries either the parsed value or the validation errors. Valid JSON is not a correct answer; verify it.

Terminal window
qq run --session 1f0c9a2e4b7d4c1e9a3f5b6d7e8f9012 "Now add the same flag to the docs"

Submits into an existing idle root session of this workspace, keeping its history. The model, profile, and approval come from this invocation, not the session’s past. Every exit prints the id you need.

--steer-stdin reads one line per steering message and injects each at the run’s next model/tool boundary; without it stdin is untouched.

--correlation job=nightly --correlation pr=123 (≤ 8, opaque to QQ) appear on the trial record and every session snapshot.

--profile NAME selects a profile from configuration or a trusted pack; it sets the model, approval mode, and tool catalog for the run and fails before starting when the name is unknown.

Terminal window
qq serve # 127.0.0.1, random port
qq serve --bind 127.0.0.1:4711
qq serve --allow-origin https://app.example.com

Runs the user-scoped server in the foreground until Ctrl-C. Without it, each qq starts a server inside itself when none is running, and quitting that qq cancels its runs (Exiting). qq serve is for keeping runs going after the TUI quits, for several TUIs on one machine, and for remote clients over a private network. It prints qq server listening at ADDR; a second qq serve reports the existing one. Stopping it cancels every queued and running run, as quitting an owning TUI does.

qq run and qq ask never use a server. qq run opens the session store itself, so it cannot run while another process holds that store — a TUI, qq serve, or another qq run — and exits 4 with session store is owned by another running qq process. Queue the work from the TUI, run jobs one after another, or give concurrent CI jobs separate data directories (XDG_DATA_HOME on Linux).

The wire protocol is HTTP + SSE with resumable event cursors; see ../design/protocol.md. Remote authentication beyond loopback is being designed (../plans/multi-surface-clients.md).

- run: printenv ANTHROPIC_API_KEY | qq auth login anthropic --allow-file
env: { ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} }
- run: >
qq run --model anthropic/claude-sonnet-5 --approval auto
--allow-shell "cargo test" --timeout-seconds 900 --max-cost-usd 2
--format jsonl "Fix the failing tests" > run.jsonl

Or skip the store entirely: QQ reads ANTHROPIC_API_KEY from the environment when nothing is stored. QQ_CONFIG_CONTENT='(version: 1, model: "…")' supplies configuration without a file. If the repository ships .qq/config.ron with sensitive sections, run qq trust first.