Skip to content

ACP Remote

acpremote is ACP Kit's transport package for exposing an existing ACP server over WebSocket and for mirroring a remote ACP endpoint back into a local ACP boundary.

It is transport-only. It does not adapt Pydantic AI, LangChain, or any other framework by itself. If you already have an acp.interfaces.Agent or a stdio ACP command, acpremote can move that ACP surface across a WebSocket boundary.

Use acpremote when the runtime already speaks ACP and you only need transport. Use acpkit when the runtime is still a Python target and should be resolved into the correct adapter first.

Core Construction Paths

The public server-side seams are:

  • serve_acp(...)
  • serve_command(...)
  • serve_stdio_command(...)

The public client-side seam is:

  • connect_acp(...)

Expose an in-memory ACP agent on the remote host:

from acpremote import serve_acp

server = await serve_acp(agent=my_acp_agent, host='127.0.0.1', port=8080)
await server.serve_forever()

Expose a stdio ACP command instead of an in-memory agent:

from acpremote import serve_command

server = await serve_command(
    ['npx', '@zed-industries/codex-acp'],
    host='127.0.0.1',
    port=8080,
)
await server.serve_forever()

Mirror a remote ACP endpoint back into a local stdio ACP server:

from acp import run_agent
from acpremote import connect_acp

agent = connect_acp('ws://127.0.0.1:8080/acp/ws')
await run_agent(agent)

If the remote server advertises remote_cwd in its metadata, connect_acp(...) uses that directory for new_session(...), load_session(...), fork_session(...), resume_session(...), and list_sessions(...) instead of forwarding the local facade's working directory verbatim.

By default connect_acp(...) also strips local host-backed client capabilities before forwarding initialize(...) upstream. That keeps the remote ACP server authoritative for filesystem and terminal ownership. Opt back into capability forwarding only when you explicitly want a local client-host passthrough model:

from acpremote import TransportOptions, connect_acp

agent = connect_acp(
    'ws://127.0.0.1:8080/acp/ws',
    options=TransportOptions(host_ownership='client_passthrough'),
)

Typical End-To-End Flows

Remote-host flow:

acpkit serve examples.langchain.workspace_graph:graph --host 0.0.0.0 --port 8080

Local mirror flow:

acpkit run --addr ws://remote.example.com:8080/acp/ws

Direct ACP transport flow:

from acpremote import serve_command

server = await serve_command(
    ["fast-agent", "--server", "--transport", "acp"],
    host="0.0.0.0",
    port=8080,
)
await server.serve_forever()

When an editor or launcher wants to shell out, the same mirror path can be wrapped with Toad:

toad acp "acpkit run --addr ws://remote.example.com:8080/acp/ws"

Default HTTP And WebSocket Surface

By default acpremote exposes three routes:

  • metadata: http://127.0.0.1:8080/acp
  • health: http://127.0.0.1:8080/healthz
  • websocket: ws://127.0.0.1:8080/acp/ws

mount_path= can move the ACP metadata and WebSocket routes together while /healthz remains a top-level liveness probe.

Command Mirroring

serve_command(...) is the important seam when the upstream runtime can already speak ACP over stdio but does not expose a reusable Python ACP agent object.

That surface is useful for tools such as:

  • Codex ACP
  • Fast Agent ACP
  • any other ACP server that already runs over stdin and stdout

It is also the right seam for the release-prep story where the remote host owns the runtime and the local machine only mirrors the transport.

Environment handling is additive. env={...} overrides selected variables while inheriting the parent process environment, so normal PATH lookup still works.

For command-backed servers, acpremote also advertises the command's effective working directory as remote_cwd in the metadata endpoint. That keeps mirrored local ACP facades aligned with the remote host instead of the local client machine.

Transport Timing

TransportOptions can emit proxy-observed timing data on the mirrored ACP stream:

from acpremote import TransportOptions, connect_acp

agent = connect_acp(
    'ws://127.0.0.1:8080/acp/ws',
    options=TransportOptions(
        emit_latency_meta=True,
        emit_latency_projection=True,
    ),
)

When enabled:

  • streamed updates can carry field_meta["acpremote"]["transport_latency"]
  • a visible Transport Latency ACP card can be emitted after each prompt turn

These numbers are proxy-observed timings measured by the local mirror, not synchronized one-way host clock measurements.

Transport Contract

Current transport behavior is intentionally narrow:

  • one WebSocket text message carries one ACP JSON message
  • binary WebSocket frames are rejected
  • transport limits are configurable through TransportOptions
  • bearer token auth is optional
  • metadata and health endpoints are served alongside the WebSocket transport

Documented Remote-Host Flows

Remote hosting is documented as an operator pattern, not as a maintained example source package.

Guide:

The documented flows cover both supported remote-host stories:

  1. adapt a Python runtime through pydantic-acp or langchain-acp
  2. expose the resulting ACP server through acpkit serve ... or acpremote.serve_acp(...)
  3. mirror it locally with acpkit run --addr ... or connect_acp(...)
  4. or skip adaptation entirely and expose a native ACP stdio command directly through serve_command(...)