> 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/services/agents.md).

# Agents

`@frites/agents` (`packages/agents`) is frites's adapter layer over the headless coding CLIs. It knows how to invoke each backend, stream and parse its events, normalize token usage and cost across providers, scrub the child environment for recursion safety, and reap a stalled child. Its only dependency is [`@frites/core`](/services/core.md), whose structural interfaces it satisfies so the engine never spawns a process itself.

For how runners fit into the overall execution model, see [Agents and runners](/architecture/agents-and-runners.md).

## Exports

`packages/agents/src/index.ts` re-exports:

| Module                   | What it provides                                                                                                             |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
| `runner.js`              | `CliRunnerDef`, `makeRunAgent`, `RunAccumulator`: the engine-path `RunAgentFn` factory                                       |
| `completion.js`          | `runCompletion`, `parseClaudeLine`, `parseCodexLine`, `ChildEvent`, `CompletionResult`, `StreamAcc`: the answer-council path |
| `claude.js` / `codex.js` | `claudeRunner` and `codexRunner` `CliRunnerDef`s                                                                             |
| `env-sandbox.js`         | `buildChildEnv`, `assertDepth`, `currentDepth`: the recursion + secret boundary                                              |
| `timeout.js`             | `startIdleTimeout`: the idle/hard reaper                                                                                     |
| `pricing.js`             | Re-exports `estimateCostUsd`, `pricingFor`, `UsageTokens` from `@frites/core` (back-compat)                                  |

`defaultRunners` is the shipped list: `[claudeRunner, codexRunner]`.

## Runners (`runner.ts`)

A `CliRunnerDef` describes one CLI backend: its `kind`, its `command`, a `buildArgv(spec, ctx)`, and an `onLine(line, emit, acc)` parser. `makeRunAgent({ runners, config, passApiKeys })` indexes the runners by kind and returns the `RunAgentFn` the engine calls.

Before spawning, `makeRunAgent` asserts the recursion depth, builds the scrubbed child env, and applies config defaults onto the spec so a per-child budget/timeout/reasoning value always takes effect even when the spec omits it:

* `maxBudgetUsd` ← `config.perChildBudgetUsd`
* `timeoutMs` ← `config.perChildTimeoutMs` (idle)
* `hardTimeoutMs` ← `config.perChildHardTimeoutMs` (absolute, off when unset)
* `reasoningEffort` ← `config.codexReasoningEffort` (codex only; claude ignores it)

`spawnAndStream` spawns the CLI **detached** (its own process group, so it can be tree-killed via `process.kill(-pid, …)`), pipes the prompt over **stdin** and closes it (the EOF is what stops the child waiting for input; a real transcript would exceed `ARG_MAX` and trip `spawn E2BIG` if passed as argv), buffers stdout into newline-delimited lines for `onLine`, writes a combined log to a temp file, and resolves an `AgentRunOutput` with a status of `succeeded`, `errored`, or `timed-out`. On idle timeout or abort it sends `SIGTERM` then escalates to `SIGKILL` after a `3000`ms grace.

## Claude runner (`claude.ts`)

Headless Claude Code, invoked as `claude -p --output-format stream-json --verbose --permission-mode bypassPermissions --strict-mcp-config --setting-sources project`, with `--model` and `--max-budget-usd` appended from the spec. It reuses the machine's subscription OAuth (keychain), so no API key is needed. `--strict-mcp-config` plus `--setting-sources project` keep the child from auto-loading frites's own MCP (a recursion guard). The `onLine` parser emits progress for tool uses, captures the assistant text/`result` as the summary, reads `total_cost_usd` as the authoritative cost, and sums Anthropic's **disjoint** input categories (fresh + cache-read + cache-creation) into the normalized input total; `output_tokens` already includes thinking, so no reasoning fold is needed.

## Codex runner (`codex.ts`)

