Codex agents

What codex-scuba bridges, which Codex app-server versions it accepts, and what the integration can and cannot do.

Connecting agentsdocs/agents/40-codex.mdx
On this page

@octostaff/codex-scuba lets an OpenAI Codex agent join Bubble threads as a first-class bot principal. It drives the Codex CLI's app-server over a persistent connection, so a Codex conversation streams, resumes, takes mid-turn steering, and raises its own approvals and questions inside a shared thread.

When to Use It

Use @octostaff/codex-scuba when Codex should participate in Bubble threads from a local machine, developer workstation, or long-running daemon that dials into Bubble.

Use @octostaff/claude-scuba instead when the agent is Claude Code, or @octostaff/reef when Bubble needs to call out to a hosted A2A or ACP agent.

Supported Codex versions

The Codex CLI comes along as the exact @openai/codex 0.146.0 dependency, including its per-platform native binary. codex-scuba launches that bundled binary directly—not a codex found on PATH—unless broker.codexPath selects another executable explicitly.

This release's compact wire contract is tested against the Codex app-server minor releases 0.143.0 through 0.154.0. Those twelve exact .0 versions are accepted during initialization; any other version, including a patch release such as 0.153.4, fails fast rather than silently claiming compatibility.

Two layers of tests cover that span. An auth-free matrix runs all twelve versions through the core handshake, model/mode discovery, ephemeral thread start/unsubscribe, the presence of Scuba's required protocol methods, and an MCP tool round trip against a local model fixture. The authenticated turn/integration suite runs against a real model only at the two ends of the span, 0.143.0 and 0.154.0. The versions between them never execute an authenticated turn, so this is not a claim that every model-backed behavior is identical on each. To pin a different tested version, install its CLI separately and set broker.codexPath to that executable.

What it can and cannot do

codex-scuba runs on the shared amphibian core, so it behaves like claude-scuba for grant-driven thread membership, draining, durable delivery, group-chat prompt framing, and interrupts. Its persistent app-server connection adds streaming, resume, mid-turn steering, and server-initiated approvals/questions:

Capabilitycodex-scubaNotes
Prompt and stream a turnYesthread/start or thread/resume, followed by turn/start and item notifications
Resume a conversationYesthe Codex thread id is the session's durable remoteSessionId, preflighted with thread/read once per process
Interrupt a turnYesturn/interrupt
Steer mid-turnYesturn/steer appends new user input to the active turn
Per-action approvalYescommand, file-change, permission, and Bubble tool requests use Starfish's approval card, with the fidelity limits below
Plan modeYes/mode plan uses Codex's experimental Plan preset, streams a checklist, asks questions, and emits final plan Markdown
Session commandsYes/model, /mode, /effort, /sandbox, /approval, /cd; values are reapplied if the app-server child restarts
Model/mode discoveryYescommand choices come from model/list and collaborationMode/list
Local Bubble toolsNot yettracked in #90

Provider tools and Office document changes

Codex Scuba exposes tools advertised by other Bubble participants through its private bubble MCP server. The endpoint is created automatically for each broker; no MCP configuration or refresh command is needed from the user. The endpoint accepts only calls from the active turn.

Codex decides which of those calls need approval, using each tool's read-only hint and the thread's approval_policy and approvals_reviewer:

Toolapproval_policyapprovals_reviewerWhat happens
Marked read-only by its provideranyanyRuns without approval
Anything elseon-request or granularuser (default)An approval card appears in the thread
Anything elseon-request or granularauto_reviewCodex's auto reviewer decides; no card
Anything elseuntrustedanyAn approval card appears in the thread
Anything elseneveranyRefused; Codex tells the model
Anything elsenever with danger-full-accessanyRuns; Codex approves it itself

An approved card becomes the call's row, and the provider's result lands on it. A rejected call never reaches the provider. Calls that Codex refuses or its auto reviewer denies show no card; the model reports them. A provider marks a tool read-only with annotations.readOnlyHint on its advertised descriptor; Office marks its read tools that way. A changed hint takes effect from the next turn. Under never, Scuba declines on every supported version, including the 0.143.0–0.147.0 releases that would otherwise still ask.

With supported Codex versions 0.143.0–0.154.0, MCP tool-list notifications and ordinary resume do not update the model's cached catalog. When tools or input schemas change, Scuba unsubscribes and resumes the same saved Codex thread before the next turn, then waits for Codex to fetch the current MCP catalog. This preserves the conversation history. Changes during an active turn take effect on the next turn; steering the current turn does not refresh its catalog. Reconnection can release transient runtime resources, so this is not a guarantee that background processes survive a catalog change.

The bridge checks every call against the live provider and input schema before requesting a result, including after its input is stored. A disconnected Office document or withdrawn tool cannot be invoked using an old descriptor. Results must match the targeted provider and run. If refresh fails, the turn reports an error and the saved conversation remains available for retry.

Threads created by the previous dynamic-tool bridge may retain those old schemas in Codex's history. Calls through that old path are rejected with an instruction to discover the current MCP tools. Scuba does not reset the conversation to remove the old descriptors.

Compatibility checks exercise all twelve supported binaries with local MCP and model fixtures, including a read-only call under never and an approved, declined, and never-refused write call. The integration suite additionally drives real Bubble, the production bridge, and a real Codex binary through late activation, changed Word selectors, adding Excel tools, a provider result, withdrawal, and an approved and a rejected write call. These checks verify the transport and catalog lifecycle; real Office-host verification remains tracked separately in #171.

Approval policy

Leave broker.codex.approvalPolicy unset to inherit Codex's effective user and trusted-project configuration. Simple overrides support never, on-request, and untrusted; native granular approval policies are also accepted. An in-thread /approval command has highest precedence for that live broker; it is reapplied after an app-server child restart, but a full bot restart returns to Scuba/native configuration.

The shared Starfish approval card is intentionally simpler than the full Codex protocol:

  • Approve maps only to a one-shot command/file acceptance. Reject uses an advertised decline decision when available and otherwise cancel; a decision set without both a one-shot accept and a safe negative choice fails closed instead of showing a misleading card. A permission approval grants the requested profile for the current turn, and an interrupt cancels it.
  • The UI cannot request acceptForSession, policy or network amendments, permission subsets, session-scoped permission grants, or strictAutoReview.
  • A Bubble tool approval covers that one call. Codex's offers to remember the approval for the session or for good are never accepted.

Plan mode and interactive-question fidelity

Use /mode plan for Codex Plan mode and /mode default to return to ordinary execution. Plan progress is shown on the existing checklist surface; the completed plan is emitted as normal Markdown.

Codex questions use Starfish's existing AskUserQuestion form. Each question currently returns exactly one string answer. Secret questions and requests with duplicate question text fail closed rather than persisting plaintext or routing an answer ambiguously. Multiple answers are unsupported, and Codex's isOther restriction cannot be represented exactly because the shared form may expose a free-text answer regardless.

broker.codex.effort accepts the non-empty reasoning-effort value advertised for the selected model. If omitted, the effective Codex/model default remains in charge; an unsupported pairing surfaces as a turn error.