Skip to content

codex: drive codex app-server as a second backend - #4

Merged
tdwd merged 1 commit into
mainfrom
codex-transport
Sep 7, 2026
Merged

codex: drive codex app-server as a second backend#4
tdwd merged 1 commit into
mainfrom
codex-transport

Conversation

@tdwd

@tdwd tdwd commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

-backend codex drives OpenAI's codex CLI over its app-server JSON-RPC protocol, on a ChatGPT subscription. The UI is untouched — everything above the Engine seam already worked in entry values, so this is a transport, an adapter, and the function that picks between them.

Probed, not guessed

Each of these is a silent failure if assumed:

fact what assuming costs
thread id is at result.thread.id no thread, every turn fails
server request ids start at 0 first approval filed as a stray reply; turn hangs
thread/start says "read-only", turn/start says {"type":"readOnly"} frame rejected, turn never starts, nothing says so
turn/start returns immediately with the turn id Send would block the Update loop
a tool item's content is on item/started approval carries ids only, so nothing to draw
modelContextWindow is stated outright gauge guesses when it need not

The sandbox one bit for real: the first version sent {"mode":"read-only"} and every turn silently failed to start. The live test caught it, review would not have.

Nothing hangs, nothing is granted silently

An unanswered server request stops a turn dead with no error anywhere. codexapproval.go answers every one. Gated actions get "cancel" — probing showed it is accepted even where availableDecisions offers only "accept", and it ends the turn cleanly. Refusing rather than granting is deliberate: the approval pane is not wired to codex yet, and granting silently would defeat the mode whose purpose is asking first.

Current limits, pinned by a live test

TestCodexLiveGatedActionsAlwaysEndTheTurn asserts both rows:

  • build and bypass work fully — codex asks for nothing and tools run.
  • ask and plan refuse gated actions until the approval pane is wired.

Verified

go vet, full suite, and two live tests against the real CLI (CATHODE_CODEX_LIVE=1, off by default since each spends a turn). The scripted stand-in in codexengine_test.go proves the framing cathode expects; the live tests prove the framing codex actually sends. Those are different claims and both are checked.

Not verified: the TUI itself under -backend codex. The engine and adapter are exercised, but nobody has driven the built binary.

-backend codex runs OpenAI's codex CLI over its app-server JSON-RPC protocol,
on a ChatGPT subscription, alongside the existing claude backend. The UI is
untouched: everything above the Engine seam already worked in `entry` values,
so this is a transport, an adapter, and the function that picks between them.

Six things were established by probing the live CLI rather than guessed, and
each one is a silent failure if assumed:

- The thread id is at result.thread.id, not result.threadId, and initialize
  needs a following `initialized` notification.
- Server request ids start at 0, so every id is a *int64. An int cannot tell
  "no id" from "id zero", and the first approval of a session would be filed
  as a reply nobody waits for.
- thread/start and turn/start spell the sandbox differently: "read-only"
  versus {"type":"readOnly"}. The first version guessed {"mode":...} and every
  turn silently failed to start.
- turn/start returns immediately with the turn id, so Send never blocks the
  Update loop and Interrupt has the id it needs.
- A tool item's content is on item/started. The approval request carries ids
  and nothing else, so pairing them by itemId is mandatory, not an
  optimisation.
- codex states modelContextWindow outright, so the context gauge is exact
  instead of the guess -ctx exists to make on claude.

An unanswered server request stops a turn dead with no error anywhere, so
codexapproval.go answers every one. Gated actions get "cancel" — probing
showed it is accepted even where availableDecisions offers only "accept", and
it ends the turn cleanly. Refusing rather than granting is the point: the
approval pane is not wired to codex yet, and granting silently would defeat
the mode whose purpose is asking first.

The Engine seam now takes a cathode mode rather than claude's
--permission-mode vocabulary, which a second backend would otherwise have had
to reverse before doing its own translation.

Verified live per mode: build and bypass work fully, ask and plan refuse gated
actions. CATHODE_CODEX_LIVE=1 runs those tests against the real CLI.
@tdwd
tdwd merged commit 960e8fe into main Sep 7, 2026
1 check passed
@tdwd
tdwd deleted the codex-transport branch September 7, 2026 16:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant