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 LatencyACP 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:
- adapt a Python runtime through
pydantic-acporlangchain-acp - expose the resulting ACP server through
acpkit serve ...oracpremote.serve_acp(...) - mirror it locally with
acpkit run --addr ...orconnect_acp(...) - or skip adaptation entirely and expose a native ACP stdio command directly through
serve_command(...)