All documentation

Agent providers

How OpenOrc starts Claude Code, Codex, and OpenCode, talks to them, and turns their output into one event format.

Checked against the source on September 27, 2026.

One process per run

OpenOrc supports three providers. Each has an adapter in packages/agents that starts the CLI, speaks its protocol, and translates its output into OpenOrc's events.

ProviderCommandProtocol
Claude Codeclaude -pNewline-delimited JSON over stdin and stdout (stream-json)
Codexcodex app-serverJSON-RPC over stdin and stdout
OpenCodeopencode acpAgent Client Protocol (ACP) over stdin and stdout

A run is one long-lived process that holds one provider session. The process starts in the conversation's folder with the environment snapshot described in Finding your tools, in its own process group (except on Windows).

  • Conversations keep their process between turns, so the next message starts instantly. An idle process closes after 10 minutes by default. Settings offer 5, 10, 30, or 60 minutes, or keeping it running.
  • Task discussions and team assignments close their process after every turn.
  • Changing the mode or the permission level replaces the process when you send the next message, and the new process resumes the same session. Changing the agent also starts a new process with the next message; Sessions and switching explains which session it gets.

Stopping a process sends SIGTERM to its process group, then SIGKILL after 2 seconds. An idle Claude process first has its input closed and gets 2 seconds to exit on its own. A run is marked complete only after the whole group has exited.

Source: packages/agents/src/run-handle.ts, packages/agents/src/process-lifetime.ts, packages/core/src/services/runs.ts

Claude Code

OpenOrc runs Claude Code in print mode with streaming JSON input and output:

claude -p --input-format stream-json --output-format stream-json
  --verbose --include-partial-messages
  --settings '<per-process settings>' --permission-mode <mode>
  [--tools Read,Grep,Glob,Bash,Edit,Write,NotebookEdit,WebFetch,WebSearch,AskUserQuestion,ExitPlanMode
   --strict-mcp-config]
  [--model <id>] [--effort <level>] [--max-turns <n>]
  (--session-id <run id> | --resume <session> [--fork-session])
  [--append-system-prompt '<OpenOrc instructions>']
  --mcp-config <private file>
  --permission-prompt-tool mcp__<server>__approve

When OpenOrc's own tools are available, --allowedTools also lists each of them, so Claude does not ask about them itself; OpenOrc applies the mode when they run. See Permissions and app tools.

  • --settings carries Fast mode and the rules for the selected mode, scoped to this process. Your own Claude settings files are not changed.
  • OpenOrc's MCP address carries a secret, so --mcp-config names a file instead: it is readable only by you, sits in a private temporary folder, and is deleted with that folder when the process closes.
  • In Plan and Accept edits, the settings add a PreToolUse hook for file edits. It sends each file path to OpenOrc with curl, which reads the address from another private file. OpenOrc resolves the real path, following symlinks, and answers: inside the allowed folder, the mode's own rules apply; outside it, Plan denies the edit and Accept edits asks you. The allowed folder is Claude's plans folder in Plan and the working folder in Accept edits. If OpenOrc does not answer, the edit is blocked. Both modes refuse to start with Claude on Windows, where this hook is not supported yet.
  • In every mode except Autonomous, --tools limits Claude to a fixed list of built-in tools and --strict-mcp-config loads only OpenOrc's MCP server. Your own Claude MCP servers load only in Autonomous, and only while the "Your Claude Code MCP servers" setting is on.
  • A new session uses the run's ID as its session ID. Later runs resume it with --resume.
  • --permission-prompt-tool sends Claude's permission requests to OpenOrc, through an approve tool on OpenOrc's MCP server.
  • The environment drops CLAUDECODE, because Claude Code refuses to start inside another Claude Code session, and sets CLAUDE_CODE_MCP_STARTUP_WAIT_MS=0, unless your environment already sets it, so slow MCP servers you configured do not delay the first turn.

Each message is written to stdin as one JSON line. Attachments are listed in the text as file paths for Claude to open with its Read tool.

{"type":"user","message":{"role":"user","content":[{"type":"text","text":"…"}]},"parent_tool_use_id":null,"session_id":""}

Control messages, such as interrupting a turn or changing the model, are JSON lines of type control_request. Two Claude behaviors surface in the interface:

  • In Plan mode, Claude presents its plan by asking to leave Plan mode (ExitPlanMode). OpenOrc saves the plan as the conversation's plan document and declines the request, so implementing stays your decision.
  • Claude's AskUserQuestion becomes an interactive question card.

Model lists and usage come from a separate short-lived process that runs no turn and saves no session: --no-session-persistence, user settings only, no tools, an empty MCP configuration, and all hooks disabled. It starts in your home folder and times out after 15 seconds (20 for usage).

Source: packages/agents/src/claude/adapter.ts, stream-json.ts, control-session.ts

Codex

OpenOrc runs the Codex app server and passes settings as configuration overrides:

codex app-server --listen stdio://
  -c service_tier="default"|"priority"
  [-c model_reasoning_effort="<level>"]
  [-c features.fast_mode=true]

