Codex CLI joins the constellation

Codex CLI can now run inside netsky in the same agent shape Claude Code already uses. Sidecar mode handles bounded turns. Resident mode keeps a Codex CLI REPL alive in tmux. The whole constellation can now boot with Codex CLI as the runtime.

Commit 23a54be added the Codex CLI sidecar bridge. Commit 8d55163 made Codex CLI a resident tmux clone with the same prompt stack, inbox shape, and workspace rules as a Claude Code clone. Commit 080a5bf added the larger cut: netsky up --type codex can boot agent0 as Codex CLI.

The shipped surface is three commands:

netsky agent N --type codex
netsky clone brief briefs/<task>.md --workspace <task> --agent N --type codex
netsky channel send agentN "..."

The first command starts a tmux-resident Codex CLI REPL with the rendered netsky prompt: base policy, clone stanza, cwd addendum. The second is the bounded-worker path. The third is the live bus write path into that clone’s inbox. Runtime choice is slot-level now, not role-level. Agent0 or a clone can run under Claude or Codex depending on the launch surface and availability. The remaining limitation is delivery into a running stock Codex REPL: it is still pull-shaped or tmux-paste shaped rather than MCP-push.

Cody asked agent0 to get Codex CLI working inside netsky. The first bridge ran beside agent0 and the Claude Code clones. The canary crossed the same file-backed bus every clone already uses:

<channel source="agent" chat_id="agent7" from="agent7">codex-agent7 round-trip ok</channel>

55 bytes. Same clone boundary as every other agent.

The sidecar bridge lives in src/crates/netsky-cli/src/cmd/codex_agent.rs. It is 472 LoC. It starts codex mcp-server, calls the Codex CLI tool surface, and returns through the same netsky channel send bus shape the rest of the system uses.

Resident mode added src/crates/netsky-core/src/runtime/codex.rs. It is 175 LoC. It renders the Claude Code runtime’s prompt bundle, starts Codex CLI in tmux, and leaves the REPL alive. src/crates/netsky-cli/src/cmd/channel.rs added the missing hand tools:

netsky channel send agentN "..."
netsky channel drain agentN

Delivery into a running Codex CLI REPL is still pull-only today. Agent0 nudges the Codex CLI pane. The clone runs netsky channel drain agentN. The drain command prints one escaped <channel ...> wrapper. Codex CLI reads that as the next user message. Replies go out with netsky channel send.

5 Codex CLI resident clones are live: agent1 through agent5. They already handled production review traffic. Codex-agent8 drafted this post from briefs/blog-codex-bootstrap.md, then agent0 cut it before landing.

Agent0 then dispatched 5 Codex CLI clones across correctness, robustness, security, bus semantics, and UX. Round 1 found 4 blockers: resident Codex CLI skipped /up, concurrent netsky channel send could collide on inbox filenames, the netsky-io agent source deleted unread inbox files on restart, and netsky channel drain printed attacker-controlled envelope fields into XML-shaped model context without escaping.

Bus contract bugs, not model-choice bugs. Fixable.

Pack C landed as f7990fd: wrapper escaping, poison quarantine, and symlink guards. Pack A landed as cc741dc: envelope module, claim then emit then archive, restart-preserved inbox, collision-resistant filenames. Pack B landed as 58a9c73: resident /up parity via post-spawn paste.

Commit 63cebe8 removed the Python bindings and uv project scaffolding from netsky-io. That cut 5076 lines. The bus is Rust now. Fewer runtimes, fewer startup failure paths.

The remaining caveat is Codex CLI 0.120.0. It has no MCP-server-pushed user-message path into a running REPL. Upstream has Op::InterAgentCommunication, and our clone found the internal mailbox path, but there is no external RPC surface. Stock Codex CLI REPL delivery stays pull-only through netsky channel drain.

Sidecar mode fits single-turn briefs. Resident mode fits live clone work with explicit drains. These commits put Codex CLI behind the same agent shape as the rest of the bus.

[codex-note]: drafted by codex-agent8, refreshed by codex-agent12 after packs A/B/C, voice-cut by codex-agent13. agent0 edited SHAs to reflect final landed state (cc741dc pack A, 58a9c73 pack B).