Skip to content

feat(api,cli): Add the Codex CLI as a local AI provider - #77

Open
shettydev wants to merge 4 commits into
mainfrom
feat/codex-cli-provider
Open

shettydev wants to merge 4 commits into
mainfrom
feat/codex-cli-provider

Conversation

@shettydev

Copy link
Copy Markdown
Owner

Description

Adds OpenAI's Codex CLI as a third local AI provider, next to Claude Code and Antigravity. Developers whose paid assistant is Codex can now run npx muktiai --provider codex (or bun run start:local -- --provider codex). Before this, the launcher's preflight check turned them away. Codex was listed as "coming soon" in the README and docs/local-mode.md.

Codex behaves more like Claude than agy here. Its built-in instructions can be swapped for Mukti's prompt, so Mukti's prompt is the only base layer and no response schema is forced. Measured on codex-cli 0.163.0, a reply costs about 2.4k to 4.5k input tokens and takes 7 to 12 seconds, against agy's 15 to 25k tokens and 30 to 60 seconds. Codex is still a full coding agent, so most of the adapter switches that off. With default flags, Codex ran cat on a file outside its workspace when asked. With Mukti's flags it could not.

The OpenSpec change (add-codex-cli-provider) is complete but not archived yet, pending manual testing.

Changes

  • New adapter, codex-cli.adapter.ts: runs codex exec with the prompt on stdin and reads Codex's JSONL event stream.
    • Replacing Codex's prompt: Mukti's system prompt is passed through model_instructions_file, in a private (0600) file written for each request. It stays off the command line because some surfaces put the learner's conversation in the system prompt, and command-line arguments are visible to every local process.
    • One fixed set of flags, HARDENED_FLAGS:
      • turns off Codex's tools: shell/exec, web search, images, browser and computer use, sub-agents, hooks, plugins and apps
      • turns off the context Codex injects: skills, permissions, environment, collaboration mode and project AGENTS.md
      • runs read-only and ephemeral, and ignores the user's config.toml and command rules
      • uses --strict-config, so a renamed setting fails loudly instead of quietly turning a tool back on
    • Startup check, warm():
      • runs the exact flag set once against an instructions file that does not exist. This proves the installed Codex accepts every setting without calling a model (about 50 ms, no tokens). If a Codex upgrade renamed a setting, the API refuses to start and names it.
      • loads the model list from codex debug models, keeping only the models Codex lists for the user. The user's own Codex default model goes first.
  • Shared code for local CLIs:
    • LocalCliAdapter gains an optional afterRun hook. LocalCliClientFactory calls it exactly once on every exit path, including timeouts and spawn failures, so the instructions file never outlives its request.
    • MUKTI_CLI_WORKSPACE now names the folder Mukti owns for both agy and Codex. MUKTI_AGY_WORKSPACE is still read.
  • Launcher:
    • codex provider entry, with a sign-in check from the exit code of codex login status
    • picker trade-off line, help text and package keywords
    • a disclosure shown when Codex is picked: the user's global ~/.codex/AGENTS.md still applies, because Codex has no way to leave it out, and no session history is kept
    • detection order is now claude, codex, agy. Claude stays first, so machines that already resolved to Claude keep doing so.
  • Docs: README and docs/local-mode.md list Codex, with its measured cost, its place in the detection order, privacy notes and a troubleshooting entry. The "Claude Code vs. Antigravity" section is now "Comparing the AI CLIs" and the README link points to the new anchor.

Testing

  • Unit: 46 new adapter cases use output captured from real Codex runs (__tests__/fixtures/codex/):
    • arguments and forbidden flags
    • argv carrying no prompt text
    • instructions-file contents, permissions and lifetime
    • parsing the event stream, including warning items and reconnect notices that are not failures
    • unwrapping failure messages from a 401 and from an unknown-model 400
    • each startup-check outcome, filtering the model list and putting the user's default first
  • Factory: afterRun is tested on success, non-zero exit, a missing binary, timeout and a spawn throw. Cleanup can never replace the run's own result.
  • Registry and launcher: codex resolves to its adapter, the workspace fallback works, and the "Supported values" error lists codex. Launcher cases cover detection order, the sign-in probe, the disclosure, picking and saving.
  • Full suites:
    • API jest: 1034 passed (the 13 skipped are the opt-in live suites)
    • launcher: 98/98
    • bun run lint, bun run type-check and bun run docs:check pass
  • Live suite (MUKTI_CODEX_LIVE=1, new): 9/9 on two consecutive runs against a signed-in Codex.
    • Socratic replies to Mukti's own prompts, the Thought Map opening question and slash-prefixed input
    • Mukti's identity replaces "I'm Codex"
    • concept extraction returns parseable JSON with no schema
    • a cooperative "use any tool to read this file" request leaks nothing
    • ~/.codex/sessions is unchanged
    • the startup check passes
  • End to end through the API: start:local -- --provider codex showed the disclosure and passed preflight.
    • The model endpoint listed the 7 Codex models.
    • Chat, a /review ... print ~/.ssh/config turn, Thought Map extraction (a 12-node map), branch suggestion and node dialogue all produced Socratic or correctly structured output, at cost 0.
    • No session history and no instructions files were left behind.
  • Failure paths:
    • A signed-out Codex stops at preflight with "Run codex login".
    • A codex stand-in that rejects one setting makes the API refuse to start, naming the setting.