OpenOrc's MCP server is not on the command line, because its address carries a secret. It goes over stdin in the session's config: mcp_servers.<server>.url, and mcp_servers.<server>.tools.<tool>.approval_mode = "approve" for each of OpenOrc's own tools, so Codex does not ask about them itself; OpenOrc applies the mode when they run. See Permissions and app tools.

In Plan mode it also turns off Codex's multi_agent, browser_use, computer_use, apps, and plugins features, and disables every MCP server in your Codex configuration except OpenOrc's.

The session starts with an initialize request that identifies the client as openorc, opts in to Codex's experimental API, and says OpenOrc can show interactive tool results (MCP Apps). Then:

  • thread/start, thread/resume, or thread/fork opens the session with the working folder, the model, the sandbox and approval policy for the mode, OpenOrc's MCP server, and OpenOrc's instructions as developerInstructions. Network access inside Codex's workspace-write sandbox is off unless you approve Codex's request for it.
  • Each message is a turn/start request with text and, for PNG, JPEG, GIF, and WebP attachments, localImage items. Other files are listed in the text.
  • Codex sends requests back to OpenOrc for command approvals, file-change approvals, permission grants, questions, and MCP forms.

Model lists and usage use a second, short-lived app server with a 15-second limit.

Source: packages/agents/src/codex/adapter.ts, notifications.ts, packages/agents/src/jsonrpc.ts

OpenCode

OpenOrc runs opencode acp and talks to it with the official Agent Client Protocol SDK. It tells OpenCode that it provides neither file system access nor a terminal, so OpenCode uses its own tools for both.

  • The session starts with session/new, session/resume, or session/fork, passing OpenOrc's MCP server. Mode, model, and effort are set with session/set_config_option.
  • ACP has no system prompt field, so OpenOrc's instructions are sent as a text block before your message, on the first prompt of each process. When the session has OpenOrc's ask_user tool, a short note on how to use it goes before every message.
  • Images are sent inline as base64. Other files are listed as paths.
  • OpenOrc adds a configuration for this process only, through OPENCODE_CONFIG_CONTENT. When the session has OpenOrc's ask_user tool, it denies OpenCode's own question tool for the built-in agents and for custom agents defined in that configuration, so questions reach you as question cards; custom agents from project files or saved rules can override this. It also sets the rules for Plan mode.

OpenCode runs in Plan and Autonomous modes only. The Review everything and Accept edits modes refuse to start with OpenCode, because this integration cannot yet guarantee that OpenCode asks before every command.

OpenRouter models are OpenCode models named openrouter/<provider>/<model>. They use your OpenCode configuration and credentials.

Source: packages/agents/src/acp/adapter.ts, updates.ts, questions.ts, plan-config.ts

Live controls

ControlClaude CodeCodexOpenCode
Send during a turnAnother stdin line. Claude reads it at the next tool boundary.turn/steer into the running turnNot supported; queue the message instead
Stop the turninterrupt control request, or SIGINT if the request cannot be writtenturn/interruptsession/cancel
Change model or effortset_model and apply_flag_settings, without a restartSent with the next turn/startsession/set_config_option
Fast modefastMode setting, where available; see Models and usagepriority service tierNot available
Compact the context/compact as a messagethread/compact/start/compact as a message
Usage limitsget_usage control requestaccount/rateLimits/readNot available
Interactive tool results (MCP Apps)Not availableShown in a sandboxed frame; the frame reads resources and calls tools through CodexNot available

Claude sometimes finishes a turn and immediately starts another for a message it received mid-turn. The adapter holds the first result for 750 ms so the two appear as one turn.

Stopping a turn keeps the process and the session. If a provider rejects a live model change, the core restarts the process with the new settings and resumes the session.

The event format

All adapters translate into one shared set of 23 event types, defined with zod in packages/protocol/src/events.ts. Not every provider uses every type, and the core emits some itself, such as approval.requested. Each event carries the run ID and a timestamp.

GroupEvents
Sessionsession.started, session.completed, turn.started, turn.completed, usage.updated, error
Textmessage.delta, message.completed, thinking.started, thinking.delta, thinking.completed
Toolstool.started, tool.updated, tool.output.delta, tool.completed, file.changed
Statusactivity.updated, activity.delta, background.updated, plan.updated
Approvalsapproval.requested, approval.resolved
Nativeraw: the provider's original line, kept for debugging

Some examples of the translation:

  • A Codex commandExecution item becomes a shell tool call, and a fileChange item becomes an apply_patch tool call plus file.changed events.
  • Claude's partial stream events become text and thinking deltas; its final result line becomes turn.completed with the cost Claude reports and the model's context window (Claude reports cost as a running total for the session, not per turn); how full the context is comes from each assistant message, as usage.updated.
  • OpenCode's ACP tool_call_update snapshots are compared, and only the new output is emitted as tool.output.delta.
  • Codex items the adapter does not recognize appear as a generic activity row instead of disappearing.

raw events never reach the window or the database. They go to the provider log.

