Skip to content

Add Codex as an external agent for Maple tasks - #934

Merged
AnthonyRonning merged 3 commits into
MaplePrivacyLabs:masterfrom
benthecarman:plan-codex-integration
Sep 15, 2026
Merged

AnthonyRonning merged 3 commits into
MaplePrivacyLabs:masterfrom
benthecarman:plan-codex-integration

Conversation

@benthecarman

Copy link
Copy Markdown
Contributor

Summary

Lets a Maple Agent (GPUI) task hand work to the Codex CLI installed on the machine, the way Paseo lets one harness drive another. Codex is the first provider; the tool contract is generic so another harness can slot in.

  • Tools: agent_start, agent_send, agent_status, agent_cancel, list_agent_providers. Blocking or background per call; threads round-trip so a follow-up keeps context; at most four agents per task.
  • Driver: one codex app-server child per thread, spoken to over its JSON-RPC stdio protocol, under the user's own Codex account and configuration. Codex's own sandbox_mode and approval_policy govern what it may do. Maple sends only the prompt, the working directory, and the feature flag that enables Codex's question tool outside plan mode.
  • Approvals: what Codex asks approval for lands in Maple's existing permission card through a new external origin on the pending-permission table. In Allow all Maple accepts on the user's behalf. Requests left open when a turn ends are withdrawn.
  • Questions: blocking (plan mode) and asynchronous (default mode) both open Maple's question card; an async answer that arrives after the turn ended starts a follow-up turn on the same thread.
  • Transcript: the tool call's row streams the agent's text, commands with exit codes, file changes, and todo list; a row above the composer carries a Stop control. Background turns persist their activity so a reopened task still shows it, then tell the task to read the result.
  • Lifecycle: Stop interrupts and kills the process group (Codex does not always end a sandboxed command on interrupt); the thread stays on disk and the next send resumes it. Children die on runtime stop, logout, and task delete.
  • Integrations: a Codex card with version and sign-in state, off by default. Enabling installs the handoff, committee, and advisor skills into the account's Goose skills directory; disabling removes only what Maple wrote.

Design decisions are recorded in apps/maple-agent/docs/external-agents.md.

Testing

  • just ci in apps/maple-agent: fmt, four clippy sets, workspace build and tests, headless tests.
  • Driver tests run against a fake app-server fixture (the test binary re-executed behind a shell shim, Unix only): Allow-all auto-approval with streamed rows, Read-only approval routing and decline, cancel with process-group kill and resume on the same thread, background completion, blocking and async question round-trips, registry limits.
  • Manual smoke on Linux with Codex 0.154 against a disposable project: integrations card and toggle, tools reaching the model, approvals both ways through the card, Codex's config governing the sandbox, blocking and async questions, background delivery and reopen, Stop killing the sandboxed child, follow-up on the same thread, the four-agent cap, skills in the / menu.

Not covered

  • Windows and macOS runtime smoke. Windows compiles the same code with codex.exe preferred over the npm codex.cmd shim; the driver fixture tests are Unix only.
  • Task delete and sign-out with a running agent were tested only in unit tests.
  • Flatpak builds report external agents as unsupported.

🤖 Generated with Claude Code

benthecarman and others added 3 commits September 15, 2026 01:11
A Maple task can now hand work to the Codex CLI installed on the
machine through five generic tools: agent_start, agent_send,
agent_status, agent_cancel, and list_agent_providers. Goose stays the
engine; Codex runs as one `codex app-server` child per thread, under
the user's own Codex account and configuration, driven over its
JSON-RPC stdio protocol.

Codex's approval requests come to the user through Maple's existing
permission card, with a new origin on the pending-permission table so
the decision routes back to the waiting agent instead of Goose. In
Allow all Maple accepts on the user's behalf. Questions, blocking in
plan mode or asynchronous in the default mode, open Maple's question
card; an asynchronous answer that lands after the turn ended starts a
follow-up turn on the same thread.

The tool call's transcript row streams the agent's text, commands,
file changes, and todo list, and a row above the composer carries a
Stop control. A background turn persists its activity so a reopened
task still shows it, then tells the task to read the result. Stopping
an agent interrupts it and kills its process group, because Codex does
not always end a sandboxed command on interrupt; the thread stays on
disk and the next send resumes it in a fresh process. Children die on
runtime stop, logout, and task delete.

