Add runtime execution targets for SSH/Docker and nested environments

Open 💬 0 comments Opened Jun 14, 2026 by yukimaru77

What variant of Codex are you using?

CLI / TUI.

What feature would you like to see?

I would like Codex to have a first-class way to switch tool execution into runtime environments such as SSH hosts, Docker containers, and nested combinations like ssh:host>docker:container.

This matters because once the real work moves inside a remote host or container, Codex can no longer use its built-in tools naturally against that environment. Reading files, editing files, applying patches, inspecting images, or running follow-up commands must be wrapped inside local shell commands such as ssh ..., docker exec ..., or both. That is workable for one-off commands, but it is a poor execution model for agentic work: it burns tokens on quoting and command scaffolding, produces noisy and oversized function outputs, hides the real execution target from the tool contract, and forces the model to reason about remote state through shell-string indirection instead of normal tool semantics.

The mental model should instead be the same as what a human developer already does:

ssh my-server
# work there
docker exec -it my-container bash
# work inside the container

but made native to Codex, so shell/file/edit tools, subagents, status UI, approvals, and transcript evidence all know which environment they are operating in.

In other words, Codex should be able to keep one conversation while executing tools in:

  • the local machine
  • an SSH host
  • a Docker container
  • a Docker container on an SSH host
  • multiple different targets used by different subagents in the same task

Why raw ssh ... / docker exec ... commands are not enough

Today the workaround is to ask Codex to run commands like ssh host '...' or docker exec container ... from the local shell. That works for simple one-off commands, but it is not the same as letting Codex actually operate in that environment.

The wrapper-command approach has several practical problems:

  • The tool layer still thinks it is local, so file reads, file edits, cwd, shell metadata, and status UI do not naturally match the real target.
  • Quoting and heredocs become brittle, especially for scripts, patches, multiline commands, and nested SSH + Docker work.
  • Subagents can accidentally fall back to local execution unless the target environment is part of the tool contract.
  • Resume/compaction has no clear compact representation of “where the work is currently happening.”
  • The user cannot easily see whether a command/file edit is local, on an SSH host, or inside a container.
  • Safety/approval UX is weaker because the true target is hidden inside an arbitrary shell command string.

A native execution target would make “where Codex is working” explicit, inspectable, and testable.

Demo

I recorded a short demo here:

https://youtu.be/YRjKSqD2E2k

The prompt used in the demo did not explicitly ask the model to call an env_switch tool. It asked for a normal remote/container workflow:

Please use subagents only. The main agent should coordinate the work but should not run the remote SSH, Docker, or benchmark commands directly.

Have one subagent run the benchmark on `ssh saitou`, and another subagent run it on `ssh saitou-h200`.

On each remote host, create a GPU-capable Docker container and run a PyTorch CPU vs GPU matrix multiplication benchmark inside that container. Use the same benchmark parameters on both CPU and GPU, and report the exact commands or transcript evidence, the container name/id, GPU model, PyTorch/CUDA status, matrix size, warmups, repetitions, CPU timing, and GPU timing.

The main thread stayed local and coordinated the work. Each subagent entered its own SSH host and then worked inside a Docker container on that host.

What changes at the item level?

The useful comparison is not the benchmark result; it is what Codex has to do for each tool item.

Without a native execution target, every operation remains a local shell item that must manually tunnel into the real target:

1. shell: ssh saitou 'docker run ...'
2. shell: ssh saitou "docker exec -i codex-matmul-bench ... python -" <<'PY'
3. shell: ssh saitou "docker exec codex-matmul-bench sed -n '1,120p' /work/file.py"
4. shell: ssh saitou "docker exec codex-matmul-bench sh -lc 'cat > /work/file.py <<EOF ...'"

That has several bad effects on the actual agent loop:

  • The tool call is still “local shell”, so the real target is hidden inside an opaque command string.
  • Built-in file/edit/patch tools cannot naturally operate on the remote/container filesystem.
  • Every follow-up item repeats ssh ..., docker exec ..., shell quoting, heredoc plumbing, and path translation.
  • Function outputs become noisier because they include wrapper failures, quoting errors, and large command transcripts rather than just the target operation result.
  • The model has to keep reasoning about remote state through shell-string indirection; after resume/compaction, that state is easy to lose.

With a native runtime target, the item stream becomes more like this:

1. env_switch: target=ssh, host=saitou
   -> environment_id = ssh:saitou

2. exec_command: environment_id=ssh:saitou, cmd="docker run ..."
   -> creates/reuses the container on that host

3. env_switch: target=docker, container=codex-matmul-bench, extend=current
   -> environment_id = ssh:saitou>docker:codex-matmul-bench

4. exec_command: environment_id=ssh:saitou>docker:codex-matmul-bench, cmd="python bench.py"
   -> runs directly in the container target

