Cogitator
CLI

Headless mode

cogitator run -p: one session without a terminal, the answer as text, JSON or stream-json, many turns as stream-json input, kept sessions to resume and fork.

cogitator run -p (--print) runs an agent without a terminal: for scripts, CI jobs and other programs that drive Cogitator. Only the answer, or protocol lines, reach stdout. The CLI's own messages and the runtime's logs go to stderr, so stdout can be piped into jq or read line by line.

cogitator run -p "Name three primary colors"
git diff | cogitator run -p "Review this diff"
cogitator run -p --output-format json "Summarize README.md" | jq -r .result

With both a prompt argument and a piped stdin, what comes on stdin follows the prompt after a blank line, so git diff | cogitator run -p "Review this" sends the instruction and the diff.

The model, the config file and the .env are found the same way as for cogitator run (see CLI).

Options

OptionDefaultDescription
-p, --print-Run headless, the prompt from the argument, stdin after it when piped
--output-format <format>texttext (the answer), json (one result object), stream-json (NDJSON events)
--input-format <format>textstream-json: turns as NDJSON lines on stdin, needs stream-json output
--resume <session-id>-Continue a kept session
--fork-With --resume: continue in a new session forked from it
--session-id <id>newThe id of the new session (with --resume only together with --fork)
--json-schema <schema>-A JSON Schema, inline or a file, the answer must match
--no-session-persistence-Keep the session in this process only, over memory.adapter too

Flags that do not go together (a headless flag without -p, --fork without --resume, -p with -i, a schema that cannot be read) exit with 2 before anything runs.

Output formats

text prints the answer and a newline. With --json-schema it prints the structured answer as JSON.

json prints one result object:

{
  "type": "result",
  "subtype": "success",
  "is_error": false,
  "session_id": "thread_6c1f...",
  "turn_id": "turn_0a3e...",
  "result": "Red, yellow and blue.",
  "duration_ms": 914,
  "total_cost_usd": 0,
  "usage": { "input_tokens": 34, "output_tokens": 9, "total_tokens": 43 }
}

subtype is success, paused, interrupted or error. A paused turn adds pending_approvals and interrupts, a structured answer structured_output, a failure error with message and code.

stream-json prints every event of the stream protocol as it happens, one JSON per line, each with session_id and parent_tool_use_id, and ends each turn with the result line above:

{"type":"start","messageId":"msg_...","threadId":"s1","session_id":"s1","parent_tool_use_id":null}
{"type":"text-delta","id":"txt_...","delta":"Red","session_id":"s1","parent_tool_use_id":null}
{"type":"finish","messageId":"msg_...","status":"completed","session_id":"s1","parent_tool_use_id":null}
{"type":"result","subtype":"success","session_id":"s1","result":"Red, yellow and blue.", ...}

The events of a subagent, or of an agent used as a tool, carry the id of the tool call that started its run in parent_tool_use_id, so a reader can nest them under that call.

Many turns: stream-json input

With --input-format stream-json --output-format stream-json the session reads one message per line from stdin until it ends:

{"type":"user","message":{"role":"user","content":"Plan the release"}}
{"type":"user","message":{"role":"user","content":"Skip the changelog"},"delivery":"steer"}
{"type":"resume","decisions":{"call_1":{"approved":true}}}
{"type":"interrupt"}
  • user without delivery is a turn of its own, run after the turns before it. With delivery it reaches the turn going on (or starting), and starts a turn when there is none: steer (after the current step's tool calls), followUp (when the agent would stop) or inject (context for the next model call). content is text, or parts: {"type":"text","text"} and images as {"type":"image","url"} or {"type":"image","source":{"type":"base64","media_type","data"}}.
  • resume answers a paused turn: decisions by tool call id, responses by interrupt id, defaultDecision for the rest.
  • interrupt stops the turn going on. It ends with an interrupted result.

Lines that cannot be read are skipped with a note on stderr. When stdin ends, the turns still queued run, and the process exits with the code of the last one.

Sessions

Every headless run is a session, its id is the memory thread. Without memory.adapter in the config, sessions are kept in SQLite at ~/.cogitator/sessions.db (COGITATOR_SESSIONS_DB names another file), so a later process continues them:

cogitator run -p --session-id plan "Draft a release plan" > /dev/null
cogitator run -p --resume plan "Shorten it to three steps"
cogitator run -p --resume plan --fork --session-id plan-b "Make it five steps instead"

--fork copies the session up to its last entry into a new one, so the original stays as it was. A config with memory.adapter keeps sessions in its own store. --no-session-persistence keeps the session in the process alone, also when the config names a memory.adapter, and refuses --resume (exit 2).

Approvals

A headless run has nobody to ask, so a tool call that needs approval ends the turn paused: exit code 3, the calls in pending_approvals. Answer them with a resume line in a stream-json session:

echo '{"type":"resume","defaultDecision":{"approved":true}}' |
  cogitator run -p --resume <session-id> --input-format stream-json --output-format stream-json

For live approvals, drive the agent through the app-server, which asks with approval/request.

Exit codes

CodeWhen
0The last turn completed
1It failed, was interrupted, or its answer does not match --json-schema
2The command line is wrong
3It was left paused, waiting on approvals or answers

On this page