codex: drive codex app-server as a second backend - #4
Merged
Conversation
-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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
-backend codexdrives OpenAI's codex CLI over itsapp-serverJSON-RPC protocol, on a ChatGPT subscription. The UI is untouched — everything above theEngineseam already worked inentryvalues, 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:
result.thread.idthread/startsays"read-only",turn/startsays{"type":"readOnly"}turn/startreturns immediately with the turn iditem/startedmodelContextWindowis stated outrightThe 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.goanswers every one. Gated actions get"cancel"— probing showed it is accepted even whereavailableDecisionsoffers 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
TestCodexLiveGatedActionsAlwaysEndTheTurnasserts both rows:buildandbypasswork fully — codex asks for nothing and tools run.askandplanrefuse 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 incodexengine_test.goproves 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.