Programmatic chat creation API for Codex Desktop: spawn a sidebar-visible chat with initial prompt from an external orchestrator

Open 💬 4 comments Opened May 22, 2026 by FlareForward
💡 Likely answer: A maintainer (etraut-openai, contributor) responded on this thread — see the highlighted reply below.

What variant of Codex are you using?

App

What feature would you like to see?

A way for an external tool to programmatically create a new chat in Codex Desktop's live "Chats" sidebar with a provided initial prompt, and receive back a chat/thread ID that the external tool can use for status checks and clean teardown.

The concrete shape is up to your team. Plausible options:

  1. URL scheme: codex://new?prompt=<url-encoded-prompt>&title=<url-encoded-title> that activates Codex Desktop, creates the chat in the live sidebar, and returns the new thread ID (e.g., via stdout when invoked through open with a callback URL, or by writing to a known file path).
  2. Extension to codex remote-control: a new-chat subcommand that takes --prompt @file and --title "..." and prints the new thread ID. The infrastructure for remote-control is already in place (v0.130+).
  3. App-side MCP tool exposed by Codex Desktop that other agents can call (similar to how Codex.app already speaks to MCP servers in the other direction).
  4. App-server endpoint that an external process can POST to with the same payload.

Why I need this

I built a plugin (codex-fleet for Claude Code) that orchestrates parallel Codex chats: one operator command opens an orchestrator chat + N lane chats, each pre-loaded with its coordination prompt, so multiple Codex agents work in parallel on a shared cycle with a shared coord document. This is the workflow OpenAI's own multi-architect coordination docs describe (orchestrator + Lane A + Lane B), and it currently requires the operator to manually copy-paste each lane prompt into separate Codex Desktop windows.

I've tried two existing mechanisms and neither fully works:

  • Terminal + codex CLI: Spawning a Terminal.app window that runs codex "prompt" produces a codex-tui session (source: cli) that does appear in Codex.app's Archived chats list — but only after the session ends and the app reloads its archive cache, and never in the live "Chats" sidebar. It also leaks Terminal windows that have to be cleaned up by AppleScript.
  • codex exec "prompt": Produces sessions with originator: codex_exec / source: exec that the Codex Desktop sidebar does not index at all. We verified the -c originator="Codex Desktop" override is explicitly blocked for exec mode ("Failed to set codex exec originator override").

The result: there is no path today to land a chat in the live sidebar from an external orchestrator without driving Codex Desktop's UI with AppleScript keystrokes, which is brittle, requires Accessibility permission, can't capture the thread ID, and breaks if the operator is typing.

Additional information

Related issues I found that touch nearby territory but don't cover this:

  • #24083 (sidebar metadata) — about display of existing chats, not creation
  • #24060 (auto-updated chat titles) — title behavior of existing chats
  • #21657 (custom interaction hooks for desktop pets) — closest in spirit; extends Codex Desktop with external hooks
  • The various remote-control issues are mostly about mobile pairing and auth, not chat creation

If this lands, the codex-fleet plugin (and any similar orchestrator) becomes a one-command parallel-launch tool where each spawned Codex chat is operator-visible in the live sidebar with full session metadata, and cancel/cleanup is targeted by returned thread ID instead of relying on UI scripting.

Happy to provide the codex-fleet plugin source as a concrete consumer-side reference if useful.

View original on GitHub ↗

4 Comments

etraut-openai contributor · 3 months ago

Thanks for the feature request.

It's unfortunate that codex exec doesn't work for this use case. That was going to be my initial suggestion, but you're correct that it produces sessions that are intentionally not enumerated by the app. We should think about ways to improve this.

In the meantime, another approach you could explore is using the app server interface. This is the same interface that the desktop app is built upon. It's a more complex interface than codex exec, but it's also way more flexible. You could ask Codex to write a small script that launches codex app-server and then uses its apis to create a new thread and inject a prompt.

johnastinnett · 3 months ago

This lines up closely with a workflow I’ve been trying to make reliable.

The app-server approach suggested above seems like the right direction for the “create a sidebar-visible chat + inject initial prompt” half of the problem. In my own setup, the adjacent problem is what happens after those lane chats exist: an orchestrator needs to keep those visible top-level chats addressable as durable worker lanes, instead of spawning disposable child sessions that disappear from normal operator workflows.