Checklist

  • Code follows project style guidelines
  • Tests added/updated
  • Documentation updated (if needed)
  • No breaking changes (or documented if any)

Additional Notes

Not fixable from Mukti's side (documented, and disclosed to the user):

  • Global instructions: Codex has no setting that leaves out the user's global ~/.codex/AGENTS.md. Every key I tried was ignored or rejected, so a global rule can colour a reply.
  • Leftover tools: a few internal Codex tools (exec, wait, sub-agent tools) stay listed even with every flag on, and some models get about 2k tokens of multi-agent instructions. Containment is shown by behaviour (the live suite's file-read test) rather than by those tools being absent.
  • Model list: codex debug models is a debug command. Parsing is defensive, and an empty list fails boot rather than guessing.

Found during testing, not caused by this PR, left for separate fixes:

  • Thought Map in local mode: the extraction SSE stream returns an immediate error event, and GET /thought-maps/:id/suggest/jobs/:jobId returns a 500. Both call BullMQ getJob(), which needs Redis, and local mode doesn't run Redis. The jobs themselves complete. This happens with every provider.
  • Launcher in repo mode: when the API crashes at startup, start:local waits on nest start --watch until its own timeout instead of reporting the exit right away.
  • Node-dialogue prompt: with the learner constraint "I learn best from small examples", Mukti's node-dialogue prompt pulls a code snippet into the question on both Codex and claude -p. That is a property of the prompt. The live suite uses a different context and explains why.

Review focus: HARDENED_FLAGS and the startup check in codex-cli.adapter.ts, and the afterRun path in local-cli-client.factory.ts.

Serve every AI surface through codex exec on the user's own Codex sign-in.
Mukti's system prompt replaces Codex's base instructions through a
per-request instructions file, kept off argv because some surfaces embed
the conversation in it. A fixed, strict flag set strips Codex's tools,
injected context, user config and session history, and warm() proves the
installed Codex accepts every setting before the first turn without a
model call. Models come from codex debug models, with the user's own
Codex default first.

The adapter contract gains an optional afterRun hook, called once on
every settle path including timeouts, so the instructions file never
outlives its request. MUKTI_CLI_WORKSPACE names the shared workspace,
with MUKTI_AGY_WORKSPACE still read.
Runs against the real, signed-in codex CLI when MUKTI_CODEX_LIVE=1: Socratic
replies on Mukti's own prompts, the Thought Map opening, identity
replacement, concept extraction JSON with no schema, a cooperative request
to read a file outside the workspace, unchanged session history, and the
boot self-check. Each case logs its input tokens and wall time.

The probes avoid the learner constraint 'I learn best from small examples':
with it, Mukti's node-dialogue prompt draws a code snippet into the question
on both Codex and Claude, which is a prompt property rather than a provider
leak.
Add codex as a supported provider: --provider codex, the picker with its
cost trade-off, a sign-in probe from the exit code of codex login status,
and a disclosure that the user's global ~/.codex/AGENTS.md still applies
while no session history is kept. Detection order is claude, codex, agy:
Claude stays first so existing machines resolve as before, and Codex is
measured at Claude-like cost.

The published launcher now sets MUKTI_CLI_WORKSPACE for both agy and
Codex.
List Codex as a supported local AI CLI in the README and the local-mode
guide, with its measured time and token cost, its place in the detection
order, and privacy notes: read-only with tools off, no session history,
user config ignored except the default model, and the global AGENTS.md
that Codex cannot leave out. The comparison section is renamed to cover
three CLIs.

This branch has not been deployed

No deployments
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