Codex agents
What codex-scuba bridges, which Codex app-server versions it accepts, and what the integration can and cannot do.
docs/agents/40-codex.mdxOn 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:
| Capability | codex-scuba | Notes |
|---|---|---|
| Prompt and stream a turn | Yes | thread/start or thread/resume, followed by turn/start and item notifications |
| Resume a conversation | Yes | the Codex thread id is the session's durable remoteSessionId, preflighted with thread/read once per process |
| Interrupt a turn | Yes | turn/interrupt |
| Steer mid-turn | Yes | turn/steer appends new user input to the active turn |
| Per-action approval | Yes | command, file-change, permission, and Bubble tool requests use Starfish's approval card, with the fidelity limits below |
| Plan mode | Yes | /mode plan uses Codex's experimental Plan preset, streams a checklist, asks questions, and emits final plan Markdown |
| Session commands | Yes | /model, /mode, /effort, /sandbox, /approval, /cd; values are reapplied if the app-server child restarts |
| Model/mode discovery | Yes | command choices come from model/list and collaborationMode/list |
| Local Bubble tools | Not yet | tracked 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:
| Tool | approval_policy | approvals_reviewer | What happens |
|---|---|---|---|
| Marked read-only by its provider | any | any | Runs without approval |
| Anything else | on-request or granular | user (default) | An approval card appears in the thread |
| Anything else | on-request or granular | auto_review | Codex's auto reviewer decides; no card |
| Anything else | untrusted | any | An approval card appears in the thread |
| Anything else | never | any | Refused; Codex tells the model |
| Anything else | never with danger-full-access | any | Runs; 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
declinedecision when available and otherwisecancel; 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, orstrictAutoReview. - 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.