Skip to content

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 --extension at spawn time.
  • PlanMode / AskUserQuestion — implemented via tool-shims.ts pseudo tools that register ask, exit_plan, and todo as first-class PI tools, routing responses through extension_ui_response.
  • Hooks — implemented via hook-bridge.ts, which translates PI tool events to Cortex hook scripts.
  • Plugins — PI's native --skill flag 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.usage token 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:

  1. adapter.ts — implements AgentAdapter with spawn(), close(), kill(), and listSessions(). Returns an AgentProcess from spawn().
  2. AgentProcess — exposes send(message) for user messages and events as an async iterable of NormalizedEvent. Must also support close() and kill().
  3. event-parser.ts — translates the backend's native event format to NormalizedEvent discriminated union members.
  4. Registration — add the adapter to the ADAPTERS map in agent-adapter/index.ts, add capabilities to capabilities.ts, and include the backend label in the Backend type union in types.ts.

The normalization layer (agent-adapter/normalize/) provides shared utilities for event stream queuing, tool name translation, and hook specification that all backends use.