The acp.http module provides a WebSocket-based transport layer for ACP, enabling agents and clients to communicate over HTTP instead of stdio. This is useful for deploying agents as remote web services.
While stdio transport works well for local agents spawned as child processes, many deployment scenarios require network-based communication:
- Agents running on cloud infrastructure (e.g., AWS Bedrock AgentCore)
- Multiple clients connecting to a shared agent service
- Agents behind load balancers or API gateways
The HTTP/WebSocket transport uses the same Connection API as stdio, so existing agent and client code works without changes.
The adapter uses a 4-queue architecture to bridge WebSocket messages with asyncio streams:
INCOMING: WebSocket.recv() → StreamReader.feed_data() → ACP Connection
OUTGOING: ACP Connection → StreamWriter.write() → deque buffer → WebSocket.send()
Two background tasks pump messages through the bridge:
- Receive loop: Reads from WebSocket, feeds bytes to
asyncio.StreamReader - Send loop: Drains the outgoing
dequebuffer and sends via WebSocket
This design allows AgentSideConnection and ClientSideConnection to work unchanged — they read/write asyncio streams as usual.
Bridges any WebSocket connection with asyncio StreamReader/StreamWriter:
from acp.http import WebSocketStreamAdapter
adapter = WebSocketStreamAdapter(websocket)
await adapter.start()
# Use adapter.reader and adapter.writer with ACP Connection
conn = AgentSideConnection(agent, adapter.writer, adapter.reader)
# Clean up
await adapter.close()Async context manager for client-side WebSocket connections:
from acp.http import connect_http_agent
async with connect_http_agent(client, "ws://localhost:8080/ws") as conn:
await conn.initialize(protocol_version=PROTOCOL_VERSION)
session = await conn.new_session(mcp_servers=[], cwd=".")
await conn.prompt(session_id=session.session_id, prompt=[text_block("Hello")])Accepts optional **ws_kwargs passed through to websockets.connect() (e.g., max_size, extra_headers, compression).
Any object implementing recv(), send(), and close() works with the adapter:
class WebSocketLike(Protocol):
async def recv(self) -> str | bytes: ...
async def send(self, data: str | bytes) -> None: ...
async def close(self) -> None: ...Pre-built wrapper for Starlette's WebSocket (which uses receive_text()/send_text() instead of recv()/send()):
from acp.http import StarletteWebSocketWrapper
wrapped = StarletteWebSocketWrapper(starlette_websocket)
adapter = WebSocketStreamAdapter(wrapped)Serve an ACP agent over WebSocket using Starlette:
from starlette.applications import Starlette
from starlette.routing import WebSocketRoute
from starlette.websockets import WebSocket
from acp.agent.connection import AgentSideConnection
from acp.http import StarletteWebSocketWrapper, WebSocketStreamAdapter
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
wrapped = StarletteWebSocketWrapper(websocket)
adapter = WebSocketStreamAdapter(wrapped)
await adapter.start()
agent = MyAgent()
conn = AgentSideConnection(
to_agent=agent,
input_stream=adapter.writer,
output_stream=adapter.reader,
listening=False,
)
await conn.listen()
await adapter.close()
app = Starlette(routes=[WebSocketRoute("/ws", websocket_endpoint)])See examples/http_echo_agent.py for a complete runnable server.
Connect to a remote agent:
from acp.http import connect_http_agent
from acp import PROTOCOL_VERSION, text_block
from acp.schema import ClientCapabilities, Implementation
async with connect_http_agent(MyClient(), "ws://agent.example.com/ws") as conn:
await conn.initialize(
protocol_version=PROTOCOL_VERSION,
client_capabilities=ClientCapabilities(),
client_info=Implementation(name="my-client", title="My Client", version="0.1.0"),
)
session = await conn.new_session(mcp_servers=[], cwd="/workspace")
await conn.prompt(session_id=session.session_id, prompt=[text_block("Hello!")])See examples/http_client.py for a complete runnable client with interactive prompt.
websockets>=12.0— Required (core dependency). Used byconnect_http_agentfor client-side connections.starlette— Optional, for server-sideStarletteWebSocketWrapper. Install separately:pip install starlette uvicorn.
- Messages use newline-delimited JSON format (one JSON-RPC message per line), consistent with stdio transport.
- Default maximum WebSocket message size is 50MB (matching stdio buffer limit). Override via
max_sizekwarg. - The
StarletteWebSocketWrappercurrently handles text frames only. Binary WebSocket frames are converted to UTF-8.