<!-- Markdown mirror. Canonical: https://empryo.com/docs/surfaces/headless -->

# Headless & output formats

> The contract for empryo --headless: plain, JSON, and JSONL event output shapes, multi-turn chat over stdin, exit codes, and what the runner disables.

![A headless run: the prompt, the stream, and the receipt at the end.](https://empryo.com/screenshots/headless.webp)

*A headless run: the prompt, the stream, and the receipt at the end.*

`--headless` runs the same agent with no terminal UI, for scripts, CI, and automation. This page is the output contract, for end-to-end patterns see the [CI recipe](/docs/recipes/headless).

```bash
empryo --headless "explain the auth middleware"
echo "find unused exports" | empryo --headless        # prompt from stdin
```

Assistant text streams to **stdout**. Progress (model, tools, footer) goes to **stderr**, so pipes stay clean. Output is one of three shapes.

## Plain (default)

Assistant text streams straight to stdout. When stdout is a TTY it is Markdown-rendered, piped, it is raw text. Force either way with `--render` / `--no-render`. Add `--quiet` to drop the stderr progress lines, or `--diff` to print the changed files at the end.

## JSON, `--json`

One pretty-printed object after the run completes. No streaming.

```json Result object
{
  "model": "anthropic/claude-opus-4-8",
  "mode": "default",
  "prompt": "list all TODOs",
  "output": "…assistant's final answer…",
  "steps": 7,
  "tokens": { "input": 18432, "output": 1204, "cacheRead": 16000 },
  "toolCalls": ["grep", "read", "find"],
  "filesEdited": ["src/auth/session.ts"],
  "duration": 38211
}
```

The `error` key is present only on failure.

## Events, `--events`

A JSONL stream: one JSON object per line, emitted in real time. Each has a `type`:

| Event | Payload |
|-------|---------|
| `start` | `model`, `mode`, `session`, `genome` (`files`/`symbols` or null), `workers` |
| `text` | `content`, an assistant text delta |
| `tool-call` | `tool`, `toolCallId`, `input` |
| `tool-result` | `tool`, `toolCallId`, `summary` (truncated to 200 chars) |
| `step` | `step`, `tokens` (cumulative + last-step input/output/cacheRead) |
| `cache-miss` | `reason`, `readTokens`, `writtenTokens`, the prompt cache missed and roughly what re-writing the prefix cost |
| `warning` | `message` |
| `error` | `error` |
| `done` | `output`, `steps`, `tokens`, `toolCalls`, `filesEdited`, `duration` |
| `session-saved` | `sessionId` (only with `--save-session`) |

```bash Parse events with jq
empryo --headless --events "refactor the store" | while read -r line; do
  case "$(jq -r .type <<<"$line")" in
    tool-call) echo "→ $(jq -r .tool <<<"$line")" ;;
    done)      echo "done in $(jq -r .duration <<<"$line")ms" ;;
  esac
done
```

## Chat, `--chat`

A multi-turn REPL over stdin. Each line is a turn, a trailing `\` continues to the next line. A blank line is skipped. `--json` and `--events` apply per turn. On exit (EOF or Ctrl+C) the session is auto-saved and a resume hint is printed.

```bash
empryo --headless --chat <<'EOF'
what does the session store do?
now add a TTL field to it
EOF
```

Chat mode emits `ready` before each prompt, `turn-done` per turn, and `chat-done` at the end.

## Exit codes

| Code | Meaning |
|------|---------|
| `0` | Success |
| `1` | Error |
| `2` | Timeout (`--timeout` fired) |
| `130` | Aborted (SIGINT) |

## What headless disables

- The TUI, editor panel, and splash screen.
- Interactive approvals, destructive actions auto-allow.
- User steering. There is no live stdin during a run (except `--chat`).

> **Tip:**
> On small CI jobs, `EMPRYO_NO_GENOME=1` (or `--no-genome`) skips the startup scan for a faster cold start. Cap runaway runs with `--max-steps` and `--timeout`.