Headless Codex, invoked as `codex exec --ignore-user-config --json --skip-git-repo-check -s workspace-write -C <cwd> -c approval_policy="never"`, then `-c model_reasoning_effort="<v>"` (when set), `-m <model>` (when set), and `-` (read prompt from stdin). It reuses the machine's ChatGPT sign-in (`~/.codex/auth.json`); approval is set via `-c approval_policy="never"` because the `--ask-for-approval` flag exits 2, and the `workspace-write` sandbox lets it edit within the worktree. `--ignore-user-config` prevents loading `config.toml` (which could route to the gateway and recurse). The NDJSON schema drifts between versions, so the parser is defensive: it pattern-matches event types for progress, captures the latest message as the summary, passes `input_tokens` through (codex's value is already the inclusive total, with cached as a subset), and **folds `reasoning_output_tokens` into `output_tokens`** so the total is comparable with claude. `cost_usd` is honored when present (the API-key path); the ChatGPT backend usually omits it.

> `model_reasoning_effort="minimal"` is **not** safe on the stock codex model. It 400s because it is incompatible with the built-in `web_search`/`image_gen` tools. frites ships `high` as the default, so use `low`/`medium`/`high`.

## Completions (`completion.ts`)

`runCompletion(kind, prompt, opts)` is the **answer-only** path used by the answer council: a single agent runs read-only (no worktree, no editing) and returns its text plus normalized cost/tokens, streaming `ChildEvent`s (`start`/`text`/`reasoning`/`tool`/`usage`) live via `opts.onEvent`.

* **Claude** runs with `--output-format stream-json --verbose --include-partial-messages` for token-level deltas, `--strict-mcp-config`, `--setting-sources project` (never `user`, which could set `ANTHROPIC_BASE_URL` to the gateway and fork-bomb), and `--disallowedTools Edit Write NotebookEdit` as a read-only guard.
* **Codex** runs with `-s read-only` and `-o <file>` (a final-message fallback written outside the repo if the event stream yields no `agent_message`), matching the execute path's reasoning depth.

It runs in the caller's real repo when `opts.cwd` is a valid absolute path (so reads actually work), otherwise in a temp scratch dir; only scratch dirs frites creates are cleaned up. `parseClaudeLine` and `parseCodexLine` are pure, fixture-tested per-line parsers shared by this path, handling both codex's newer `thread/turn/item` events and the legacy `msg`-wrapped shape.

## Environment sandbox (`env-sandbox.ts`)

The child environment is built by **allowlist**, never by copying `process.env`. This is the recursion guard and secret-minimization boundary for full-auto agents.

* `buildChildEnv` copies only the `ALLOWLIST` vars (`HOME`, `PATH`, locale, `CODEX_HOME`, `CLAUDE_CODE_OAUTH_TOKEN`, the XDG dirs, …), optionally passes `ANTHROPIC_API_KEY`/`OPENAI_API_KEY` when `passApiKeys` is set, then (as defense in depth) deletes every base-URL var in `SCRUB_EXACT` (`ANTHROPIC_BASE_URL`, `ANTHROPIC_API_URL`, `OPENAI_BASE_URL`, `OPENAI_API_BASE`, `CODEX_BASE_URL`) so a child can never be pointed back at frites. It then sets `FRITES_DEPTH = depth + 1` and `FRITES_CHILD = 1`.
* `currentDepth` reads `FRITES_DEPTH` from the env; `assertDepth(depth, maxDepth)` throws the recursion-fuse error when `depth >= maxDepth`.

## Timeouts (`timeout.ts`)

`startIdleTimeout({ idleMs, hardMs, onFire })` reaps a child that has gone **silent**, not one that is merely slow. `touch()` (called on every chunk of child output) resets the idle countdown, so a child that keeps streaming runs as long as it stays productive. Only a genuine deadlock, stalled read, or output-less spin trips it. `hardMs` is an optional non-resetting absolute ceiling for the pathological "spinning forever while still dribbling bytes" case (off when undefined/0). `onFire` runs at most once, with whichever timer tripped first, and `touch()` is inert afterward. Both the runner and completion paths drive their reaping through this controller.

## Pricing (`pricing.ts`)

A thin back-compat re-export of `estimateCostUsd`, `pricingFor`, and `UsageTokens` from [`@frites/core`](/services/core.md), so the engine path and the answer-council path estimate child spend identically from one source of truth. See [Cost telemetry](/concepts/cost-telemetry.md) and [Pricing](/reference/pricing.md).


---

# 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/services/agents.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.
