A Go-based relay server that translates HTTP/WebSocket requests into ACP (Agent Client Protocol) JSON-RPC messages, spawning isolated agent subprocesses per session with dedicated working directories.
- HTTP API (Port 8080): REST-style request/response for simple use cases
- WebSocket API (Port 8081): Bidirectional streaming for real-time, interactive communication
- Management API (Port 8082): Health checks and runtime configuration (localhost only)
- Process Isolation: One agent subprocess per session with isolated working directories
- LLM-Optimized Errors: Verbose error messages with explanations, possible causes, and suggested actions
- Concurrent Sessions: Support for multiple concurrent agent sessions
- Clean Shutdown: Proper cleanup of agent processes on session termination
Security and UX enhancements inspired by obra/packnplay:
- Environment Isolation: Only safe variables (TERM, LANG, LC_*) passed to containers
- Container Reuse: Existing containers reused when possible, reducing startup time
- Runtime Detection: Auto-detect Docker, Podman, or Colima
- Interactive Setup:
acp-relay setupguides first-time configuration - XDG Support: Standard Linux/Unix directory structure (~/.config, ~/.local/share)
- Structured Logging:
--verboseflag for detailed debug output - Container Labels: Track managed containers via Docker labels
See docs/packnplay-improvements.md for details.
The ACP Relay Server uses a three-layer architecture:
┌─────────────────────────────────────────────────────────────┐
│ Frontend Layer │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ HTTP Server │ │ WebSocket │ │ Management │ │
│ │ (Port 8080) │ │ Server │ │ API │ │
│ │ │ │ (Port 8081) │ │ (Port 8082) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
└─────────┼──────────────────┼──────────────────┼──────────────┘
│ │ │
┌─────────┼──────────────────┼──────────────────┼──────────────┐
│ │ Translation Layer │ │
│ │ (HTTP/WS ↔ JSON-RPC) │ │
│ └──────────┬───────┘ │ │
│ │ │ │
└────────────────────┼──────────────────────────┼──────────────┘
│ │
┌────────────────────┼──────────────────────────┼──────────────┐
│ │ Backend Layer │ │
│ │ (Session Manager) │ │
│ ┌─────▼──────────────────────────▼─────┐ │
│ │ Session Manager │ │
│ │ - Process Lifecycle Management │ │
│ │ - Stdio Bridge (channels ↔ pipes) │ │
│ └─────┬──────────┬──────────┬──────────┘ │
│ │ │ │ │
│ ┌──────────▼─┐ ┌─────▼────┐ ┌─▼──────────┐ │
│ │ Agent │ │ Agent │ │ Agent │ │
│ │ Process 1 │ │ Process 2│ │ Process N │ │
│ │ (stdio) │ │ (stdio) │ │ (stdio) │ │
│ └────────────┘ └──────────┘ └────────────┘ │
└───────────────────────────────────────────────────────────────┘
- Frontend Layer: HTTP and WebSocket handlers accept incoming requests
- Translation Layer: Converts HTTP/WebSocket messages to JSON-RPC 2.0 format
- Backend Layer: Manages agent subprocesses, handles stdio communication via channels
- Session Manager: Creates isolated agent processes with dedicated working directories
- Stdio Bridge: Goroutines that bridge Go channels with process stdin/stdout
- Go 1.23 or higher
- An ACP-compatible agent binary
Build the relay server:
git clone https://github.com/harper/acp-relay
cd acp-relay
make build
# Or manually:
# go build -o acp-relay ./cmd/relayCreate or modify config.yaml:
server:
http_port: 8080
http_host: "0.0.0.0"
websocket_port: 8081
websocket_host: "0.0.0.0"
management_port: 8082
management_host: "127.0.0.1" # Localhost only for security
agent:
command: "/usr/local/bin/acp-agent" # Path to your agent binary
args: [] # Optional command-line arguments
env: {} # Optional environment variables
startup_timeout_seconds: 10
max_concurrent_sessions: 100For container mode, run the interactive setup:
./acp-relay setupThis will:
- Detect available container runtimes
- Guide configuration choices
- Generate config file at
~/.config/acp-relay/config.yaml - Complete in <5 minutes
Start the relay server:
./acp-relay --config config.yamlYou should see:
Starting HTTP server on 0.0.0.0:8080
Starting WebSocket server on 0.0.0.0:8081
Starting management API on 127.0.0.1:8082
./acp-relay --verbose --config ~/.config/acp-relay/config.yamlCheck server health:
curl http://localhost:8082/api/healthCreate a session via HTTP:
curl -X POST http://localhost:8080/session/new \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "session/new",
"params": {
"workingDirectory": "/tmp/workspace"
},
"id": 1
}'See docs/api.md for complete API documentation including:
- HTTP API endpoints
- WebSocket protocol
- Management API
- Request/response examples
- Error handling
Run all tests:
go test ./... -vRun tests for a specific package:
go test ./internal/session -v
go test ./internal/http -v
go test ./internal/websocket -vacp-relay/
├── cmd/relay/ # Main entry point
│ └── main.go
├── internal/
│ ├── config/ # Configuration loading
│ ├── jsonrpc/ # JSON-RPC message types
│ ├── session/ # Session and process management
│ ├── http/ # HTTP server and handlers
│ ├── websocket/ # WebSocket server
│ ├── management/ # Management API
│ └── errors/ # LLM-optimized error handling
├── docs/
│ └── api.md # API documentation
├── config.yaml # Default configuration
├── go.mod
└── README.md
http_port: Port for HTTP API (default: 8080)http_host: Host binding for HTTP API (default: 0.0.0.0)websocket_port: Port for WebSocket API (default: 8081)websocket_host: Host binding for WebSocket API (default: 0.0.0.0)management_port: Port for Management API (default: 8082)management_host: Host binding for Management API (default: 127.0.0.1)
command: Path to the ACP agent binary (required)args: Array of command-line arguments to pass to the agentenv: Map of environment variables to set for the agentstartup_timeout_seconds: Time to wait for agent startup (default: 10)max_concurrent_sessions: Maximum number of concurrent sessions (default: 100)
The ACP Relay Server supports running agents in Docker containers for isolation and reproducibility.
- Docker installed and running
- Docker image built:
docker build -t acp-relay-agent:latest .
Set agent.mode: "container" in config.yaml:
agent:
mode: "container"
env:
ANTHROPIC_API_KEY: "${ANTHROPIC_API_KEY}"
container:
image: "acp-relay-agent:latest"
docker_host: "unix:///var/run/docker.sock"
network_mode: "bridge"
memory_limit: "512m"
cpu_limit: 1.0
workspace_host_base: "/tmp/acp-workspaces"
workspace_container_path: "/workspace"
auto_remove: true
startup_timeout_seconds: 10docker build -t acp-relay-agent:latest ../acp-relay --config config.yamlSessions will be created in Docker containers with isolated workspaces.
"Cannot connect to Docker daemon"
- Verify Docker is running:
docker ps - Check Docker socket path in config
"Docker image not found"
- Build the image:
docker build -t acp-relay-agent:latest . - Verify it exists:
docker images | grep acp-relay-agent
"Container exits immediately"
- Check container logs:
docker logs <container-id> - Verify environment variables are set
See docs/container-mode.md for detailed documentation.
The ACP Relay Server provides LLM-optimized error messages that include:
- Error Type: Categorized error identifier
- Explanation: Human-readable explanation of what went wrong
- Possible Causes: List of potential reasons for the error
- Suggested Actions: Step-by-step actions to resolve the issue
- Relevant State: Contextual information about the error
- Recoverable: Whether the error is recoverable
Example error response:
{
"jsonrpc": "2.0",
"error": {
"code": -32000,
"message": "The session 'sess_abc123' does not exist...",
"data": {
"error_type": "session_not_found",
"explanation": "The relay server doesn't have an active session with this ID.",
"possible_causes": [
"The session was never created (missing session/new call)",
"The session ID was mistyped or corrupted",
"The session expired due to inactivity timeout"
],
"suggested_actions": [
"Create a new session using session/new",
"Verify you're using the correct session ID"
],
"relevant_state": {
"session_id": "sess_abc123"
},
"recoverable": true
}
},
"id": 1
}Problem: Sessions fail to create with "agent connection timeout" error
Solutions:
- Verify the agent binary exists:
ls -l /path/to/agent - Check the agent can run manually:
/path/to/agent --help - Review stderr logs for agent error messages
- Ensure required environment variables are set in
config.yaml
Problem: Cannot connect to WebSocket server
Solutions:
- Verify the WebSocket server is running on the correct port
- Check firewall rules allow connections to port 8081
- Ensure your client is connecting to the correct URL (ws:// not wss://)
Problem: Server crashes with "too many open files" error
Solutions:
- Increase system file descriptor limit:
ulimit -n 4096 - Reduce
max_concurrent_sessionsin config.yaml - Ensure sessions are properly closed when no longer needed
- Management API: Bound to localhost (127.0.0.1) by default to prevent external access
- Process Isolation: Each session runs in its own subprocess with a dedicated working directory
- WebSocket Origin Checking: Currently accepts all origins (TODO: implement proper checking)
- Authentication: Not implemented (consider adding authentication middleware)
MIT License - see LICENSE file for details
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Write tests for new functionality
- Follow Go conventions and run
go fmt - Submit a pull request
For issues and questions:
- GitHub Issues: https://github.com/harper/acp-relay/issues
- Documentation: docs/api.md