agents/openai.yaml
interface:
display_name: "AI Coding Agents — Remote Runtime"
short_description: "Design remote coding-agent runtimes"
default_prompt: "Use $ai-coding-agents-remote-runtime to design remote sessions, bridge transport, local-UI remote-execution, reconnect logic, or permission bridging for a coding-agent runtime."
data/sources.json
{
"metadata": {
"skill": "ai-coding-agents-remote-runtime",
"title": "AI Coding Agents Remote Runtime - Sources",
"description": "Official documentation and implementation references for remote coding-agent sessions, bridge transport, and local-UI remote-execution patterns",
"last_updated": "2026-07-11",
"updated": "2026-07-11",
"total_sources": 15,
"version": "1.1"
},
"categories": {
"official_documentation": [
{
"name": "Claude Code Documentation",
"url": "https://code.claude.com/docs/en/",
"type": "documentation",
"relevance": "Primary runtime documentation for Claude Code modes and remote behavior",
"update_frequency": "monthly",
"access": "free",
"add_as_web_search": true
}
],
"implementation_references": [
{
"name": "Claude Code GitHub Repository",
"url": "https://github.com/anthropics/claude-code",
"type": "repository",
"relevance": "Authoritative implementation reference for remote sessions, bridge transport, and SDK-to-REPL adaptation",
"update_frequency": "weekly",
"access": "free",
"add_as_web_search": false
},
{
"name": "Anthropic: Claude Code Best Practices",
"url": "https://www.anthropic.com/engineering/claude-code-best-practices",
"type": "guide",
"relevance": "Broader operational patterns relevant to multi-mode coding-agent runtimes",
"update_frequency": "quarterly",
"access": "free",
"add_as_web_search": true
}
],
"cross_runtime_references": [
{
"name": "Codex CLI Documentation",
"url": "https://github.com/openai/codex",
"type": "documentation",
"relevance": "Cross-runtime comparison point for remote-capable coding-agent CLIs",
"update_frequency": "monthly",
"access": "free",
"add_as_web_search": false
},
{
"name": "OpenAI Codex App Server Daemon Source",
"url": "https://github.com/openai/codex/blob/9f42c89c0112771dc29100a6f3fc904049b2655f/codex-rs/app-server-daemon/README.md",
"type": "repository_source",
"relevance": "Pinned first-party source for app-server daemon lifecycle, remote-control bootstrap, JSON lifecycle output, update loop, and serialized mutations",
"update_frequency": "pinned",
"access": "free",
"add_as_web_search": false
},
{
"name": "OpenAI Codex App Server Protocol Exporter Source",
"url": "https://github.com/openai/codex/blob/9f42c89c0112771dc29100a6f3fc904049b2655f/codex-rs/app-server-protocol/src/export.rs",
"type": "repository_source",
"relevance": "Pinned first-party source for TypeScript and JSON schema protocol artifact generation, generated-code headers, and experimental API filtering",
"update_frequency": "pinned",
"access": "free",
"add_as_web_search": false
},
{
"name": "OpenAI Codex App Server Schema Fixture Tests",
"url": "https://github.com/openai/codex/blob/9f42c89c0112771dc29100a6f3fc904049b2655f/codex-rs/app-server-protocol/tests/schema_fixtures.rs",
"type": "repository_source",
"relevance": "Pinned first-party source for schema fixture validation, protocol drift detection, fixture diffs, and explicit schema update workflow",
"update_frequency": "pinned",
"access": "free",
"add_as_web_search": false
},
{
"name": "OpenAI: Codex for (almost) everything",
"url": "https://openai.com/index/codex-for-almost-everything/",
"type": "product_post",
"relevance": "April 2026 first-party source for remote devboxes, multiple terminals, app/browser workflows, memory, automations, and long-running work",
"update_frequency": "static",
"access": "free",
"add_as_web_search": true
},
{
"name": "Model Context Protocol Specification",
"url": "https://modelcontextprotocol.io/",
"type": "specification",
"relevance": "Reference when remote runtimes also bridge tool servers and typed control flows",
"update_frequency": "quarterly",
"access": "free",
"add_as_web_search": true
},
{
"name": "Goose 2.0: ACP and new TUI",
"url": "https://goose-docs.ai/blog/2026/04/08/goose-acp-and-new-tui/",
"type": "announcement",
"relevance": "April 8, 2026: ACP becomes default server mode in Goose 2.0 (not a subcommand). HTTP daemon and WebSocket bridge are opt-in overlays. Relevant to ACP framing in remote-runtime transport selection.",
"update_frequency": "static",
"access": "free",
"add_as_web_search": false
},
{
"name": "Claude Code Routines (web-scheduled-tasks)",
"url": "https://code.claude.com/docs/en/web-scheduled-tasks",
"type": "documentation",
"relevance": "Routines as cloud-hosted remote execution: trigger → container start → agent loop → artifacts → result. Lifecycle model for cloud-container remote runtime mode.",
"update_frequency": "monthly",
"access": "free",
"add_as_web_search": true
},
{
"name": "Claude Code: Continue local sessions from any device with Remote Control",
"url": "https://code.claude.com/docs/en/remote-control",
"type": "documentation",
"relevance": "Verified 2026-07-11. Primary source for the shipped Remote Control feature: server mode (`claude remote-control`, --spawn, --capacity), interactive mode (--remote-control/--rc), /remote-control slash command, outbound-HTTPS-only no-inbound-ports network model, short-lived scoped credentials, and explicit contrast table with Claude Code on the web.",
"update_frequency": "monthly",
"access": "free",
"add_as_web_search": true
},
{
"name": "Claude Code on the web",
"url": "https://code.claude.com/docs/en/claude-code-on-the-web",
"type": "documentation",
"relevance": "Verified 2026-07-11. Cloud-VM session model (claude.ai/code, --cloud flag) distinct from Remote Control; each session gets an isolated VM with the repo cloned. Sibling of Routines on the same cloud-session substrate.",
"update_frequency": "monthly",
"access": "free",
"add_as_web_search": true
},
{
"name": "openai/codex issue #25552 — app-server-daemon README staleness",
"url": "https://github.com/openai/codex/issues/25552",
"type": "issue",
"relevance": "Verified 2026-07-11. Corrects a stale claim about top-level `codex remote-control` bootstrap behavior; clarifies it runs foreground app-server with remote control enabled for one invocation, while daemon lifecycle lives under explicit subcommands.",
"update_frequency": "pinned",
"access": "free",
"add_as_web_search": false
},
{
"name": "openai/codex discussion #21935 — codex remote-control intended direction",
"url": "https://github.com/openai/codex/discussions/21935",
"type": "discussion",
"relevance": "Verified 2026-07-11. Clarifies `codex remote-control` (v0.130+) is infrastructure for SSH workflows and custom integrations, not the user-facing QR/mobile-pairing flow — that flow is mediated by the Codex desktop app.",
"update_frequency": "pinned",
"access": "free",
"add_as_web_search": false
}
]
}
}
learnings.consolidated.md
# ai-coding-agents-remote-runtime — Consolidated Learnings
Curated, dated, committed memory for this skill. Pruned from raw `learnings.md` via `agents-skills-feedback-loop/scripts/consolidate.py`. Human-approved.
Cap: 60 entries. When exceeded, promote durable rules to `references/`.
## Filter Override
<!-- Add 2-4 bullets that sharpen what counts as a learning for this skill. Leave empty to use the default filter from agents-skills-feedback-loop/references/learnings-format.md. -->
## Patterns That Work
## Mistakes to Avoid
## Domain Knowledge
## Open Questions
## Consolidated Principles
learnings.md
# ai-coding-agents-remote-runtime — Learnings
Raw, dated entries. Consolidate periodically into `learnings.consolidated.md` via `agents-skills-feedback-loop/scripts/consolidate.py`.
- 2026-07-11: July 2026 audit found the skill's remote-runtime patterns had drifted from being grounded only in an internal `claude_code` source snapshot to now having live, citable shipped products: Claude Code's `remote-control` feature (`claude remote-control`/`--remote-control`/`/remote-control`, outbound-HTTPS-only with no inbound ports, short-lived scoped credentials, session URL + QR from `claude.ai/code`) is architecturally distinct from the Desktop app's SSH-to-remote-host feature (execution genuinely moves to the remote host) and from Claude Code on the web / Routines (cloud VM, no local machine involved at all). Also corrected a stale claim (flagged in `openai/codex` issue #25552) that top-level `codex remote-control` manages daemon bootstrap — it actually runs a foreground app-server for one invocation; daemon lifecycle is under explicit `codex remote-control start/stop/restart` subcommands, and Codex's user-facing QR/mobile pairing is mediated by the desktop app, not the CLI. Added a "no-inbound-ports relay" transport shape to `transport-selection.md` since the existing WebSocket/SSE/HTTP-POST/ACP tree had no entry for NAT'd, roaming, or otherwise untrusted-network executing machines.
references/bridge-transport-and-permission-bridging.md
# Bridge Transport And Permission Bridging
## Table Of Contents
- [Design Goal](#design-goal)
- [Typed Control Messages](#typed-control-messages)
- [Permission Bridging](#permission-bridging)
- [SDK-To-REPL Message Adaptation](#sdk-to-repl-message-adaptation)
- [Reconnect Behavior](#reconnect-behavior)
## Design Goal
Remote coding-agent transport should not be “chat messages over WebSocket.” It should separate conversation messages from control-plane requests such as approvals, cancellations, reconnect notices, and plugin or server refresh.
## Typed Control Messages
`RemoteSessionManager.ts` uses separate SDK control requests and responses:
- normal SDK messages are forwarded to the UI
- control requests handle approval
- control-cancel requests clear pending approval state
- unsupported control types receive structured errors instead of hanging
That is the right baseline for a bridge protocol.
## Permission Bridging
Remote approval is bridged by:
- storing pending requests by request ID
- surfacing a local approval UI
- returning a structured allow or deny result
`remotePermissionBridge.ts` adds two practical fallbacks:
- synthetic assistant messages for remote tool use
- stub local tool objects when the remote tool does not exist locally
Those patterns let the local client render and decide on remote actions without implementing the full tool.
Concrete flow worth copying:
1. remote runtime emits a typed permission request with `request_id`
2. local bridge stores the pending request by ID
3. local UI renders an approval surface using request metadata, not backend internals
4. user allows or denies
5. bridge sends a structured success response
6. if the remote side cancels first, local state clears the pending prompt instead of leaving orphaned UI
Credential design worth copying (verified against `code.claude.com/docs/en/remote-control`, 2026-07-11): use multiple short-lived credentials, each scoped to a single purpose (e.g. one for registering the local process with the relay, a different one for each connecting viewer) and expiring independently, rather than one long-lived shared session secret. That way revoking a compromised viewer credential does not force the executing side to re-authenticate, and vice versa.
## SDK-To-REPL Message Adaptation
`sdkMessageAdapter.ts` exists because the remote backend speaks one message shape while the REPL expects another. That is a reusable lesson:
- keep the transport schema stable
- adapt it once at the client boundary
- do not make every UI component understand backend-native wire messages
## Reconnect Behavior
The remote manager also distinguishes:
- connected
- reconnecting
- disconnected
- viewer-only no-interrupt behavior
A coding-agent remote client should model these states explicitly. Reconnect is not the same as healthy; viewer-only is not the same as a full controller.
## Edge Cases And Workarounds
Important behaviors that are easy to miss in a scratch build:
- unsupported control subtype
- respond with a structured error so the server does not hang waiting for a reply
- viewer-only client
- do not send interrupts
- do not behave like a full controller during reconnect or title-update flows
- missing local tool definition for a remote tool use
- create a stub display model instead of crashing the transcript renderer
- reconnecting versus disconnected
- these should be separate states in the local UI
- "trying to recover" and "session is gone" are different operator experiences
- stale pending approvals on disconnect
- clear or cancel them deterministically
Useful implementation tip:
- keep one adapter at the boundary that translates backend-native messages into the local REPL message model
- do not leak transport-native message shapes into every UI component
references/local-ui-remote-execution-model.md
# Local UI, Remote Execution Model
## Table Of Contents
- [Design Goal](#design-goal)
- [Execution Split](#execution-split)
- [Remote Modes](#remote-modes)
- [Command And Tool Surface](#command-and-tool-surface)
- [Two Different "Remote" Products — Don't Conflate Them](#two-different-remote-products--dont-conflate-them)
- [SSH-Like Sessions](#ssh-like-sessions)
## Design Goal
Remote coding-agent runtimes should keep user interaction local while allowing the agent loop and tools to run elsewhere. As of July 2026 this is a shipped pattern, not just an internal prototype: Claude Code's `remote-control` feature (verified against `code.claude.com/docs/en/remote-control`) and its Desktop app's SSH-host connection are two independently real, differently-architected instances of the same design goal — see below for why they are not interchangeable.
## Execution Split
The runtime split should be explicit:
- local client renders the REPL or viewer
- remote runtime owns the agent loop and tool execution
- messages flow back over a typed transport
- control actions such as approval or interrupt are routed separately
This avoids pretending the local CLI is executing tools it does not control.
## Remote Modes
A local-first coding-agent CLI exposes several different remote-oriented modes. Claude Code's shipped `remote-control` surface (verified 2026-07-11) is a concrete, citable instance of this taxonomy:
- **server mode** — `claude remote-control` runs in the foreground and serves multiple concurrent sessions (`--spawn same-dir|worktree|session`, `--capacity N`); it prints a session URL and (on spacebar) a QR code
- **interactive-with-remote-control** — `claude --remote-control` (alias `--rc`) starts one normal interactive session that is simultaneously drivable from another device
- **attach-from-existing-session** — the `/remote-control` (alias `/rc`) slash command promotes an already-running local session to remotely steerable, carrying over conversation history
- **viewer/controller from claude.ai/code or the mobile app** — the remote client is not a peer session, it is a window into the one local session; some commands (e.g. `/plugin`, `/resume`) are deliberately local-only
Treat those as distinct runtime modes, not feature flags on one generic session object — each has a different default network posture (foreground-blocking vs multiplexed) and a different set of commands it forwards.
## Command And Tool Surface
Remote mode also filters commands and starts with a narrower local tool surface. That is the right pattern:
- do not expose every local-only command in remote mode
- do not assume the local client has every remote tool available
- preserve a single semantic session, but narrow the operational UI appropriately
## Two Different "Remote" Products — Don't Conflate Them
Two shipped Claude Code features both answer "how do I use Claude Code away from my desk," and they are architecturally opposite. Picking the wrong one as your mental model produces the wrong design:
| | Desktop app → remote SSH host | `remote-control` |
|---|---|---|
| Where does the agent loop run? | On the remote SSH host (Claude Code is auto-installed there on first connect) | On the machine you started it on — never moves |
| Where does the REPL/UI live? | Desktop app, tunneled over the SSH connection | Any device (phone, browser, VS Code) via `claude.ai/code` or the mobile app |
| Network shape | Outbound SSH connection you already have working (`ssh user@host`) | Outbound-only HTTPS from the local machine; **no inbound port is ever opened**; the client and local machine rendezvous through the Anthropic API |
| Session history | Siloed per surface — desktop app, remote CLI, and remote VS Code each keep separate session lists | One session, mirrored; renaming from the phone updates the local title too |
| Right mental model | Classic "SSH-like" remote-execution devbox: filesystem, cwd, and process all genuinely live remotely | Cross-device **steering/mirroring** of a session whose filesystem and tools never leave the original machine |
Both are real and both matter, but they solve different problems:
- Use the **remote-execution devbox** model when the point is to run tools against a filesystem and environment that only exists on another machine (a build server, a GPU box, a long-lived dev container).
- Use the **outbound-poll mirroring** model when the point is to keep working from a different physical device on the *same* environment, without exposing that machine to inbound connections.
## SSH-Like Sessions
The reusable pattern behind the remote-execution devbox model:
- the REPL/UI stays on the connecting client
- tools, filesystem, and process state execute on the remote host
- auth and cwd state are remote-aware, and first-connect auto-installs the agent binary if it is missing
- treat each remote host as its own session-history namespace — do not assume a session created on the SSH host is visible from a different local surface
That is a reusable pattern for coding-agent runtimes that need remote execution without fully turning the UI into a web client. It is a different pattern from outbound-poll mirroring (above) even though both are commonly described as "remote."
references/openai-codex-app-server-protocol-codegen.md
# OpenAI Codex App-Server Protocol Codegen
Sources:
- OpenAI Codex repo, commit `9f42c89c0112771dc29100a6f3fc904049b2655f`
- `codex-rs/app-server-protocol/src/export.rs`
- `codex-rs/app-server-protocol/tests/schema_fixtures.rs`
Use this reference when building a remote runtime, app server, editor bridge, or local daemon with a typed control protocol.
## What To Steal
### Generate protocol artifacts from one source
Codex's app-server protocol exports TypeScript and JSON schema artifacts from protocol definitions. The useful pattern is source-of-truth protocol generation:
- Define protocol types in one owning crate/module.
- Export client-facing artifacts.
- Mark generated files with a clear generated-code header.
- Filter experimental APIs when producing stable public artifacts.
Known trap:
- Handwritten protocol types in CLI, daemon, and UI code drift quickly. The remote runtime then fails at the worst boundary: reconnect, approval, or app update.
### Experimental API filtering
Codex's exporter can distinguish experimental surface from stable surface. Import this into any bridge protocol with third-party clients.
Design rule:
- Stable clients only receive stable method/schema artifacts.
- Experimental artifacts are generated separately or explicitly labeled.
- Runtime rejects unsupported experimental calls with structured errors.
Known trap:
- A single "latest schema" endpoint becomes an accidental compatibility promise for work-in-progress methods.
### Schema fixture tests
Codex tests generated schema fixtures and offers a write/update path for fixtures. The reusable shape:
- Generate schema output in tests.
- Compare against committed fixture.
- Show useful diff on mismatch.
- Provide a deliberate fixture-update command.
This is a strong guard against invisible protocol drift.
Known trap:
- Snapshot tests that silently rewrite generated protocol files in CI are not tests. Schema updates should be explicit review events.
## Portable Remote Runtime Contract
```text
ProtocolDefinition
stable_methods
experimental_methods
request_types
response_types
event_types
GeneratedArtifacts
typescript_client
json_schema
generated_header
fixture_version
```
Bridge clients should validate:
- method exists in the selected protocol tier
- request payload matches schema
- response or error payload matches schema
- reconnect/control events are versioned
## Tests To Require
- Generated TypeScript or client artifact includes a generated-code header.
- Experimental methods are absent from stable schema output.
- Schema fixture diff fails CI when protocol changes.
- Fixture update command is manual and reviewable.
- Unknown method returns structured unsupported-method error.
- Client and daemon versions expose enough metadata to explain compatibility failures.
## Source Links
- [app-server protocol exporter](https://github.com/openai/codex/blob/9f42c89c0112771dc29100a6f3fc904049b2655f/codex-rs/app-server-protocol/src/export.rs)
- [app-server schema fixtures](https://github.com/openai/codex/blob/9f42c89c0112771dc29100a6f3fc904049b2655f/codex-rs/app-server-protocol/tests/schema_fixtures.rs)
references/openai-codex-app-server-remote-control.md
# OpenAI Codex App Server Remote Control
Source snapshot: OpenAI Codex commit `9f42c89c0112771dc29100a6f3fc904049b2655f` (2026-05-24), especially `codex-rs/app-server-daemon/README.md`, `codex-rs/app-server-protocol`, `codex-rs/app-server-transport`, and `codex-rs/cli/src/remote_control_cmd.rs`.
Web sources checked 2026-05-25:
- OpenAI, "Codex for (almost) everything", Apr 16, 2026: https://openai.com/index/codex-for-almost-everything/
- Codex use cases: https://developers.openai.com/codex/use-cases
Re-verified 2026-07-11 against live sources (staleness correction below):
- `openai/codex` issue #25552 (app-server-daemon README describes stale top-level `codex remote-control` behavior)
- `openai/codex` discussion #21935 (intended direction for the `codex remote-control` entrypoint, v0.130.0+)
- Codex CLI v0.130 release notes (`codex remote-control` top-level command)
## Table Of Contents
- [Design Goal](#design-goal)
- [Daemon Lifecycle](#daemon-lifecycle)
- [Machine-Readable Control](#machine-readable-control)
- [Remote Host Bootstrap](#remote-host-bootstrap)
- [Long-Running Work](#long-running-work)
- [Known Traps](#known-traps)
## Design Goal
Model remote runtime as local UI clients talking to a durable app-server process, not as a UI that owns every session directly. Codex uses app-server and daemon commands so desktop, mobile, SSH, and CLI surfaces can attach to a managed backend.
## Daemon Lifecycle
Codex daemon operations include:
- start
- restart
- enable remote control
- disable remote control
- stop
- version
- bootstrap with remote control
Mutating lifecycle operations are serialized per `CODEX_HOME`. Copy this invariant so concurrent start/restart/enable operations cannot corrupt daemon state.
## Machine-Readable Control
Codex daemon commands write exactly one JSON object on success. Consumers should parse JSON, not terminal prose.
For remote runtimes:
- keep lifecycle output stable and typed
- include socket path, backend, local CLI version, and running server version
- keep human rendering outside the protocol layer
## Correction (verified 2026-07-11): top-level `codex remote-control` is foreground infra, not a bootstrap wrapper
Codex maintainers flagged (`openai/codex` issue #25552) that the `app-server-daemon` README's claim about top-level `codex remote-control` behavior was stale. Current behavior:
- `codex remote-control` with **no subcommand** starts a **foreground** app-server with local transports disabled and remote control enabled for that single invocation only — it does not manage daemon lifecycle.
- Daemon lifecycle (start/stop/restart/enable/disable) lives under explicit subcommands: `codex remote-control start`, `codex remote-control stop`, etc.
- Per `openai/codex` discussion #21935, `codex remote-control` is positioned as **infrastructure for custom integrations and SSH-remote workflows**, not a ready-made browser/QR-code remote session starter. The user-facing QR/mobile-pairing flow (ChatGPT mobile app) is mediated by the **Codex desktop app**, not the CLI alone.
- This is a useful contrast with Claude Code's `remote-control`, where the CLI itself is the primary user-facing entry point (it prints the session URL and QR code directly) — do not assume the two products' "remote-control" commands play the same role just because the name matches.
Design rule: when documenting or building a CLI `remote-control`-style command, be explicit about whether it is a user-facing steering entry point or low-level daemon plumbing for other surfaces to build on. Conflating the two produces wrong integration guides.
## Remote Host Bootstrap
The Codex bootstrap pattern assumes a managed standalone install under `CODEX_HOME`, records daemon settings, starts app-server as a detached process, and launches an updater loop.
Design rule:
- bootstrap should be idempotent
- bootstrap should leave a clear state directory
- update behavior should be explicit, not a side effect of every start
## Long-Running Work
OpenAI's April 2026 Codex update describes scheduled work, reused threads, memory, remote devboxes, multiple terminal tabs, and work carried forward across days or weeks. A remote runtime must therefore support:
- re-attach after UI disconnect
- durable thread IDs
- explicit remote-control enablement
- safe access to local credentials and files on the connected host
- clear ownership of who can start, stop, or update the daemon
## Known Traps
- Binding session lifetime to a GUI window or SSH pipe.
- Printing human text where remote clients expect one JSON object.
- Updating the daemon binary before restarting the app-server that depends on it.
- Letting concurrent lifecycle commands race in the same runtime home.
- Treating mobile/desktop remote control as just another transport, without credential and host-ownership rules.
references/openai-codex-as-mcp-server.md
---
source_snapshot: openai/codex main branch (verified 2026-05-25)
anchors:
- codex-rs/cli/src/main.rs — McpServer subcommand variant
- codex-rs/mcp-server/ — codex-mcp crate (src/main.rs, src/lib.rs, src/codex_tool_runner.rs, src/message_processor.rs)
- codex-rs/app-server-daemon/ — distinct HTTP daemon crate (different transport, different audience)
---
# OpenAI Codex As MCP Server
## When To Use
Use this reference when designing or reviewing the surface that lets external editors or AI runtimes drive Codex over the Model Context Protocol (MCP). It covers the `codex mcp-server` subcommand, the `codex-rs/mcp-server` crate, and the contrast with the existing app-server-daemon.
## What It Covers
- `codex mcp-server` subcommand — stdio MCP server mode
- `codex-rs/mcp-server` crate structure and responsibilities
- How external clients drive Codex as a tool
- Contrast with `codex-rs/app-server-daemon` (HTTP daemon, different transport and audience)
- Approval bridging inside the MCP server path
## The `codex mcp-server` Subcommand
`codex-rs/cli/src/main.rs` exposes a top-level `McpServer` subcommand:
> "Start Codex as an MCP server (stdio)"
When launched, Codex runs as a long-lived stdio process. MCP clients (editors, Claude Code, other agents) connect over standard input/output using the MCP wire protocol. The client sends requests; Codex processes them through its agent loop and returns results.
This is the primary integration path for editors and orchestrators that want to drive Codex as a **tool**, rather than as a UI-bearing terminal application.
## The `codex-rs/mcp-server` Crate
Source: `codex-rs/mcp-server/src/`
Key modules:
| Module | Role |
|--------|------|
| `main.rs` | Entrypoint — bootstraps the MCP stdio server |
| `lib.rs` | Library exports |
| `codex_tool_config.rs` | Tool configuration: which Codex tools are exposed via MCP and with what schema |
| `codex_tool_runner.rs` | Execution engine — routes MCP tool-call requests into Codex's internal agent loop |
| `message_processor.rs` | Parses incoming MCP messages and dispatches to the appropriate handler |
| `outgoing_message.rs` | Formats MCP-compliant responses |
| `exec_approval.rs` | Approval workflow for command execution requests received over MCP |
| `patch_approval.rs` | Approval workflow for code-patch requests received over MCP |
The approval modules (`exec_approval.rs`, `patch_approval.rs`) are notable: because the MCP client may be another AI agent rather than a human, Codex still enforces its approval model. Approvals can be either auto-resolved by policy or forwarded back to the calling client as MCP-formatted elicitation requests.
## How External Clients Drive Codex
```text
editor / Claude Code / orchestrator
|
| (MCP wire protocol, stdin/stdout)
v
codex mcp-server process
message_processor -> codex_tool_runner
|
+--> Codex agent loop (same core as interactive TUI)
+--> exec_approval / patch_approval (honor AskForApproval policy)
+--> outgoing_message -> MCP response back to client
```
From the client side, Codex appears as a set of MCP tools (file editing, code execution, search, apply-patch, etc.). The calling runtime never needs to know how Codex implements those tools internally.
## Contrast: MCP Server vs App-Server-Daemon
These are two unrelated remote-integration surfaces with different transports and different audiences:
| Dimension | `codex mcp-server` | `codex-rs/app-server-daemon` |
|-----------|-------------------|------------------------------|
| Transport | stdio (MCP wire) | HTTP (typed REST / OpenAPI) |
| Protocol | Model Context Protocol | JSON/HTTP control protocol |
| Primary audience | Editors, AI orchestrators, MCP clients | Desktop GUI clients, remote-control tooling |
| Crate | `codex-rs/mcp-server` | `codex-rs/app-server-daemon` |
| Companion | `codex mcp-server` CLI subcommand | `codex app-server` / `codex remote-control` |
| Session model | One Codex session per MCP connection | Multiple sessions managed by daemon |
| Approval routing | MCP elicitation back to calling client | Local or remote human UI |
When an editor wants Codex as a background tool, it uses `mcp-server`. When a desktop GUI wants to manage multiple Codex sessions with HTTP round-trips, it uses `app-server-daemon`.
## Design Rules
- Treat the MCP server path as a first-class integration target, not a debugging interface.
- Approval policy (`AskForApproval`) applies equally when the caller is another AI agent; do not auto-approve everything from a "trusted" client.
- The MCP server should not re-implement core business logic; it routes requests through the same `codex_tool_runner` that the interactive session uses.
- Distinguish MCP stdio transport from the HTTP daemon transport in documentation, examples, and error messages — conflating them produces wrong integration guides.
- Editors using the MCP path should receive structured elicitation requests when approval is needed, not silent failures.
## Anti-Patterns
- Treating `codex mcp-server` and `app-server-daemon` as interchangeable because both allow external control. The wire protocol, session model, and audience differ.
- Bypassing `exec_approval` / `patch_approval` on the assumption that the MCP caller is an AI and "already trusted." Policy still applies.
- Building editor integrations as in-process SDK embeds when the `mcp-server` stdio mode already provides a clean, isolated integration boundary.
references/recipe-reconnect-with-sequence.md
# Recipe: Resumable Streams with Sequence Numbers
A step-by-step implementation recipe for reconnect-safe remote sessions using sequence-numbered messages. Implements the "sequence-aware resume" pattern described in `transport-selection.md` and `bridge-transport-and-permission-bridging.md`.
## Table of Contents
- [Goal](#goal)
- [Components](#components)
- [Step 1 — Assign sequence numbers server-side](#step-1--assign-sequence-numbers-server-side)
- [Step 2 — Retain messages with a bounded ring buffer](#step-2--retain-messages-with-a-bounded-ring-buffer)
- [Step 3 — Handle the resume handshake](#step-3--handle-the-resume-handshake)
- [Step 4 — Client reconnect loop](#step-4--client-reconnect-loop)
- [Step 5 — Edge cases to handle](#step-5--edge-cases-to-handle)
- [Step 6 — ACP stdio variant](#step-6--acp-stdio-variant)
- [Anti-patterns](#anti-patterns)
- [Related](#related)
## Goal
After any network drop, the client reconnects and the server delivers exactly the messages the client missed — no duplicates, no gaps, no full-replay cost.
Success criteria: a client that disconnects mid-turn and reconnects within the retention window sees all missed transcript and control events in order, with no agent-side side effects.
## Components
| Component | Responsibility |
|-----------|---------------|
| `SeqStore` | Server-side ring buffer of recent messages keyed by `(session_id, seq)` |
| `seq` field | Monotonically increasing integer on every server-pushed message |
| `resume` handshake | First message from client on reconnect; carries `last_seq` |
| `RetentionPolicy` | How long / how many messages to keep for reconnect eligibility |
| `ReconnectState` | Client-side enum: `Connected | Reconnecting(attempt, last_seq) | Disconnected` |
## Step 1 — Assign sequence numbers server-side
Every message the server pushes gets a `seq` before transmission:
```python
# Pseudocode — adapt to your language/framework
class SessionSender:
def __init__(self, session_id: str, seq_store: SeqStore):
self._session_id = session_id
self._seq_store = seq_store
self._next_seq = 0
def send(self, ws, message: dict) -> None:
message["seq"] = self._next_seq
self._seq_store.put(self._session_id, self._next_seq, message)
ws.send_json(message)
self._next_seq += 1
```
Key rules:
- `seq` is per-session, not global.
- Sequence numbers are never reused, even after partial reconnect.
- Control messages (permission prompts, cancellations) get `seq` like any other message.
## Step 2 — Retain messages with a bounded ring buffer
```python
class SeqStore:
"""
Bounded in-memory ring buffer. Replace with Redis ZSET or DB for multi-process.
"""
MAX_RETAIN = 2000 # messages
def __init__(self):
self._store: dict[str, list[tuple[int, dict]]] = {}
def put(self, session_id: str, seq: int, message: dict) -> None:
buf = self._store.setdefault(session_id, [])
buf.append((seq, message))
if len(buf) > self.MAX_RETAIN:
buf.pop(0)
def since(self, session_id: str, last_seq: int) -> list[dict]:
buf = self._store.get(session_id, [])
return [msg for (seq, msg) in buf if seq > last_seq]
def evict(self, session_id: str) -> None:
self._store.pop(session_id, None)
```
Tune `MAX_RETAIN` for your session message volume. A 10-minute session at 3 messages/second uses ~1800 messages.
## Step 3 — Handle the resume handshake
On new WebSocket connection, the server reads the first message before routing transcript traffic:
```python
async def on_connect(ws, session_manager, seq_store):
first = await ws.receive_json()
if first["type"] == "resume":
session_id = first["session_id"]
last_seq = first["last_seq"]
session = session_manager.get(session_id)
if session is None:
await ws.send_json({"type": "error", "code": "session_not_found"})
await ws.close()
return
missed = seq_store.since(session_id, last_seq)
for msg in missed:
await ws.send_json(msg)
# Re-attach the live sender to this new WebSocket
session.attach_sender(ws)
elif first["type"] == "new_session":
session = session_manager.create()
await ws.send_json({"type": "session_created", "session_id": session.id, "seq": -1})
session.attach_sender(ws)
else:
await ws.send_json({"type": "error", "code": "unexpected_handshake"})
await ws.close()
```
## Step 4 — Client reconnect loop
```typescript
// TypeScript pseudocode
type ReconnectState =
| { kind: "connected" }
| { kind: "reconnecting"; attempt: number; lastSeq: number }
| { kind: "disconnected"; reason: string };
class ReconnectingSession {
private state: ReconnectState = { kind: "connected" };
private lastSeq = -1;
private ws: WebSocket | null = null;
private readonly MAX_ATTEMPTS = 8;
private readonly BASE_DELAY_MS = 250;
onMessage(msg: ServerMessage) {
this.lastSeq = msg.seq;
// dispatch to UI / transcript store
}
onDisconnect() {
if (this.state.kind !== "connected") return;
this.state = { kind: "reconnecting", attempt: 0, lastSeq: this.lastSeq };
this.scheduleReconnect();
}
private scheduleReconnect() {
if (this.state.kind !== "reconnecting") return;
const { attempt, lastSeq } = this.state;
if (attempt >= this.MAX_ATTEMPTS) {
this.state = { kind: "disconnected", reason: "max_attempts_exceeded" };
return;
}
const delay = Math.min(this.BASE_DELAY_MS * 2 ** attempt, 30_000);
setTimeout(() => this.connect(lastSeq), delay);
}
private connect(lastSeq: number) {
this.ws = new WebSocket(SESSION_WS_URL);
this.ws.onopen = () => {
this.ws!.send(JSON.stringify({
type: "resume",
session_id: SESSION_ID,
last_seq: lastSeq,
}));
};
this.ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
this.state = { kind: "connected" };
this.onMessage(msg);
};
this.ws.onclose = () => {
if (this.state.kind === "reconnecting") {
this.state = {
...this.state,
attempt: this.state.attempt + 1,
};
this.scheduleReconnect();
}
};
}
}
```
## Step 5 — Edge cases to handle
| Case | Handling |
|------|---------|
| `last_seq` outside retention window | Server replies `{type: "error", code: "seq_expired"}`. Client falls back to full-transcript reload or shows "session too old to resume" |
| `session_id` not found | Server replies `{type: "error", code: "session_not_found"}`. Client clears local state, starts new session |
| Duplicate reconnect (two clients resume same session) | Server detaches old WebSocket, attaches new one; old client gets `{type: "displaced"}` |
| Server restart (no in-memory seq store) | Use Redis ZSET or DB-backed store. Recovery is same as `seq_expired` fallback |
| Viewer-only mode | Resume handshake carries `role: "viewer"`. Server skips control-message delivery for viewers |
## Step 6 — ACP stdio variant
For ACP (editor ↔ agent stdio), sequence-number resume works the same way but over stdin/stdout line-delimited JSON:
```
# Client (editor) reconnect message
{"jsonrpc": "2.0", "method": "session/resume", "params": {"session_id": "...", "last_seq": 42}}
# Server (agent) catch-up replay
{"jsonrpc": "2.0", "method": "session/message", "params": {..., "seq": 43}}
{"jsonrpc": "2.0", "method": "session/message", "params": {..., "seq": 44}}
```
Session state is held by the agent process (or daemon), not the editor. ACP reconnect is process re-attach, not full restart.
## Anti-patterns
- Using wall-clock timestamps instead of sequence numbers for "resume from here." Clocks drift; sequence numbers do not.
- Storing seq-store in the WebSocket connection object. The store must outlive the connection.
- Re-assigning `seq = 0` on every reconnect. New messages will collide with retained messages in the store.
- Delivering duplicate messages on reconnect because the server did not check `seq > last_seq` strictly.
## Related
- [`transport-selection.md`](transport-selection.md) — When to use WebSocket vs SSE vs HTTP POST vs ACP stdio
- [`bridge-transport-and-permission-bridging.md`](bridge-transport-and-permission-bridging.md) — Control-message schema and permission bridging
references/transport-selection.md
# Transport Selection Decision Tree
## Table of Contents
- [Decision Tree](#decision-tree)
- [Transport Comparison](#transport-comparison)
- [Criteria](#criteria)
- [Hybrid patterns](#hybrid-patterns)
- [No-inbound-ports relay (outbound-poll mirroring)](#no-inbound-ports-relay-outbound-poll-mirroring)
- [Anti-patterns](#anti-patterns)
- [Sequence number conventions (WebSocket resume)](#sequence-number-conventions-websocket-resume)
- [Related](#related)
Use this reference when choosing between WebSocket, SSE, HTTP POST, and ACP stdio for a remote coding-agent session. Decide per direction (read vs. write) and per session mode before coding.
## Decision Tree
```
Start: What does this channel carry?
│
├── Control traffic (permission prompts, cancellations, interrupt signals)
│ └── Bidirectional, low-latency required?
│ ├── YES → WebSocket control channel
│ └── NO → HTTP POST (write-side) + SSE or polling (read-side)
│
├── Transcript / streaming agent output (read-heavy)
│ └── Does the client need to push anything back on the same connection?
│ ├── YES → WebSocket (multiplex reads and acks on one pipe)
│ └── NO → SSE (simpler, HTTP/2 compatible, survives load-balancer restarts better)
│
├── Single-shot writes (user message submission, approve/deny response)
│ └── HTTP POST — never upgrade to WebSocket just because the reads are streaming
│
└── Editor integration (Zed, JetBrains, VS Code fork, IntelliJ)
└── ACP stdio — line-delimited JSON over spawned subprocess;
session-ID re-attach on reconnect, not replay
```
## Transport Comparison
| Transport | Direction | Latency | Reconnect model | When to prefer |
|-----------|-----------|---------|-----------------|----------------|
| **WebSocket** | Bidirectional | Low | Client reconnects; server re-delivers in-flight on seq number match | Control channels; mixed read+write streams that must stay ordered |
| **SSE** | Server → client only | Low | Browser/HTTP layer reconnects automatically with `Last-Event-ID` | Pure streaming output; simpler infrastructure; HTTP/2 environments |
| **HTTP POST** | Client → server only | Medium | Stateless; idempotent with request ID | Single-shot writes: submitting a message, answering a prompt |
| **ACP stdio** | Bidirectional | Lowest (IPC) | Session-ID re-attach on new stdin pipe; process may restart | Editor integrations; local daemon ↔ editor; never crosses the internet |
## Criteria
### Use WebSocket when
- The channel carries both control messages and transcript events on one pipe
- The server must push control events (cancellations, reconnect notifications) to the client unprompted
- Ordered delivery with sequence numbers matters for reconnect-resume correctness
- The client needs to ack or respond to server-pushed events within the same stream
### Use SSE when
- The session is read-heavy: the client consumes a stream of agent turns and renders them
- Writes are infrequent and well-suited to separate HTTP POST calls
- You want automatic HTTP-layer reconnect (`Last-Event-ID` + `EventSource`)
- You are behind an HTTP/2 load balancer and want to avoid WebSocket upgrade complexity
### Use HTTP POST when
- The write is a discrete user action: submitting a prompt, approving a tool call, cancelling a task
- The response can come back synchronously in the HTTP reply or asynchronously on the read channel
- Idempotency is straightforward to implement (use a stable `request_id` in the body)
### Use ACP stdio when
- The consumer is an editor that spawns the agent as a child process
- You need the lowest possible latency and no network hop
- Session-ID re-attach on new `stdin` FD is acceptable (the process may restart between editor sessions)
- You want to codegen method dispatch from the ACP schema rather than hand-write WebSocket handlers
## Hybrid patterns
**Split read/write transport** is the most common robust pattern for internet-facing sessions:
```
Client reads: SSE or WebSocket (server → client)
Client writes: HTTP POST (client → server)
```
This decouples reconnect semantics. If the read channel drops, the client reconnects SSE/WebSocket from the last event ID. Writes in-flight already have HTTP-level retries. The two channels never confuse each other's ordering.
**Downgrade path:** start with HTTP polling as a fallback if SSE or WebSocket is blocked by a proxy. Never expose polling as the default; it adds unnecessary latency and load.
## No-inbound-ports relay (outbound-poll mirroring)
A fifth transport shape that the decision tree above omits, and that a non-expert will not think to ask for: when the "remote" party is actually a client mirroring into a session that must keep running on an untrusted or NAT'd machine (a laptop, not a data-center host), do not open an inbound port on that machine at all.
Verified production example (`code.claude.com/docs/en/remote-control`, checked 2026-07-11): Claude Code's cross-device session steering makes **outbound HTTPS requests only** from the local machine and never opens an inbound port. The local process registers with a cloud API and polls for work; the remote browser/mobile client also talks to the cloud API; the cloud side relays between them over a streaming connection. Credentials are multiple short-lived tokens, each scoped to a single purpose and expiring independently — not one long-lived session secret shared across both legs.
Use this shape when:
- the executing machine is a laptop/workstation that sleeps, roams networks, and sits behind NAT or a firewall you do not control
- you cannot assume the remote viewer and the executing machine can ever open a direct connection to each other
- you want the executing machine to be able to revoke a compromised viewer credential without also invalidating its own connection to the relay
Do not use this shape as a substitute for the SSH-stdio-devbox pattern (see `local-ui-remote-execution-model.md`) — the point of the SSH pattern is that *execution* moves to the remote host; the point of the no-inbound-ports relay is that execution stays put and only *steering* moves.
Anti-pattern: building a bespoke WebSocket-to-the-laptop bridge that requires the laptop to accept inbound connections (via port-forwarding, ngrok-style tunnels, or a static IP). That reintroduces exactly the NAT/firewall/security-review problem the relay pattern exists to avoid.
## Anti-patterns
- Upgrading every channel to WebSocket because "it's faster." HTTP POST writes are simpler to reason about, retry, and audit.
- Mixing control messages and transcript output on one untyped WebSocket pipe with no schema. Control events become invisible in the stream.
- Using ACP stdio for internet-facing remote sessions. ACP is a local IPC protocol; it has no auth, no TLS, and no rate-limiting layer.
- Reusing the SSE `Last-Event-ID` as a session-resume token. That is a client-side hint, not a durable server-side checkpoint.
## Sequence number conventions (WebSocket resume)
When using WebSocket with sequence-aware resume, assign a monotonically increasing `seq` to every server-pushed message. On reconnect:
1. Client sends `{type: "resume", session_id: "...", last_seq: N}`
2. Server delivers messages from `N+1` onward
3. Messages with `seq ≤ N` are discarded server-side after a configurable retention window
For recipes, see [`recipe-reconnect-with-sequence.md`](recipe-reconnect-with-sequence.md).
## Related
- [`bridge-transport-and-permission-bridging.md`](bridge-transport-and-permission-bridging.md) — WebSocket control messages, permission routing, reconnect
- [`recipe-reconnect-with-sequence.md`](recipe-reconnect-with-sequence.md) — Resumable stream implementation
- [`../ai-coding-agents-sessions/references/resume-path-decision-tree.md`](../../ai-coding-agents-sessions/references/resume-path-decision-tree.md) — When to use session ID vs picker vs ACP re-attach
SKILL.md
---
name: ai-coding-agents-remote-runtime
description: "Designs remote execution and bridge runtimes for coding agents. Use when implementing remote sessions, local-UI remote-execution, reconnect logic, or permission bridging."
compatibility: Portable core. Works on Claude Code and Codex.
version: "1.1"
last_validated: 2026-07-11
---
# AI Coding Agents Remote Runtime
Use this skill to design or review coding-agent runtimes where execution happens remotely but the user still interacts through a local CLI or terminal UI.
This skill covers remote sessions, bridge transports, viewer-only clients, SSH-style local-UI remote-tool flows, and remote approval routing.
## ASCII Flow
```text
local client / terminal UI
|
v
bridge transport
WebSocket | SSE | HTTP POST | ACP stdio | SSH-like tunnel
|
v
remote runtime
agent loop + tools + sandbox + session store
|
+--> permission request -> bridge -> local owner decision -> remote execution
+--> stream events -> bridge -> local rendering
+--> reconnect -> sequence resume or replay from checkpoint
```
## Quick Reference
| Question | Read | Outcome |
|----------|------|---------|
| What should the remote runtime model look like? | [`references/local-ui-remote-execution-model.md`](references/local-ui-remote-execution-model.md) | Local UI, remote agent loop, viewer modes, and mode boundaries |
| How should bridge transport and approval work? | [`references/bridge-transport-and-permission-bridging.md`](references/bridge-transport-and-permission-bridging.md) | WebSocket control flow, permission bridging, reconnect, and message adaptation |
| Which transport should I use (WebSocket vs SSE vs HTTP POST vs ACP stdio)? | [`references/transport-selection.md`](references/transport-selection.md) | Decision tree, criteria, hybrid patterns, and anti-patterns |
| How do I build reconnect with sequence-number resume? | [`references/recipe-reconnect-with-sequence.md`](references/recipe-reconnect-with-sequence.md) | Step-by-step resumable stream recipe with ring buffer and client reconnect loop |
| How does OpenAI Codex structure app-server and remote-control daemon lifecycle? | [`references/openai-codex-app-server-remote-control.md`](references/openai-codex-app-server-remote-control.md) | Daemon lifecycle, JSON control output, remote host bootstrap, long-running work |
| How does OpenAI Codex keep app-server protocol clients in sync? | [`references/openai-codex-app-server-protocol-codegen.md`](references/openai-codex-app-server-protocol-codegen.md) | Schema-driven TypeScript/JSON artifacts, experimental filtering, fixture tests, and explicit update workflow |
| How does `codex mcp-server` work and how does it differ from the app-server-daemon? | [`references/openai-codex-as-mcp-server.md`](references/openai-codex-as-mcp-server.md) | `McpServer` subcommand, `codex-rs/mcp-server` crate, approval bridging over MCP, contrast with HTTP daemon |
## When To Use
- Design remote coding-agent sessions with a local CLI frontend
- Build bridge or direct-connect transport for a coding-agent runtime
- Add viewer-only remote session clients or assistant-style observers
- Route permission requests from remote execution back to local UI
- Model SSH-like “local REPL, remote tools” behavior
## Use Other Skills
| Need | Use Instead |
|------|-------------|
| Session persistence and resume lifecycle | [`../ai-coding-agents-sessions/SKILL.md`](../ai-coding-agents-sessions/SKILL.md) |
| Tool approval system design | [`../ai-coding-agents-permissions/SKILL.md`](../ai-coding-agents-permissions/SKILL.md) |
| Plugin architecture | [`../ai-coding-agents-plugins/SKILL.md`](../ai-coding-agents-plugins/SKILL.md) |
## Default Workflow
1. **Separate UI from execution.** Decide what runs locally, what runs remotely, and what state must be mirrored between them.
2. **Treat remote control as a first-class mode.** Viewer-only, full control, SSH proxy, and remote-creation flows should be explicit runtime modes.
3. **Split transcript traffic from control traffic.** Keep SDK or transcript messages separate from typed control requests such as permission prompts, cancellations, reconnect notifications, and unsupported-control errors.
4. **Choose transport shape deliberately.** Reads and writes do not need the same transport. Hybrid patterns such as WebSocket or SSE for reads plus HTTP POST for writes are often easier to recover and reason about than pretending the whole session is one bidirectional pipe.
5. **Bridge unknown tools safely.** Remote execution may reference tools that do not exist in the local client; normalize them into synthetic local renderables instead of failing the UI.
6. **Track pending control requests.** Permission prompts and cancellations need stable request IDs, local bookkeeping, and explicit cleanup so reconnects and late cancellations do not leak stale UI state.
7. **Plan reconnect behavior.** Distinguish transient reconnecting, permanent disconnect, and viewer-only no-interrupt modes. Decide whether the client resumes from sequence numbers, replays from checkpoints, or only reconnects live.
8. **Keep approval local where possible.** Remote execution can ask; the local controller should decide and respond with a structured result.
9. **Test degraded modes.** Verify network drops, reconnect backoff, remote interrupt, stale control requests, unsupported control subtypes, and local or remote command filtering.
## Host Rules
- Local UI and remote execution should share one semantic session contract.
- Control messages should use a typed schema separate from normal transcript messages.
- Reads and writes may use different transports if that improves ordering, retry semantics, or observability.
- Pending permission requests must be keyed, cancellable, and removable from local state when the server cancels or the session dies.
- Remote mode should expose only the commands that make sense in a remote session.
- Unknown remote tools should still render through synthetic local message or tool-stub paths rather than crashing the local UI.
- Viewer-only clients must not accidentally send interrupts or mutate remote state.
- Unsupported control-request subtypes should return a structured error response instead of hanging the remote side.
- SSH-like proxy modes should render locally and execute remotely without pretending they are fully local sessions.
## Build Order
1. Define the shared session contract and runtime modes.
2. Implement typed control messages separately from transcript messages.
3. Stand up the read and write transports, even if they start as one simple channel.
4. Add permission request routing with stable IDs and cancellation.
5. Add reconnect and resume behavior.
6. Add synthetic local rendering for remote-only tools and control events.
## Core Invariants
- The local UI is a controller and renderer, not the source of truth for remote execution.
- Transcript traffic and control traffic must never be ambiguous on the wire.
- Every remote control request must be attributable, cancellable, and terminal.
- Viewer-only sessions must be unable to mutate remote state.
- Unknown remote capabilities must degrade into renderable local objects, not disappear.
## Failure Modes
- Network reconnect loops that duplicate transcript or control events.
- Permission prompts that survive cancellation or session death.
- Local UI trying to execute a remote-only tool directly.
- Viewer clients leaking interrupts or approve actions.
- Remote servers sending unsupported control subtypes with no structured fallback.
- Conflating a cloud-hosted session (execution never touches the user's machine) with a locally-executing session mirrored to another device (execution stays local, only steering moves). They have different data-residency, credential, and failure-domain properties; see the Shipped Reference Implementations table above.
## Minimal Viable Version
- One remote session mode with explicit local-controller ownership.
- One typed message family for transcript events and one for control events.
- Stable request IDs for permission prompts and cancellations.
- Basic reconnect with clear user-visible disconnected versus reconnecting state.
- Remote tool uses rendered locally, even if the local client cannot execute them.
## What Strong Implementations Add
- Hybrid transports with different read and write semantics.
- Sequence-aware resume or replay from checkpoints.
- Viewer-only and SSH-like proxy modes with different capabilities.
- Server-driven cancellation, reconnect backoff, and pending-request cleanup.
- Full telemetry for control latency, reconnect attempts, and approval round-trips.
- **ACP stdio transport** for editor integrations (Zed, JetBrains, etc.) with session-ID re-attach.
- **Local typed-HTTP daemon** (OpenAPI-generated) decoupling GUI clients from the CLI entry point.
- **Schema-driven protocol-method codegen** so the wire contract is source-of-truth for dispatch.
## Known Traps
- Treating transport reconnect as if it were session resume and replaying the wrong in-flight state.
- Assuming the local client and remote runtime share the same tool registry, approval model, or capability envelope.
- Modeling viewer mode as presentation-only and accidentally exposing execution or approval paths that should remain server-owned.
- Leaving permission prompts and pending requests as local UI state instead of durable remote-runtime objects.
- Merging all remote traffic into one chat stream and losing the distinction between control messages, tool results, and durable session state.
## Common Anti-Patterns
- Treating the whole remote session as one undifferentiated chat stream.
- Assuming the local client and remote runtime always share the same tool registry.
- Using transport reconnect as if it were session resume.
- Modeling viewer mode as a UI flag instead of a runtime capability boundary.
- Leaving permission prompts as UI state instead of durable runtime objects.
## Shipped Reference Implementations (July 2026)
The patterns in this skill are not hypothetical — they map onto products you can inspect directly today. Ground any design decision in the real thing before inventing a new taxonomy; verified 2026-07-11:
| Runtime mode this skill describes | Shipped as | Where execution lives | Key primitives |
|---|---|---|---|
| Outbound-poll mirroring / viewer + controller | Claude Code **Remote Control** (`code.claude.com/docs/en/remote-control`) | Stays on the local machine that started it | `claude remote-control` (server mode, `--spawn same-dir\|worktree\|session`, `--capacity N`), `claude --remote-control`/`--rc` (interactive), `/remote-control`/`/rc` (attach existing session), session URL + QR from `claude.ai/code` or the mobile app; outbound HTTPS only, no inbound ports |
| Remote-execution devbox (SSH-stdio) | Claude Code Desktop app's SSH-host connection | Moves to the remote SSH host; agent auto-installs there on first connect | Point-and-connect `ssh user@host`; session history is siloed per surface |
| Cloud-hosted container (no local session) | **Claude Code on the web** (`claude.ai/code`, `--cloud` flag) and **Routines** (`code.claude.com/docs/en/web-scheduled-tasks`) | Anthropic-managed VM/container, fresh per session or per trigger | Repo clone into isolated VM; Routines add schedule/API/GitHub triggers on top of the same cloud-session substrate |
| App-server daemon + typed control | OpenAI Codex `codex app-server` / `codex app-server-daemon` | Local daemon process, `CODEX_HOME`-scoped | `start`/`stop`/`restart`/`enable-remote-control`/`disable-remote-control`/`bootstrap`, one JSON object per command |
| Remote-control infra for SSH/custom clients (distinct from the daemon above) | OpenAI Codex `codex remote-control` (v0.130+) | Local machine, foreground unless a `start` subcommand is used | Foreground `codex remote-control` enables remote control for one invocation; `codex remote-control start/stop/restart` manages the daemon; **this is infrastructure for SSH workflows and custom integrations, not a ready-made browser/QR flow** — Codex's user-facing QR/mobile pairing is mediated by the desktop app, not this CLI command |
| Editor-spawned stdio agent | ACP (Agent Client Protocol, Zed-originated, Apache-licensed) — adopted by Zed, JetBrains, Google, GitHub, and 25+ agents; Goose 2.0 made it the default server mode | Spawned subprocess on the editor's machine | Line-delimited JSON-RPC 2.0 over stdin/stdout; session-ID re-attach on reconnect |
Two naming traps this table exists to prevent:
- Claude Code's and Codex's commands are both called "remote control" but are not peers: Claude's is the primary user-facing entry point (prints its own session URL/QR); Codex's is low-level plumbing that other surfaces (the desktop app) build the user-facing flow on top of. Do not assume feature parity from name similarity alone.
- "Remote Control" (session stays local, mirrored) and "Claude Code on the web" / Routines (session runs in the cloud, nothing local) are Anthropic's own explicit contrast pair, not two names for the same thing. Pick the one that matches your actual failure and data-residency model before designing around either.
## Cloud-Hosted Remote Execution
Modern coding-agent platforms offer a third remote-runtime mode beyond local-to-remote bridge and developer devbox: **cloud-hosted containers** that run without a local session.
**Routines / cloud containers:** a trigger (schedule, API call, GitHub event) starts a fresh cloud container, the agent loop executes, artifacts are persisted, and results are delivered via webhook or session URL. No local machine involvement after trigger. The container lifecycle is: trigger → container start → execute (agent loop + tools + sandbox) → persist artifacts → webhook/session result. Treat as a remote-task lifecycle distinct from local-agent and devbox tasks. **Claude Code on the web** (`claude.ai/code`, or `claude --cloud`) is the on-demand sibling of Routines: same cloud-VM substrate, triggered interactively instead of by schedule/API/webhook, with sessions that persist across browser closes and can run in parallel across repos.
**Codex devboxes:** microVM/Firecracker-style isolated environments for Codex cloud tasks. Lifecycle mirrors Routines but with branched workspaces, multiple terminals, and app/browser tools. The isolation substrate is microVM-class, not container-class — security boundary differs.
**`claude -p` non-interactive execution:** `claude -p "prompt"` runs Claude Code in non-interactive (piped) mode — no terminal UI, output to stdout. This is a first-class primitive for CI/webhook-triggered runs: a CI step or webhook handler calls `claude -p` with the task description and captures the output. It combines with the Responses-API remote session model: the piped execution runs on the local machine but behaves like a remote job from the CI perspective. Authentication, approval mode, and sandbox settings all apply normally.
Design implications:
- Treat Routines-spawned tasks and `claude -p` CI runs as remote-task lifecycle objects, not as local-agent tasks, for ownership and cancellation purposes.
- Both modes are fresh-session each invocation: `AGENTS.md` / `CLAUDE.md` provides context; there is no transcript memory across runs.
- Design prompts to be self-contained per invocation.
## Cross-Platform Patterns (Goose)
Goose 2.0 (AAIF, April 2026) makes ACP the **default server mode** — the daemon starts in ACP mode automatically. HTTP daemon and WebSocket bridge are opt-in overlays, not the primary transport. This is a correction from earlier Goose descriptions where `goose acp` was a subcommand.
Goose ships two remote-runtime shapes worth importing: **ACP stdio as editor transport**, and a **local daemon with typed API** (goosed + OpenAPI). Both shift the skill's default assumption that "remote" means "across the internet."
### ACP (Agent Client Protocol) as local stdio transport
ACP is Zed's line-delimited JSON stdio protocol for editor ↔ agent. In Goose 2.0, ACP is the default server mode; editors (Zed, JetBrains, IntelliJ, PyCharm, WebStorm, VS Code forks) spawn the process and drive it. Reconnect is session-ID re-attach, not replay.
- **Pattern:** treat ACP as a first-class remote transport class alongside WebSocket. The `local UI ↔ remote agent` split still holds — the editor is the UI, the agent process is the executor — but the wire is a spawned subprocess, not a socket.
- **Anti-pattern:** building "IDE integration" as an in-process SDK embed. That couples editor lifecycle to agent lifecycle and makes crashes, upgrades, and custom distros impossible to isolate.
- **Recipe:** separate ACP server plumbing from the agent core. The `goose-acp-macros` pattern (proc-macros that generate method-routing code from the ACP schema) is the right level of indirection — your protocol-method set should be code-generated from the schema, not hand-written and drifting.
### Codegen for protocol methods
When the wire is typed (ACP, MCP, your own bridge protocol), hand-written method dispatch drifts from the schema as protocols evolve. Goose uses `goose-acp-macros` to generate handler scaffolding at compile time from the ACP meta/schema JSON.
- **Pattern:** treat the protocol schema as source-of-truth; generate server method dispatch, client stubs, and message validators from it.
- **Anti-pattern:** copy-pasting message type definitions into handwritten `match` arms. Every protocol bump becomes a multi-file change with nothing to verify against.
- **Recipe:** commit `acp-schema.json` (or equivalent) to the repo. CI regenerates dispatch code and fails the build if handwritten code diverges.
### Local daemon with typed HTTP API (goosed + OpenAPI)
Goose's desktop UI does not spawn the CLI for each action. A background daemon (`goosed`) runs, and the UI calls it through an OpenAPI-typed HTTP surface. The UI and daemon regenerate client/server code from the OpenAPI spec on each build.
- **Pattern:** model local multi-process coding agents as UI → typed-RPC → daemon, with session state owned by the daemon and the UI treated as one of many possible clients.
- **Anti-pattern:** the UI calling `goose` or `claude` as a shell subprocess and parsing stdout. That is fine for a CLI-native experience but produces fragile GUIs and makes multi-window or multi-agent coordination impossible.
- **Recipe:** version the daemon API separately from the binary (see `ai-coding-agents-release-distribution` — API version is its own compatibility contract). Provide an auto-generated OpenAPI client as the install-time artifact for third-party UIs.
### Agent-as-ACP-client delegation
ACP is bidirectional in practice: Goose can *also* act as an ACP client and delegate work to Claude Code or Codex running as ACP servers. This is the mirror image of the transport discussed above, and the skill's existing Viewer-only / SSH-proxy / Full-control mode taxonomy needs a fourth mode: **agent-delegating**, where the local runtime is neither UI nor executor — it is orchestrator.
- **Pattern:** add `agent-delegating` as a named mode; its control messages and approval routing follow the same typed-message discipline as the others.
- **Anti-pattern:** implementing delegation inside the provider layer (see `ai-coding-agents-provider-runtime` — agent-as-provider belongs there) *and* as a remote-runtime mode. Pick one; they interact but are not duplicates. Provider-level delegation is turn-scoped; remote-runtime delegation is session-scoped.
## Navigation
### References
- [`references/local-ui-remote-execution-model.md`](references/local-ui-remote-execution-model.md) — Local UI, remote agent loop, viewer-only mode, and SSH-style execution
- [`references/bridge-transport-and-permission-bridging.md`](references/bridge-transport-and-permission-bridging.md) — WebSocket control messages, permission routing, reconnect, and SDK-to-REPL adaptation
- [`references/transport-selection.md`](references/transport-selection.md) — Decision tree for WebSocket vs SSE vs HTTP POST vs ACP stdio
- [`references/recipe-reconnect-with-sequence.md`](references/recipe-reconnect-with-sequence.md) — Resumable stream recipe with sequence numbers and client reconnect loop
- [`references/openai-codex-app-server-remote-control.md`](references/openai-codex-app-server-remote-control.md) — OpenAI Codex app-server daemon lifecycle, machine-readable remote control, bootstrap, and long-running work
- [`references/openai-codex-app-server-protocol-codegen.md`](references/openai-codex-app-server-protocol-codegen.md) — OpenAI Codex app-server protocol codegen, experimental method filtering, and schema fixture validation
- [`references/openai-codex-as-mcp-server.md`](references/openai-codex-as-mcp-server.md) — `codex mcp-server` subcommand, `codex-rs/mcp-server` crate, approval bridging over MCP, contrast with app-server-daemon
### Data
- [`data/sources.json`](data/sources.json) — Primary documentation and source references for remote runtime guidance
### Related Skills
- [`../ai-coding-agents-permissions/SKILL.md`](../ai-coding-agents-permissions/SKILL.md)
- [`../ai-coding-agents-sessions/SKILL.md`](../ai-coding-agents-sessions/SKILL.md)
- [`../agents-mcp/SKILL.md`](../agents-mcp/SKILL.md)
## Fact-Checking
- Known bugs, regressions, framework/compiler/runtime footguns, and version-specific crash or workaround guidance must be verified against current primary web sources before being treated as current fact.
- The core patterns (typed control messages, permission bridging, reconnect states) were originally grounded in an internal April 2026 `claude_code` source snapshot; the Shipped Reference Implementations section above replaces that internal grounding with the live, citable product docs (`code.claude.com/docs/en/remote-control`, `code.claude.com/docs/en/claude-code-on-the-web`, `code.claude.com/docs/en/web-scheduled-tasks`), verified 2026-07-11.
- Exact flags, defaults, and version gates (e.g. `--spawn`, `--capacity`, minimum CLI versions noted in the Claude Code docs, Codex's `codex remote-control` subcommand set) move monthly. Re-check the live docs before quoting an exact flag name or version number in user-facing guidance.
## Learnings Loop
Before applying this skill on a non-trivial task, read `learnings.consolidated.md` in this directory (and `learnings.md` if present).
After applying it, if you encountered a pattern worth remembering, a mistake worth preventing, or a domain fact that surprised you, append one dated bullet to `learnings.md` via `agents-skills-feedback-loop/scripts/append_learning.py`. Do not modify `SKILL.md` itself.