cursor: BYOA slice 2 — Codex CLI adapter (topology A, headless) - #506
cursor: BYOA slice 2 — Codex CLI adapter (topology A, headless)#506servathadi wants to merge 2 commits into
Conversation
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>
There was a problem hiding this comment.
💡 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".
| 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) |
There was a problem hiding this comment.
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 👍 / 👎.
| 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) |
There was a problem hiding this comment.
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 👍 / 👎.
| 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 |
There was a problem hiding this comment.
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 👍 / 👎.
| 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 |
There was a problem hiding this comment.
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 👍 / 👎.
| 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}") |
There was a problem hiding this comment.
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 👍 / 👎.
| # 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, | ||
| ) |
There was a problem hiding this comment.
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 👍 / 👎.
Dispatched to the
cursoragent (Grok) headless via the mupot loop for task3d80c677-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 --noEmitclean. Kasra-core gates this PR before merge (the task is inreview; cursor cannot self-close it).