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

FlagMeaning
--input FILERead the request from this file instead of stdin.
--model IDTier 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"
}

The events

Newline-delimited JSON, flushed per event so streaming actually streams. Every line has a type.

typeFieldsMeaning
readyThe provider is built and the turn has started.
reasoningdeltaA piece of the engine's thinking, when the engine exposes it.
tokendeltaA piece of the answer text.
toolname, args, status, duration_ms, resultA tool call finished. status is ok or fail; result is cut to 5,000 characters.
spinnerlabelA transient state hint. label is a string, or null when the spinner clears. Render it as a status line or ignore it.
segment_endBoundary between rounds, for example before a tool call.
donetokens, reasoning_tokens, elapsed_ms, ttft_msThe turn finished. Token counts are the number of deltas emitted.
errormessage, kindThe 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.