Skip to content

2389-research/acp-relay

Repository files navigation

ACP Relay Server

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.

Features

  • 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

Pack'n'Play Improvements

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 setup guides first-time configuration
  • XDG Support: Standard Linux/Unix directory structure (~/.config, ~/.local/share)
  • Structured Logging: --verbose flag for detailed debug output
  • Container Labels: Track managed containers via Docker labels

See docs/packnplay-improvements.md for details.

Architecture

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)    │       │
│         └────────────┘  └──────────┘  └────────────┘       │
└───────────────────────────────────────────────────────────────┘

Key Components

  • 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

Quick Start

Prerequisites

  • Go 1.23 or higher
  • An ACP-compatible agent binary

Installation

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/relay

Configuration

Create 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: 100

First-Time Setup

For container mode, run the interactive setup:

./acp-relay setup

This will:

  • Detect available container runtimes
  • Guide configuration choices
  • Generate config file at ~/.config/acp-relay/config.yaml
  • Complete in <5 minutes

Running

Start the relay server:

./acp-relay --config config.yaml

You 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

Running with Verbose Logging

./acp-relay --verbose --config ~/.config/acp-relay/config.yaml

Testing

Check server health:

curl http://localhost:8082/api/health

Create 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
  }'

API Documentation

See docs/api.md for complete API documentation including:

  • HTTP API endpoints
  • WebSocket protocol
  • Management API
  • Request/response examples
  • Error handling

Development

Running Tests

Run all tests:

go test ./... -v

Run tests for a specific package:

go test ./internal/session -v
go test ./internal/http -v
go test ./internal/websocket -v

Project Structure

acp-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

Configuration Options

Server Configuration

  • 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)

Agent Configuration

  • command: Path to the ACP agent binary (required)
  • args: Array of command-line arguments to pass to the agent
  • env: Map of environment variables to set for the agent
  • startup_timeout_seconds: Time to wait for agent startup (default: 10)
  • max_concurrent_sessions: Maximum number of concurrent sessions (default: 100)

Container Mode

The ACP Relay Server supports running agents in Docker containers for isolation and reproducibility.

Prerequisites

  • Docker installed and running
  • Docker image built: docker build -t acp-relay-agent:latest .

Configuration

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: 10

Building the Image

docker build -t acp-relay-agent:latest .

Running

./acp-relay --config config.yaml

Sessions will be created in Docker containers with isolated workspaces.

Troubleshooting

"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.

Error Handling

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
}

Troubleshooting

Agent fails to start

Problem: Sessions fail to create with "agent connection timeout" error

Solutions:

  1. Verify the agent binary exists: ls -l /path/to/agent
  2. Check the agent can run manually: /path/to/agent --help
  3. Review stderr logs for agent error messages
  4. Ensure required environment variables are set in config.yaml

WebSocket connection refused

Problem: Cannot connect to WebSocket server

Solutions:

  1. Verify the WebSocket server is running on the correct port
  2. Check firewall rules allow connections to port 8081
  3. Ensure your client is connecting to the correct URL (ws:// not wss://)

Too many open files

Problem: Server crashes with "too many open files" error

Solutions:

  1. Increase system file descriptor limit: ulimit -n 4096
  2. Reduce max_concurrent_sessions in config.yaml
  3. Ensure sessions are properly closed when no longer needed

Security Considerations

  • 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)

License

MIT License - see LICENSE file for details

Contributing

Contributions are welcome! Please:

  1. Fork the repository
  2. Create a feature branch
  3. Write tests for new functionality
  4. Follow Go conventions and run go fmt
  5. Submit a pull request

Support

For issues and questions:

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

No packages published