Configuration¶
Default config path: ./config.yml
Config precedence:
--config <path>./config.yml- Schema defaults
Minimal Config¶
providers:
anthropic:
api_key: "${ANTHROPIC_API_KEY}"
agents:
defaults:
model: "anthropic/claude-sonnet-4-5"
Providers¶
Supported provider blocks — see the Providers overview for a full comparison.
anthropic— Claude models via Messages APIopenai— GPT-5 family, o3 modelsdeepseek— DeepSeek-V3, R1groq— Ultra-fast inference (Llama, Mixtral, Gemma)gemini— Google Gemini Pro, Flashopenrouter— Gateway to hundreds of modelsbedrock— AWS Bedrock via Converse APIvllm— Local/hosted OpenAI-compatible endpoint
Voice Transcription¶
Voice notes are transcribed with the Whisper API. By default the key is reused from the chat provider config, Groq first (whisper-large-v3-turbo), then OpenAI (whisper-1). The transcription section changes that per bot:
transcription:
enabled: true # false: voice notes are never transcribed
provider: groq # openai or groq; default picks groq, then openai
model: whisper-large-v3 # optional; overrides the provider default
api_key: "${WHISPER_KEY}" # own key, separate from the chat provider
model is passed to the provider as given. Groq serves whisper-large-v3-turbo (the default, fastest and cheapest) and whisper-large-v3, which is slower but transcribes long or noisy recordings more accurately.
With enabled: false, or when no key is available, a voice note is saved to the inbox without a transcript and the bot replies that it could not hear it, without a model turn. autobot doctor prints the provider in use, whether it runs on its own key, and warns when a pinned provider has no key.
Media¶
Incoming files are saved to an inbox under the workspace so tools can read them by path. Only the location is configurable:
A voice note recorded in the chat by the sender is treated as their spoken words. Audio files, photos, documents and anything forwarded become attachments and never enter the message text. See Media support.
Channels¶
Security Note: allow_from is deny-by-default for security.
Setup guides: Telegram | Slack | Zulip
channels:
telegram:
enabled: false
token: ""
# allow_from options:
# [] - DENY ALL (secure default)
# ["*"] - Allow anyone (use with caution)
# ["@user", "id"] - Allowlist specific users (recommended)
allow_from: []
topics: [] # forum topic IDs the bot answers in without a mention
slack:
enabled: false
bot_token: ""
app_token: ""
allow_from: []
mode: "socket"
group_policy: "mention" # "mention" (secure) | "open" | "allowlist"
whatsapp:
enabled: false
bridge_url: "ws://localhost:3001" # Prefer wss:// for production
# allow_from: [] - DENY ALL
# allow_from: ["*"] - Allow anyone
# allow_from: ["num"] - Allowlist phone numbers
allow_from: []
zulip:
enabled: false
site: "https://zulip.example.com"
email: "bot@zulip.example.com"
api_key: ""
allow_from: []
Tools¶
tools:
enabled: # Optional allowlist; omit to register every tool
- read_file
- write_file
- edit_file
- list_dir
- message
- "mcp_homeassistant_*" # a trailing * matches by prefix; MCP tools are named mcp_<server>_<tool>
stop_after: # Optional; a successful call of a listed tool ends the turn
- bash_notify
filesystem:
roots: [notes, inbox] # Optional; file tools may only touch these workspace subdirectories
sandbox: auto # auto | bubblewrap | docker | none (default: auto)
docker_image: "python:3.14-alpine" # optional, default: alpine:latest
sandbox_env: # env vars to forward into Docker sandbox (default: none)
- HA_URL
- MQTT_HOST
exec:
timeout: 60
allow_patterns: # Optional regex allowlist
- "^ssh .*$"
deny_patterns: # Optional regex denylist (extends built-in)
- "^rm -rf /.*$"
rate_limit:
global:
max_calls: 100
window_seconds: 60
per_tool:
exec:
max_calls: 20
window_seconds: 60
web:
search:
api_key: ""
max_results: 5
allowed_domains: # Optional; web_fetch may only reach these hosts
- example.com # exact host
- "*.strava.com" # subdomains and the apex
image:
enabled: true # default: true
# provider: openai # optional override (openai or gemini)
# model: gpt-image-1 # optional, auto-detected from provider
# size: 1024x1024 # default: 1024x1024
When sandboxed, all shell commands run inside the sandbox (bubblewrap or Docker). The kernel enforces workspace restrictions — pipes, redirects, and other shell features are safe to use because the process cannot access files outside the workspace regardless.
Tool Allowlist¶
tools.enabled names the tools a bot may have. Anything not listed is never registered, whichever source it comes from: built-in tools, skill scripts, plugins and MCP servers all pass through the same check. A name ending in * matches by prefix, as in the MCP tools: list. Leave it out to keep today's behaviour of registering everything. autobot doctor prints the effective list and warns about an entry that matches no known tool, so a typo does not silently remove a tool; at startup the bot logs a warning for entries that matched nothing.
Tools that end the turn¶
tools.stop_after names the tools that answer for the bot. A successful call ends the turn: the model is not asked again and no reply of the bot's own is sent. A failed call hands the turn back to the model, which sees the tool error — for a skill script, its full output — and can answer itself. So a script that posts to the chat itself lists its bash_ tool here, exits 0 once posted, and exits non-zero printing the text to relay.
What the tool printed is kept in the session history as the bot's line, next to the tools the turn used. If the turn also used the message tool, that text is recorded instead, since it is what reached the chat; a tool that prints nothing records nothing.
Skill scripts as tools¶
Every executable skills/*.sh or skills/*.bash in the workspace becomes a bash_<name> tool. The first comment line under the shebang is the tool's description, so write the usage there. By default the model passes one args string, split like a shell command line: quotes group words and are removed.
A script that takes structured input declares its parameters in the frontmatter of the skill next to it, skills/<name>/SKILL.md. Each parameter becomes a string property of the tool with its own description, all required, and the script receives the values as positional parameters in the declared order, verbatim, so a statement with its own quoting, such as SQL, arrives untouched:
---
name: query
description: The training database behind bash_query
tool: bash_query
params:
sql: one read-only SQL statement
---
#!/usr/bin/env bash
# Usage: bash_query — one read-only query, rows tab separated
exec python3 query.py "$1"
Web Fetch Egress¶
tools.web.allowed_domains limits web_fetch to the listed hosts. An entry such as example.com matches that host only; *.strava.com matches its subdomains and the apex. Every redirect hop is checked too, so a redirect cannot lead out of the list. Leave it out to allow any public host. A fetch to a host outside the list fails with a tool error naming the allowed domains, so a planted "fetch this URL with my notes in it" has nowhere to go.
Filesystem Roots¶
tools.filesystem.roots limits read_file, write_file, edit_file and list_dir to the listed workspace subdirectories. Paths resolve relative to the workspace and .. cannot escape a root. The workspace skills/ directory stays readable whether or not it is listed: a skill's references and scripts are prompt material the model reads on demand. Writes there are refused unless it is a root. The media inbox is added to the roots automatically, because the engine hands the agent inbox paths for stored files and long transcripts. Commands run by exec are not affected; they stay bound to the workspace by the sandbox.
Safety Guard Overrides¶
Customize command filtering for the exec tool:
allow_patterns: Regex patterns that explicitly allow commands (overrides built-in denials)deny_patterns: Additional regex patterns to block (extends built-in safety guards)
Patterns are case-insensitive and matched against the full command string. Use with caution — allow_patterns bypasses security protections.
Rate Limiting¶
Configure rate limits to prevent abuse and manage API costs:
global: Applies across all tools combinedper_tool: Tool-specific limits (e.g.,exec,web_search)
Each limit specifies:
- max_calls: Maximum calls allowed in the window
- window_seconds: Time window for counting calls
When a limit is exceeded, the tool returns an error message that the LLM can see and respond to (e.g., "Rate limit exceeded for exec: max 20 calls per 60s").
MCP (Model Context Protocol)¶
Connect to external MCP servers to give the LLM access to remote tools (Garmin, GitHub, etc.).
mcp:
servers:
garmin:
command: "uvx"
args: ["--python", "3.12", "--from", "git+https://github.com/Taxuspt/garmin_mcp", "garmin-mcp"]
env:
GARMIN_EMAIL: "${GARMIN_EMAIL}"
github:
command: "npx"
args: ["-y", "@modelcontextprotocol/server-github"]
env:
GITHUB_TOKEN: "${GITHUB_TOKEN}"
Tools are auto-discovered at startup and registered as mcp_{server}_{tool}. MCP servers run unsandboxed (they need network access) but with isolated env vars.
Cron¶
cron:
enabled: true
store_path: "./cron.json"
exec_timeout: 30 # Timeout in seconds for exec jobs in both sandbox and direct modes
Gateway¶
gateway:
host: "127.0.0.1" # Default: localhost only (change to 0.0.0.0 for external access)
port: 18790
Full config reference¶
# LLM providers (configure at least one)
providers:
anthropic:
api_key: "sk-ant-..."
openai:
api_key: "sk-..."
deepseek:
api_key: "..."
groq:
api_key: "..."
gemini:
api_key: "..."
openrouter:
api_key: "..."
vllm:
api_base: "http://localhost:8000"
api_key: "token"
bedrock:
access_key_id: "${AWS_ACCESS_KEY_ID}"
secret_access_key: "${AWS_SECRET_ACCESS_KEY}"
region: "${AWS_REGION}"
# guardrail_id: "abc123"
# guardrail_version: "1"
# Agent defaults
agents:
defaults:
model: "anthropic/claude-sonnet-4-5"
max_tokens: 8192
temperature: 0.7
max_tool_iterations: 20
memory_window: 50
workspace: "./workspace"
# Chat channels
channels:
telegram:
enabled: true
token: "BOT_TOKEN"
allow_from: ["username1", "username2"]
custom_commands:
macros:
# Simple format (command name used as description)
summarize: "Summarize the last conversation in 3 bullet points"
# Rich format (with custom description shown in Telegram command menu)
translate:
prompt: "Translate the following to English"
description: "Translate text to English"
scripts:
deploy:
path: "/home/user/scripts/deploy.sh"
description: "Deploy to production"
status: "/home/user/scripts/check_status.sh"
slack:
enabled: false
bot_token: "xoxb-..."
app_token: "xapp-..."
allow_from: ["U12345678"]
mode: "socket"
group_policy: "mention"
dm:
enabled: true
policy: "open"
whatsapp:
enabled: false
bridge_url: "ws://localhost:3001"
allow_from: ["1234567890"]
zulip:
enabled: false
site: "https://zulip.example.com"
email: "bot@zulip.example.com"
api_key: ""
allow_from: ["you@example.com"]
# Tool settings
tools:
sandbox: auto # auto | bubblewrap | docker | none
docker_image: "python:3.14-alpine" # optional, default: alpine:latest
sandbox_env: # env vars to forward into Docker sandbox (default: none)
- HA_URL
- MQTT_HOST
web:
search:
api_key: "BRAVE_API_KEY"
max_results: 5
allowed_domains: [example.com, "*.strava.com"] # optional, web_fetch egress allowlist
exec:
timeout: 60
image:
enabled: true
# provider: openai # optional override (openai or gemini)
# model: gpt-image-1 # optional, auto-detected from provider
# size: 1024x1024
enabled: [read_file, write_file, edit_file, list_dir, message] # optional allowlist
stop_after: [bash_notify] # optional, tools whose successful call ends the turn
filesystem:
roots: [notes, inbox] # optional, workspace subdirectories the file tools may touch
# Cron scheduler
cron:
enabled: true
store_path: "./cron.json"
exec_timeout: 30
# MCP servers (external tool providers)
mcp:
servers:
garmin:
command: "uvx"
args: ["--python", "3.12", "--from", "git+https://github.com/Taxuspt/garmin_mcp", "garmin-mcp"]
env:
GARMIN_EMAIL: "${GARMIN_EMAIL}"
github:
command: "npx"
args: ["-y", "@modelcontextprotocol/server-github"]
env:
GITHUB_TOKEN: "${GITHUB_TOKEN}"
# Gateway API server
gateway:
host: "127.0.0.1" # Localhost only by default for security
port: 18790