OpenAB is configured via a TOML file (default: config.toml). Environment variables can be interpolated using ${VAR_NAME} syntax.
At least one adapter section ([discord] or [slack]) is required.
Specify the config source with --config / -c:
# Local file (default: config.toml when omitted)
openab run -c config.toml
# Remote URL via HTTPS (recommended)
openab run -c https://example.com/config.toml
# Remote URL via HTTP (warns — avoid in production; config contains secrets)
openab run -c http://internal.example.com/config.tomlRemote config is fetched via HTTP GET with a 10-second timeout and a 1 MiB response size limit. Environment variable expansion (${VAR}) works identically on both local and remote config content.
Security best practice: Never hardcode secrets in remote config files. Use environment variable references like
bot_token = "${DISCORD_BOT_TOKEN}"and inject the actual values via local environment variables or Kubernetes Secrets. OpenAB expands${VAR}identically for both local and remote config.
Discord adapter. Requires a Discord bot token.
| Key | Type | Default | Description |
|---|---|---|---|
bot_token |
string | required | Discord bot token. Use ${DISCORD_BOT_TOKEN} for env var. |
allow_all_channels |
bool | omit | auto-detect | true = all channels; false = only allowed_channels. Omitted = inferred from list (non-empty → false, empty → true). |
allowed_channels |
string[] | [] |
Channel IDs to allow. Only checked when allow_all_channels resolves to false. |
allow_all_users |
bool | omit | auto-detect | true = any user; false = only allowed_users. Omitted = inferred from list. |
allowed_users |
string[] | [] |
User IDs to allow. Only checked when allow_all_users resolves to false. |
allow_bot_messages |
string | "off" |
"off" — ignore all bot messages. "mentions" — only process bot messages that @mention this bot. "all" — process all bot messages (capped by max_bot_turns). |
trusted_bot_ids |
string[] | [] |
When non-empty, only these bot IDs pass the bot gate. Empty = any bot (mode permitting). Ignored when allow_bot_messages = "off". |
allow_user_messages |
string | "involved" |
"involved" — reply in threads bot has participated in without @mention; channel messages require @mention; DMs always process. "mentions" — always require @mention. "multibot-mentions" — like "involved", but require @mention once another bot has posted in the thread. |
allow_dm |
bool | false |
true = respond to Discord DMs; false = ignore DMs. allowed_users still applies in DMs. Each DM user consumes one session slot. |
max_bot_turns |
u32 | 20 |
Max consecutive bot turns per thread before throttling. Human message resets the counter. Note: when allow_bot_messages = "all", a separate hardcoded cap of 10 (MAX_CONSECUTIVE_BOT_TURNS) stops bot replies regardless of this value. |
Slack adapter using Socket Mode. Requires both a Bot User OAuth Token and an App-Level Token.
| Key | Type | Default | Description |
|---|---|---|---|
bot_token |
string | required | Bot User OAuth Token (xoxb-...). |
app_token |
string | required | App-Level Token (xapp-...) for Socket Mode. |
allow_all_channels |
bool | omit | auto-detect | Same behavior as Discord. |
allowed_channels |
string[] | [] |
Slack channel IDs (e.g. C0123456789). |
allow_all_users |
bool | omit | auto-detect | Same behavior as Discord. |
allowed_users |
string[] | [] |
Slack user IDs (e.g. U0123456789). |
allow_bot_messages |
string | "off" |
Same as Discord. |
trusted_bot_ids |
string[] | [] |
Slack Bot User IDs (U...). Find via: click bot profile → Copy member ID. |
allow_user_messages |
string | "involved" |
Same as Discord. |
max_bot_turns |
u32 | 20 |
Same as Discord. |
Custom Gateway adapter for platforms like Telegram and LINE. Connects to the gateway via WebSocket.
| Key | Type | Default | Description |
|---|---|---|---|
url |
string | required | WebSocket URL of the gateway (e.g. ws://openab-gateway:8080/ws). |
platform |
string | "telegram" |
Platform name for session key namespacing (e.g. "telegram", "line"). |
token |
string | — | Shared token for WebSocket authentication (optional but recommended). |
bot_username |
string | — | Bot username for @mention gating in groups. |
allow_all_channels |
bool | omit | auto-detect | true = all channels; false = only allowed_channels. Omitted = inferred from list (non-empty → false, empty → true). |
allowed_channels |
string[] | [] |
Chat/group IDs to allow. Only checked when allow_all_channels resolves to false. |
allow_all_users |
bool | omit | auto-detect | true = any user; false = only allowed_users. Omitted = inferred from list. |
allowed_users |
string[] | [] |
User IDs to allow. Only checked when allow_all_users resolves to false. |
The AI agent subprocess that OpenAB spawns to handle messages via ACP.
| Key | Type | Default | Description |
|---|---|---|---|
command |
string | required | Agent binary (e.g. kiro-cli, claude, codex, gemini, copilot, opencode, cursor-agent). |
args |
string[] | [] |
CLI arguments passed to the agent. |
working_dir |
string | "/tmp" |
Working directory for the agent process. |
env |
map | {} |
Extra environment variables (e.g. { ANTHROPIC_API_KEY = "${ANTHROPIC_API_KEY}" }). |
# Kiro CLI
[agent]
command = "kiro-cli"
args = ["acp", "--trust-all-tools"]
working_dir = "/home/agent"
# Claude Code
[agent]
command = "claude"
args = ["--acp"]
working_dir = "/home/agent"
env = { ANTHROPIC_API_KEY = "${ANTHROPIC_API_KEY}" }
# Codex
[agent]
command = "codex"
args = ["--acp"]
working_dir = "/home/agent"
env = { OPENAI_API_KEY = "${OPENAI_API_KEY}" }
# Gemini CLI
[agent]
command = "gemini"
args = ["--acp"]
working_dir = "/home/agent"
env = { GEMINI_API_KEY = "${GEMINI_API_KEY}" }
# GitHub Copilot
[agent]
command = "copilot"
args = ["--acp", "--stdio"]
working_dir = "/home/agent"
# opencode
[agent]
command = "opencode"
args = ["acp"]
working_dir = "/home/node"
# Cursor Agent
[agent]
command = "cursor-agent"
args = ["acp", "--model", "auto", "--workspace", "/home/agent"]
working_dir = "/home/agent"Session pool settings for managing concurrent agent sessions.
| Key | Type | Default | Description |
|---|---|---|---|
max_sessions |
usize | 10 |
Maximum number of concurrent agent sessions. When full, the oldest idle session is suspended (recoverable); if all sessions are busy, new requests are rejected. |
session_ttl_hours |
u64 | 4 |
Session time-to-live in hours. Idle sessions are reclaimed after this period. The example config uses 24. |
Emoji reaction feedback on messages to show agent processing status.
| Key | Type | Default | Description |
|---|---|---|---|
enabled |
bool | true |
Enable/disable reaction feedback. |
remove_after_reply |
bool | false |
Remove the status reaction after the agent replies. |
tool_display |
string | "full" |
How tool calls are rendered: "full" (complete title), "compact" (count summary, e.g. ✅ 3 · 🔧 1 tool(s)), or "none" (hidden). |
Customize the emoji for each processing stage.
| Key | Default | Description |
|---|---|---|
queued |
👀 | Message received, queued for processing. |
thinking |
🤔 | Agent is thinking / generating. |
tool |
🔥 | Agent is calling a tool. |
coding |
👨💻 | Agent is writing code. |
web |
⚡ | Agent is doing web operations. |
done |
🆗 | Agent finished successfully. |
error |
😱 | Agent encountered an error. |
Fine-tune reaction timing behavior (milliseconds).
| Key | Default | Description |
|---|---|---|
debounce_ms |
700 |
Debounce interval before updating the reaction emoji. |
stall_soft_ms |
10000 |
Soft stall threshold — warn if no progress. |
stall_hard_ms |
30000 |
Hard stall threshold — consider the agent stuck. |
done_hold_ms |
1500 |
How long to show the done emoji before removing (if remove_after_reply). |
error_hold_ms |
2500 |
How long to show the error emoji before removing. |
Speech-to-text transcription for voice messages. Uses an OpenAI-compatible /audio/transcriptions endpoint.
| Key | Type | Default | Description |
|---|---|---|---|
enabled |
bool | false |
Enable voice message transcription. |
api_key |
string | "" |
API key for the STT service. When empty and base_url contains groq.com, the GROQ_API_KEY environment variable is used automatically. For local servers, use api_key = "not-needed". |
model |
string | "whisper-large-v3-turbo" |
Model name to use for transcription. |
base_url |
string | "https://api.groq.com/openai/v1" |
Base URL of the STT API. Any OpenAI-compatible /audio/transcriptions endpoint works. |
Everything cron-related lives under [cron].
[cron]
usercron_enabled = true # enable hot-reload (default: false)
usercron_path = "cronjob.toml" # relative to $HOME, or absolute
[[cron.jobs]]
enabled = true # optional, default: true
schedule = "0 9 * * 1-5" # cron expression (5-field POSIX)
channel = "123456789" # target channel/thread ID
message = "summarize yesterday's merged PRs" # message sent to agent
platform = "discord" # optional, default: "discord"
sender_name = "DailyOps" # optional, default: "openab-cron"
timezone = "America/New_York" # optional, default: "UTC"
thread_id = "" # optional, post to existing thread
[[cron.jobs]]
schedule = "0 0 * * 0"
channel = "123456789"
message = "generate weekly status report"
platform = "discord"
timezone = "UTC"| Key | Type | Default | Description |
|---|---|---|---|
usercron_enabled |
bool | false |
Enable usercron hot-reload. Must be explicitly set to true. |
usercron_path |
string | — | Path to the external cronjob.toml. Relative paths resolve from $HOME. |
| Key | Type | Default | Description |
|---|---|---|---|
enabled |
bool | true |
Set false to disable without removing the entry. |
schedule |
string | required | Cron expression (minute, hour, day-of-month, month, day-of-week). |
channel |
string | required | Target Discord channel/thread ID or Slack channel ID. |
message |
string | required | Message sent to the agent as a prompt. |
platform |
string | "discord" |
Target platform ("discord" or "slack"). |
sender_name |
string | "openab-cron" |
Sender attribution shown in the prompt context. |
timezone |
string | "UTC" |
IANA timezone for schedule evaluation (e.g. "America/New_York", "Europe/Berlin"). |
thread_id |
string | "" |
Optional thread ID to post into an existing thread. |
The external cronjob.toml uses [[jobs]] (same fields). See Usercron docs for details.
Cron expression format:
┌───────────── minute (0-59)
│ ┌───────────── hour (0-23)
│ │ ┌───────────── day of month (1-31)
│ │ │ ┌───────────── month (1-12)
│ │ │ │ ┌───────────── day of week (0-7, 0 and 7 = Sunday)
│ │ │ │ │
* * * * *
Behaviors:
- Scheduler evaluates expressions once per minute
- If a previous execution is still running, the next tick is skipped (no overlap)
- Failed executions are logged but do not block other jobs or chat traffic
- Stateless — no persistence needed, re-evaluated from config on restart
When deploying with the Helm chart (charts/openab), the config.toml is generated from values.yaml. Each agent is defined under the agents map:
agents:
kiro:
command: kiro-cli
args: ["acp", "--trust-all-tools"]
discord:
enabled: true
allowedChannels: ["1234567890"]
allowBotMessages: "mentions"
trustedBotIds: ["9876543210"]
pool:
maxSessions: 10
sessionTtlHours: 24
reactions:
enabled: true
stt:
enabled: true
apiKey: "your-groq-key"Key mapping (values.yaml → config.toml):
| Helm value | Config key |
|---|---|
agents.<name>.discord.allowedChannels |
[discord] allowed_channels |
agents.<name>.discord.allowBotMessages |
[discord] allow_bot_messages |
agents.<name>.discord.trustedBotIds |
[discord] trusted_bot_ids |
agents.<name>.discord.allowUserMessages |
[discord] allow_user_messages |
agents.<name>.slack.* |
[slack] * (same pattern) |
agents.<name>.pool.maxSessions |
[pool] max_sessions |
agents.<name>.pool.sessionTtlHours |
[pool] session_ttl_hours |
agents.<name>.reactions.enabled |
[reactions] enabled |
agents.<name>.reactions.toolDisplay |
[reactions] tool_display |
agents.<name>.stt.apiKey |
[stt] api_key |
agents.<name>.cronjobs[].enabled |
[[cron.jobs]] enabled |
agents.<name>.cronjobs[].schedule |
[[cron.jobs]] schedule |
agents.<name>.cronjobs[].channel |
[[cron.jobs]] channel |
agents.<name>.cronjobs[].message |
[[cron.jobs]] message |
agents.<name>.cronjobs[].platform |
[[cron.jobs]] platform |
agents.<name>.cronjobs[].senderName |
[[cron.jobs]] sender_name |
agents.<name>.cronjobs[].timezone |
[[cron.jobs]] timezone |
agents.<name>.cronjobs[].threadId |
[[cron.jobs]] thread_id |
⚠️ Use--set-string(not--set) for Discord/Slack IDs to avoid float64 precision loss:helm upgrade --install mybot charts/openab \ --set-string agents.kiro.discord.allowedChannels[0]="1234567890"
See charts/openab/values.yaml for the full list of Helm values including persistence, image, resources, and multi-agent examples.
Any value can reference environment variables with ${VAR_NAME}:
bot_token = "${DISCORD_BOT_TOKEN}"Undefined variables resolve to an empty string.