Headless
crowe headless runs one turn of the same agent loop the console runs, with the terminal renderer swapped for a JSON event stream. A host that does not want to parse terminal output (an editor chat participant, an HTTP gateway, a test runner) writes one JSON object to stdin and reads one JSON object per line from stdout until it sees done or error. crowe serve is built on it.
Flags
| Flag | Meaning |
|---|---|
--input FILE | Read the request from this file instead of stdin. |
--model ID | Tier to run when the request carries no model. Default auto. |
The request
One JSON object. The full message history goes in every time; the process holds no state between runs.
{
"messages": [
{"role": "user", "content": "..."},
{"role": "assistant", "content": "..."},
{"role": "user", "content": "..."}
],
"model": "crowelm-flash",
"session": "vscode-abc123"
}
messages, required. Rolesuserandassistantwith string content. Entries with any other role, or empty content, are dropped before the turn runs; tool messages are not tracked on input, the agent recreates them as it runs tools. After filtering, the array must be non-empty and end with a user turn, or the run ends with anerrorof kindinput.model, optional. Any selector from the model table, orauto: the first tier in the chain whose provider is supported here and has credentials in the environment. The request'smodelwins over--model.session, optional. An opaque tag, defaultheadless. It labels the local telemetry and the transcript the session runtime keeps under~/.crowe-logic/runtime/.
The events
Newline-delimited JSON, flushed per event so streaming actually streams. Every line has a type.
| type | Fields | Meaning |
|---|---|---|
ready | The provider is built and the turn has started. | |
reasoning | delta | A piece of the engine's thinking, when the engine exposes it. |
token | delta | A piece of the answer text. |
tool | name, args, status, duration_ms, result | A tool call finished. status is ok or fail; result is cut to 5,000 characters. |
spinner | label | A transient state hint. label is a string, or null when the spinner clears. Render it as a status line or ignore it. |
segment_end | Boundary between rounds, for example before a tool call. | |
done | tokens, reasoning_tokens, elapsed_ms, ttft_ms | The turn finished. Token counts are the number of deltas emitted. |
error | message, kind | The turn ended early. kind is input, config, provider, cancelled or runtime. |
Exit codes: 0 after done, 1 on a provider error, 2 on an input error, 3 on a configuration error (the tier named has no credentials in the environment, and the message names the missing variables), 130 when interrupted.
A captured turn
Run on 2026-09-01 against CroweLM Flash. The two token events are the engine's leading newlines and the word.
$ echo '{"messages":[{"role":"user","content":"Reply with the single word ready."}],"model":"crowelm-flash","session":"docs-sample"}' | crowe headless
{"type": "ready"}
{"type": "token", "delta": "\n"}
{"type": "token", "delta": "\nready"}
{"type": "done", "tokens": 2, "reasoning_tokens": 0, "elapsed_ms": 2195, "ttft_ms": 2193}
A bad request answers on the same channel:
$ echo '{}' | crowe headless
{"type": "error", "message": "Input must include a non-empty 'messages' array", "kind": "input"}
The shape of a tool event, from the contract:
{"type":"tool","name":"...","args":"...","status":"ok|fail","duration_ms":N,"result":"..."}
Tools and credentials
A turn whose last user message classifies as plain chat or creative writing goes out without tool schemas, the same gating the console applies. Set CROWE_TOOLS_ALWAYS=1 to send the tool schemas on every turn. The module entry point, python -m cli.headless, also takes --tools and --no-tools; the latter gives a bare model answer for grounded-versus-bare comparisons.
Session control lines such as /steer and /dataset in the last user turn are answered locally, as ready, one token and done, without calling a model. The provider is built from the model table and the keys in the environment; the error event says which variable is missing when a tier cannot be reached from this machine.