Skip to content

Headless Execution

Use headless mode when you want Anode to do one job and exit. It is the right surface for shell scripts, CI, and programmatic callers.

anode -x "explain this repository"
anode execute "explain this repository"
anode exec "explain this repository"

If no prompt argument is given, Anode reads stdin:

cat prompt.txt | anode execute

When you want to combine an instruction with piped content, include both in stdin or build one prompt string:

{ printf 'diagnose this failure:\n\n'; cat failure.log; } | anode execute
anode -x \
--profile study \
--model anthropic/claude-sonnet-4-6 \
--max-turns 20 \
--approval auto \
--run-label local-debug \
"debug this test failure"
FlagUse
--profileSelect a built-in or custom profile.
--modelOverride the model for this run.
--max-turnsStop after a fixed number of model/tool turns.
--approvalask, auto, or unrestricted; yolo is an alias for unrestricted.
--run-labelAttach a label to the run.
--allow-toolRestrict tool access. Repeat or comma-separate values.
--stream-jsonEmit newline-delimited run events.
--stream-json-inputRead JSON user messages from stdin. Requires --stream-json.
anode -x \
--allow-tool read \
--allow-tool finder \
--allow-tool web_search \
"find public API handlers"

When --allow-tool is present, the run receives only those tool names or glob patterns.

ModeUse
askDefault. Ask before risky tools. Can block non-interactive runs.
autoLet policy auto-approve allowed calls. Good for read and safe shell workflows.
unrestrictedSkip confirmation. Use only in disposable workspaces.

For automation, prefer explicit permissions and tool allowlists over unrestricted.

anode -x "summarize this repository" --stream-json

Each stdout line is a JSON object:

{
"id": "evt_123",
"run_id": "R_123",
"type": "system.init",
"status": "running",
"payload": {
"profile_name": "craft",
"active_model": "anthropic/claude-sonnet-4-6",
"web_enabled": true,
"tools": ["read", "finder"],
"permission": {
"context": "main_run",
"rules": 3,
"allow": 1,
"reject": 1,
"ask": 1
}
},
"created_at": "2026-05-12T00:00:00Z"
}

Fields:

FieldUse
idEvent ID.
run_idRun ID.
parent_run_idParent run ID for child runs. Omitted when empty.
parent_tool_use_idTool call that spawned a child run. Omitted when empty.
typeEvent type.
statusrunning, waiting_approval, complete, error, or canceled.
payloadType-specific JSON data.
created_atTimestamp.

Event types currently include:

TypeMeaning
run.startedRun created.
system.initModel, profile, tools, web, MCP status, and compact permission policy counts.
user.messageUser prompt.
assistant.messageAssistant text.
tool.callTool call prepared.
tool.resultTool result returned.
approval.requestedTool approval needed.
approval.resolvedApproval completed.
validation.startedValidation started.
validation.resultValidation result, including status, class, stdout/stderr evidence, recovery hint on failure, or skip_reason when validation was skipped.
child_run.startedSubagent started.
child_run.resultSubagent finished.
usageToken usage update when the provider reports it.
result.finalFinal run result.
errorRun error.
canceledRun canceled.

Example:

anode -x "run tests" --stream-json | jq -c 'select(.type=="result.final")'
anode -x --stream-json --stream-json-input

Send one JSON object per line:

{"type":"user","message":{"role":"user","content":"find the CLI entrypoint"}}

The message schema:

FieldUse
typeMust be user.
steerOptional boolean. true injects guidance at the next safe interruption point.
message.roleMust be user.
message.contentString or content block array.

Content block example:

{"type":"user","message":{"role":"user","content":[{"type":"text","text":"now inspect the config loader"}]}}

Anode exits after stdin closes and the active turn finishes.

0 means the command completed successfully. Non-zero means setup failed, provider/model selection failed, the run failed, validation failed, or a required prompt/config value was missing. If the event stream closes without a final result, Anode records an immediate harness error event for the run when the run ID is known, so the ledger does not wait for stale-run recovery to become inspectable.

Provider startup failures are explicit. Direct headless errors keep the not connected prefix and include the typed active provider/model detail plus a recovery hint. Stream JSON runs carry the same provider detail in the result.final payload and run ledger instead of collapsing missing active config, malformed active model strings, missing providers, and removed models into an indistinct runtime error.

Keep going: