> For the complete documentation index, see [llms.txt](https://docs.fritesai.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.fritesai.com/architecture/agents-and-runners.md).

# Agents & runners

The `@frites/agents` package (`packages/agents`) is the child-execution layer. It turns an `AgentSpec` plus an `AgentRunContext` (cwd, prompt, abort signal, progress callback) into an `AgentRunOutput`, spawning the headless Claude or Codex CLI, streaming its NDJSON output, accumulating cost/token telemetry, and enforcing timeouts. It also owns the [environment sandbox](/architecture/isolation.md) recursion guard. See the package overview in [services/agents](/services/agents.md).

## The runner abstraction

A `CliRunnerDef` (`packages/agents/src/runner.ts`) describes one backend:

* `kind`: `claude-cli` or `codex-cli`.
* `command`: the binary (`claude` / `codex`).
* `buildArgv(spec, ctx)`: the argument vector.
* `onLine(line, emit, acc)`: parses one stdout line (typically NDJSON), emitting progress messages and accumulating results into a `RunAccumulator`.

`makeRunAgent({ runners, config, passApiKeys })` builds the `RunAgentFn` the engine calls. For each spawn it:

1. Looks up the runner by `spec.kind` (errors if none is registered).
2. Reads `currentDepth()` and calls `assertDepth(depth, config.maxDepth)`, the recursion fuse.
3. Builds the allowlist child env via `buildChildEnv`.
4. Applies config defaults to the spec so per-child budget/timeout/reasoning take effect even when the spec omits them: `maxBudgetUsd ?? perChildBudgetUsd`, `timeoutMs ?? perChildTimeoutMs`, `hardTimeoutMs ?? perChildHardTimeoutMs`, and (codex only) `reasoningEffort ?? codexReasoningEffort`.
5. Spawns and streams.

## Spawn and streaming

`spawnAndStream` (`packages/agents/src/runner.ts`) runs the child:

* **Detached process group** (`detached: true`) so the whole tree can be killed via `process.kill(-pid, signal)`.
* **Prompt over stdin, not argv.** Real transcripts exceed `ARG_MAX`, so passing the prompt as an argument would spawn `E2BIG`. The runner writes the prompt to stdin and closes it; the EOF stops the child waiting for more input (both `claude -p` and `codex -` read to EOF). `EPIPE` is swallowed if the child dies before draining.
* **Line-buffered NDJSON.** stdout is split on newlines; each non-empty line is handed to `def.onLine`, wrapped in a try/catch so schema drift never crashes the runner.
* **Logging.** All stdout/stderr is buffered and written to a per-run log file in `tmpdir()` (`frites-<id>-<ts>.log`); the path is returned as `logPath`.
* **Close handling.** On close, status is `timed-out` if a timeout fired, else `succeeded` (exit 0) or `errored`. An aborted child reports error `aborted`; a non-zero exit reports `exit code <n>`.

## Timeout behavior

Timeouts are **idle**, not wall-clock (`packages/agents/src/timeout.ts`). `startIdleTimeout` arms a countdown of `idleMs` (the spec's `timeoutMs`, default `perChildTimeoutMs` = 600000 = 10 min) that resets on every stdout/stderr chunk via `idle.touch()`. A child that keeps streaming events runs as long as it stays productive; only genuine silence (a deadlock, a stalled read, an output-less spin) reaps it. This replaced an older fixed wall-clock deadline that killed exhaustive runs mid-flight.

An optional non-resetting absolute ceiling (`hardMs` / `hardTimeoutMs` / `perChildHardTimeoutMs`) is the secondary backstop for the "spinning forever while still emitting bytes" case; it is off by default for normal children. When a timeout fires, the runner sends `SIGTERM` to the process group, then escalates to `SIGKILL` after a 3-second grace (`KILL_GRACE_MS`). The same path handles external aborts (client disconnect via `ctx.signal`).

## Claude headless runner

`claudeRunner` (`packages/agents/src/claude.ts`) invokes `claude` with:

```
-p --output-format stream-json --verbose
--permission-mode bypassPermissions
--strict-mcp-config --setting-sources project
```

Plus `--model` and `--max-budget-usd` when the spec supplies them. Headless Claude reuses the machine's subscription OAuth (keychain), so no API key is needed; for headless use this draws the metered Agent-SDK credit. `--strict-mcp-config` and `--setting-sources project` keep the child from auto-loading frites itself (recursion guard). `--permission-mode bypassPermissions` lets the worktree child edit without interactive approvals; the worktree plus the final human diff review is the boundary (see [safety model](/product/safety-model.md)).

`onLine` parses the stream-json events: a `system/init` emits "session started", `assistant` content emits `using <tool>` for `tool_use` blocks and accumulates text into `summary`, and the final `result` event captures `total_cost_usd` (authoritative) and usage. Claude reports fresh / cache-read / cache-write input as disjoint categories; the accumulator sums them into the total `inputTokens` and records the cache subsets. `output_tokens` already includes thinking, so no reasoning fold is needed.

## Codex headless runner

`codexRunner` (`packages/agents/src/codex.ts`) invokes `codex` with:

```
exec --ignore-user-config --json --skip-git-repo-check
-s workspace-write -C <cwd> -c approval_policy="never"
```

Plus `-c model_reasoning_effort="<effort>"` when set, `-m <model>` when set, and a trailing `-` so codex reads the prompt from stdin. Codex reuses the machine's ChatGPT sign-in (`~/.codex/auth.json`). `--ignore-user-config` prevents loading `config.toml` (which might route back to the gateway, causing recursion). Approval is disabled via `-c approval_policy="never"` (the `--ask-for-approval` flag exits 2); `-s workspace-write` lets it edit within the worktree.

**Reasoning effort.** `codexReasoningEffort` defaults to `"high"` so codex analyzes as hard as claude before acting; a per-agent `reasoningEffort` overrides it. Only `low`/`medium`/`high` are safe: `"minimal"` returns a 400 on the stock codex model because it is incompatible with the built-in web\_search/image\_gen tools. Claude has no equivalent flag; its depth comes from the model plus the shared directive.

The codex NDJSON schema drifts between versions, so `onLine` is intentionally defensive: it reads `obj.type ?? obj.msg?.type`, maps command/patch/message events to coarse progress strings ("running a command", "editing files", "thinking"), and accumulates `summary` from whichever text field is present. Codex `input_tokens` is already the inclusive total (cached is a subset), so it is passed through; hidden `reasoning_output_tokens` are folded into `outputTokens` so the total is comparable with claude. The ChatGPT backend usually omits `cost_usd`; when present (API-key path) it is authoritative.

## Child directive and completions

Every substantive child prompt (answer, action, and execute paths) has the shared thoroughness directive appended (`childDirective`, default `DEFAULT_CHILD_DIRECTIVE` in `packages/core/src/config.ts`). It tells all backends to read before answering, trace the actual execution path, consider edge cases, and verify by running the build/tests. This is the provider-agnostic half of "make every agent thorough": it lifts codex to claude-like depth. Background/utility turns (title generation, summarization, the fan-out judge) deliberately skip it; set `childDirective` to `""` to disable. The agents package also exposes answer-only completions for the gateway answer/action councils, which use the same runners with answer-only permission constraints (see [safety model](/product/safety-model.md)).

## Pricing hooks

Both runners normalize token usage into the same `AgentRunOutput`/`Candidate` shape (`inputTokens` total, `cacheReadTokens`/`cacheCreationTokens` subsets, reasoning-inclusive `outputTokens`). Claude reports authoritative `costUsd`; codex against the ChatGPT backend reports none, so the engine estimates its spend from the configured `pricing` table and captured tokens. See [cost telemetry](/concepts/cost-telemetry.md) and the [pricing reference](/reference/pricing.md).

## Env-sandbox integration

Before any spawn, `makeRunAgent` calls `assertDepth` and `buildChildEnv`, which build the child environment by allowlist (never copying `process.env`), withhold API keys unless opted in, scrub base-URL variables, and increment `FRITES_DEPTH`. This is the recursion guard and secret-minimization boundary, detailed in [isolation](/architecture/isolation.md) and the canonical [safety model](/product/safety-model.md).

## Related

* [Agents service](/services/agents.md): package overview.
* [Isolation](/architecture/isolation.md): worktree lifecycle and the env sandbox.
* [Core engine](/architecture/core-engine.md): how the engine drives runners.
* [Safety model](/product/safety-model.md): permission posture per surface.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.fritesai.com/architecture/agents-and-runners.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
