Skip to content

cursor: BYOA slice 2 — Codex CLI adapter (topology A, headless) - #506

Closed
servathadi wants to merge 2 commits into
mainfrom
cursor/task-3d80c677
Closed

cursor: BYOA slice 2 — Codex CLI adapter (topology A, headless)#506
servathadi wants to merge 2 commits into
mainfrom
cursor/task-3d80c677

Conversation

@servathadi

Copy link
Copy Markdown
Contributor

Dispatched to the cursor agent (Grok) headless via the mupot loop for task 3d80c677-a8c1-4e2b-aebb-e0bd753e29c9.

Task done-when: A codex runtime adapter conforming to runtime-adapter/v1 dispatches via codex exec [PROMPT] headless (--sandbox, --json), reads mupot's remote MCP via ~/.codex/config.toml [mcp_servers] (url + bearer_token_env_var; note: NO SSE, streamable-HTTP only), filters own-assignee open tasks, isolates a worktree, verifies, lands at review, never deploys; conformance smoke green; a codex agent + token can be minted and attached end-to-end (dry-run acceptable if no live codex creds); PR opened (NOT merged).

Driver verified: cursor committed real work + tsc --noEmit clean. Kasra-core gates this PR before merge (the task is in review; cursor cannot self-close it).

kasra and others added 2 commits July 23, 2026 15:01
Make the topology-A workers the reference adapter: shared client with
server-derived identity, declared runtime types, fleet-attach:v1, and
land-at-review rails — so later BYOA harnesses copy one contract, not
bespoke glue.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add topology-A codex-worker that dispatches via `codex exec --sandbox --json`,
reads mupot MCP over streamable-HTTP (url + bearer_token_env_var, no SSE),
filters own-assignee open tasks, isolates a worktree, verifies, and lands at
review — proving the contract generalizes past cursor for cursor→codex failover.

Co-authored-by: Cursor <cursoragent@cursor.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 958066f042

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/codex-worker.py
git("worktree", "add", "-b", branch, str(worktree), "main")
try:
ensure_codex_mcp_config(cfg.mcp_url, CODEX_MCP_ENV_VAR, write=True)
proc = codex_run(worktree, build_brief(task, worktree, branch), cfg.token)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Mark timed-out Codex tasks as blocked

When codex exec exceeds TIMEOUT, subprocess.run raises TimeoutExpired at this call, so execution skips the existing verification failure path; the finally removes the worktree and main merely logs the exception. The task consequently remains in_progress, while subsequent cycles poll only status="open", permanently stranding it. Catch the timeout and report the task as blocked, as mumcp-worker.py already does.

Useful? React with 👍 / 👎.

Comment thread scripts/codex-worker.py
Comment on lines +214 to +217
commits = git("log", "main..HEAD", "--oneline", cwd=worktree, check=False).stdout.strip()
if not commits:
return False, "no commits — codex produced no work"
tsc = subprocess.run(["npx", "tsc", "--noEmit"], cwd=str(worktree), capture_output=True, text=True)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject uncommitted Codex output before delivery

If Codex creates at least one commit but leaves later edits uncommitted, this check compiles the dirty working tree and reports success, while git push sends only the committed snapshot. The resulting PR can therefore omit the changes that made verification pass or completed the task, yet the task is moved to review as verified. Require a clean worktree before accepting the result, or verify the committed tree rather than the working directory.

Useful? React with 👍 / 👎.

Comment on lines +205 to +207
def _load_ed25519_private_key(key_path: Path) -> Any:
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives.serialization import load_pem_private_key

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Declare the signing library required by signed attach

On a fresh installation, configuring CURSOR_KEY, MUMCP_KEY, or CODEX_KEY enters this path and raises ModuleNotFoundError because cryptography is not part of the standard library and the repository has no Python dependency manifest declaring it. boot_session then swallows the failure as non-fatal, so key-backed agents continue processing tasks without successfully attaching; additionally, the server's downgrade guard prevents bearer attach once a key is registered. Package or document/install this dependency, or use an already-declared signing implementation.

Useful? React with 👍 / 👎.

Comment thread scripts/codex-worker.py
Comment on lines +154 to +156
if prior and f'bearer_token_env_var = "{env_var}"' in prior.group(0) and f'url =' in prior.group(0):
log(f"mcp config already present in {CODEX_CONFIG}")
return stanza

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Update Codex MCP configuration when the endpoint changes

When ~/.codex/config.toml already contains a mupot stanza with the expected bearer environment-variable name, this condition accepts any url rather than verifying it equals mcp_url. Changing MUPOT_MCP therefore leaves Codex connected to the old pot while the driver exports the new pot's bearer token, which both breaks MCP context and can disclose that credential to the stale endpoint. Compare the parsed URL with the requested endpoint and rewrite the stanza when they differ.

Useful? React with 👍 / 👎.

Comment on lines +430 to +438
try:
ack = attach(cfg, identity, host=host)
agent_view = ack.get("agent") if isinstance(ack.get("agent"), dict) else {}
log(
f"attach ok runtime={agent_view.get('runtime', cfg.runtime)!r} "
f"status={agent_view.get('status', 'running')!r}"
)
except Exception as exc: # noqa: BLE001 - attach failure must not block task work
log(f"attach failed (non-fatal this cycle): {exc}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Detach on-demand workers before process exit

Each cursor, mumcp, and Codex driver declares lifecycle="on_demand", calls this attach before polling, and then exits after its one-shot cycle without invoking either bearer or signed detach; the shared adapter does not implement a detach operation despite declaring its constants. A successful attach sets the fleet row to running, so even a cycle with no tasks leaves the now-exited process reported as running until it merely becomes stale, rather than explicitly offline. Pair successful attaches with the appropriate detach in a shutdown/finally path.

Useful? React with 👍 / 👎.

Comment thread scripts/codex-worker.py
Comment on lines +417 to +424
# Still resolve identity when a token exists so dry-run exercises the contract path.
try:
identity: RuntimeIdentity = boot_session(
cfg,
presence_adapter="codex",
presence_capabilities=["build"],
log=log,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep Codex dry runs from writing remote state

With DRY_RUN=1 and an existing token, this branch calls boot_session, which performs a real attach POST and a real presence_register MCP write before the read-only poll. This contradicts the documented DRY_RUN behavior of “poll + print, do nothing” and can mark an agent running or alter dispatcher presence while an operator is only attempting to preview execution. Resolve identity and poll through read-only calls in this branch, or make boot_session explicitly dry-run aware.

Useful? React with 👍 / 👎.

@servathadi

Copy link
Copy Markdown
Contributor Author

Superseded by #522 (522 = this PR's 15 files + 4 more, full rebuild + fix). Closing in favor of #522.

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