How OpenOrc works
The processes OpenOrc runs, how it talks to each agent, and the path a message takes from the composer and back.
The parts
OpenOrc is an Electron app with no language model of its own. It starts the agent CLIs you already installed and signed in to (Claude Code, Codex, and OpenCode) and talks to them through their machine-readable interfaces. Each CLI calls its model provider directly with your login; OpenOrc doesn't proxy that traffic.
- Interface
- A React app in each window. Everything it shows from OpenOrc's records comes from the core.
- Core
- A Node.js process that owns the database, starts the agents, runs Git, and offers OpenOrc's tools to agents through a local Model Context Protocol (MCP) server.
- Agent CLIs
- One child process of the core per active run. Agents read and edit files and run commands in the thread's folder themselves.
- Electron main
- Creates the windows and starts the core. It also runs terminals and the Preview browser, and encrypts secrets.
Every process
| Process | Started by | What it does |
|---|---|---|
| Electron main | Your system | Windows and menus, terminal shells, Preview views, encrypted secrets, project icons, and app update checks on macOS and Windows |
| Renderer | Main, one per window | The interface, sandboxed, with no Node.js access |
| Preview pages | Main | One renderer per Preview, in a separate browser session. Up to six are kept alive. |
| Visualization and MCP App frames | The window | One sandboxed renderer per frame, so a stuck page can't hold up the others |
| Core | Main | A Node.js process named openorc-core. It owns the SQLite database, starts agents, runs Git, serves OpenOrc's tools, and runs schedules, Slack, and upkeep. |
| Embedding worker | Core, on first use | A thread that computes memory search vectors, so loading the model never blocks the core |
| Editor worker | Core, when you open a TypeScript or JavaScript file | A thread that runs TypeScript's language service for the editor. It stops after a while without use. |
| Formatter worker | Core, on first use | A thread that runs the copy of Prettier that ships with OpenOrc. A project's own formatter runs as its own process. |
| Agent CLIs | Core | claude, codex app-server, or opencode acp, one long-lived process per active run |
| Short-lived CLIs | Core | Sign-in checks, model lists, usage, skills, update checks, and background jobs such as titles, code explanations, and memory summaries |
git and gh | Core | Worktrees, diffs, checkpoints, commits, pushes, and pull requests |
| Login shell | Core | Runs at startup, on Rescan, when Settings opens, and before agent update checks, to find the agents. Not on Windows, where OpenOrc searches PATH itself. |
| Setup scripts | Core | A project's setup script, run with /bin/bash -lc in each new worktree |
| Terminal shells | Main | Your shell as a login shell, one per Terminal tab |
| Device helpers | Core, while device support is on | expo-device-hub and agent-device, on your own Node.js, to start simulators and emulators and stream their screens |
The core also listens on 127.0.0.1 for agents' tool calls, and, while you allow paired phones, on your network for the phone app. Network connections lists every server.
Source: apps/desktop/src/main/index.ts, apps/desktop/src/core/index.ts
How the interface talks to the core
When a window loads, it asks main for a connection to the core. Main creates a message channel, hands one end to the core and the other to the window, and takes no further part. The window asks main directly only for what needs Electron, such as terminals, the Preview, the folder picker, opening links, window controls, and app updates.
Every request is a small JSON object with a number the window chose:
{ "type": "rpc", "id": 42, "method": "runs.start", "params": { … } }- About 270 methods are grouped by name, such as
threads.*,runs.*,tasks.*,review.*,pulls.*, andmemory.*. Each has a parameter schema, andpackages/protocol/src/rpc.tscollects them all. - The core checks the envelope, then the method's parameters, and replies with a result or an error. A malformed message is dropped without a reply. Requests have no timeout.
- The core sends each reply only to the window that asked.
Besides replies, the core pushes these to every window:
| Message | What it carries |
|---|---|
frame | New agent events for one run, at most one frame per run every 16 ms |
invalidate | Names of data that changed, such as threads. The window waits 50 ms to collect more, then fetches only what is on screen again. |
notify | A desktop notification, sent only while notifications are on. The window shows it only if you aren't looking at that thread. |
startup, ready | Startup progress, then the core's process ID and MCP port |
Frames feed a live transcript store, separate from fetched data, and the screen updates at most once per animation frame. Opening a long thread loads its last 4 turns, then 4 more at a time as you scroll up. Before the core reads stored events, it flushes what is still in flight, so a loaded transcript never misses events.
Source: packages/protocol/src/rpc.ts, packages/core/src/openorc.ts (handle), packages/core/src/frames.ts, apps/desktop/src/renderer/src/lib/rpc.ts
How OpenOrc runs each agent
Finding the agents
Apps started from the Dock or a menu don't get your terminal's PATH, so on macOS and Linux the core asks your login shell. It runs your shell with -ilc and a short script that prints PATH and where codex, claude, and opencode are. The shell gets 8 seconds and a minimal environment, and only those answers are kept. On Windows there is no probe: OpenOrc looks for each agent on the PATH it started with, trying the extensions PATHEXT lists, so npm's .cmd launchers count. OPENORC_CODEX_BIN, OPENORC_CLAUDE_BIN, or OPENORC_OPENCODE_BIN, set before OpenOrc starts, take precedence.
Each agent is then checked with its own commands: --version, plus codex login status, claude auth status, or opencode auth list to see whether you are signed in. Results are cached for 60 seconds. These checks never read the credentials the tools store. Only Settings → Cloud looks for API keys to copy, as Cloud threads describes.
Speaking to them
| Agent | Command | Protocol | OpenOrc's brief goes in |
|---|---|---|---|
| Claude Code | claude -p | One JSON message per line over standard input and output | --append-system-prompt |
| Codex | codex app-server | JSON-RPC over standard input and output | The session's developer instructions |
| OpenCode | opencode acp | Agent Client Protocol over standard input and output | A text block before your first message |
Each process gets its own address on OpenOrc's MCP server, which carries OpenOrc's tools: tasks, memory, messages between threads, the browser, and approvals. Claude Code reads the address from a private file; Codex and OpenCode receive it over standard input. When an agent wants permission, the request comes back to OpenOrc, which shows it in the thread or decides it by the mode. What the brief says is on Instructions.
One process per run
- A thread keeps its agent process between turns, so the next message starts at once. An idle process closes after 10 minutes by default (Close idle processes after, under General).
- Task runs and replies in task discussions close their process after every turn.
- Changing the agent, the mode, or the permission level starts a new process with your next message. It resumes the session where that is possible.
- Stopping a process sends
SIGTERMto its process group, thenSIGKILLafter 2 seconds. On Windows,taskkillends the process and every process it started. An idle Claude process first has its input closed, so it can exit on its own.
Live controls
| Control | Claude Code | Codex | OpenCode |
|---|---|---|---|
| Message during a turn | Read at the next tool boundary | Steers the running turn | Waits in the queue |
| Stop the turn | Interrupt request | turn/interrupt | session/cancel |
| Change model or effort | In place | With the next turn | In place |
| Fast mode | Where your account has it | Priority service tier | Not available |
| Usage limits | Asked of Claude Code | Read from your account | Not available |
Stopping a turn keeps the process and the session, so the next message continues with the same context. See Agents and models for choosing models and effort.
Source: packages/core/src/services/shell-environment.ts, system.ts, packages/agents/src/claude/adapter.ts, codex/adapter.ts, acp/adapter.ts, process-lifetime.ts
The path a message takes
- You press Send. A new thread sends
threads.start. While the agent is working, the composer sendsruns.sendif the turn can take the message, orthreads.queueto hold it. Between turns it sendsruns.sendwhen the thread's process is still running and nothing that needs a restart has changed, andruns.startotherwise. - The core checks the request and works out the thread's folder, agent, model, and mode. It decides whether to resume the agent's last session or start fresh with a handoff, and takes a shared hold on the folder, so Git operations that need it alone wait or fail while the agent works.
- If the thread uses a worktree that doesn't exist yet, the core creates it and runs the project's setup script.
- For
runs.start, the core records the run and your message. In a Git project, before a thread's first run, it saves the starting files so the first change can be undone. Then it builds the brief and starts the agent CLI. Forruns.send, it passes the message to the process that is already running. - The CLI calls its provider and reports what it does. An adapter translates that into OpenOrc's events, and each event goes to the database in batches (every 50 ms or 500 events), to the window in frames, and to Slack or a task discussion when they are watching. The Changes panel refreshes at most once a second, and every 8 seconds while the turn runs.
- When the turn ends, the core saves the token usage and the final reply. In a Git project it records a checkpoint of the thread's files, so each turn's changes can be shown and undone on their own. It sends a notification and the next queued message, if there is one.
- When the process exits, the run gets its final state, the hold on the folder is released, and, if memory is on, the run is queued for summarizing.
Source: packages/core/src/services/threads.ts (continueThread), run-launch.ts, runs.ts, run-settlement.ts, apps/desktop/src/renderer/src/components/Conversation.tsx
Runs, turns, and sessions
- Run
- The life of one agent process: its agent and model, its folder, the provider's session ID, its state (starting, running, success, error, or cancelled), token usage, and final reply. Each run belongs to exactly one thread, task, or task comment.
- Turn
- One request and the agent's work until it stops. A run can hold many turns.
- Session
- The provider's own conversation. OpenOrc stores its ID, and the next run with the same agent resumes the newest one. A fork uses the provider's own fork call, so it gets a session of its own.
- Event
- One of 26 shared types that every adapter translates into: session and turn events, text and thinking, tool calls and output, file changes, status updates, approvals, plans, and subagents.
One agent can't continue another's session. Switching a thread to an agent that hasn't worked in it starts a fresh session with a handoff: your last 4 requests (up to 400 characters each) and the previous agent's last reply (up to 1,500 characters), added to the new agent's brief. In a project thread, switching back to an agent that already worked there resumes its own older session. In a Workspace thread, every change of agent or folder starts fresh with a handoff.
A Claude Code session belongs to the Claude account that made it. Moving a thread to another Claude account copies its newest session into that account's folder, and the next run resumes the copy as a fork.
When an agent runs subagents of its own, the adapter reports each one as it starts and as it changes. The core gives each subagent a child thread under the parent, with the subagent's events in a run of its own, so its work reads like any other conversation. See Subagents.
If a provider reports that a session no longer exists, the thread offers Start a fresh session with a handoff.
Startup, shutdown, and recovery
Startup
- Main picks the data folder, takes the single-instance lock (a second copy focuses the first window and quits), starts the core, and opens the window.
- On macOS and Linux, the core stops any agent processes that the last session of OpenOrc left behind, using the records it keeps in the data folder.
- It opens the database and applies any pending migrations.
- It recovers from the last session: runs that were still open are marked as failed with OpenOrc closed while this session was open. Send a message to continue where you left off. Interrupted discussion replies and plans are marked as such.
- On macOS and Linux, it asks your login shell for
PATHand the agents. Then it starts the MCP server and tells every window it is ready. - It starts its timers and sends messages still waiting in a thread's queue, which can start an agent right after launch.
The timers wake snoozed threads and check schedules every 60 seconds, check open pull requests every 5 minutes, run database upkeep after 60 seconds and then every 6 hours, and check for agent updates after 30 seconds and then every 6 hours.
Shutdown
Main closes every terminal and Preview, then asks the core to shut down and waits up to 12 seconds. The core denies pending approvals, closes every agent process, flushes the database and the provider log, and closes. If you quit while an agent is working, its turn stops and the run is recorded as failed; your next message resumes the session.
Source: packages/core/src/openorc.ts (create, close), packages/agents/src/process-recovery.ts, packages/core/src/services/runs.ts
Next steps
Checked against OpenOrc's source on November 9, 2026.