Skip to content

Headless Mode

Run the agent without the TUI for scripting and automation.

Usage

smelt --headless "explain this codebase"

A message argument is required. Without one, smelt exits with code 1 and prints error: --headless requires a message argument to stderr.

The message follows the same rules as the TUI input box, including @file attachments:

smelt --headless "summarize @src/main.rs"

Slash commands (/resume, /clear, etc.) are interactive-only and exit 1 with "…" requires interactive mode. The shell escape (!cmd) does work — it runs the command via sh -c and exits without calling the model.

Provider and Model

Use the same flags as interactive mode:

smelt --headless \
  --model openai/gpt-5 \
  "fix the failing tests"

Or override the connection inline:

smelt --headless \
  --api-base https://api.openai.com/v1 \
  --api-key-env OPENAI_API_KEY \
  --type openai \
  --model gpt-5 \
  "fix the failing tests"

The API key is read from the env var named by --api-key-env (or the configured provider's api_key_env). If authentication isn't resolved at startup, smelt prints the error to stderr and exits 1 before sending the message.

See the CLI reference for the full flag list. Sampling, reasoning effort, system prompt overrides, and --set all work in headless mode.

Output Format

Text (default)

smelt --headless "summarize this repo"
  • stdout — the final assistant message (printed once the turn completes)
  • stderr — thinking, tool activity (one line per call: ✓ tool_name(args) (123ms)), retries, token/cost summary, errors

Use -v / --verbose to include tool output on stderr.

When both stdout and stderr are terminals (interactive use), the final message is printed to stderr so it appears alongside tool output. When either stream is piped or redirected, the final message goes to stdout — giving you a clean answer suitable for files or downstream commands.

JSON

smelt --headless --format json "summarize this repo"

Every engine event is emitted as one JSON object per line (JSONL) to stdout. Nothing else is written to stdout in this mode — no token summary, no final message reprint. The stream ends after TurnComplete or TurnError.

Permissions

Headless mode never prompts:

  • Yolo mode (--mode yolo) — all permission requests are auto-approved
  • Other modes — permission requests are auto-denied; the tool call fails and the model has to recover

Default rules still apply, so read-only tools (read_file, glob, grep, allowed bash patterns) run silently in every mode. See Permissions for the defaults and how to widen them via init.lua.

For fully autonomous scripting, combine with --mode yolo:

smelt --headless --mode yolo "fix the failing tests"

Color

ANSI colors on stderr respect NO_COLOR, TERM=dumb, FORCE_COLOR, and TTY detection. Override with --color:

smelt --headless --color=never "fix the bug" 2>log.txt
smelt --headless --color=always "fix the bug" 2>&1 | less -R

Exit Codes

Code Meaning
0 Turn completed (including TurnError in JSON mode — see the stream)
1 Missing message, auth failure, slash command, or shell escape error
130 Interrupted by SIGINT / SIGTERM (Ctrl-C)

On interrupt, smelt sends a cancel to the engine and exits 130 once the current request unwinds.

Sessions

Headless turns are one-shot. --resume is ignored, and the session is not persisted — there is no resume hint printed on exit. To chain turns, drive smelt from your script and feed prior context through the prompt.

Examples

Pipe the final answer to a file:

smelt --headless "summarize @src/main.rs" > summary.txt

Stream structured events for programmatic consumption:

smelt --headless --format json "fix the bug" \
  | jq -c 'select(.type == "TurnComplete")'

Use in a CI pipeline, logging stderr for inspection:

smelt --headless --mode yolo --color=never \
  "run cargo clippy and fix any warnings" 2>smelt.log