╔══════════════════════════════════════════════════════════════╗ ║ ║ ║ _ ___ ___ ___ _ _ ║ ║ /_\ / __| _ \ | _ )_ __(_)__| |__ _ ___ ║ ║ / _ \ (__| _/ | _ \ '_|| / _` / _` |/ -_) ║ ║ /_/ \_\___|_| |___/|_| |_\__,_\__, \___| ║ ║ |___/ ║ ╠══════════════════════════════════════════════════════════════╣ ║ ║ ║ 🤖 Kiro ───┐ ║ ║ ├──► acp 🌉 ──► 🦞 OpenClaw ──► 🌍 world ║ ║ 🤖 Claude ──┘ ║ ║ ║ ║ https://github.com/xiwan/acp-bridge ║ ╚══════════════════════════════════════════════════════════════╝ ~ Local AI agents 🔌 ACP protocol 🦞 The world ~
A bridge service that exposes local CLI agents (Kiro CLI, Claude Code, etc.) via ACP (Agent Client Protocol) over HTTP, with async job support and Discord push notifications.
┌──────────┐ ┌──────────┐ HTTP JSON req ┌──────────────┐ ACP stdio ┌──────────────┐ │ Discord │◀──────────▶│ OpenClaw │──────────────────▶│ ACP Bridge │──────────────▶│ CLI Agent │ │ User │ Discord │ Gateway │◀──── SSE stream ───│ (uvicorn) │◀── JSON-RPC ──│ kiro/claude │ └──────────┘ └──────────┘◀── /tools/invoke ──└──────────────┘ └──────────────┘ (async job push)
Two invocation modes:
acp-client.sh, wait for resultTwo agent modes:
/tools/invokesession/request_permission (prevents Claude from hanging)acp-bridge/ ├── main.py # Entry: process pool, handler registration, job/health endpoints ├── src/ │ ├── acp_client.py # ACP process pool + JSON-RPC connection management │ ├── agents.py # Agent handlers (ACP mode + PTY fallback) │ ├── jobs.py # Async job manager (submit, monitor, webhook callback) │ ├── sse.py # ACP session/update → SSE event conversion │ └── security.py # Security middleware (IP allowlist + Bearer Token) ├── skill/ │ ├── SKILL.md # Kiro/OpenClaw skill definition │ └── acp-client.sh # Client script (bash + jq) ├── test/ │ └── test.sh # Integration tests ├── config.yaml # Service configuration ├── pyproject.toml └── uv.lock
kiro-cli, claude-agent-acp)curl, jq, uuidgencd acp-bridge
cp config.yaml.example config.yaml
# Edit config.yaml with your settings
uv sync
uv run main.py
server:
host: "0.0.0.0"
port: 8001
session_ttl_hours: 24
shutdown_timeout: 30
pool:
max_processes: 20
max_per_agent: 10
webhook:
url: "http://<openclaw-ip>:18789/tools/invoke"
token: "<OPENCLAW_GATEWAY_TOKEN>"
security:
auth_token: "${ACP_BRIDGE_TOKEN}"
allowed_ips:
- "127.0.0.1"
agents:
kiro:
enabled: true
mode: "acp"
command: "kiro-cli"
acp_args: ["acp", "--trust-all-tools"]
working_dir: "/tmp"
description: "Kiro CLI agent"
claude:
enabled: true
mode: "acp"
command: "claude-agent-acp"
acp_args: []
working_dir: "/tmp"
description: "Claude Code agent (via ACP adapter)"
export ACP_BRIDGE_URL=http://<bridge-ip>:8001
export ACP_TOKEN=<your-token>
# List available agents
./skill/acp-client.sh -l
# Sync call
./skill/acp-client.sh "Explain the project structure"
# Streaming call
./skill/acp-client.sh --stream "Analyze this code"
# Markdown card output (ideal for IM display)
./skill/acp-client.sh --card -a kiro "Introduce yourself"
# Specify agent
./skill/acp-client.sh -a claude "hello"
# Multi-turn conversation
./skill/acp-client.sh -s 00000000-0000-0000-0000-000000000001 "continue"
Submit long-running tasks and get results pushed to Discord automatically.
curl -X POST http://<bridge>:8001/jobs \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"agent_name": "kiro",
"prompt": "Refactor the module",
"discord_target": "user:<user-id>",
"callback_meta": {"account_id": "default"}
}'
# → {"job_id": "xxx", "status": "pending"}
curl http://<bridge>:8001/jobs/<job_id> \
-H "Authorization: Bearer <token>"
POST /jobs → Bridge executes in background → On completion POST to OpenClaw /tools/invoke → OpenClaw sends to Discord via message tool → User receives result
| Scenario | Format | Example |
|---|---|---|
| Server channel | channel:<id> or #name | channel:1477514611317145732 |
| DM (direct message) | user:<user_id> | user:<user-id> |
account_id refers to the OpenClaw Discord bot account (usually default), not the agent name.
GET /jobs — List all jobs + status stats| Method | Path | Description | Auth |
|---|---|---|---|
| GET | /agents | List registered agents | Yes |
| POST | /runs | Sync/streaming agent call | Yes |
| POST | /jobs | Submit async job | Yes |
| GET | /jobs | List all jobs + stats | Yes |
| GET | /jobs/{job_id} | Query single job | Yes |
| GET | /health | Health check | No |
| GET | /health/agents | Agent status | Yes |
| DELETE | /sessions/{agent}/{session_id} | Close session | Yes |
ACP_TOKEN=<token> bash test/test.sh http://127.0.0.1:8001
Covers: agent listing, sync/streaming calls, multi-turn conversation, Claude, async jobs, error handling.
(agent, session_id) pair maps to an independent CLI ACP subprocesssession/request_permission is auto-replied with allow_always (Claude compatibility)/health is unauthenticated (for load balancer probes)${ENV_VAR} environment variable references| Symptom | Cause | Fix |
|---|---|---|
403 forbidden | IP not in allowlist | Add IP to allowed_ips |
401 unauthorized | Incorrect token | Check Bearer token |
pool_exhausted | Concurrency limit reached | Increase max_processes |
| Claude hangs | Permission request not answered | Already handled (auto-allow) |
| Discord push fails | Wrong or missing account_id | Use default, not agent name |
| Discord 500 | Bad target format | DM: user:<id>, channel: channel:<id> |
| Job stuck | Agent process anomaly | Auto-marked failed after 10min |