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
| code | meaning |
|---|---|
0 | the turn completed |
1 | the turn failed, the submit was rejected, or the session closed under it |
130 | the 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
| flag | what 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 / --model | pick 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.