Source: packages/protocol/src/events.ts, packages/agents/src/claude/stream-json.ts, codex/notifications.ts, acp/updates.ts

Sessions and switching

When a run starts, the provider reports its session ID (session.started), and OpenOrc stores it on the run. The next run in the same conversation, with the same agent, resumes the newest session: --resume for Claude, thread/resume for Codex, session/resume for OpenCode. Forking a conversation uses each provider's fork call, so the fork gets its own session.

Switching a conversation to a provider that has never worked in it starts a fresh session with a handoff. OpenOrc cannot move one provider's session into another, so the handoff is a short text summary built from stored messages: the last 4 of your requests (up to 400 characters each) and the previous agent's last reply (up to 1,500 characters). It goes into OpenOrc's instructions for the new session, not into the transcript. The exact text is on What OpenOrc tells agents.

In a project conversation, switching back to a provider that already worked in it resumes that provider's own older session, without a handoff. In a Workspace conversation, including one started from Slack, every change of agent or folder starts a fresh session with a handoff.

When a provider reports that a session no longer exists, OpenOrc recognizes the error text and offers to start a fresh session with a handoff.

Source: packages/core/src/services/threads.ts (continueThread, handoffBrief)

Models and usage

OpenOrc asks each CLI for its models:

  • Claude Code: the model list from the control session's initialize reply, including which models offer Fast mode, plus whether your account can use it. OpenOrc also keeps a short built-in list for Claude: a fallback for when discovery fails, and older models the CLI still runs but no longer lists, which are always added.
  • Codex: the model/list request, page by page.
  • OpenCode: the model option of a temporary ACP session, which is deleted afterwards.

Some Claude options depend on the installed Claude Code version:

  • The Ultracode effort needs Claude Code 2.1.278 or newer, on models that offer Extra high.
  • Fast mode on models from the built-in list needs Claude Code 2.1.219 or newer. For models Claude Code lists itself, Fast mode follows what it reports.
  • Some built-in models need a minimum version, such as 2.1.251 for Claude Fable 5.1. A model your Claude Code is too old for stays in the list, marked "Needs Claude Code <version> or newer".

Fast mode is also off when CLAUDE_CODE_DISABLE_FAST_MODE=1 is set, or when Claude Code is set to use Amazon Bedrock, Google Vertex AI, or Microsoft Foundry (CLAUDE_CODE_USE_BEDROCK, CLAUDE_CODE_USE_VERTEX, or CLAUDE_CODE_USE_FOUNDRY).

Lists are cached per provider for 5 minutes and dropped when the environment snapshot changes. If a refresh fails, the last list stays, marked as stale.

Usage limits come from the CLIs too. Claude answers a get_usage control request with its own login, and Codex answers account/read and account/rateLimits/read. OpenOrc never reads the tokens these tools store and never calls provider APIs with them. When Claude's usage cannot be read, the panel falls back to the rate-limit windows Claude reported during recent runs. Codex rate-limit reset credits are redeemed only when you press the button.

Source: packages/core/src/services/model-catalog.ts, run-catalog.ts, claude-model-catalog.ts, provider-usage.ts, packages/agents/src/claude/usage.ts, codex/usage.ts

Provider logs

Each provider's native output lines are appended to logs/provider/events-<time>.ndjson in the profile folder, for debugging. Claude's and Codex's token-by-token streaming lines are skipped; OpenCode's are kept. A line over 64 KiB is replaced by a preview and its size.

A new file starts every 10 MiB or every day. Files older than 14 days are deleted, and the oldest files go first when the folder passes 512 MiB. OpenOrc never reads this log back. Deleting a conversation does not remove its lines before they expire.

Source: packages/core/src/services/provider-log.ts

Importing CLI sessions

You can continue a session you started in a terminal. When you ask for importable sessions, OpenOrc reads the CLIs' own session files to find the ones for the project folder:

  • Claude Code: ~/.claude/projects/<project path>/*.jsonl, where the project path has every character other than letters and digits replaced by -
  • Codex: ~/.codex/sessions/<year>/<month>/<day>/*.jsonl. Codex does not sort sessions by project, so OpenOrc opens every session file there and reads its first lines to learn which folder the session ran in. It stops reading a file as soon as that folder is another project's.

An imported session becomes a conversation: its messages are converted into OpenOrc events, and its session ID is stored so your next message resumes it. These files use each CLI's private format, which can change between versions. Imports happen only when you ask.

Source: packages/agents/src/import.ts, packages/core/src/services/imports.ts

Skills

Typing / in the composer lists skills. OpenOrc finds them by reading the first 8 KiB of each SKILL.md in the project's .claude/skills folder, your ~/.claude/skills folder (or CLAUDE_CONFIG_DIR), and installed Claude Code plugins. It reads only their names and descriptions and never runs them. Choosing one adds /<name> to your message; the agent decides what to do with it. Because the list comes from Claude Code's folders, Codex and OpenCode may not recognize every entry.

Source: packages/agents/src/skills.ts