Two agents, one chat layer
How Codex and Claude fit into one chat layer: what the shared layer owns, what each adapter owns, what the dispatch layer decides, and where the two agents differ in accounts, access levels, and abilities.
Checked against the source on 30 September 2026
On this page
Codex and Claude side by side
HB Code runs two coding agents. Codex runs through the Codex app-server, a child process that speaks JSON-RPC. Claude runs through the agent host, a Node process that runs the Claude Agent SDK. Both agents feed one chat layer: one outbox, one run registry, one set of timeline rows, one approval card, one tool catalog, and one RPC contract for the phones. A session uses one agent for its whole life.
The largest difference is how long things live. The figure shows two runs of one session and a settings change between them.
- Chat runchat/runs
Shared by both agents. A session has one active run at a time, and the outbox starts the next run after the first one settles.
- Codex app-serverMAX_LIVE_APP_SERVERS = 3
One child per profile pair serves many sessions and many runs. At most 3 idle children stay resident, and the registry grows when all of them are busy.
- Codex threadthread/start · dynamicTools
Gets the tool list once. thread/resume has no field for tools, so a new setting reaches only a thread that starts later.
- Claude agent hostopen · appCapabilities
One Node process per run. The open request carries the tool list of that moment, the model, the effort, and the permission mode.
| Topic | Codex | Claude |
|---|---|---|
| Process and lifetime | The codex-app-server child process. One child per profile pair serves many sessions. At most 3 idle children stay resident, and the registry grows when all of them are busy. | The agent host, a Node process that starts the claude executable. Each run gets a new host, and the host ends with its run. Reads such as the model catalog, a title, or a history page run in a one-shot host. |
| Protocol | JSON-RPC lines over stdin and stdout: requests, notifications, and requests from the app-server to the desktop. | One JSON object per line over stdin and stdout. A request carries an id, and a line without an id is an event. The desktop gives each request 35 seconds. |
| Where history comes from | thread/ | The host reads the saved transcript with the SDK call get |
| When a message counts as sent | The thread history holds a user | The saved transcript holds the message UUID that the desktop saved for the operation in claude_ |
| Tool list lifetime | Sent as dynamic | Sent as app |
| Approvals | The app-server sends a request to the desktop. The access level sets the sandbox and the approval policy. | The SDK calls can |
| Accounts | Codex auth profiles. The active profile pays for the next turn. | The default account in Claude’s own folder and added account folders. The active account pays for the next run. |
| Subagents or child threads | Child threads of the root thread. The desktop follows an open child thread live through a watch. | Subagents of the run, which a workflow can start in groups. Their turns arrive through the parent’s host, and a subagent takes no direct input. |
| What HB Code stores | codex_ | claude_ |
The layer rule: chat, dispatch, adapters
The Rust code has one rule for the two agents. The shared chat layer in desktop/src-tauri/src/chat never names an adapter. When a step needs the agent, chat calls agents/dispatch. dispatch reads the session’s agent and calls agents/codex or agents/claude. The two adapters never import each other.
- chat/desktop/src-tauri/src/chat
The agent-neutral layer. No file in it names agents::codex or agents::claude. It calls agents/dispatch when a step needs the agent.
- agents/dispatch/7 files · mod.rs: session()
Reads the session row and picks the adapter. Both adapters also call its session lookup, and nothing else in it.
- agents/codex/ and agents/claude/two adapters
Each adapter runs its own engine and maps it to the chat contract. Neither one names the other.
- usage_history/mod.rs · recording.rs
The second place that names both adapters. It reads the active Codex profile and the active Claude account, because it saves limit readings of both.
Both adapters use the shared types and services of chat, for example the send request, the timeline rows, and the run registry. Both also call one function of dispatch, the session lookup. usage_history is the second module that names both adapters, because it saves the limit readings of both account types. Code that serves one agent names that adapter directly. The HTML document worker in services/agent_tools is an example: it leases a Codex app-server.
What the dispatch layer decides
agents/dispatch has seven files. Most functions read the session row and match on its agent. Some functions have a different rule, because their caller has no session agent at hand or because only one adapter keeps the state.
| File | What it decides | Who answers |
|---|---|---|
| mod. | The session lookup by base session ID. Every other file starts here. | A missing row fails with “The chat session does not exist.” |
| selection. | The model catalog for the picker, the first model and effort of a session, and the check of model and effort before a send. | The session’s agent. The check returns a permit for that agent. A Claude permit can’t move to a fresh thread after a missing thread link. |
| send. | The three ability flags, the send itself, the history check of one uncertain operation, the active turn, the release of an accepted steer for a replay, the rollback of the latest turn, and the removal of a dead thread link. | The send follows the permit: a Claude permit goes to the Claude adapter, and without a permit only a Codex steer continues. The history check follows the session’s agent. The other four are Codex only. |
| runs. | Questions of the run registry: does an agent turn still own this run, may a stale run be reclaimed, cancel a run, and watch a child thread. | Turn ownership is always the Codex adapter, which keeps no records for a Claude session. A registered Codex turn or a live Claude host blocks a reclaim. For a cancel, Claude answers first, then Codex. A Claude session gets no child thread watch. |
| approvals. | The answer to an approval card. The resolve call names a request, a thread, a turn, and an item, and no session. | Claude answers first when it holds a request with this id. Otherwise the Codex app-server that owns the request settles it. |
| conversations. | The agent’s own conversation ID, the list and the preview of saved conversations, the session title, the session that already owns a conversation, and the link of a new or a duplicated session. | The session’s agent, or the agent in the request. A Claude list needs a project directory. Codex writes titles with gpt-5.6-luna, and Claude with haiku. |
| timeline. | The latest window, an older page, a child thread and its pages, the list of agent threads with the workflows that started them, and command output. | The session’s agent. For Claude, a child thread is a subagent, and only Claude fills the workflows list. Codex returns an empty one. Output that the desktop stored as an artifact exists only for Codex runs. Claude output comes from the host call tool |
A session picks its agent when it is created
session.create carries agent and an optional conversationId. agent is codex or claude, and the request type has no default for it, so a request without agent fails to decode. conversationId names a saved conversation of that agent: a Codex thread ID or a Claude session ID.
- When a session already continues that conversation, create returns that session. A conversation has one owner.
- Otherwise the desktop saves the session row with chat_agent and with pending chat settings.
- The adapter links the session. Codex links the chosen thread. Claude writes a row in claude_sessions with the chosen session ID, or with a new UUID for a new conversation.
- When the link fails, the desktop deletes the new session and returns the error.
The column chat_agent accepts only codex and claude, and no code changes it after the insert. A session therefore never changes its agent. A duplicated session keeps the agent and starts its own conversation: Claude gets a new session ID at once, and Codex starts a new thread at the first send.
The desktop remembers the last choice in the WebView’s local storage under plantocode:new-session-agent:v1 and preselects it in the next dialog. Without a saved value it preselects Codex. The value is a preselection, and the create request always names the agent.
One access level, two mechanisms
A message carries one of four HB Code access levels. Each adapter maps the level to the agent’s own mechanism. Codex gets a permission profile and an approval policy. Claude gets one of its permission modes. The interface shows each agent’s own name for the level.
| Access level | Codex: name, permissions, approvalPolicy | Claude: name, permissionMode |
|---|---|---|
| read-only | Read only. :read-only, on-request | Plan mode. plan |
| full-auto | Full auto. :workspace, on-request | Accept edits. accept |
| full-access | Full access. :danger-full-access, on-request | Auto mode. auto |
| unrestricted | Never ask. :danger-full-access, never | Bypass permissions. bypass |
Codex receives the pair with thread/start and again with each turn/start, so a new level applies to the next turn. An ephemeral Codex request always runs with :read-only and never. Claude receives the mode with the open request of each run, so a new level applies to the next run. For auto, the host asks the SDK whether the chosen model supports Auto mode. When it doesn’t, the open fails and the run doesn’t start.
What each agent can do
The submit service asks agents/dispatch for three flags before it saves a message: steer, replace_latest, and speed_modes. Codex has all three, and Claude has none. A request that needs a missing ability fails at submit with a validation error, so it never enters the outbox. The other differences live in the adapters.
| Ability | Codex | Claude |
|---|---|---|
| Steer a running turn | Yes. The message joins the active turn. | No. Submit rejects the steer. The message waits in the queue, or it goes after you stop the run. |
| Replace the latest sent message | Yes. The desktop rolls back the latest turn and sends the edited message. | No. Submit rejects the replacement. |
| Speed mode | Yes. Fast sends service | No. Submit rejects every mode other than standard. |
| Stop and send now | Yes. It takes back an accepted steer and sends it as a new turn. | No. A Claude session has no accepted steer to take back. |
| Goals | Yes. One run can hold many turns. | No. The call fails with “Goals are available in Codex sessions.” |
| HB Code tools | The same catalog. Codex runs these tools without asking. | The same catalog. maintain_ |
| HTML document worker | A Codex turn on the session’s app-server, with the settings of the calling turn. | The same Codex worker. The tool call leases a Codex app-server, so the document is written by Codex also in a Claude session. |
| Child threads | A child thread can accept direct input when Codex reports can | A subagent never accepts direct input. |
| Command output | The desktop stores live output as an artifact, and reads saved output from the app-server. | The host reads saved output on demand with tool |
| Continue a saved conversation | Yes. The project directory is optional in the list request. | Yes. The list request needs a project directory. |
Accounts, limits, and usage history
Each agent has its own accounts and its own plan limits. Codex has auth profiles, and Claude has the default account and added account folders. A switch changes who pays for the next turn. It never moves a conversation. The desktop keeps a Codex limit reading for 60 seconds, and one reading per Claude account for 60 seconds.
Every fresh limit reading also goes into the table agent_usage_samples in the desktop database. The save runs on its own task, so a limit read never waits for it and never fails because of it.
| Rule | Value |
|---|---|
| Row | agent, account_ |
| Saved windows | Only the windows for all models: session (the rolling five hours) and weekly. Codex windows are known by their length, 300 and 10,080 minutes. A Claude window for one model is not saved. |
| Repeat filter | A reading is skipped when the newest saved one of the same window is under 10 minutes old, has the same reset time, and differs by less than 0.5 percentage points. |
| Retention | Readings stay 35 days. The desktop deletes older ones at most once an hour. |
| What a read returns | The weekly readings of the active account from the last 14 days, and the time of the oldest saved reading of that agent. |
| Codex tokens | For Codex, the read adds up to 30 days of daily tokens and a summary from the app-server call account/ |
Settings > Agents on the desktop shows both agents side by side, and then the usage, the history charts, and the accounts of the selected agent. It reads the history through the command agent_usage_history_command. The phones read the same data through system.agentUsageHistory.