The integration is off by default. Enabling it in Settings installs
the handoff, committee, and advisor skills into the account's Goose
skills directory; disabling removes only what Maple wrote. The tools
are always allowed because the agent's own actions are what get gated.

Driver tests run against a fake app-server fixture: the test binary
re-executed behind a shell shim, so the driver resolves and spawns it
exactly as it would the real CLI.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Show the Codex row even before the CLI is installed, so the card can
say how to get it, and draw it with the OpenAI mark (simple-icons,
CC0) instead of the generic plug. Enabling stays gated on a detected,
new-enough installation; sign-in state is reported on the card but
does not gate the toggle, because the tool itself says what to do
when a sign-in is missing.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Describe the tools, the skills, how Codex is driven, who answers its
approvals in each Maple mode, how questions and background turns reach
the transcript, and the current limits.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@AnthonyRonning

Copy link
Copy Markdown
Contributor

End-to-end tested this branch on macOS in an isolated full-stack dev environment (local OpenSecret + billing, Pro fixture account, real Codex CLI 0.154.0-alpha.6.2 signed in via ChatGPT). Found one bug — fix committed locally on top of this branch — plus two friction points worth addressing.

Fixed: macOS PATH mismatch between Settings and task tools.
Settings detects Codex through the runtime's recovered interactive login-shell PATH, but the task tools (list_agent_providers, agent_start, and the shell tool) independently probed Goose's default bash shell. Where the user's login shell is zsh and Codex sits on a zsh-only PATH entry (e.g. ~/.local/bin), Settings showed Codex installed and signed in while tasks reported "Codex is not installed, or codex is not on PATH" and silently fell back to the internal delegate. The fix threads the recovered host PATH through SessionAgentConfiguration into MapleDeveloperClient so Settings, the shell tool, and external-agent launch all search the same PATH. The old bash probe remains as the fallback when no PATH was recovered, and non-macOS behavior is unchanged. Includes a regression test with a temporary fake Codex install verifying Settings detection, shell lookup, and the spawned child's PATH all agree, and that process-global PATH is untouched. Component just ci passes, and real delegation was verified end to end (agent_start → Codex thread completed → expected marker, empty command/file lists on a no-write task).

Needs fixing: the model cannot recall truncated agent output.
MAX_RESULT_TEXT_CHARS (4,000, tail-kept) caps what the model ever sees from agent_start/agent_send/agent_status, and the full text is dropped at capture time, so nothing the model can reach retains it. On a read-only repo-inspection task, Codex produced a ~9.8k-character report; the model received only the last 4k, concluded the beginning was lost, and spent three follow-up turns asking Codex to resend pieces of a response Codex had already fully produced (confirmed against Codex's own rollout JSONL: Maple's stored result is an exact tail-cut of the completed output). Truncating the inline result is fine, but the full output needs an on-demand recall path — e.g. persist it and let the model page through with the read tool, the same pattern the shell tool already uses ("output exceeded the limit, use the read tool"). Having the model re-ask the agent for its own output burns turns, tokens, and agent quota, and it will keep happening on any substantial report.

Friction: provider is required on follow-up tools but unused for lookup.
agent_send/agent_status/agent_cancel all require provider, yet require_provider only validates the string and the registry resolves purely by agent_id, which is already provider-namespaced and session-unique (codex-1, …). The model omitted provider twice in a row (Invalid arguments: missing field provider) before getting it right. Consider dropping it from the follow-up tools or making it optional — agent_start is the only call that actually chooses a provider.

@AnthonyRonning

Copy link
Copy Markdown
Contributor

Filed the follow-ups from testing as #940 (assigned to you): the truncated agent output has no recall path, and the follow-up tools require a redundant provider field.

The macOS PATH fix described above is pushed to plan-codex-integration on the main repo (commit 93ed364c, on top of this branch's head); I'll open it as a follow-up PR against master once this lands. Merging this now.

@AnthonyRonning AnthonyRonning left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

End-to-end tested on macOS in an isolated full-stack dev environment: integration detection, real Codex CLI delegation, permissions plumbing, and component CI all verified. Follow-ups filed as #940; the macOS PATH fix comes as a follow-up PR.

@AnthonyRonning
AnthonyRonning merged commit 92517a2 into MaplePrivacyLabs:master Sep 15, 2026
22 checks passed
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.

2 participants