One useful observation from my local workflow: if I manually spend a few seconds creating the live sidebar chats first, the rest of the orchestration shape fits naturally. Once a normal sidebar-visible thread exists, it already has the right lifecycle, visibility, project scoping, and operator affordances. The missing piece is a supported way to attach that existing thread to an orchestrator as a routable subagent/lane.

I’ve been able to make that style of workflow work locally, but only by directly mutating local thread metadata / parent-child edge state. And reversing that mutation before any version updates. That proves the state shape is viable, but it’s obviously not something users should have to do directly.

I opened a narrower companion issue for that second half here:

https://github.com/openai/codex/issues/24398

And I put together a small reference branch showing one App Server-shaped way to expose it:

https://github.com/johnastinnett/codex/tree/codex/thread-attachments-rpc

That branch does not solve chat creation by itself. The connection I see is:

  1. app-server creates a normal sidebar-visible thread and injects the initial prompt, or the user creates that thread manually in the Desktop app
  2. app-server returns or is given the thread ID
  3. an attachment RPC binds that existing thread to an orchestrator as a persistent, routable subagent/lane
  4. the external orchestrator can then address the lane by thread ID without UI scripting or local state surgery

So I think this issue and #24398 are complementary: this issue covers programmatic thread creation, while #24398 covers durable orchestration over those created or manually prepared threads once they exist.

FlareForward · 3 months ago

Working solution for programmatic sidebar thread creation

We ran into this exact problem and solved it — here's the key and a working implementation.

---

The key: ephemeral: false in thread/start

The app-server JSON-RPC protocol already supports persistent threads. The ephemeral field in thread/start defaults to true (disposable, not written to disk). Setting it to false persists the thread to ~/.codex/state_5.sqlite — the shared database the desktop app polls — and threads appear in the sidebar within seconds.

Implementation

We use a broker pattern: an app-server-broker.mjs daemon starts codex app-server as a subprocess and proxies JSON-RPC over a Unix socket. The client connects, creates a thread, sets its name, and fires off the initial turn:

\\\`javascript
// Create a persistent (non-ephemeral) thread
const { thread } = await client.request("thread/start", {
cwd,
approvalPolicy: "never",
sandbox: "workspace-write",
serviceName: "codex-fleet",
ephemeral: false, // ← persisted to state_5.sqlite → sidebar-visible
experimentalRawEvents: false,
});

// Set the sidebar display name (non-fatal on older CLI versions)
try {
await client.request("thread/name/set", { threadId: thread.id, name: "My Project Lane A" });
} catch {}

// Fire-and-forget the initial prompt — socket.end() flushes before FIN,
// so the broker receives turn/start even when the client closes immediately
client.request("turn/start", {
threadId: thread.id,
input: [{ type: "text", text: prompt, text_elements: [] }],
model: null,
effort: null,
outputSchema: null,
}).catch(() => {});
\\\`

Why fire-and-forget works

socket.write() is synchronous in the Node.js buffer. socket.end() flushes the buffer before sending FIN, so the broker subprocess receives the turn/start message even when the client closes immediately after. The agent runs independently in the broker subprocess; closing the client connection doesn't stop it.

Verification

We verified with a smoke test: all 3 threads (orchestrator + lane-a + lane-b) appeared in ~/.codex/session_index.jsonl within 3 seconds and showed sidebarVisibility: true. Threads created this way are indistinguishable from ones created interactively in the desktop app.

---

Hope this helps! This is the pattern we're using to auto-spawn multi-lane parallel Codex sessions directly into the sidebar without any manual copy-paste.

FlareForward · 3 months ago

Clarification after deeper testing: the ephemeral: false mechanism above works for the VS Code extension context (threads written to ~/.codex/state_5.sqlite appear in the VS Code Codex sidebar). The standalone Codex desktop app appears to be cloud-backed — it stores/fetches threads from OpenAI's API and caches in its own Electron LevelDB (~/Library/Application Support/Codex/), and does not read from ~/.codex/state_5.sqlite. So programmatically-created threads via the broker approach show up in VS Code but not in the desktop app's sidebar.

If there's a way to target the desktop app's thread store (either a public API or a documented IPC protocol), we'd love to know — that would complete the picture.