Backends¶
A backend is Cortex's adapter for a specific coding-agent CLI. Cortex
does not call LLM APIs directly. It spawns a coding agent (Claude Code,
PI, or Codex) as a child process, sends messages to it, and consumes a
normalized event stream. Each backend implements the AgentAdapter
interface defined in agent-server/src/agent-adapter/types.ts.
Supported backends¶
| Backend | Status | Binary | npm package | Feature level |
|---|---|---|---|---|
| Claude Code | Supported | claude |
@anthropic-ai/claude-code |
Full (8/8 capabilities) |
| PI | Supported | pi |
@mariozechner/pi-coding-agent |
Full (8/8 capabilities) |
| Codex | Planned | codex |
— | Partial (3/8 capabilities) |
How backends work¶
When an agent session starts, Cortex resolves the active profile (from
profiles.json or the --profile flag) to determine which backend to use.
It then calls getAdapter(backend) to get the adapter instance and calls
adapter.spawn(config) to start a session.
The AgentSpawnConfig carries the full session context: system prompt,
plugin directories, tool allowlist, MCP server config, hooks, model name,
and backend-specific passthroughs. The adapter translates this into
backend-native CLI arguments and spawns the coding agent.
From there, Cortex sends user messages and receives a normalized event
stream. The normalization layer (agent-adapter/normalize/) translates
each backend's native event format into a common NormalizedEvent
discriminated union, so the orchestration layer never needs to know which
backend is running.
Feature matrix¶
Cortex defines eight capabilities that a backend may support. The orchestration layer checks capabilities before attempting backend-specific operations.
| Capability | Claude Code | PI | Codex | Description |
|---|---|---|---|---|
hooks |
yes | yes | no | PreToolUse/PostToolUse/Stop hooks via hook-bridge |
plugins |
yes | yes | no | Role-scoped skill plugins via --skill or equivalent |
mcp |
yes | yes | yes | MCP tool server integration |
plan-mode |
yes | yes | no | EnterPlanMode/ExitPlanMode tool support |
ask-user-question |
yes | yes | no | AskUserQuestion tool support |
system-prompt-override |
yes | yes | yes | Custom system prompt injection |
session-resume |
yes | yes | yes | Resume an existing session |
tool-allowlist |
yes | yes | no | Restrict available tools to a subset |
Claude Code¶
The reference backend. Supports all eight capabilities natively. Two adapter modes are available:
Print mode (claudeBackend: "print", default). Uses claude -p
--stream-json for one-shot turns. Each user message spawns a fresh Claude
invocation. Fast, stateless, and the recommended mode for most use cases.
TUI mode (claudeBackend: "tui"). Spawns an interactive Claude session
under tmux and tails the session's JSONL file for events. Supports
multi-turn conversation with session persistence. Heavier resource usage
but allows interactive workflows.
Claude Code adapter session pool is keyed by channel for session reuse.
Cost reporting reverse-derives USD from message.usage token counts using
Anthropic's published pricing.
PI¶
Full feature parity with Claude Code. PI's adapter bridges the gap where PI's native feature set differs:
- MCP — implemented via
mcp-bridge.ts, an extension that connects PI to Cortex's MCP server. Auto-injected via--extensionat spawn time. - PlanMode / AskUserQuestion — implemented via
tool-shims.tspseudo tools that registerask,exit_plan, andtodoas first-class PI tools, routing responses throughextension_ui_response. - Hooks — implemented via
hook-bridge.ts, which translates PI tool events to Cortex hook scripts. - Plugins — PI's native
--skillflag maps to Cortex's plugin system.
PI sessions use --session <path> for resume and --system-prompt for
system prompt override. The adapter handles LF-only NDJSON framing for
PI's event stream.
Codex¶
Codex currently supports three capabilities: MCP, system prompt override, and session resume. The adapter is present in the codebase but the backend is marked as planned rather than supported.
Selecting a backend¶
Backends are selected per profile in $CORTEX_HOME/config/profiles.json
(see configuration.md for the full profiles schema):
{
"defaultProfile": "plan",
"profiles": {
"plan": {
"model": "claude-sonnet-4-20250514",
"backend": "claude"
},
"execute": {
"model": "claude-sonnet-4-20250514",
"backend": "pi"
}
}
}
The backend field accepts "claude", "pi", or "codex". If omitted,
it defaults to "claude".
Thread templates can also specify a profile per agent, allowing different agents in the same pipeline to use different backends. See threads.md for template configuration.
Fallback behavior¶
Each profile entry can specify a fallback array of alternative profiles.
If the primary backend call fails with a transient error (network timeout,
rate limit, authentication), Cortex iterates through the fallback chain in
order. Each fallback entry inherits unspecified fields from the primary.
Example:
{
"plan": {
"model": "claude-sonnet-4-20250514",
"backend": "claude",
"fallback": [
{ "model": "claude-sonnet-4-20250514", "backend": "pi" }
]
}
}
Usage-limit throttling and auto-resume¶
The fallback chain handles individual failed calls. A separate mechanism handles the rolling usage limit that providers enforce over a multi-hour window. When a backend reports that the five-hour usage window is exhausted and every configured fallback has also been spent, Cortex stops sending new work against that limit until the window resets, and records each piece of work that was interrupted — both direct conversations and threads.
Cortex reads the reset time the provider reports and lifts the throttle a few seconds after the window opens again. At that point it reopens each interrupted unit of work and injects a short note telling the agent the limit has cleared and to continue where it left off. A direct conversation resumes in its own channel with the prior context intact; a thread continues from its last step. Resumes are staggered a few seconds apart so they do not immediately exhaust the freshly reset window.
The throttle state and the list of interrupted work persist in
schedules.json, so a restart during the window loses nothing: on startup
Cortex re-arms the timer, or resumes immediately if the window already passed
while it was down. Work that has gone stale (recorded more than six hours
earlier), a channel that already has a live agent, or a thread that has since
finished are skipped rather than resumed.
Auto-resume is on by default. Set CORTEX_AUTO_RESUME=0 in the .env file to
leave interrupted work paused for manual continuation instead.
Cost reporting¶
Cost reporting differs by backend:
- Claude Code — reverse-derives USD cost from
message.usagetoken counts (input/output) using Anthropic's published per-model pricing. Costs are written to$CORTEX_HOME/data/costs.jsonl. - PI — cost reporting depends on the PI coding agent's provider configuration. The adapter captures whatever cost metadata PI emits.
- Codex — cost reporting is not yet implemented.
All cost records follow the same JSONL format and are subject to a 90-day
rolling retention window. Cost queries via MCP tools aggregate across all
backends — see mcp.md for the cost_query tool.
Adding a new backend¶
New backends implement the AgentAdapter interface in a new directory
under agent-server/src/agent-adapter/. The required surface:
adapter.ts— implementsAgentAdapterwithspawn(),close(),kill(), andlistSessions(). Returns anAgentProcessfromspawn().AgentProcess— exposessend(message)for user messages andeventsas an async iterable ofNormalizedEvent. Must also supportclose()andkill().event-parser.ts— translates the backend's native event format toNormalizedEventdiscriminated union members.- Registration — add the adapter to the
ADAPTERSmap inagent-adapter/index.ts, add capabilities tocapabilities.ts, and include the backend label in theBackendtype union intypes.ts.
The normalization layer (agent-adapter/normalize/) provides shared
utilities for event stream queuing, tool name translation, and hook
specification that all backends use.