Configuration Reference
September 18, 2026 ยท View on GitHub
This document describes all configuration options for opencode-chat-bridge.
Quick Setup
- Copy
chat-bridge.json.exampletochat-bridge.jsonand edit local bridge settings - Create
opencode.jsonwith thechat-bridgeagent - Run
bun src/cli.ts
chat-bridge.json is intentionally ignored by git. Keep deployment-specific connector settings, local paths, and credentials there. Commit safe defaults or examples to chat-bridge.json.example instead.
ACP backend
The bridge can launch any ACP v1 stdio agent. Configure the executable, argument vector, stable backend identity, and optional workspace profile in chat-bridge.json:
{
"sessionStorePath": "./.opencode/chat-sessions.json",
"acp": {
"command": "/usr/local/bin/ferrum",
"args": ["acp"],
"backendId": "ferrum",
"profileDir": "./profiles/ferrum-chat"
}
}
Each connector/thread receives a deterministic working directory. The profile is copied into that directory before the ACP process starts, so it can provide backend-specific policy, instructions, and skills. The bridge persists sessionId, canonical cwd, and backendId in sessionStorePath; it resumes matching sessions after restart and deletes the mapping and backend session on /clear.
backendId must change when the configured backend or its incompatible session format changes. ACP processes inherit the bridge environment; keep credentials outside profiles and the session store.
Ferrum backend notes
Ferrum is a small Rust-native Linux coding agent. It can be used directly as an ACP v1 stdio backend:
ferrum acp
Ferrum is useful for chat bot deployments because it starts quickly, has low runtime overhead, persists durable JSONL sessions, and has explicit tool policy. The bridge has been validated with Ferrum for executable/argument selection, per-thread workspaces, text and thought streaming, command discovery, cancellation, process restart, session resume/delete, and restrictive workspace policy.
A typical Ferrum chat profile keeps broad local authority off by default and exposes specific capabilities through MCP:
mcp_enabled = true
[tools]
deny = ["write", "edit", "bash", "wait"]
writable_roots = ["."]
[[mcp.servers]]
name = "search"
command = "/usr/local/bin/search-mcp"
args = []
enabled = true
Useful Ferrum features for this setup:
AGENTS.mdcontext loading and Agent Skills-style instruction packages- configurable providers, models, thinking, safety, and context budget
- OpenAI-compatible providers and ChatGPT/Codex OAuth
- bounded native tools for file access, search, editing, and shell execution
- allow/deny tool exposure policy
- stdio MCP client support with namespaced tools
- project-local
.ferrum/config.tomlrestrictions for tools, roots, skills, MCP access, safety, and tool-round limits
Ferrum is not a sandbox: an allowed tool or MCP server runs with the Unix permissions of the Ferrum process. Its safety model is still meaningful. Native tools and shell execution are bounded and policy-checked; writable roots and safety tiers limit mutation; MCP servers get a filtered environment, explicit env allowlists, bounded protocol frames/schema/output, stderr withholding from model-visible errors, sanitized tool-name collision checks, transport quarantine on framing failure, and Linux process-tree cleanup through delegated cgroup-v2 when available.
For production chat bots, configure only trusted MCP commands, pass only required environment variables, keep credentials outside copied profiles and session stores, and prefer narrow MCP tools over enabling general shell access.
Session picker
The project/session picker is disabled by default because it exposes saved ACP working directories, session titles, and optional recent history previews.
Enable it explicitly:
{
"sessionPicker": {
"enabled": true,
"connectors": ["whatsapp"],
"mirrorIntervalSeconds": 60
}
}
Commands when enabled:
/p, /p <n> list/select saved working directories
/s, /s <n> list/select saved sessions interactively
/m, /m <n> stop/start read-only mirror mode
/r reload current selected session
/d detach without deleting the backend session
connectors limits where the picker is available. If empty, all connectors may use it. Keep it disabled for shared rooms unless the session paths and history are safe to expose there.
opencode.json (OpenCode backend)
The opencode.json file defines the secure OpenCode agent configuration:
{
"$schema": "https://opencode.ai/config.json",
"default_agent": "chat-bridge",
"agent": {
"chat-bridge": {
"description": "Secure chat assistant",
"mode": "primary",
"prompt": "You are a helpful assistant. You can search the web and check time.",
"permission": {
"read": "deny",
"edit": "deny",
"bash": "deny",
"glob": "deny",
"grep": "deny",
"task": "deny",
"todowrite": "deny",
"todoread": "deny",
"webfetch": "deny",
"codesearch": "deny",
"question": "allow",
"time_*": "allow",
"weather_*": "allow",
"web-search_*": "allow"
}
}
}
}
Key Settings
| Setting | Description |
|---|---|
default_agent | Agent used by default for new sessions |
agent.*.mode | "primary" for main agents, "subagent" for helpers |
agent.*.prompt | System prompt for the agent |
agent.*.permission | Tool permissions (allow/deny/ask) |
Permission Configuration
Permission Actions
| Action | Behavior |
|---|---|
"allow" | Tool executes immediately |
"deny" | Tool blocked, error returned to LLM |
"ask" | Requires user confirmation |
For chat bots, use "allow" or "deny". The "ask" action requires interactive confirmation.
Tool Names
Built-in tools:
read- Read filesedit- Edit filesbash- Execute commandsglob- Find filesgrep- Search file contentstask- Spawn subagentstodowrite- Write todostodoread- Read todoswebfetch- Fetch URLscodesearch- Search codequestion- Ask user questions
MCP Tool Permissions
MCP tools use the pattern <server>_<tool>:
{
"permission": {
"time_*": "allow",
"weather_*": "allow",
"web-search_*": "allow"
}
}
Wildcard matching: * matches any characters, so weather_* allows all weather tools.
Available MCP Servers
Check installed MCP servers:
opencode mcp list
Common servers:
| Server | Tools |
|---|---|
time | time_get_current_time, time_convert_time |
weather | weather_get_weather, weather_get_forecast, weather_search_location |
web-search | web-search_full-web-search, web-search_get-web-search-summaries, web-search_get-single-web-page-content |
chrome-devtools | Browser automation (deny for chat bots) |
Any MCP server can be used. Check available servers with opencode mcp list.
Disabling MCP Servers Locally
Your global OpenCode config (~/.config/opencode/opencode.json) may have MCP servers enabled that you don't want the chat bot to use. You can disable them in your local project config.
Problem: Global config has chrome-devtools enabled, but you don't want the chat bot to use it.
Solution: Add an mcp section to your project's opencode.json:
{
"$schema": "https://opencode.ai/config.json",
"model": "anthropic/claude-sonnet-4-5",
"default_agent": "chat-bridge",
"mcp": {
"chrome-devtools": {
"enabled": false
}
},
"agent": {
"chat-bridge": {
...
}
}
}
How it works:
- Local config overrides global config
- You only need to specify
"enabled": false"- not the full server definition - Other MCP servers (time, weather, web-search) remain enabled from global config
Important: Disabling the MCP server prevents tools from being loaded. But the model may still "think" it has access if tools were visible in a previous session. For complete blocking, combine with permission denials:
{
"mcp": {
"chrome-devtools": { "enabled": false }
},
"agent": {
"chat-bridge": {
"permission": {
"chrome-devtools_*": "deny"
}
}
}
}
The wildcard chrome-devtools_* denies ALL tools from that MCP server.
Disabling a Whole MCP Server vs Single Functions
There are two ways to block MCP tools:
Method 1: Disable the Entire MCP Server
Use the mcp section to prevent the server from loading at all:
{
"mcp": {
"chrome-devtools": {
"enabled": false
}
}
}
This completely disables ALL tools from that server. The tools won't be loaded or visible to the model.
Method 2: Deny Individual Functions
Use the permission section to block specific tools while keeping others:
{
"agent": {
"chat-bridge": {
"permission": {
"weather_set_default_location": "deny"
}
}
}
}
This allows most weather tools but blocks the one that saves state.
Method 3: Wildcard Deny All Functions from an MCP
Use * to deny all tools from a server without disabling it:
{
"agent": {
"chat-bridge": {
"permission": {
"chrome-devtools_*": "deny"
}
}
}
}
Comparison
| Goal | Method | Config |
|---|---|---|
| Block entire MCP server | MCP disable | "mcp": { "server": { "enabled": false } } |
| Block all tools from MCP | Wildcard deny | "permission": { "server_*": "deny" } |
| Block one specific tool | Single deny | "permission": { "server_tool": "deny" } |
| Allow one tool, block rest | Selective | "server_*": "deny" + "server_tool": "allow" |
Example: Allow Only Some web-search Tools
{
"permission": {
"web-search_*": "deny",
"web-search_get-web-search-summaries": "allow"
}
}
This denies all web-search tools by default, then explicitly allows only the lightweight summary search.
Why Both MCP Disable AND Permission Deny?
| Method | What it does |
|---|---|
mcp.enabled: false | Server not loaded, tools don't appear |
permission: deny | Tools blocked at execution time |
Using both provides defense in depth:
- MCP disable prevents tools from loading
- Permission deny blocks execution if tools somehow load
- The model won't list capabilities it can't use
Skills
Skills are custom instruction sets that the AI can load via the skill tool.
They provide domain-specific context, formatting preferences, or personas.
Creating Skills
Skills live in .opencode/skills/<name>/SKILL.md:
.opencode/
skills/
weather/
SKILL.md
assistant/
SKILL.md
Example skill (.opencode/skills/weather/SKILL.md):
# Weather Skill
When reporting weather, always include:
- Temperature in Celsius
- Wind speed and direction
- Humidity percentage
Format as a markdown table for chat readability.
Enabling Skills
- Add
skill: allowto agent permissions inopencode.json:
{
"agent": {
"chat-bridge": {
"permission": {
"skill": "allow"
}
}
}
}
-
The bridge automatically symlinks
.opencode/to session directories -
Users can invoke skills via chat:
!oc /weather # Loads the weather skill
Skills vs Commands vs Tools
| Type | Location | Purpose |
|---|---|---|
| Skills | .opencode/skills/ | Instructions loaded by AI via skill tool |
| Commands | .opencode/commands/ | Prompts invoked with /name, shown in /help |
| Tools | .opencode/tools/ | Code the AI can execute |
Notes
- Skills are local config - don't commit secrets to git
- The
.opencode/directory is gitignored by default - Skills require both
skill: allowpermission and the symlink setup
Tool Messages
toolMessages controls which tool activity the bridge returns to the chat
channel. Tool authorization remains part of the ACP backend security profile;
these settings affect presentation only.
{
"toolMessages": {
"mode": "events",
"showArguments": false,
"showOutputFor": ["bash"],
"maxTraceEntries": 20
}
}
modeselects the tool-call presentation:offhides tool calls.eventssends one immutable message per call and is the default.statusmaintains one editable message with the current tool and totals.tracemaintains one editable cumulative tool-call ledger.
showCallsis a legacy compatibility switch and is no longer needed in new configurations. If present,falsealways resolves tomode: "off".showArgumentsadds up to three compact arguments to each call notice. Long path-like values retain their filename/suffix; other values retain their beginning. Arguments can contain local paths, queries, URLs, or other sensitive input, so the default isfalse.showOutputForlists tool-name substrings whose output is returned to chat. The default["bash"]provides progress from shell commands. Use["*"]to return output from every tool, or an empty list to suppress all direct tool output. Forwarding all output can expose sensitive tool results and create substantial chat traffic. This setting is independent frommode;mode: "off"can still forward selected output.maxTraceEntriesbounds eachtracemessage. Longer traces continue in additional editable messages without discarding earlier calls.
The mode is global across WhatsApp, Telegram, Slack, Discord, Mattermost, Matrix, and Web. Editable modes correlate progressive ACP updates by tool-call ID. If a message can no longer be edited, the bridge continues in a replacement message. The CLI retains event-style output.
For an editable audit trail with arguments:
{
"toolMessages": {
"mode": "trace",
"showArguments": true,
"showOutputFor": [],
"maxTraceEntries": 20
}
}
For example, to show individual tool calls and output from Bash and time MCP tools:
{
"toolMessages": {
"mode": "events",
"showArguments": false,
"showOutputFor": ["bash", "mcp__time"]
}
}
To show only the agent's final response:
{
"toolMessages": {
"mode": "off",
"showArguments": false,
"showOutputFor": []
}
}
Verbose Error Messages
verboseErrors controls whether operational error messages surfaced to chat
users include the underlying backend error detail. It currently applies to
ACP session listing, loading, and reload errors (the catch blocks that used
to return a fixed friendly string).
{
"verboseErrors": false
}
verboseErrors: false(default) keeps the existing friendly UX. Users see only the generic message such asCould not list saved ACP sessions.; the underlying error is still written to the bridge logs.verboseErrors: trueappends the backend error detail on a new line, for exampleCould not list saved ACP sessions.\nbackend unreachable. Whitespace in the detail is collapsed, empty details are dropped, and the detail is bounded to 500 characters so ACP/backend errors cannot exceed connector message limits (e.g. Discord's 2000-character cap).
Security warning. Backend error messages may expose internal paths, endpoints, hostnames, stack-trace fragments, or other sensitive operational details to anyone in the chat room. Only enable
verboseErrorsin trusted environments (private DMs, locked-down channels) and never in shared public channels where untrusted users can read the bot's replies. The detail is bounded but not redacted.
Matrix HTML Formatting
By default, bot responses are sent as plain text. When formatHtml is enabled,
the Matrix connector converts markdown responses to HTML before sending, using
the Matrix formatted_body field. Matrix clients render the HTML while plain
text clients (IRC bridges, etc.) see the unformatted fallback.
Enable in chat-bridge.json
{
"matrix": {
"formatHtml": true
}
}
What it does
With formatHtml: false (default) | With formatHtml: true |
|---|---|
sendText() - plain text only | sendMessage() with format: org.matrix.custom.html |
| Markdown syntax visible as raw text | Tables, bold, lists rendered natively |
| Works on all clients equally | HTML for Matrix, plain text fallback for others |
When to use it
- Enable when your primary audience uses Matrix/Element clients
- Leave disabled for IRC-bridged rooms or plain text environments
- The plain text
bodyis always included as a fallback
CLI Options
# Interactive mode
bun src/cli.ts
# Single prompt
bun src/cli.ts "What time is it?"
Interactive Commands
| Command | Description |
|---|---|
exit | Exit the CLI |
quit | Exit the CLI |
Agent Configuration
Multiple Agents
Define multiple agents for different purposes:
{
"agent": {
"chat-bridge": {
"description": "Secure chat assistant",
"mode": "primary",
"permission": {
"read": "deny",
"bash": "deny"
}
},
"researcher": {
"description": "Deep research mode",
"mode": "primary",
"permission": {
"web-search_*": "allow"
}
},
"coder": {
"description": "Code-focused assistant",
"mode": "primary",
"permission": {
"read": "allow",
"glob": "allow",
"grep": "allow",
"edit": "deny"
}
}
}
}
Agent Settings
| Setting | Description | Default |
|---|---|---|
mode | "primary" or "subagent" | "all" |
description | Human-readable description | - |
prompt | System prompt | OpenCode default |
model | LLM model override | Config default |
temperature | Response randomness | Model default |
permission | Tool permissions | Config default |
Model Configuration
Default Model
IMPORTANT: You MUST set the model field in opencode.json. Without it, OpenCode defaults to opencode/big-pickle (a free but less capable model).
Set in opencode.json:
{
"model": "anthropic/claude-sonnet-4-5"
}
Per-Agent Model
Override for specific agents:
{
"agent": {
"fast-helper": {
"model": "anthropic/claude-haiku-4-5",
"permission": { "read": "deny" }
}
}
}
Available Models
Check available models:
opencode model list
Common models:
anthropic/claude-sonnet-4-*- Good balanceanthropic/claude-opus-4-*- Most capableanthropic/claude-haiku-4-*- Fastestopenai/gpt-4o- OpenAIgoogle/gemini-*- Google
Environment Variables
Trigger Prefix
The trigger prefix can be set globally in chat-bridge.json or overridden per connector via environment variables:
| Env var | Connector | Fallback |
|---|---|---|
SLACK_TRIGGER | Slack | chat-bridge.json trigger |
DISCORD_TRIGGER | Discord | chat-bridge.json trigger |
MATRIX_TRIGGER | Matrix | chat-bridge.json trigger |
MATTERMOST_TRIGGER | Mattermost | chat-bridge.json trigger |
WHATSAPP_TRIGGER | chat-bridge.json trigger | |
WEB_TRIGGER | Web | chat-bridge.json trigger |
This lets you run multiple connectors with different triggers from the same config.
Other Variables
# OpenCode configuration
OPENCODE_MODEL="anthropic/claude-sonnet-4-20250514"
OPENCODE_CONFIG="/path/to/opencode.json"
# For chat connectors
MATRIX_ACCESS_TOKEN="syt_..."
DISCORD_TOKEN="..."
User Allowlists
Each chat connector can restrict access to a connector-specific list of user IDs. Empty list means all users are allowed. For WhatsApp, allowedUsers filters non-owner senders; the linked WhatsApp account itself is always allowed.
These IDs are platform-native identifiers, not always human-friendly usernames or phone numbers. For example, Slack uses member IDs like U01ABC123, while WhatsApp may use a sender ID shown in connector logs.
WhatsApp also supports personal linked-account mode:
{
"whatsapp": {
"respondToOthers": false
}
}
When respondToOthers is false, only messages sent by the linked WhatsApp account can trigger the bot. Messages from other people are ignored even if they use the trigger. WhatsApp self-chat plain text is always accepted as a prompt from the owner; other chats still require the configured trigger.
For WhatsApp personal deployments, prefer whatsapp.respondToOthers: false over allowedUsers; WhatsApp sender IDs may be LID-style identifiers rather than phone numbers and can be hard to map to contacts.
In chat-bridge.json:
{
"slack": { "allowedUsers": ["U01ABC123", "U02DEF456"] },
"whatsapp": { "allowedUsers": ["34600111222"] },
"matrix": { "allowedUsers": ["@alice:matrix.org"] },
"discord": { "allowedUsers": ["123456789012345678"] },
"mattermost": { "allowedUsers": ["user-id-1"] }
}
Environment variable overrides:
| Env var | Connector |
|---|---|
SLACK_ALLOWED_USERS | Slack |
WHATSAPP_ALLOWED_USERS | |
WHATSAPP_RESPOND_TO_OTHERS | WhatsApp owner-only mode (false = ignore other people) |
MATRIX_ALLOWED_USERS | Matrix |
DISCORD_ALLOWED_USERS | Discord |
MATTERMOST_ALLOWED_USERS | Mattermost |
Breaking change: WhatsApp now uses allowedUsers / WHATSAPP_ALLOWED_USERS. The older allowedNumbers / WHATSAPP_ALLOWED_NUMBERS names were removed.
Example Configurations
Minimal (CLI Only)
{
"$schema": "https://opencode.ai/config.json",
"default_agent": "chat-bridge",
"agent": {
"chat-bridge": {
"mode": "primary",
"permission": {
"read": "deny",
"edit": "deny",
"bash": "deny",
"time_*": "allow",
"web-search_*": "allow"
}
}
}
}
Full Production
{
"$schema": "https://opencode.ai/config.json",
"model": "anthropic/claude-sonnet-4-5",
"default_agent": "chat-bridge",
"mcp": {
"chrome-devtools": { "enabled": false },
"antigravity-img": { "enabled": false }
},
"agent": {
"chat-bridge": {
"description": "Secure chat assistant",
"mode": "primary",
"prompt": "You are a helpful assistant in a chat interface.\n\nYOUR CAPABILITIES (only these):\n1) Web search and fetching web pages\n2) Time and timezone queries\n3) Document library access\n\nYOU DO NOT HAVE:\n- Browser automation or Chrome DevTools\n- Image generation\n- Filesystem access\n- Code execution\n\nDo NOT mention capabilities you don't have.",
"permission": {
"read": "deny",
"edit": "deny",
"bash": "deny",
"glob": "deny",
"grep": "deny",
"task": "deny",
"todowrite": "deny",
"todoread": "deny",
"webfetch": "deny",
"codesearch": "deny",
"question": "allow",
"chrome-devtools_*": "deny",
"generate_image": "deny",
"image_quota": "deny",
"time_*": "allow",
"weather_*": "allow",
"web-search_*": "allow"
}
}
}
}
This configuration allows time, weather, and web-search MCP tools while denying all filesystem and dangerous tools.
Session Management
Bot sessions are stored outside the project git repo to prevent them from cluttering your OpenCode session list.
Why outside the git repo? OpenCode uses the git root to create a unique project hash. If session directories are inside the repo, all bot sessions end up in the same project hash as your dev sessions. By storing them outside (in ~/.cache/), OpenCode assigns them to the global project instead, keeping them completely separate from your development sessions.
Session Storage Location
Default: ~/.cache/opencode-chat-bridge/sessions/<connector>/<channel-id>/
Override: Set SESSION_BASE_DIR environment variable:
SESSION_BASE_DIR=/path/to/sessions
Session Directory Structure
~/.cache/opencode-chat-bridge/sessions/
slack/
C0ABC123_1710000000_111/ # Slack thread session (channel:threadTs)
C0ABC123_1710000000_222/ # Another thread in same channel
matrix/
_room1_server.org/ # Matrix room session (special chars sanitized)
mattermost/
abc123def456/ # Mattermost channel session
whatsapp/
1234567890/ # WhatsApp chat session
Session Cleanup
Sessions are cleaned up in two ways:
1. Startup cleanup (days-based): Old on-disk session directories are deleted when a connector starts.
# .env
SESSION_RETENTION_DAYS=7 # Default: 7 days
2. Runtime expiry (minutes-based): A background sweep expires inactive in-memory sessions, removes their persisted ACP mappings, and deletes their on-disk cache.
# .env
SESSION_RETENTION_MINS=30 # Default: 30 for Slack, unset for others
Sessions with active in-flight queries are never evicted. Both mechanisms coexist -- startup cleanup handles long-dead sessions, runtime expiry handles sessions that go stale while the bot is running.
Session Commands
Users can manage their sessions via chat:
Slack:
!oc /status- Show session info and directory location!oc /clearor!oc /reset- Clear session history!oc /help- Show available commands
Matrix:
!oc /status- Show session info!oc /clearor!oc /reset- Clear session history!oc /help- Show available commands
Mattermost:
!oc /status- Show session info!oc /clearor!oc /reset- Clear session history!oc /help- Show available commands
WhatsApp:
!oc /status- Show session info!oc /clearor!oc /reset- Clear session history!oc /help- Show available commands
Session Continuity
Each conversation thread maintains its own session. Users can reference previous messages as long as:
- The session hasn't been manually cleared (
/clear) - The session hasn't expired due to
SESSION_RETENTION_MINSinactivity - The session is less than
SESSION_RETENTION_DAYSold (on-disk cleanup at startup) - The connector hasn't been restarted (in-memory sessions are lost on restart)
For Slack specifically, sessions are per-thread. Other connectors use per-channel/room sessions.
Debugging Sessions
To inspect a session directory:
ls -la ~/.cache/opencode-chat-bridge/sessions/slack/C0ABC123/
OpenCode stores session data in ~/.local/share/opencode/storage/session/<project-hash>/. Since bot sessions are outside any git repo, they go to the global project (~/.local/share/opencode/storage/session/global/) instead of your dev project's hash.
To view session files for a specific bot session:
cd ~/.cache/opencode-chat-bridge/sessions/slack/C0ABC123
opencode session list
This won't pollute your main project session list since it's a different "project" (directory outside git).
Troubleshooting
"Agent not found"
Ensure default_agent matches an agent name:
{
"default_agent": "chat-bridge",
"agent": {
"chat-bridge": { ... }
}
}
"Tool blocked"
Check permission configuration. Tool calls are blocked if:
- Tool is set to
"deny" - Tool not explicitly allowed and no default
"*": "allow"
"No response"
- Check OpenCode is installed:
opencode --version - Check
opencode.jsonexists in working directory - Check for errors in output
"Wrong model being used" / "Big Pickle"
If the bot uses opencode/big-pickle instead of your intended model:
- Add
"model": "anthropic/claude-sonnet-4-5"toopencode.json - Without the
modelfield, OpenCode defaults to free models - Verify with:
opencode modelsto list available models
"Images not displaying in Matrix"
If MCP tool images aren't showing in Matrix chat:
- Tool results contain image path markers (e.g.,
[DOCLIBRARY_IMAGE]...[/DOCLIBRARY_IMAGE]) - These come in tool_result events, not in response text chunks
- The Matrix connector captures tool results via
updateevents - Check logs for
[IMAGE]messages