5. read/edit/apply_patch/exec follow-up items use the same target id or the current default target
   -> no repeated ssh/docker wrapper in every item

That is the core feature request: make the target an explicit field/registered environment, not a hidden shell prefix.

In the recorded demo, the main thread stayed local while two subagents independently used different nested targets:

main thread:
  local coordination only

subagent A:
  ssh:saitou
  ssh:saitou>docker:codex-matmul-bench-saitou-20260614-232012

subagent B:
  ssh:saitou-h200
  ssh:saitou-h200>docker:codex-matmul-bench-saitou-h200-20260614-231921

The benchmark evidence was only used as a realistic workload that required SSH, Docker, GPU visibility, Python execution, and transcript collection. The important behavior was that each subagent could keep working in its own target while the UI/status line and tool contract preserved where the work was happening.

Token impact

Using the same prompt for this two-host SSH + Docker + PyTorch benchmark task:

without env_switch: Time used: 5m, Tokens used: 81.2K
with env_switch:    Time used: 6m, Tokens used: 47.6K

The token usage was roughly cut in half. The wall-clock time was slightly longer in this run, but I would not treat that as a performance conclusion because agent execution and LLM scheduling are nondeterministic.

Related issues and how this differs

I found several related issues, but none seem to be exactly this request:

  • #23074 asks for multiple SSH remote contexts in one thread. This proposal is compatible with that idea, but it is lower-level and CLI/tool-call oriented: every shell/file/edit tool call can target a concrete execution environment, including nested Docker containers.
  • #27336 asks for named local environment overlays. This proposal is not mainly about local environment variables or toolchain overlays; it is about remote/container execution backends.
  • #24441 asks for remote CLI install/update similar to VS Code Remote. This proposal would likely need that kind of versioned remote provisioning, but the user-facing goal is runtime switching into execution targets.
  • #23854 discusses scalable remote execution patterns at an architectural level. This is a smaller, directly user-facing mechanism for “run Codex’s tools over there now.”
  • #19817 / devcontainer-related issues are about project/container setup. This proposal also covers entering already-running containers or containers created mid-task, including containers on SSH hosts.

Prototype

I understand external code PRs are by invitation only, so I am opening the issue first for design discussion. I also have a working prototype in my fork to make the proposal concrete:

https://github.com/yukimaru77/codex/pull/1

That PR is intentionally a consolidated prototype/demo branch. If the Codex team is interested in the direction, I would split it into smaller reviewable stages in my fork before asking for any upstream review (for example: remote provisioning, environment metadata/status, env_switch tool semantics, TUI status/resume behavior, and raw SSH/Docker advisories).

The prototype implements this behind an experimental feature flag. The current shape is roughly:

  • env_switch to select a runtime target such as local, SSH, Docker, or nested SSH > Docker.
  • env_status / env_list so the agent can inspect where it is currently executing and what targets exist.
  • Per-tool environment_id targeting, plus a default current environment for compatible tools.
  • target=local to return to local execution.
  • Versioned remote provisioning under a separate server directory, similar in spirit to VS Code Remote, so it does not depend on or overwrite whatever codex binary the remote user may already have installed.
  • Failure if the required remote server version cannot be provisioned, rather than silently falling back to an old remote Codex binary.
  • TUI status badges so the user can see when the active target is local, SSH, Docker, or nested SSH > Docker.
  • Bounded tool descriptions/advisories that encourage switching when the model starts doing raw SSH/Docker work, without rewriting the global prompt.

Acceptance criteria

A complete version would feel right if:

  • A user can ask for a task involving SSH hosts and Docker containers without manually spelling out the implementation tool.
  • Codex can switch execution to local, SSH, Docker, and nested SSH > Docker targets during a live thread.
  • Subagents can independently work in different targets while the main agent remains local.
  • The active target is visible in the TUI/status line and in command/file-edit evidence.
  • Shell, file read, file edit, and patch tools consistently use the selected target.
  • The user can explicitly return to local execution.
  • Resuming a session preserves enough target metadata that the UI and tool execution do not silently snap back to local.
  • Remote provisioning is versioned and isolated from any user-installed codex binary on the remote host.
  • The feature fails clearly when the required remote runtime cannot be installed/launched.
  • Raw ssh / docker exec / docker run commands can produce a lightweight advisory suggesting a native target switch when appropriate.

Security and UX considerations

I think the target should always be explicit in user-visible places:

  • before running shell commands
  • around file edits and patches
  • in approval prompts
  • in the status line
  • in transcript evidence

Permission on one host/container should not imply permission on another. A destructive command should show the exact target. This is one of the reasons I think a native target model is safer than hiding remote/container work inside shell command strings.

View original on GitHub ↗