Skip to article
Browse chapters

Running headless

One turn on a pipe with --print, the three output formats, the host protocol on stdin, and the JSON-RPC server.

One turn, one exit code

--print runs a single turn and exits. It is the same binary and the same kernel; only the surface differs.

bingo --print "summarise the changes in src/"

Without a prompt argument, the whole of stdin is the prompt:

git diff | bingo --print "review this diff"
cat prompt.txt | bingo --print

bingo also runs headless whenever stdin or stdout is not a terminal, even without --print — a redirected run never tries to take the screen.

Exit codes

codemeaning
0the turn completed
1the turn failed, the submit was rejected, or the session closed under it
130the turn was interrupted

Output formats

--output-format says what goes on stdout.

text         prose on stdout, everything else on stderr  (default)
json         one bingo Frame per line on stdout, nothing else
stream-json  Claude Code's envelope, one object per line  (ADR-0007)

text is for a person or a shell pipeline: the answer, and nothing else, on stdout. Errors and notices go to stderr — as prose when a person is reading it, and as [error] code=… msg=… when a program is.

json is bingo’s own event stream, serialised. Each line is one Frame exactly as the kernel published it, so a host folds them with the same reducer every other surface uses. This is the format to parse if you are building on bingo rather than around it — see the event stream.

stream-json is a compatibility encoder for hosts that already drive claude -p --output-format stream-json. It is a lossy projection of the same frames, never a second event model: a system/init line first, assistant and user lines as the turn runs, and a result line at the end carrying is_error, num_turns, duration_ms and usage. Fields bingo cannot fill are left out rather than invented, and a result line’s cost is 0.0 because nothing here prices a turn.

Driving many turns: the host protocol

--input-format stream-json turns stdin into Claude Code’s host protocol: one JSON object per line, a turn per prompt, until stdin closes and every prompt has been answered.

bingo --print --input-format stream-json --output-format stream-json

A prompt line looks like this:

{"type":"user","message":{"role":"user","content":"read src/lib.rs"},"parent_tool_use_id":null}

Control requests travel the same way. {"type":"control_request",…,"request": {"subtype":"interrupt"}} stops the running turn and is acknowledged with a control_response. A subtype this surface cannot honour is answered with an error rather than silence, so a host never waits for a reply that will not come.

--input-format stream-json is a headless protocol and needs --print. Without it the run is refused with --input-format stream-json is a headless protocol: it needs --print.

Answering permission prompts

With nobody at the keyboard, a question that nobody can answer is refused: bingo denies it with the reason, or cancels it when the question has no denial to give. The turn continues or fails honestly; it never hangs.

--permission-prompt-tool stdio hands the questions to the host on the other end of the protocol instead. A can_use_tool request goes out carrying the tool name and its input, and the host’s control_response decides — a tool runs only when the host allowed it in as many words. An error response, a missing verdict and an unknown one are all denials.

bingo --print --input-format stream-json --permission-prompt-tool stdio

It needs the protocol to answer on. Given alone, the run is refused with --permission-prompt-tool needs --input-format stream-json: there is no other way for an answer to arrive.

The blunter instruments still work headlessly: --permission-mode, --allowed-tools 'Bash(git status:*)' and --dangerously-skip-permissions. See settings and permissions.

Other flags that matter on a pipe

flagwhat it does
--max-turns <N>stop the turn after this many model rounds
--cwd <path>the session’s working directory
--session-id <id>an opaque key naming the session, for hosts that route by it
--continue / --resume <id>reopen a session instead of starting one
--settings <path>an extra settings file above the user, project and local layers
--mcp-config <path>a JSON file whose mcpServers are added for this run
--provider / --modelpick the provider and the model for this run

The JSON-RPC server

For a GUI, an IDE or anything that drives sessions over a longer life than one turn, bingo serve speaks JSON-RPC 2.0 over NDJSON (ADR-0007).

bingo serve --stdio

One client, one message per line, on stdin and stdout. Stdout carries nothing but JSON-RPC messages; every diagnostic goes to stderr. The methods are the kernel’s own host API one for one — initialize first, then session/open, session/submit, session/answer and the rest — and events arrive as event notifications carrying a Frame verbatim. Writes return {} at once; the outcome arrives later as an IntentAck event whose intent is the id the client minted.

--stdio is the only transport there is today, and leaving it out is an error: serve needs a transport: --stdio is the one there is.

The full method list and the committed schema are in schemas and protocols.