Skip to main content

ACPBox

OpenAI-compatible HTTP API that acts as a gateway to the Agent Client Protocol (ACP). Clients use the usual OpenAI endpoints (/v1/models, /v1/chat/completions, /v1/responses); the gateway runs the API with uvicorn and keeps one ACP agent process per worker over stdio (JSON-RPC), not HTTP.

Problem

Many tools and SDKs expect an OpenAI-style API. ACP agents (e.g. OpenCode via opencode acp, or Cursor Agent via agent acp) speak the Agent Client Protocol over stdin/stdout. This gateway provides a single HTTP entry point: one base URL, OpenAI-shaped API, with one ACP binary instance per uvicorn worker.

How it works

  1. Config – YAML and env define the agent command, env vars, and workspace (ACPBOX_ACP_WORKSPACE, default ./workspace; Docker /workspace) passed as ACP session/new cwd.
  2. One ACP process per worker – The app runs under uvicorn. In lifespan each worker starts one ACP agent subprocess and keeps it for the worker's lifetime. With 8 workers you get 8 ACP binary instances. ACP uses stdio only (JSON-RPC, newline-delimited).
  3. Reuse per request – Each request in that worker uses the same process: session/new -> optional session/set_mode -> session/prompt, then the response is returned. The process is not terminated after each request.
  4. Translation – OpenAI requests are converted to ACP JSON-RPC; ACP content (e.g. session/update agent_message_chunk) is converted back to OpenAI chat/responses format.
    • GET /v1/models – Uses the worker's ACP process: initialize (once) and session/new, returns modes (modes.availableModes[].id, e.g. OpenCode's plan, build) as the list of models.
    • POST /v1/chat/completions – Uses the worker's ACP process; model selects the ACP mode. Reply as chat completion, or Server-Sent Events when "stream": true (OpenAI-style chat.completion.chunk lines and data: [DONE]). With stream: false, optional acp is { "steps": [ reasoning + command summaries ] } (no raw chunks). With stream: true, each chunk may still carry raw acp from the wire (see docs/api-mapping.md).
    • POST /v1/responses – Same; optional chat_id for client-side continuity (non-streaming only); acp.steps when present, like chat.
    • GET /v1/agent – Returns info about the current agent (type, config path, known permissions, writable status).
    • GET/PUT/PATCH /v1/agent/permissions – Read and manage agent permissions at runtime. Changes are written to the agent's native config file and apply to all future requests. See docs/api-mapping.md.

See docs/spec.md and docs/agent-client-protocol/docs/protocol/transports.mdx for details.

sequenceDiagram
    participant Client
    participant Gateway
    participant AgentProcess

    Note over Gateway,AgentProcess: One ACP process per uvicorn worker (started in lifespan)
    Client->>Gateway: GET /v1/models
    Gateway->>AgentProcess: initialize, session/new (reuse process)
    AgentProcess-->>Gateway: modes (e.g. plan, build)
    Gateway-->>Client: list of models

    Client->>Gateway: POST /v1/chat/completions
    Gateway->>AgentProcess: session/new, session/prompt (same process)
    AgentProcess-->>Gateway: session/update, session/prompt result
    Gateway-->>Client: chat completion

Quick setup

Install

The package is on PyPI. That installs the acpbox CLI (uvicorn-driven server, see pyproject.toml).

pipx - isolated install, good for a long-lived CLI. You still need a config.yaml (copy config.example.yaml from this repo, or from GitHub).

pipx install acpbox
acpbox --config ./config.yaml

Upgrade later with pipx upgrade acpbox. Try once without installing globally:

acpbox --config ./config.yaml

pip

pip install acpbox
acpbox --config ./config.yaml

Git clone - develop or pin to a revision.

git clone https://github.com/EvilFreelancer/acpbox
cd acpbox
cp config.example.yaml config.yaml
# edit config.yaml
pip install -e .
acpbox --config ./config.yaml

From a checkout without the console script, after pip install -r requirements.txt:

python -m acpbox.main --config ./config.yaml
  1. Config – Copy config.example.yaml to config.yaml and adjust. Every option can also be set via environment (see .env.example).

  2. Env – Copy .env.example to .env and set values. All options (ACPBOX_CONFIG_PATH, ACPBOX_ACP_*, ACPBOX_GATEWAY_*) can be configured via env.

    Agent command (OpenCode vs Cursor) – set acp.command in config.yaml or ACPBOX_ACP_COMMAND as a JSON array of strings.

    Backend config.yaml Shell (env)
    OpenCode command: ["opencode", "acp"] export ACPBOX_ACP_COMMAND='["opencode","acp"]'
    Cursor Agent command: ["agent", "acp"] export ACPBOX_ACP_COMMAND='["agent","acp"]'
    Claude (ACP adapter) command: ["claude-agent-acp"] export ACPBOX_ACP_COMMAND='["claude-agent-acp"]'
    Codex (ACP adapter) command: ["codex-acp"] export ACPBOX_ACP_COMMAND='["codex-acp"]'

    Use an absolute path if the binary is not on PATH (e.g. ["/home/you/.local/bin/agent","acp"]). Cursor Agent must be installed and logged in (agent login) so the subprocess can reach your account.

  3. Run – Start acpbox with ACPBOX_CONFIG_PATH (and other env vars) as needed. Use gateway.workers in YAML or ACPBOX_GATEWAY_WORKERS for worker count (one ACP subprocess per worker). ACPBOX_GATEWAY_THREADS is forwarded into uvicorn.run only if your uvicorn supports a threads= argument (many builds do not; ASGI uses asyncio per process).

