A Rust bridge service between Discord and any ACP-compatible coding CLI (Kiro CLI, Claude Code, Codex, Gemini, etc.) using the Agent Client Protocol over stdio JSON-RPC.
┌──────────────┐ Gateway WS ┌──────────────┐ ACP stdio ┌──────────────┐
│ Discord │◄─────────────►│ agent-broker │──────────────►│ coding CLI │
│ User │ │ (Rust) │◄── JSON-RPC ──│ (acp mode) │
└──────────────┘ └──────────────┘ └──────────────┘
- Pluggable agent backend — swap between Kiro CLI, Claude Code, Codex, Gemini via config
- @mention trigger — mention the bot in an allowed channel to start a conversation
- Thread-based multi-turn — auto-creates threads; no @mention needed for follow-ups
- Edit-streaming — live-updates the Discord message every 1.5s as tokens arrive
- Emoji status reactions — 👀→🤔→🔥/👨💻/⚡→👍+random mood face
- Session pool — one CLI process per thread, auto-managed lifecycle
- ACP protocol — JSON-RPC over stdio with tool call, thinking, and permission auto-reply support
- Kubernetes-ready — Dockerfile + k8s manifests with PVC for auth persistence
See docs/discord-bot-howto.md for a detailed step-by-step guide.
In short:
- Go to https://discord.com/developers/applications and create an application
- Bot tab → enable Message Content Intent
- OAuth2 → URL Generator → scope:
bot→ permissions: Send Messages, Send Messages in Threads, Create Public Threads, Read Message History, Add Reactions, Manage Messages - Invite the bot to your server using the generated URL
cp config.toml.example config.tomlEdit config.toml:
[discord]
bot_token = "${DISCORD_BOT_TOKEN}"
allowed_channels = ["YOUR_CHANNEL_ID"]
[agent]
command = "kiro-cli"
args = ["acp", "--trust-all-tools"]
working_dir = "/tmp"export DISCORD_BOT_TOKEN="your-token"
# Development
cargo run
# Production
cargo build --release
./target/release/agent-broker config.tomlIf no config path is given, it defaults to config.toml in the current directory.
In your Discord channel:
@AgentBroker explain this code
The bot creates a thread. After that, just type in the thread — no @mention needed.
Note: Currently only Kiro CLI is supported and tested. Other ACP-compatible CLIs (Claude Code, Codex, Gemini) should work in theory but are untested. Contributions and bug reports welcome.
Swap the [agent] block to use any ACP-compatible CLI. The env field supports ${VAR} expansion from the process environment.
# Kiro CLI (default)
[agent]
command = "kiro-cli"
args = ["acp", "--trust-all-tools"]
working_dir = "/tmp"
# Claude Code
[agent]
command = "claude"
args = ["--acp"]
working_dir = "/tmp"
env = { ANTHROPIC_API_KEY = "${ANTHROPIC_API_KEY}" }
# Codex
[agent]
command = "codex"
args = ["--acp"]
working_dir = "/tmp"
env = { OPENAI_API_KEY = "${OPENAI_API_KEY}" }
# Gemini
[agent]
command = "gemini"
args = ["--acp"]
working_dir = "/tmp"
env = { GEMINI_API_KEY = "${GEMINI_API_KEY}" }[discord]
bot_token = "${DISCORD_BOT_TOKEN}" # supports env var expansion
allowed_channels = ["123456789"] # channel ID allowlist
[agent]
command = "kiro-cli" # CLI command
args = ["acp", "--trust-all-tools"] # ACP mode args
working_dir = "/tmp" # agent working directory
env = {} # extra env vars passed to the agent
[pool]
max_sessions = 10 # max concurrent sessions
session_ttl_hours = 24 # idle session TTL
[reactions]
enabled = true # enable emoji status reactions
remove_after_reply = false # remove reactions after reply
[reactions.emojis]
queued = "👀"
thinking = "🤔"
tool = "🔥"
coding = "👨💻"
web = "⚡"
done = "🆗"
error = "😱"
[reactions.timing]
debounce_ms = 700 # intermediate state debounce
stall_soft_ms = 10000 # 10s idle → 🥱
stall_hard_ms = 30000 # 30s idle → 😨
done_hold_ms = 1500 # keep done emoji for 1.5s
error_hold_ms = 2500 # keep error emoji for 2.5sThe Docker image bundles both agent-broker and kiro-cli in a single container (agent-broker spawns kiro-cli as a child process).
┌─ Kubernetes Pod ─────────────────────────────────────────────────┐
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ agent-broker (main process, PID 1) │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌───────────┐ │ │
│ │ │ Discord │ │ Session Pool │ │ Reaction │ │ │
│ │ │ Gateway WS │ │ (per thread) │ │ Controller│ │ │
│ │ └──────┬───────┘ └──────┬───────┘ └───────────┘ │ │
│ │ │ │ │ │
│ └─────────┼──────────────────┼────────────────────────────┘ │
│ │ │ │
│ │ @mention / │ spawn + stdio │
│ │ thread msg │ JSON-RPC (ACP) │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ kiro-cli acp --trust-all-tools (child process) │ │
│ │ │ │
│ │ stdin ◄── JSON-RPC requests (session/new, prompt) │ │
│ │ stdout ──► JSON-RPC responses (text, tool_call, done) │ │
│ │ stderr ──► (ignored) │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ ┌─ PVC Mount (/data) ──────────────────────────────────────┐ │
│ │ ~/.kiro/ ← settings, skills, sessions │ │
│ │ ~/.local/share/kiro-cli/ ← OAuth tokens (data.sqlite3) │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────┘
│
│ WebSocket (wss://gateway.discord.gg)
▼
┌──────────────────┐ ┌──────────────┐
│ Discord API │ ◄─────► │ Discord │
│ Gateway │ │ Users │
└──────────────────┘ └──────────────┘
- Single container — agent-broker is PID 1, spawns kiro-cli as a child process
- stdio JSON-RPC — ACP communication over stdin/stdout, no network ports needed
- Session pool — one kiro-cli process per Discord thread, up to
max_sessions - PVC — persists OAuth tokens and settings across pod restarts
Use this prompt with any coding CLI (Kiro CLI, Claude Code, Codex, Gemini, etc.) on the host that has helm and kubectl access to your cluster:
Install agent-broker on my local k8s cluster using the Helm chart from https://thepagent.github.io/agent-broker. My Discord bot token is in the environment variable DISCORD_BOT_TOKEN and my channel ID is <REPLACE_WITH_YOUR_CHANNEL_ID>. After install, authenticate kiro-cli inside the pod using kiro-cli login --use-device-flow, then restart the deployment.
docker build -t agent-broker:latest .
docker tag agent-broker:latest <your-registry>/agent-broker:latest
docker push <your-registry>/agent-broker:latest# Create the secret with your bot token
kubectl create secret generic agent-broker-secret \
--from-literal=discord-bot-token="your-token"
# Edit k8s/configmap.yaml with your channel IDs
kubectl apply -f k8s/configmap.yaml
kubectl apply -f k8s/pvc.yaml
kubectl apply -f k8s/deployment.yamlkiro-cli requires a one-time OAuth login. The PVC persists the tokens across pod restarts.
kubectl exec -it deployment/agent-broker -- kiro-cli login --use-device-flowFollow the device code flow in your browser, then restart the pod:
kubectl rollout restart deployment agent-broker| File | Purpose |
|---|---|
k8s/deployment.yaml |
Single-container pod with config + data volume mounts |
k8s/configmap.yaml |
config.toml mounted at /etc/agent-broker/ |
k8s/secret.yaml |
DISCORD_BOT_TOKEN injected as env var |
k8s/pvc.yaml |
Persistent storage for auth + settings |
The PVC persists two paths via subPath:
~/.kiro— settings, skills, sessions~/.local/share/kiro-cli— OAuth tokens (data.sqlite3→auth_kvtable), conversation history
├── Dockerfile # multi-stage: rust build + debian-slim runtime with kiro-cli
├── config.toml.example # example config with all agent backends
├── k8s/ # Kubernetes manifests
│ ├── deployment.yaml
│ ├── configmap.yaml
│ ├── secret.yaml
│ └── pvc.yaml
└── src/
├── main.rs # entrypoint: tokio + serenity + cleanup + shutdown
├── config.rs # TOML config + ${ENV_VAR} expansion
├── discord.rs # Discord bot: mention, threads, edit-streaming
├── format.rs # message splitting (2000 char limit)
├── reactions.rs # status reaction controller (debounce, stall detection)
└── acp/
├── protocol.rs # JSON-RPC types + ACP event classification
├── connection.rs # spawn CLI, stdio JSON-RPC communication
└── pool.rs # thread_id → AcpConnection map
- sample-acp-bridge — ACP protocol + process pool architecture
- OpenClaw — StatusReactionController emoji pattern
MIT
