# EONYX Headless Mode: Running the Agent Without the TUI


EONYX also runs **headless**: it does the task and exits.
That is what scripts, pipes and CI need.
Predictable output, JSON events, meaningful exit codes.

<!--more-->

## ▶️ Running it

### The run subcommand

```bash
eonyx run "describe what this repository does"
eonyx run -m deepseek-v4-pro --yolo "fix the failing test"
```

The prompt comes from the arguments **or** from stdin.
The argument wins.
If it is given, stdin is never read.
So when you pipe, do not repeat the prompt as an argument:

```bash
eonyx run "explain the error in the log"                  # prompt from the argument
{ echo "explain the error:"; cat error.log; } | eonyx run # prompt from stdin
```

Flags: `-m/--model`, `-s/--session <id>`, `-c/--continue`, `--yolo`, `--json`, `--system-prompt`.

### stdout versus stderr

In text mode the streams are separated.
That makes the output easy to pipe.

- **stdout** — only the model's final answer, streamed.
- **stderr** — reasoning, tool activity (`→ name`, `← result`) and the summary line `\n[tokens: N in, M out] [session: <id>]`.

So redirecting the result is safe:

```bash
eonyx run "generate a README from the code" > README.draft.md
```

### Sessions

Headless runs are saved too.
The conversation can be continued.

```bash
eonyx run "start refactoring the parser"   # creates a session
eonyx run -c "now add tests"               # continues the most recent one
eonyx sessions                             # list: id, date, message count
```

## 🤖 For CI and scripts

### --json mode

`--json` prints **NDJSON** to stdout, one JSON record per line.
Easy to parse in a pipeline.

```bash
eonyx run --json "fix the test" | jq -c 'select(.type=="tool_end")'
```

Event types: `text`, `reasoning`, `tool_start` (`name`, `input`), `tool_end` (`name`, `result`, `is_error`).
The final `result` carries `text`, `input_tokens`, `output_tokens`, `cached_tokens` and `session`.

### Exit codes

CI has to tell outcomes apart.
So the codes mean something:

| Code | Meaning                                                         |
| ---- | --------------------------------------------------------------- |
| 0    | success                                                         |
| 1    | general error (config, provider, persistence)                   |
| 2    | usage error (invalid arguments)                                 |
| 3    | API/model error during streaming                                |
| 4    | task finished, but a tool was **blocked** by the gate           |
| 5    | hit the `max_steps` limit without a final answer                |
| 130  | interrupted (Ctrl-C)                                            |

```bash
eonyx run --yolo "run the linter and fix the findings" || echo "exit $?"
```

{{< admonition type="tip" title="Codes 4 and 5 are not “just an error”" >}}
Code `4` means the agent wanted to act and had no permission.
Add the tool to `allowed_tools`, or run with `--yolo`.
Code `5` means the task did not fit into `max_steps`.
Raise the limit in the config.
{{< /admonition >}}

### A CI example

```bash
#!/usr/bin/env bash
set -euo pipefail
export DEEPSEEK_API_KEY="$CI_LLM_KEY"

eonyx run --yolo --json "run go test ./... and fix the failing tests" \
  | tee agent.log \
  | jq -r 'select(.type=="result") | .text'
```

## 📋 Summary

{{< admonition type="abstract" title="Cheat sheet" >}}
- **Running it**
  - `eonyx run "…"` — prompt from the arguments or stdin (pipe)
  - flags `-m`, `--yolo`, `--system-prompt`
- **Streams**
  - stdout — only the model's final answer
  - stderr — tool activity and token stats
- **`--json`**
  - NDJSON events: `text`/`reasoning`/`tool_start`/`tool_end`/`result`
  - convenient to parse with `jq`
- **Exit codes**
  - distinguish success from an API error (3)
  - permission block (4), `max_steps` limit (5)
- **Sessions**
  - headless runs are saved
  - `-c` continues the most recent one, `-s <id>` a specific one
{{< /admonition >}}

