Agent Backends#
Every node runs a coding-agent CLI — its agent — headlessly inside the iteration loop: fractal assembles the prompt, spawns the agent as a subprocess in the node’s worktree, parses its output stream, and records the session, model, and cost on the step’s database row. Which agent runs, over which provider route, with which model and reasoning effort, is node configuration — and each of the four axes can be overridden per step.
These backends ship built in:
Agent |
CLI |
Per-node config file |
Provider routes |
Forks sessions |
Enforces step budget |
|---|---|---|---|---|---|
|
Claude Code |
|
|
yes |
yes |
|
Codex CLI |
|
|
no |
no |
|
Grok Build CLI |
|
— |
yes |
no |
|
opencode |
|
— |
yes |
no |
|
Oh My Pi |
|
— |
yes |
no |
Each backend keeps a per-node config directory at <node_dir>/.<name>/
(.claude, .codex, …) holding the config file above, seeded at
fractal node init — a child inherits its parent’s live copy, falling
back to the package seed. Editing that file steers the agent’s own
settings (permissions, default model) for this node only. The agent’s
stderr from the latest launch streams to <node_dir>/<name>.err, with
durable per-failure snapshots under <node_dir>/tmp/err/.
Selecting agent, provider, model, and effort#
Four node config keys drive the selection (see Configuration for the full key reference):
agentThe full agent command. The first word names a registered backend and must be an executable on
PATH; any extra words are spliced into every invocation (e.g.claude --some-flag). Commands split on whitespace only — quotes and backslashes are rejected. Default: inherited from the nearest ancestor node.providerProvider route for the agent (
openrouter). Unset means the vendor-native endpoint. Inherited from the nearest ancestor when unset; backends with no route axis (grok,opencode,omp) ignore the key entirely, so a routed ancestor never mis-routes them.modelModel id, passed to the agent CLI’s model flag. Default: none — the agent then runs on its own config-file default (fractal records that model on the step row but does not pass it on the command line).
effortReasoning-effort level, passed through unvalidated to the backend’s effort flag — an unknown level surfaces as the agent’s own error. Default: none.
Set them at init or retune them between runs:
$ fractal node init parser --agent codex --provider openrouter \
--model openai/gpt-5.3-codex --effort high
$ fractal node config set model=openai/gpt-5.3 effort=medium
The loop binds all four when it boots, so a config edit lands on the next
fractal node start. Mid-run, the steering surface is step frontmatter:
step files are re-read every iteration, and the agent:, provider:,
model:, and effort: keys override the node config for that step
alone (see The Iteration Loop for the frontmatter contract). A per-step
agent or provider override is validated at that step’s launch — registered
backend, binary on PATH, supported route — and a failed validation
fails the step, never the run.
Each backend spells the effort flag its own way; fractal translates:
Agent |
Effort flag |
|---|---|
|
|
|
|
|
|
|
|
|
|
Backend requirements#
claude#
Requires the claude binary (Claude Code CLI) installed and
authenticated. claude runs on the user’s own config home
(~/.claude, or $CLAUDE_CONFIG_DIR) — fractal never touches its
auth or session storage. The node’s settings.json rides the
--settings flag and outranks worktree and user settings, so per-node
permissions, model, and environment live there. claude reports its own
USD spend per invocation, and it is the only backend that enforces the
per-step budget cap in-flight (the loop passes --max-budget-usd; a
budget stop is a clean stop, not a failure). Supports the openrouter
route.
codex#
Requires the codex binary (Codex CLI) installed and logged in. Each
node’s .codex directory serves as its CODEX_HOME, with
auth.json symlinked to the global home (~/.codex, or
$CODEX_HOME when set in the launching environment) — credentials
are shared, never copied, and token refreshes update the global file.
Cost is token-priced from the pricing cache, so recorded spend (and any
cost cap) requires a model with a pricing entry. codex sessions
(threads) cannot be forked — resume them in place instead. When an
explicit model is configured, fractal node start runs a bounded probe
invocation before the run, because some accounts reject models the
pricing table can price — a rejection relays codex’s own diagnostic.
Supports the openrouter route.
grok#
Requires the grok binary (Grok Build CLI). Each node’s .grok
directory serves as its GROK_HOME, with auth.json symlinked to the
global home (~/.grok, or $GROK_HOME when set in the launching
environment); an XAI_API_KEY in the environment is picked up as well.
grok reports its spend on the stream’s final frame — trusted as-is,
since subscription/pool models have no pricing-table entry — with token
pricing only as a fallback. grok reports no tool activity on its
stream and suppresses reasoning output, so a quiet pane during a grok
step is normal.
opencode#
Requires the opencode binary. The node’s opencode.json is passed
via OPENCODE_CONFIG; provider API keys (OPENROUTER_API_KEY,
ANTHROPIC_API_KEY, …) are auto-detected from the environment.
Runs headlessly with --auto, so every permission is auto-approved.
Cost is self-reported per internal step and accumulated. Sessions persist
in opencode’s own central database, so there is no per-session transcript
file to inspect.
omp#
Requires the omp binary (Oh My Pi). The node’s .omp directory
serves as its PI_CODING_AGENT_DIR — config (config.yml),
credentials, and session files all live there. Runs headlessly with
--yolo, so every tool call is auto-approved; provider keys ride the
environment. Cost is self-reported per turn and accumulated.
The openrouter route#
claude and codex can be routed through OpenRouter instead of their
vendor-native endpoints: set provider to openrouter (or
provider: openrouter on a single step). The route authenticates on
OPENROUTER_API_KEY alone — export it in the shell that runs
fractal node start, which forwards it into the node’s tmux session; a
missing key fails preflight and any later launch. Model ids on the route
are OpenRouter slugs, author-prefixed: openai/gpt-5.3-codex,
anthropic/claude-sonnet-4.6.
Routed claude prices spend from the stream’s token usage through the
pricing cache instead of recording the CLI’s own figure — a slug the cache
cannot price records no cost at all, never a wrong figure.
Preflight#
Before its first iteration, a run fails fast when the node cannot possibly work:
the agent binary must be on
PATH;an
openrouter-routed node must haveOPENROUTER_API_KEYset;codexwith an explicit model runs its acceptance probe;a run whose agent prices from tokens needs the pricing cache — a stale cache warns and is used, a missing cache that cannot be fetched aborts.
A failed preflight records the run as exited with the reason. Steps
that override the agent or provider re-validate at their own launch.
Costs#
Every step row carries the spend the backend’s stream reported, flushed figure by figure as it arrives — a killed or timed-out step keeps the last figure recorded. Read spend with the cost commands (full surfaces in fractal node):
$ fractal node cost spent parser
$ fractal node cost remaining parser
$ fractal node cost breakdown parser
From the user’s point of view:
Cost is recorded, never estimated. A step whose backend reported no figure stores no cost, and readers treat that as unknown — never $0. A step that plausibly burned spend without a figure is marked
unpricedin its row metadata.The figure’s source varies by backend.
claude(native),grok,opencode, andompreport their own spend on the stream;codex— andclaudebehindopenrouter— are priced from token counts against the pricing cache (~/.fractal/pricing.json, LiteLLM’s public price table, fetched at run start and refreshed daily during long runs).Only claude enforces the per-step cap in-flight. The loop enforces
max_costandmax_iter_costbetween launches for every backend, butmax_step_costis a hard limit only forclaude(--max-budget-usd) and warn-only everywhere else. Budget semantics are covered in The Iteration Loop.A cost cap requires trackable spend. On a token-priced or routed agent the model must be set and present in the pricing table, or the step fails at launch with a specific error.
Sessions and transcripts#
Every step row records the agent, model, and session id that ran it; in a continuous node the steps of one iteration resume a single session per agent, and the iteration row carries it too. These are real provider session ids — they can be resumed or forked outside the loop.
fractal node chat is the user surface: send one prompt to a node’s
agent and stream the reply.
$ fractal node chat parser "what is blocking the tests?"
By default the chat runs a fresh session. Options:
--session <id>Fork a recorded session (a
sessionvalue off a step or iteration row) instead of starting fresh.--currentFork the node’s current loop session (excludes
--sessionand--resume).--resumeContinue
--sessionin place instead of forking it.--model <name>Model override (default: the node’s configured model).
--path <dir>Worktree directory (default:
.).
The resulting session id is echoed to stderr as session: <id>, so the
thread can be continued with --session <id> --resume. A chat records
nothing in the database — it never disturbs the node’s rows or budgets.
Because codex cannot fork, --current and the forking
--session form fail on codex nodes; continue a codex thread
with --session <id> --resume.
For direct inspection, each backend persists transcripts in its own layout:
Agent |
Transcript location |
|---|---|
|
|
|
|
|
|
|
|
|
none — sessions live in opencode’s central database |
There is no transcript CLI command: programmatic access goes through the
library surface (fractal.core.agent.Agent.transcript, fronted by
fractal.core.session.Sessions) in API Reference.
Adding a backend#
A backend is one implementation module — invocation argv, stream parser,
transcript layout, capability attributes — registered by name in the
fractal.core.agent registry, alongside the built-in
fractal/impl/{claude,codex,grok,opencode,omp}.py modules. An embedding
application registers in process with fractal.core.agent.register; a
tree can also carry an agents.py hook file in its root node’s data
directory, whose exported fractal.core.agent.Agent subclasses register
by their name in every fractal process that touches the tree. The
base-class contract is documented in the API reference under
API Reference.