Unifying Codex, OpenCode, and Pi in Remote Sandboxes with ACP

typescript dev.to

Imagine Codex needs to inspect a repository in a remote VM. It needs to read that copy, run commands there, ask for permission, and send updates back to your app. That is a session, not a shell command.

Agent Client Protocol, or ACP, gives a client and a coding agent a shared session contract. The client starts an ACP-capable agent process, initialises the connection, creates a session, sends prompts, receives updates, handles requests from the agent, and closes the session when it is done. It can use stdin and stdout, which suits an agent running in a remote environment.

The protocol was built for an app, such as a code editor, to run a coding agent and talk to it. Usually they run on the same machine and exchange messages over stdin and stdout.

We can use the same arrangement remotely. The app stays where it is; the coding agent runs in a Sandbox. The protocol connects them. It does not create the Sandbox or install the agent, but Codex, OpenCode, and Pi can all use the same connection once they are running.

The shared client uses ACP's TypeScript SDK.

Run the agent in the same place as its files and commands.

This article uses Agent Markup Language, aka AML, as a concrete example. It is an MIT-licensed TypeScript and JSX runtime for composing agent workflows, tools, Sandboxes, and durable Workspaces. We recently moved its built-in coding agents to ACP so their session lifecycle no longer changes with the selected execution environment.

In AML, Sandbox and Workspace are named runtime resources, not generic labels. A Sandbox supplies the execution environment and attaches the working files; a Workspace owns those files and their persistence.

An AML Agent provider is the common interface for a coding agent. codexAgent(), opencodeAgent(), and piAgent() each return one. A provider maps that agent's own configuration and capabilities; the shared runtime handles the session, messages, stdin and stdout, MCP servers, cancellation, and cleanup.

What ACP standardises

Running an agent remotely needs two things. The remote machine needs the agent program installed. Your app needs a way to send it a prompt, receive its updates, and stop it when the work ends. What ACP does is standardise the protocol between your app and the agent.

ACP does not install anything for you. If your remote container or VM will run Codex, OpenCode, or Pi, that program needs to be in the image or snapshot first. For a quick experiment, you can install it as part of setup. For repeatable work, build it into the image so startup is predictable.

The Sandbox owns the remote environment: where the process runs and what access it has. The Agent provider supplies the command and configuration. ACP then connects your app to that process in the same way regardless of which agent you pick.

The protocol does not make remote execution safe on its own. The Sandbox's filesystem, network, and process restrictions do that work.

How many ways are there to run an agent?

Before we implemented ACP, Sandbox support was three separate integrations. Each agent had its own answer to a basic question: how do I run when the files and commands are somewhere else?

  • Pi stayed inside the app as an SDK. We rebuilt Pi's Bash tool so that a shell command called the Sandbox runner instead of the host shell. Pi itself was not an RPC client talking to the Sandbox. We provided a custom bridge for its Bash tool.
  • Codex ran as a CLI inside the Sandbox. We created its temporary Codex state there, wrote any JSON output schema there, called the CLI for each turn, read its JSON events, kept track of its thread ID, and removed its state afterwards.
  • OpenCode needed two paths. On the application host, we started an OpenCode server, waited for it to listen, and connected a client to it. In a Sandbox, we ran the opencode CLI instead, kept a separate database and configuration there, and parsed its JSON output. The CLI started OpenCode's server inside the Sandbox.

Those were all sensible ways to make one agent work. Together, they made every new Sandbox provider a bigger job. It needed to support an embedded tool bridge for Pi, a managed CLI for Codex, and both a server/client path and a separate CLI path for OpenCode.

ACP cleared that up. All three now use the same protocol to talk to the runtime. Each Agent provider tells the runtime which program to run and how to configure it. The shared code starts that program in the chosen Sandbox, sets up the session, attaches MCP servers, sends the prompt and FollowUps, reads updates, handles cancellation, and cleans up.

The agents still differ in their models, credentials, tools, and the way they work. The runtime stopped maintaining a separate way of running each one in a Sandbox.

A remote Sandbox example

This is an example with the shape of a Daytona setup. With no image or snapshot selected, the provider asks Daytona to create a default environment.

import { Agent, AmlRuntime, codexAgent, daytonaSandbox, localWorkspace, Sandbox, Workspace } from "@aml-jsx/sdk"

const Codex = codexAgent({})

const RemoteSandbox = daytonaSandbox({
  config: {
    apiKey: process.env.DAYTONA_API_KEY!,
  },
})

const Project = localWorkspace({
  directory: process.cwd(),
})

const runtime = new AmlRuntime()

await runtime.evaluate(
  <Workspace id="project" provider={Project}>
    <Sandbox access="read-write" provider={RemoteSandbox}>
      <Agent provider={Codex}>
        Inspect the repository, run the relevant tests, and implement the requested change.
      </Agent>
    </Sandbox>
  </Workspace>,
)
Enter fullscreen mode Exit fullscreen mode

For Daytona, the runtime creates the remote Sandbox and transfers the Workspace files in and out. If that Sandbox can run codex-acp, it starts the program there and speaks ACP with it over stdin and stdout. When the work ends, it copies the Workspace changes back and releases the remote Sandbox.

Your workflow does not need its own remote-agent setup. Change the Agent provider to OpenCode or Pi. Change the Sandbox to Docker, trusted local execution, or Modal. The setup code changes, but the tree that describes the work stays the same.

The result is a shared session path, with environment setup still explicit. A compatible Sandbox must supply the selected agent executable; the runtime no longer needs a different session path for Codex, OpenCode, and Pi.


AML is under active development, and its public APIs may change before the first stable release. Inspect the ACP engine, Agent providers, and Sandbox adapters in the repository, or see the runtime model and current provider support on the project site.

Star AML on GitHub if this is useful, and drop a comment: what would you use it for in your stack?

Source: dev.to

arrow_back Back to Tutorials