Or with Docker Compose (reads .env and runs the acpbox service). Set AGENTS in .env to install missing agent binaries at container startup (comma-separated opencode, cursor, claude, codex). Runtime ACPBOX_ACP_COMMAND must match the installed binary (see Agent command table above). The image CMD is acpbox (uvicorn inside acpbox.main.run), with ACPBOX_GATEWAY_WORKERS and ACPBOX_GATEWAY_THREADS passed through the environment.

  1. Use – Point any OpenAI client at http://localhost:8080/v1 (or your host/port). List models, call chat completions or responses; the gateway translates to ACP and back.

Agent permissions management

The gateway detects the agent type from acp.command and exposes endpoints to manage its permissions at runtime. Supported agents:

Agent Command Config file Permission format
OpenCode opencode acp {workspace}/opencode.json "permission": {"read": "allow", ...}
Claude claude-agent-acp {workspace}/.claude/settings.json "permissions": {"allow": [...], "deny": [...]}
Codex codex-acp {workspace}/codex.json "permission": {...} + approval_mode
Cursor agent acp {workspace}/.cursor/settings.json "permission": {...}

Permissions are written to the agent's native config file on disk. Since each request creates a new ACP session (session/new), the agent picks up updated permissions on the next request.

Presets let you flip permissions in bulk: allow_all (full access), deny_all (lock everything down), ask_all (OpenCode only). Combine a preset with explicit overrides to allow only specific tools:

curl -X PUT http://localhost:8080/v1/agent/permissions \
  -H 'Content-Type: application/json' \
  -d '{"preset": "deny_all", "permissions": {"atlassian_*": "allow"}}'

The writable field in responses indicates whether the config file can be written to. If it is read-only, write endpoints return 403.

See docs/api-mapping.md for full endpoint reference.

Tests

Tests use a mock ACP over stdio (fake subprocess that responds with JSON-RPC). Route tests: tests/test_models.py, tests/test_chat.py, tests/test_responses.py, tests/test_sessions.py. Agent config tests: tests/test_agent_config.py (adapters, detection, API, writability). Unit tests for mapping, errors, session_store, config, and stdio client in tests/unit/. Fixtures in tests/conftest.py.

From repo root:

pip install -e ".[dev]"
pytest tests/ -v

Adding your own ACP in Docker

Build an image that includes acpbox and your ACP agent binary (e.g. opencode acp). Set acp.command and acp.env in config or .env. The server runs with uvicorn via acpbox; each worker starts one ACP process in lifespan. To run 8 ACP instances, set ACPBOX_GATEWAY_WORKERS=8 (or gateway.workers in YAML). See docs/deployment.md.

Specifications

License

This project is licensed under the MIT License, see the LICENSE file in the repository root for details.

Metadata

Release files for acpbox 0.2.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for acpbox 0.2.3
File Size Uploaded
acpbox-0.2.3.tar.gz 86.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for acpbox 0.2.3
File Interpreter ABI Platform
acpbox-0.2.3-py3-none-any.whl Python 3 none any Details

Total release size: 118.6 kB

Release files / acpbox-0.2.3.tar.gz

Download URL acpbox-0.2.3.tar.gz
Size 86.4 kB
Tags Source
SHA-256 checksum
How to use checksums
b8964d2807377ad17181bd809278399a3a27b91b61e16fc13bf519bb661eb494
BLAKE2b-256 checksum
How to use checksums
6910b91314e7ea99582184e09c7270180b027a92a38ca4acfabd8cfa368ebe03
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / acpbox-0.2.3-py3-none-any.whl

Download URL acpbox-0.2.3-py3-none-any.whl
Size 32.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ea7deef5e1ff72494cd82b8fd84b4d753201b196e2bad0735fab4df726d2dbd9
BLAKE2b-256 checksum
How to use checksums
0a4c5dd6d519bfc5a9d838ba7fb163d256d3bd308b849e5ae1bac17ad8c6d1a8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release history Release notifications | RSS feed

This release

0.2.3 This release

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page