A lightweight, secure, cloud-native ACP harness that bridges Discord and any Agent Client Protocol-compatible coding CLI (Kiro CLI, Claude Code, Codex, Gemini, etc.) over stdio JSON-RPC β delivering the next-generation development experience.
ββββββββββββββββ Gateway WS ββββββββββββββββ ACP stdio ββββββββββββββββ
β Discord ββββββββββββββββΊβ openab ββββββββββββββββΊβ 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/openab 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.
Swap backends using the agent.preset Helm value or manual config. Tested backends:
| Preset | CLI | ACP Adapter | Auth |
|---|---|---|---|
| (default) | Kiro CLI | Native kiro-cli acp |
kiro-cli login --use-device-flow |
codex |
Codex | @zed-industries/codex-acp | codex login --device-auth |
claude |
Claude Code | @agentclientprotocol/claude-agent-acp | claude setup-token |
gemini |
Gemini CLI | Native gemini --acp |
Google OAuth or GEMINI_API_KEY |
helm repo add openab https://openabdev.github.io/openab
helm repo update
# Kiro CLI (default)
helm install openab openab/openab \
--set discord.botToken="$DISCORD_BOT_TOKEN" \
--set-string discord.allowedChannels[0]="YOUR_CHANNEL_ID"
# Codex
helm install openab openab/openab \
--set discord.botToken="$DISCORD_BOT_TOKEN" \
--set-string discord.allowedChannels[0]="YOUR_CHANNEL_ID" \
--set agent.preset=codex
# Claude Code
helm install openab openab/openab \
--set discord.botToken="$DISCORD_BOT_TOKEN" \
--set-string discord.allowedChannels[0]="YOUR_CHANNEL_ID" \
--set agent.preset=claude
# Gemini
helm install openab openab/openab \
--set discord.botToken="$DISCORD_BOT_TOKEN" \
--set-string discord.allowedChannels[0]="YOUR_CHANNEL_ID" \
--set agent.preset=geminiThen authenticate inside the pod (first time only):
# Kiro CLI
kubectl exec -it deployment/openab -- kiro-cli login --use-device-flow
# Codex
kubectl exec -it deployment/openab -- codex login --device-auth
# Claude Code
kubectl exec -it deployment/openab -- claude setup-token
# Then: helm upgrade openab openab/openab --set env.CLAUDE_CODE_OAUTH_TOKEN="<token>"
# Gemini (Google OAuth β open URL in browser, curl callback from pod)
kubectl exec -it deployment/openab -- gemini
# Or use API key: helm upgrade openab openab/openab --set env.GEMINI_API_KEY="<key>"Restart after auth: kubectl rollout restart deployment openab
For non-Helm deployments, swap the [agent] block:
# Kiro CLI (default)
[agent]
command = "kiro-cli"
args = ["acp", "--trust-all-tools"]
working_dir = "/tmp"
# Codex (requires codex-acp in PATH)
[agent]
command = "codex-acp"
args = []
working_dir = "/tmp"
# Claude Code (requires claude-agent-acp in PATH)
[agent]
command = "claude-agent-acp"
args = []
working_dir = "/tmp"
# 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 openab and kiro-cli in a single container (openab spawns kiro-cli as a child process).
ββ Kubernetes Pod ββββββββββββββββββββββββββββββββββββββββββββββββββ
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β openab (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 β openab 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 one of these prompts with any coding CLI (Kiro CLI, Claude Code, Codex, Gemini, etc.) on the host that has helm and kubectl access to your cluster:
Kiro CLI (default):
Install openab on my local k8s cluster using the Helm chart from https://openabdev.github.io/openab. My Discord bot token is in the environment variable DISCORD_BOT_TOKEN and my channel ID is <REPLACE_WITH_YOUR_CHANNEL_ID>. After install, follow the NOTES output to authenticate, then restart the deployment.
Codex:
Install openab on my local k8s cluster using the Helm chart from https://openabdev.github.io/openab with
--set agent.preset=codex. My Discord bot token is in the environment variable DISCORD_BOT_TOKEN and my channel ID is <REPLACE_WITH_YOUR_CHANNEL_ID>. After install, follow the NOTES output to authenticate, then restart the deployment.
Claude Code:
Install openab on my local k8s cluster using the Helm chart from https://openabdev.github.io/openab with
--set agent.preset=claude. My Discord bot token is in the environment variable DISCORD_BOT_TOKEN and my channel ID is <REPLACE_WITH_YOUR_CHANNEL_ID>. After install, follow the NOTES output to authenticate, then restart the deployment.
Gemini:
Install openab on my local k8s cluster using the Helm chart from https://openabdev.github.io/openab with
--set agent.preset=gemini. My Discord bot token is in the environment variable DISCORD_BOT_TOKEN and my channel ID is <REPLACE_WITH_YOUR_CHANNEL_ID>. After install, follow the NOTES output to authenticate, then restart the deployment.
docker build -t openab:latest .
docker tag openab:latest <your-registry>/openab:latest
docker push <your-registry>/openab:latest# Create the secret with your bot token
kubectl create secret generic openab-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/openab -- kiro-cli login --use-device-flowFollow the device code flow in your browser, then restart the pod:
kubectl rollout restart deployment openab| File | Purpose |
|---|---|
k8s/deployment.yaml |
Single-container pod with config + data volume mounts |
k8s/configmap.yaml |
config.toml mounted at /etc/openab/ |
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
