LibreFang Configuration Reference
Complete reference for config.toml, covering every configurable field in the LibreFang Agent OS.
Table of Contents
- Overview
- Config Include Mechanism
- Minimal Configuration
- Full Example
- Section Reference
- Top-Level Fields
- [default_model]
- [memory]
- [auto_dream]
- [network]
- [web]
- [media]
- [links]
- [channels]
- [[mcp_servers]]
- [a2a]
- [[fallback_providers]]
- [[users]]
- Channel Overrides
- [browser]
- [reload]
- [exec_policy]
- [approval]
- [budget]
- [thinking]
- [tts]
- [docker]
- [canvas]
- [auto_reply]
- [broadcast]
- [inbox]
- [[bindings]]
- [pairing]
- [extensions]
- [vault]
- [webhook_triggers]
- [proxy]
- [[sidecar_channels]]
- [session]
- [terminal]
- [queue]
- [external_auth]
- [vertex_ai]
- [oauth]
- [auth_profiles]
- [tool_policy]
- [tool_timeouts]
- [proactive_memory]
- [context_engine]
- [audit]
- [health_check]
- [plugins]
- [prompt_intelligence]
- Environment Variables
- Validation
Overview
LibreFang reads its configuration from a single TOML file:
~/.librefang/config.toml
On Windows, ~ resolves to C:\Users\<username>. If the home directory cannot be determined, the system temp directory is used as a fallback.
Key behaviors:
- Every struct in the configuration uses
#[serde(default)], which means all fields are optional. Omitted fields receive their documented default values. - Channel sections (
[channels.discord],[channels.slack], etc.) areOption<T>-- when absent, the channel adapter is disabled. Including the section header (even empty) enables the adapter with defaults. Telegram is now sidecar-only and uses[[sidecar_channels]]instead. - Secrets are never stored in config.toml directly. Instead, fields like
api_key_envandbot_token_envhold the name of an environment variable that contains the actual secret. This prevents accidental exposure in version control. - Sensitive fields (
api_key,shared_secret) are automatically redacted in debug output and logs.
Config Include Mechanism
LibreFang supports splitting configuration across multiple files using the include field. This enables modular configs (e.g., separate files for channels, MCP servers, or environment-specific overrides).
# ~/.librefang/config.toml
include = ["channels.toml", "mcp.toml", "overrides/prod.toml"]
Rules:
- Paths are relative to the directory containing the root config file.
- Absolute paths are rejected with an error at startup.
..path traversal components are rejected for security.- Maximum include nesting depth: 10 levels.
- Include files are deep-merged before the root config. Root config values always override values from included files.
- Include files can themselves contain
includearrays (subject to the depth limit).
This mechanism is useful for keeping secrets in a separate file with stricter filesystem permissions, or for sharing a base config across multiple environments.
Minimal Configuration
The simplest working configuration only needs an LLM provider API key set as an environment variable. With no config file at all, LibreFang boots with Anthropic as the default provider:
# ~/.librefang/config.toml
# Minimal: just override the model if you want something other than defaults.
# Set ANTHROPIC_API_KEY in your environment.
[default_model]
provider = "anthropic"
model = "claude-sonnet-4-20250514"
api_key_env = "ANTHROPIC_API_KEY"
Or to use a local Ollama instance with no API key:
[default_model]
provider = "ollama"
model = "llama3.2:latest"
base_url = "http://localhost:11434"
api_key_env = ""
Full Example
# ============================================================
# LibreFang Agent OS -- Complete Configuration Reference
# ============================================================
# --- Top-level fields ---
home_dir = "~/.librefang" # LibreFang home directory
data_dir = "~/.librefang/data" # SQLite databases and data files
log_level = "info" # trace | debug | info | warn | error
api_listen = "127.0.0.1:4545" # HTTP/WS API bind address
network_enabled = false # Enable OFP peer-to-peer network
api_key = "" # API Bearer token (empty = unauthenticated)
dashboard_user = "" # Dashboard login username (empty = no login required)
dashboard_pass = "" # Dashboard login password (supports vault:KEY syntax)
mode = "default" # stable | default | dev
update_channel = "stable" # stable | beta | rc — CLI update channel
language = "en" # Locale for CLI/messages
usage_footer = "full" # off | tokens | cost | full
prompt_caching = true # Enable LLM prompt caching
stable_prefix_mode = false # Reduce prompt cache invalidation
max_cron_jobs = 500 # Global max cron jobs
tool_timeout_secs = 300 # Global timeout for all tools (seconds)
cors_origin = [] # CORS allowed origins
include = [] # Config file includes
[provider_urls]
# ollama = "http://192.168.1.100:11434/v1"
[provider_api_keys]
# nvidia = "NVIDIA_API_KEY"
[provider_regions]
# qwen = "intl" # Use Qwen international endpoint (dashscope-intl)
# minimax = "china" # Use MiniMax China endpoint (MINIMAX_CN_API_KEY)
# --- Tool Timeouts ---
[tool_timeouts]
agent_send = 600 # Override for agent_send tool
agent_spawn = 600 # Override for agent_spawn tool
"mcp_browser_*" = 900 # Glob pattern for browser MCP tools
shell_exec = 300 # Override for shell_exec tool
# --- Default LLM Provider ---
[default_model]
provider = "anthropic"
model = "claude-sonnet-4-20250514"
api_key_env = "ANTHROPIC_API_KEY"
# base_url = "https://api.anthropic.com" # Optional override
# --- Fallback Providers ---
[[fallback_providers]]
provider = "ollama"
model = "llama3.2:latest"
api_key_env = ""
# base_url = "http://localhost:11434" # Uses catalog default if omitted
[[fallback_providers]]
provider = "groq"
model = "llama-3.3-70b-versatile"
api_key_env = "GROQ_API_KEY"
# --- Memory ---
[memory]
# sqlite_path = "~/.librefang/data/librefang.db" # Auto-resolved if omitted
embedding_model = "all-MiniLM-L6-v2"
consolidation_threshold = 10000
decay_rate = 0.1
# --- Network (OFP Wire Protocol) ---
[network]
listen_addresses = ["/ip4/0.0.0.0/tcp/0"]
bootstrap_peers = []
mdns_enabled = true
max_peers = 50
shared_secret = "" # Required when network_enabled = true
# --- Web Tools ---
[web]
search_provider = "auto" # auto | brave | jina | tavily | perplexity | duckduckgo
cache_ttl_minutes = 15
timeout_secs = 15
[web.brave]
api_key_env = "BRAVE_API_KEY"
max_results = 5
country = ""
search_lang = ""
freshness = ""
[web.tavily]
api_key_env = "TAVILY_API_KEY"
search_depth = "basic" # basic | advanced
max_results = 5
include_answer = true
[web.jina]
api_key_env = "JINA_API_KEY"
max_results = 5
[web.perplexity]
api_key_env = "PERPLEXITY_API_KEY"
model = "sonar"
[web.fetch]
max_chars = 50000
max_response_bytes = 10485760 # 10 MB
timeout_secs = 30
readability = true
# --- Media Understanding ---
[media]
image_description = true
audio_transcription = true
video_description = false
max_concurrency = 2
# image_provider = "openai"
# audio_provider = "openai"
# --- Link Understanding ---
[links]
enabled = false
max_links = 3
max_content_bytes = 102400
timeout_secs = 10
# --- MCP Servers ---
[[mcp_servers]]
name = "filesystem"
timeout_secs = 30
env = []
[mcp_servers.transport]
type = "stdio"
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
[[mcp_servers]]
name = "remote-tools"
timeout_secs = 60
env = ["REMOTE_API_KEY"]
[mcp_servers.transport]
type = "sse"
url = "https://mcp.example.com/events"
[[mcp_servers]]
name = "my-http-backend"
timeout_secs = 30
[mcp_servers.transport]
type = "http_compat"
base_url = "https://tools.example.com"
headers = [{name = "Authorization", value_env = "MY_API_KEY"}]
[[mcp_servers.transport.tools]]
name = "search"
description = "Search documents"
path = "/search"
method = "post"
# --- A2A Protocol ---
[a2a]
enabled = false
name = "LibreFang Agent OS"
description = ""
listen_path = "/a2a"
[[a2a.external_agents]]
name = "research-agent"
url = "https://agent.example.com/.well-known/agent.json"
# --- RBAC Users ---
[[users]]
name = "Alice"
role = "owner" # owner | admin | user | viewer
api_key_hash = ""
[users.channel_bindings]
telegram = "123456"
discord = "987654321"
[[users]]
name = "Bob"
role = "user"
[users.channel_bindings]
slack = "U0123ABCDEF"
# --- Channel Adapters ---
# (See "Channels" section below for all 44 adapters)
# Telegram uses a sidecar adapter (in-process channel removed in #5241):
[[sidecar_channels]]
name = "telegram"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.telegram"]
channel_type = "telegram"
[sidecar_channels.env]
TELEGRAM_BOT_TOKEN = "..."
# Discord (sidecar)
[[sidecar_channels]]
name = "discord"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.discord"]
channel_type = "discord"
[sidecar_channels.env]
DISCORD_BOT_TOKEN = "..."
# Slack (sidecar)
[[sidecar_channels]]
name = "slack"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.slack"]
channel_type = "slack"
[sidecar_channels.env]
SLACK_APP_TOKEN = "xapp-..."
SLACK_BOT_TOKEN = "xoxb-..."
# [[sidecar_channels]]
# name = "qq"
# command = "python3"
# args = ["-m", "librefang.sidecar.adapters.qq"]
# channel_type = "qq"
# [sidecar_channels.env]
# QQ_APP_ID = "your-app-id"
# WeCom: see sidecar example below (was [channels.wecom])
# [[sidecar_channels]]
# name = "wecom"
# command = "python3"
# args = ["-m", "librefang.sidecar.adapters.wecom"]
# channel_type = "wecom"
# [sidecar_channels.env]
# WECOM_BOT_ID = "aibxxxxxxx"
# WeChat migrated to a sidecar:
# [[sidecar_channels]]
# name = "wechat"
# command = "python3"
# args = ["-m", "librefang.sidecar.adapters.wechat"]
# channel_type = "wechat"
# [sidecar_channels.env]
# WECHAT_BOT_TOKEN = "" # blank → triggers QR login
# --- Browser Automation ---
[browser]
headless = true
viewport_width = 1280
viewport_height = 720
timeout_secs = 30
idle_timeout_secs = 300
max_sessions = 5
# --- Config Hot-Reload ---
[reload]
mode = "hybrid" # off | restart | hot | hybrid
debounce_ms = 500
# --- Shell Exec Policy ---
[exec_policy]
mode = "allowlist" # deny | allowlist | full
timeout_secs = 30
max_output_bytes = 102400
no_output_timeout_secs = 30
# --- Budget ---
[budget]
max_hourly_usd = 0.0
max_daily_usd = 0.0
max_monthly_usd = 0.0
alert_threshold = 0.8
default_max_llm_tokens_per_hour = 0
# --- Extended Thinking ---
[thinking]
budget_tokens = 10000
stream_thinking = false
# --- TTS ---
[tts]
enabled = false
max_text_length = 4096
timeout_secs = 30
[tts.openai]
voice = "alloy"
model = "tts-1"
format = "mp3"
speed = 1.0
# --- Docker Sandbox ---
[docker]
enabled = false
image = "python:3.12-slim"
network = "none"
memory_limit = "512m"
cpu_limit = 1.0
timeout_secs = 60
read_only_root = true
mode = "off" # off | non_main | all
scope = "session" # session | agent | shared
cap_add = [] # e.g., ["NET_ADMIN"]
tmpfs = ["/tmp:size=64m"]
pids_limit = 100
# --- Vault ---
[vault]
enabled = true
# path = "~/.librefang/vault.enc"
# --- Webhook Triggers ---
[webhook_triggers]
enabled = false
token_env = "LIBREFANG_WEBHOOK_TOKEN"
max_payload_bytes = 65536
rate_limit_per_minute = 30
# --- HTTP Proxy ---
[proxy]
# http_proxy = "http://proxy.corp.example:8080"
# https_proxy = "http://proxy.corp.example:8080"
# no_proxy = "localhost,127.0.0.1,.internal.corp"
# --- Session ---
[session]
retention_days = 0
max_sessions_per_agent = 0
cleanup_interval_hours = 24
# --- Terminal ---
[terminal]
enabled = true
# allow_remote = false
# allow_unauthenticated_remote = false
# allowed_origins = []
# require_proxy_headers = false
# tmux_enabled = true
# max_windows = 16
# tmux_binary_path = ""
# --- Queue ---
[queue]
max_depth_per_agent = 0
max_depth_global = 0
task_ttl_secs = 3600
[queue.concurrency]
main_lane = 3
cron_lane = 2
subagent_lane = 3
# --- Audit ---
[audit]
retention_days = 90
# --- External Auth (OAuth2/OIDC) ---
[external_auth]
enabled = false
# issuer_url = "https://accounts.google.com"
# client_id = "your-client-id"
# client_secret_env = "LIBREFANG_OAUTH_CLIENT_SECRET"
# --- Vertex AI ---
[vertex_ai]
# project_id = "my-gcp-project"
# region = "us-central1"
# credentials_path = "/path/to/service-account.json"
# --- Context Engine ---
[context_engine]
engine = "default"
# plugin = "qdrant-recall"
# --- Plugins ---
[plugins]
plugin_registries = []
Section Reference
Top-Level Fields
These fields sit at the root of config.toml (not inside any [section]).
| Field | Type | Default | Description |
|---|---|---|---|
home_dir | path | ~/.librefang | LibreFang home directory. Stores config, agents, skills. |
data_dir | path | ~/.librefang/data | Directory for SQLite databases and persistent data. |
log_level | string | "info" | Log verbosity. One of: trace, debug, info, warn, error. |
api_listen | string | "127.0.0.1:4545" | Bind address for the HTTP/WebSocket/SSE API server. Alias: listen_addr. |
network_enabled | bool | false | Enable the OFP peer-to-peer network layer. |
api_key | string | "" (empty) | API authentication key. When set, all endpoints except /api/health require Authorization: Bearer <key>. Empty means unauthenticated (local development only). |
cors_origin | list of strings | [] | CORS allowed origins added to the allow list (in addition to localhost). E.g., ["https://dash.example.com"]. |
mode | string | "default" | Kernel operating mode. See below. |
language | string | "en" | Language/locale code for CLI output and system messages. |
usage_footer | string | "full" | Controls usage info appended to responses. See below. |
prompt_caching | bool | true | Enable LLM provider prompt caching. Adds cache hints to system prompts (Anthropic: cache_control, OpenAI: automatic prefix caching). |
stable_prefix_mode | bool | false | When enabled, avoids volatile system-prompt additions (recalled memory, canonical context) that change every turn, improving provider-side prompt cache hit rates. |
max_cron_jobs | usize | 500 | Global maximum number of cron jobs across all agents. |
workspaces_dir | path or null | null | Root directory for agent workspaces. Defaults to ~/.librefang/workspaces. Contains agent working directories and the hands/ subdirectory for user custom hands. |
include | list of strings | [] | Config file includes (relative paths). See Config Include Mechanism. |
provider_urls | map of string→string | {} | Provider base URL overrides. Maps provider ID to custom base URL (e.g., ollama = "http://192.168.1.100:11434/v1"). Useful for self-hosted or proxied endpoints. |
provider_api_keys | map of string→string | {} | Provider API key env var overrides. Maps provider ID to the name of an environment variable holding the key (e.g., nvidia = "NVIDIA_API_KEY"). When not set for a provider, the convention {PROVIDER_UPPER}_API_KEY is used. |
provider_regions | map of string→string | {} | Provider region selection. Maps provider ID to a region name defined in the provider's registry TOML (e.g., qwen = "intl"). Overrides the provider's base URL and optionally its API key env var. Applied before provider_urls (lower priority). |
tool_timeout_secs | u64 | 300 | Global timeout in seconds for all tool executions. Can be overridden per-tool via [tool_timeouts] section. |
tool_timeouts | map of string→u64 | {} | Per-tool timeout overrides. Keys are exact tool names or glob patterns. See [tool_timeouts] section for details. |
mode values:
| Value | Behavior |
|---|---|
stable | Conservative: no auto-updates, pinned models, frozen skill registry. Uses FallbackDriver. |
default | Balanced: standard operation. |
dev | Developer: experimental features enabled. |
usage_footer values:
| Value | Behavior |
|---|---|
off | No usage information shown. |
tokens | Show token counts only. |
cost | Show estimated cost only. |
full | Show both token counts and estimated cost (default). |
[default_model]
Configures the primary LLM provider used when agents do not specify their own model.
[default_model]
provider = "anthropic"
model = "claude-sonnet-4-20250514"
api_key_env = "ANTHROPIC_API_KEY"
# base_url = "https://api.anthropic.com"
| Field | Type | Default | Description |
|---|---|---|---|
provider | string | "anthropic" | Provider name. Supported: anthropic, gemini, openai, groq, openrouter, deepseek, together, mistral, fireworks, ollama, vllm, lmstudio, perplexity, cohere, ai21, cerebras, sambanova, huggingface, xai, replicate. |
model | string | "claude-sonnet-4-20250514" | Model identifier. Aliases like sonnet, haiku, gpt-4o, gemini-flash are resolved by the model catalog. |
api_key_env | string | "ANTHROPIC_API_KEY" | Name of the environment variable holding the API key. The actual key is read from this env var at runtime, never stored in config. |
base_url | string or null | null | Override the API base URL. Useful for proxies or self-hosted endpoints. When null, the provider's default URL from the model catalog is used. |
[memory]
Configures the SQLite-backed memory substrate, including vector embeddings and memory decay.
[memory]
# sqlite_path = "/custom/path/librefang.db"
embedding_model = "all-MiniLM-L6-v2"
consolidation_threshold = 10000
decay_rate = 0.1
| Field | Type | Default | Description |
|---|---|---|---|
sqlite_path | path or null | null | Explicit path to the SQLite database file. When null, defaults to {data_dir}/librefang.db. |
embedding_model | string | "all-MiniLM-L6-v2" | Model name used for generating vector embeddings for semantic memory search. |
embedding_provider | string or null | null | Embedding provider (e.g., "openai", "ollama"). Auto-detected if null. |
embedding_api_key_env | string or null | null | Environment variable name holding the API key for the embedding provider. |
consolidation_threshold | u64 | 10000 | Number of stored memories before automatic consolidation is triggered to merge and prune old entries. |
consolidation_interval_hours | u64 | 24 | How often memory consolidation runs (hours). 0 = disabled. |
decay_rate | f32 | 0.1 | Memory confidence decay rate. 0.0 = no decay (memories never fade), 1.0 = aggressive decay. Values between 0.0 and 1.0. |
[auto_dream]
Background memory consolidation ("dreams") — asks opt-in agents to reflect on and consolidate their own memory via a 4-phase prompt (Orient / Gather / Consolidate / Prune). Dreams trigger event-driven the moment an agent finishes a turn; a sparse backstop scheduler (default 1 day) catches opted-in agents that never turn. Disabled by default; individual agents still opt in via auto_dream_enabled = true on their manifest.
[auto_dream]
enabled = false
min_hours = 24
min_sessions = 5
check_interval_secs = 86400
timeout_secs = 600
# lock_dir = "" # defaults to <data_dir>/auto_dream/
| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Master toggle. When false, no dream fires regardless of per-agent opt-in. |
min_hours | f64 | 24.0 | Minimum hours since that agent's last consolidation before the next one fires. |
min_sessions | u32 | 5 | Minimum sessions touched since that agent's last consolidation before the next one fires. Set to 0 to disable the session-count gate. |
check_interval_secs | u64 | 86400 | Backstop scheduler cadence, in seconds. Primary trigger is the AgentLoopEnd hook; this only controls the fallback for agents that never turn. |
timeout_secs | u64 | 600 | Timeout for a single dream invocation in seconds. |
lock_dir | string | "" | Optional override for the lock directory. Empty = <data_dir>/auto_dream/. Per-agent locks are stored as <dir>/<agent_id>.lock. |
A dream fires for an agent when all gates hold: enabled = true, the agent's manifest has auto_dream_enabled = true, min_hours have elapsed since its last dream, min_sessions have been touched since then, and the per-agent lock can be acquired.
Per-agent opt-in can be toggled at runtime via PUT /api/auto-dream/agents/{id}/enabled (body {"enabled": bool}) or via the Settings → Auto-Dream card on the web dashboard — the new state takes effect at the next turn end (event-driven) or the next backstop tick, whichever comes first. See /configuration/core#auto_dream for the full reference including runtime tool restriction, manual controls, and audit events.
[network]
Configures the OFP (LibreFang Protocol) peer-to-peer networking layer. Authentication has two layers: a shared_secret HMAC admission gate plus per-node Ed25519 identity with TOFU pinning (#3873). The Ed25519 keypair and trust pins live in <data_dir>/peer_keypair.json and <data_dir>/trusted_peers.json.
[network]
listen_addresses = ["/ip4/0.0.0.0/tcp/0"]
bootstrap_peers = []
mdns_enabled = true
max_peers = 50
shared_secret = "my-cluster-secret"
| Field | Type | Default | Description |
|---|---|---|---|
listen_addresses | list of strings | ["/ip4/0.0.0.0/tcp/0"] | libp2p multiaddresses to listen on. Port 0 means auto-assign. |
bootstrap_peers | list of strings | [] | Multiaddresses of bootstrap peers for DHT discovery. |
mdns_enabled | bool | true | Enable mDNS for automatic local network peer discovery. |
max_peers | u32 | 50 | Maximum number of simultaneously connected peers. |
shared_secret | string | "" (empty) | Pre-shared admission secret for OFP HMAC-SHA256. Required when network_enabled = true. Both sides must use the same value. Acts as a coarse "cluster password" gate; per-node identity is provided separately by the Ed25519 keypair persisted in the data dir, so a leaked shared_secret cannot impersonate a previously-pinned peer. Redacted in logs. |
[web]
Configures web search and web fetch capabilities used by agent tools.
[web]
search_provider = "auto"
cache_ttl_minutes = 15
timeout_secs = 15
| Field | Type | Default | Description |
|---|---|---|---|
search_provider | string | "auto" | Which search engine to use. See values below. |
cache_ttl_minutes | u64 | 15 | Cache duration for search/fetch results in minutes. 0 = caching disabled. |
timeout_secs | u64 | 15 | HTTP timeout in seconds for all web search requests. Recommended: 15 for most providers, 30+ for Jina. |
search_provider values:
| Value | Description |
|---|---|
auto | Cascading fallback: tries Tavily, then Brave, then Jina, then Perplexity, then DuckDuckGo, based on which API keys are available. |
brave | Brave Search API. Requires BRAVE_API_KEY. |
jina | Jina AI search and grounding. Requires JINA_API_KEY. |
tavily | Tavily AI-native search. Requires TAVILY_API_KEY. |
perplexity | Perplexity AI search. Requires PERPLEXITY_API_KEY. |
duckduckgo | DuckDuckGo HTML scraping. No API key needed. |
[web.brave]
[web.brave]
api_key_env = "BRAVE_API_KEY"
max_results = 5
country = ""
search_lang = ""
freshness = ""
| Field | Type | Default | Description |
|---|---|---|---|
api_key_env | string | "BRAVE_API_KEY" | Environment variable name holding the Brave Search API key. |
max_results | usize | 5 | Maximum number of search results to return. |
country | string | "" | Country code for localized results (e.g., "US", "GB"). Empty = no filter. |
search_lang | string | "" | Language code (e.g., "en", "fr"). Empty = no filter. |
freshness | string | "" | Freshness filter. "pd" = past day, "pw" = past week, "pm" = past month. Empty = no filter. |
[web.tavily]
[web.tavily]
api_key_env = "TAVILY_API_KEY"
search_depth = "basic"
max_results = 5
include_answer = true
| Field | Type | Default | Description |
|---|---|---|---|
api_key_env | string | "TAVILY_API_KEY" | Environment variable name holding the Tavily API key. |
search_depth | string | "basic" | Search depth: "basic" for fast results, "advanced" for deeper analysis. |
max_results | usize | 5 | Maximum number of search results to return. |
include_answer | bool | true | Whether to include Tavily's AI-generated answer summary in results. |
[web.jina]
[web.jina]
api_key_env = "JINA_API_KEY"
max_results = 5
| Field | Type | Default | Description |
|---|---|---|---|
api_key_env | string | "JINA_API_KEY" | Environment variable name holding the Jina AI API key. |
max_results | usize | 5 | Maximum number of search results to return. |
[web.perplexity]
[web.perplexity]
api_key_env = "PERPLEXITY_API_KEY"
model = "sonar"
| Field | Type | Default | Description |
|---|---|---|---|
api_key_env | string | "PERPLEXITY_API_KEY" | Environment variable name holding the Perplexity API key. |
model | string | "sonar" | Perplexity model to use for search queries. |
[web.fetch]
[web.fetch]
max_chars = 50000
max_response_bytes = 10485760
timeout_secs = 30
readability = true
# Optional: allow agents to reach internal services (self-hosted / K8s).
# Cloud metadata endpoints (169.254.x.x, 100.64.x.x) remain blocked unconditionally.
# ssrf_allowed_hosts = [
# "10.0.0.0/8", # CIDR — entire private subnet
# "*.internal.example.com", # glob — all subdomains
# "svc.cluster.local", # literal hostname
# ]
| Field | Type | Default | Description |
|---|---|---|---|
max_chars | usize | 50000 | Maximum characters returned in fetched content. Content exceeding this is truncated. |
max_response_bytes | usize | 10485760 (10 MB) | Maximum HTTP response body size in bytes. |
timeout_secs | u64 | 30 | HTTP request timeout in seconds. |
readability | bool | true | Enable HTML-to-Markdown readability extraction. When true, fetched HTML is converted to clean Markdown. |
ssrf_allowed_hosts | list of strings | [] | Hosts/CIDRs exempt from SSRF blocking. Supports CIDR notation ("10.0.0.0/8"), glob prefix patterns ("*.internal.example.com"), and literal IPs or hostnames. Cloud metadata ranges (169.254.0.0/16, 100.64.0.0/10) are always blocked regardless of this list. |
[media]
Configures media understanding (image description, audio transcription, video description) for messages that include attachments.
[media]
image_description = true
audio_transcription = true
video_description = false
max_concurrency = 2
# image_provider = "openai" # auto-detect if omitted
# audio_provider = "openai" # auto-detect if omitted
| Field | Type | Default | Description |
|---|---|---|---|
image_description | bool | true | Enable automatic image description for incoming image attachments. |
audio_transcription | bool | true | Enable automatic audio transcription for incoming audio attachments. |
video_description | bool | false | Enable video description. Disabled by default (expensive and slow). |
max_concurrency | usize | 2 | Maximum number of concurrent media processing tasks. |
image_provider | string or null | null | Preferred provider for image description. Auto-detected from available providers if null. |
audio_provider | string or null | null | Preferred provider for audio transcription. Auto-detected from available providers if null. |
[links]
Configures automatic link understanding — fetching and summarizing URLs found in incoming messages.
[links]
enabled = false
max_links = 3
max_content_bytes = 102400
timeout_secs = 10
| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Enable automatic link understanding. When true, URLs in messages are fetched and their content is summarized before the agent processes the message. |
max_links | usize | 3 | Maximum number of links to process per message. Additional links are ignored. |
max_content_bytes | usize | 102400 (100 KB) | Maximum content size to fetch per link in bytes. Content exceeding this is truncated. |
timeout_secs | u64 | 10 | Per-link fetch timeout in seconds. |
[channels]
All 45 channel adapters are configured under [channels.<name>]. Each channel is Option<T> -- omitting the section disables the adapter entirely. Including the section header (even empty) enables it with default values.
Universal channel fields: Every channel adapter supports the following common fields in addition to its own specific fields:
| Field | Type | Default | Description |
|---|---|---|---|
default_agent | string or null | null | Agent name to route messages to by default. |
account_id | string or null | null | Unique identifier for this bot instance. Used for multi-bot routing via [[bindings]] match rules. |
overrides | object | (defaults) | Per-channel behavior overrides. See Channel Overrides. |
Telegram
Telegram is now sidecar-only. The [channels.telegram] config block is no longer accepted. Declare Telegram as a [[sidecar_channels]] entry:
[[sidecar_channels]]
name = "telegram"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.telegram"]
channel_type = "telegram"
[sidecar_channels.env]
TELEGRAM_BOT_TOKEN = "..."
# ALLOWED_USERS = "111,@alice"
The sidecar preserves all prior runtime semantics. See [[sidecar_channels]] for the full field reference.
Discord (sidecar)
Discord migrated to an out-of-process sidecar in v2026.5; declare it as a [[sidecar_channels]] block instead of [channels.discord].
[[sidecar_channels]]
name = "discord"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.discord"]
channel_type = "discord"
[sidecar_channels.env]
DISCORD_BOT_TOKEN = "..."
# DISCORD_ALLOWED_GUILDS = "123,456"
# DISCORD_ALLOWED_USERS = "789"
# DISCORD_INTENTS = "37376"
# DISCORD_IGNORE_BOTS = "true"
# DISCORD_MENTION_PATTERNS = "hey bot,!ask"
# DISCORD_ACCOUNT_ID = "guild-42"
The sidecar preserves the same runtime semantics — gateway intents,
allowed_guilds / allowed_users filters, mention detection, and
RBAC mapping via channel_role_mapping.discord. See
[[sidecar_channels]] below for the full
supervisor field reference.
Slack (sidecar)
Slack migrated to an out-of-process sidecar in v2026.5; declare it as a [[sidecar_channels]] block instead of [channels.slack].
[[sidecar_channels]]
name = "slack"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.slack"]
channel_type = "slack"
[sidecar_channels.env]
SLACK_APP_TOKEN = "xapp-..."
SLACK_BOT_TOKEN = "xoxb-..."
# SLACK_ALLOWED_CHANNELS = "C0123,C0456"
# SLACK_UNFURL_LINKS = "false"
# SLACK_FORCE_FLAT_REPLIES = "false"
# SLACK_REACTIONS = "true"
# SLACK_ACCOUNT_ID = "workspace-prod"
The sidecar preserves the same runtime semantics — Socket Mode
WebSocket + Web API, allowed-channels filter (DMs exempt), Block Kit
interactive callbacks, thread reply context, eyes / check reactions,
multi-bot routing via SLACK_ACCOUNT_ID. Workspace-role RBAC via
channel_role_mapping.slack is no longer live: see the regression
note in the CHANGELOG.
WhatsApp (sidecar)
WhatsApp migrated to a Python sidecar (librefang.sidecar.adapters.whatsapp). The in-process [channels.whatsapp] block is no longer recognised. Both modes (Meta Cloud API + Web/QR Baileys gateway) are preserved. See WhatsApp in the channels configuration guide.
Signal (sidecar)
Signal migrated from an in-process Rust adapter to an out-of-process Python sidecar (librefang.sidecar.adapters.signal). The sidecar talks to a separately-run signal-cli-rest-api container. An existing [channels.signal] block is no longer recognised — re-declare as [[sidecar_channels]]:
[[sidecar_channels]]
name = "signal"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.signal"]
channel_type = "signal"
[sidecar_channels.env]
SIGNAL_API_URL = "https://signal-cli.example.com"
SIGNAL_NUMBER = "+15555550100"
# SIGNAL_ALLOWED_USERS = "+15555550199,+15555550200" # optional
# SIGNAL_ACCOUNT_ID = "prod-bot" # optional
# SIGNAL_POLL_INTERVAL_SECS = "2" # optional
# SIGNAL_ALLOW_LOCAL = "1" # opt-in SSRF bypass for localhost
The sidecar enforces the same SSRF guard as the Rust adapter — SIGNAL_API_URL must resolve to a public address unless SIGNAL_ALLOW_LOCAL=1 is set. Optional SIGNAL_API_KEY belongs in ~/.librefang/secrets.env.
Matrix (sidecar)
Matrix migrated from an in-process Rust adapter to an out-of-process Python sidecar (librefang.sidecar.adapters.matrix). The sidecar polls /sync on the configured homeserver via the Client-Server API. An existing [channels.matrix] block is no longer recognised — re-declare as [[sidecar_channels]]:
[[sidecar_channels]]
name = "matrix"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.matrix"]
channel_type = "matrix"
[sidecar_channels.env]
MATRIX_HOMESERVER_URL = "https://matrix.org"
MATRIX_USER_ID = "@librefang:matrix.org"
# MATRIX_ALLOWED_ROOMS = "!abc:matrix.org,!def:matrix.org" # optional
# MATRIX_ACCOUNT_ID = "prod-bot" # optional
# MATRIX_MAX_UPLOAD_BYTES = "52428800" # optional, default 50 MiB
Secret via ~/.librefang/secrets.env: MATRIX_ACCESS_TOKEN (the bot's access token from the homeserver — same shape Element uses).
Email (IMAP + SMTP) (sidecar)
Email is provided by the Python sidecar adapter (librefang.sidecar.adapters.email). The in-process [channels.email] config block was removed in the sidecar migration. Declare email as a [[sidecar_channels]] entry instead — see Email in the channels configuration guide.
Microsoft Teams (sidecar)
Teams is provided by the Python sidecar adapter (librefang.sidecar.adapters.teams). The in-process [channels.teams] config block was removed in the sidecar migration. Declare Teams as a [[sidecar_channels]] entry instead — see Microsoft Teams in the channels configuration guide.
Mattermost (sidecar)
Mattermost migrated from an in-process Rust adapter to an out-of-process Python sidecar (librefang.sidecar.adapters.mattermost). An existing [channels.mattermost] block is no longer recognised — re-declare as [[sidecar_channels]]:
[[sidecar_channels]]
name = "mattermost"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.mattermost"]
channel_type = "mattermost"
[sidecar_channels.env]
MATTERMOST_SERVER_URL = "https://mattermost.example.com"
# MATTERMOST_ALLOWED_CHANNELS = "ch-id-1,ch-id-2" # optional
# MATTERMOST_ACCOUNT_ID = "team-prod" # optional
MATTERMOST_TOKEN belongs in ~/.librefang/secrets.env.
Google Chat
Google Chat is provided by the Python sidecar adapter (librefang.sidecar.adapters.google_chat). The in-process [channels.google_chat] config block was removed in the sidecar migration. Declare Google Chat as a [[sidecar_channels]] entry instead — see Google Chat in the channels configuration guide.
Twitch
Twitch is provided by the Python sidecar adapter (librefang.sidecar.adapters.twitch). The in-process [channels.twitch] config block was removed in the sidecar migration. Declare Twitch as a [[sidecar_channels]] entry instead:
[[sidecar_channels]]
name = "twitch"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.twitch"]
channel_type = "twitch"
[sidecar_channels.env]
TWITCH_NICK = "librefang-bot"
TWITCH_CHANNELS = "channel1,channel2" # comma-separated, no '#'
# TWITCH_ACCOUNT_ID = "prod" # optional, multi-bot routing
# TWITCH_RATE_LIMIT_MSGS = "20" # 20/30s for unmodded; 100 if bot is mod
# TWITCH_RATE_LIMIT_SECS = "30"
TWITCH_OAUTH_TOKEN belongs in ~/.librefang/secrets.env — the dashboard's Channels page writes it there when you fill the Twitch form. The oauth: prefix is auto-added if you leave it off.
The sidecar defaults to TLS on irc.chat.twitch.tv:6697 (the Rust adapter used plaintext 6667, which leaked the OAuth token on every connect). Plaintext is reachable only via TWITCH_PLAINTEXT=1 for local mock listeners — never set it in production.
Rocket.Chat
Rocket.Chat is provided by the Python sidecar adapter (librefang.sidecar.adapters.rocketchat). The in-process [channels.rocketchat] config block was removed in the sidecar migration. Declare Rocket.Chat as a [[sidecar_channels]] entry instead:
[[sidecar_channels]]
name = "rocketchat"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.rocketchat"]
channel_type = "rocketchat"
[sidecar_channels.env]
ROCKETCHAT_SERVER_URL = "https://chat.example.com"
ROCKETCHAT_USER_ID = "abc123"
# ROCKETCHAT_CHANNELS = "GENERAL,room2" # optional; empty = all joined
# ROCKETCHAT_ACCOUNT_ID = "prod" # optional, multi-bot routing key
# ROCKETCHAT_POLL_INTERVAL_SECS = "2" # optional, default 2, floor 1
ROCKETCHAT_TOKEN (the personal access token) belongs in ~/.librefang/secrets.env — the dashboard's Channels page writes it there when you fill the Rocket.Chat form.
Zulip
Zulip is provided by the Python sidecar adapter (librefang.sidecar.adapters.zulip). The in-process [channels.zulip] config block was removed in the sidecar migration. Declare Zulip as a [[sidecar_channels]] entry instead:
[[sidecar_channels]]
name = "zulip"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.zulip"]
channel_type = "zulip"
[sidecar_channels.env]
ZULIP_SERVER_URL = "https://myorg.zulipchat.com"
ZULIP_BOT_EMAIL = "bot-bot@myorg.zulipchat.com"
# ZULIP_STREAMS = "engineering, general" # optional; empty = all subscribed
# ZULIP_ACCOUNT_ID = "prod" # optional, multi-bot routing key
ZULIP_API_KEY belongs in ~/.librefang/secrets.env — the dashboard's Channels page writes it there when you fill the Zulip form. The sidecar surfaces the inbound topic as thread_id and round-trips it on outbound (replies stay in the originating topic), honours Retry-After on 429 across /users/me, /register, /events, and /messages, and dedupes on message.id so a queue re-register can't re-emit the same message.
LINE
LINE is provided by the Python sidecar adapter (librefang.sidecar.adapters.line). The in-process [channels.line] config block was removed in the sidecar migration. Declare LINE as a [[sidecar_channels]] entry instead — the sidecar runs its own HTTP webhook server (no longer mounted on the LibreFang API port), so the URL you register at the LINE Developers Console is now https://<your-sidecar-host>:<LINE_WEBHOOK_PORT><LINE_WEBHOOK_PATH>:
[[sidecar_channels]]
name = "line"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.line"]
channel_type = "line"
[sidecar_channels.env]
LINE_WEBHOOK_PORT = "9090"
# LINE_WEBHOOK_PATH = "/webhook"
# LINE_ACCOUNT_ID = "production"
Set both LINE_CHANNEL_SECRET and LINE_CHANNEL_ACCESS_TOKEN in ~/.librefang/secrets.env.
Reddit is provided by the Python sidecar adapter (librefang.sidecar.adapters.reddit). The in-process [channels.reddit] config block was removed in the sidecar migration. Declare Reddit as a [[sidecar_channels]] entry instead:
[[sidecar_channels]]
name = "reddit"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.reddit"]
channel_type = "reddit"
[sidecar_channels.env]
REDDIT_CLIENT_ID = "abc123"
REDDIT_USERNAME = "librefang-bot"
REDDIT_SUBREDDITS = "rust,programming" # comma-separated
# REDDIT_ACCOUNT_ID = "prod" # optional, multi-bot routing key
# REDDIT_USER_AGENT = "myorg-bot/1.0 (by /u/me)" # override default UA
REDDIT_CLIENT_SECRET and REDDIT_PASSWORD belong in ~/.librefang/secrets.env — the dashboard's Channels page writes them there when you fill the Reddit form.
Mastodon
Mastodon is now sidecar-only. The [channels.mastodon] config block is no longer accepted. Declare Mastodon as a [[sidecar_channels]] entry:
[[sidecar_channels]]
name = "mastodon"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.mastodon"]
channel_type = "mastodon"
[sidecar_channels.env]
MASTODON_INSTANCE_URL = "https://mastodon.social"
# MASTODON_VISIBILITY = "unlisted" # public | unlisted | private | direct
# MASTODON_MAX_MESSAGE_LEN = "500" # raise for higher-limit instances
# MASTODON_ACCOUNT_ID = "prod" # optional, multi-bot routing key
MASTODON_ACCESS_TOKEN (OAuth bearer) belongs in ~/.librefang/secrets.env — the dashboard's Channels page writes it there when you fill the Mastodon form.
Bluesky
Bluesky is provided by the Python sidecar adapter (librefang.sidecar.adapters.bluesky). The in-process [channels.bluesky] config block was removed in the sidecar migration. Declare Bluesky as a [[sidecar_channels]] entry instead:
[[sidecar_channels]]
name = "bluesky"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.bluesky"]
channel_type = "bluesky"
[sidecar_channels.env]
BLUESKY_IDENTIFIER = "mybot.bsky.social"
# BLUESKY_SERVICE_URL = "https://bsky.social" # custom PDS
# BLUESKY_ACCOUNT_ID = "prod" # optional, multi-bot routing key
BLUESKY_APP_PASSWORD (Bluesky app password) belongs in ~/.librefang/secrets.env — the dashboard's Channels page writes it there when you fill the Bluesky form.
Feishu / Lark (sidecar)
Feishu / Lark is provided by the Python sidecar adapter (librefang.sidecar.adapters.feishu). The in-process [channels.feishu] config block was removed in the sidecar migration. Declare Feishu / Lark as a [[sidecar_channels]] entry instead — see Feishu / Lark in the channels integration guide.
Nextcloud Talk
Nextcloud Talk is provided by the Python sidecar adapter (librefang.sidecar.adapters.nextcloud). The in-process [channels.nextcloud] config block was removed in the sidecar migration. Declare Nextcloud as a [[sidecar_channels]] entry instead:
[[sidecar_channels]]
name = "nextcloud"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.nextcloud"]
channel_type = "nextcloud"
[sidecar_channels.env]
NEXTCLOUD_SERVER_URL = "https://cloud.example.com"
# NEXTCLOUD_ROOMS = "abc123,def456" # optional; empty = all joined
# NEXTCLOUD_ACCOUNT_ID = "prod" # optional, multi-bot routing key
# NEXTCLOUD_POLL_INTERVAL_SECS = "3" # optional, default 3, floor 1
NEXTCLOUD_TOKEN (the app password / OAuth bearer) belongs in ~/.librefang/secrets.env — the dashboard's Channels page writes it there when you fill the Nextcloud form.
Webex (sidecar)
Webex migrated to an out-of-process sidecar in v2026.5; declare it as a [[sidecar_channels]] block instead of [channels.webex].
[[sidecar_channels]]
name = "webex"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.webex"]
channel_type = "webex"
[sidecar_channels.env]
# WEBEX_ALLOWED_ROOMS = "Y2lz...A,Y2lz...B" # optional, empty = all rooms
# WEBEX_ACCOUNT_ID = "org-prod" # optional, multi-bot routing key
WEBEX_BOT_TOKEN (the bot Bearer token from developer.webex.com) belongs in ~/.librefang/secrets.env — the dashboard's Channels page writes it there when you fill the Webex form.
DingTalk (sidecar)
DingTalk is provided by the Python sidecar adapter (librefang.sidecar.adapters.dingtalk), stream mode only. The in-process [channels.dingtalk] config block was removed in the sidecar migration. Declare DingTalk as a [[sidecar_channels]] entry instead — see DingTalk in the channels configuration guide.
The legacy in-process webhook mode (HMAC-SHA256 over timestamp + secret + body) is NOT ported — operators who relied on webhook mode must switch to stream subscription in the DingTalk admin console.
ntfy
ntfy is provided by the Python sidecar adapter (librefang.sidecar.adapters.ntfy). The in-process [channels.ntfy] config block was removed in the sidecar migration. Declare ntfy as a [[sidecar_channels]] entry instead — see ntfy in the channels configuration guide.
Gotify
Gotify is now sidecar-only. The [channels.gotify] config block is no longer accepted. Declare Gotify as a [[sidecar_channels]] entry:
[[sidecar_channels]]
name = "gotify"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.gotify"]
channel_type = "gotify"
[sidecar_channels.env]
GOTIFY_SERVER_URL = "https://gotify.example.com"
# GOTIFY_ACCOUNT_ID = "prod" # optional, multi-bot routing
GOTIFY_APP_TOKEN (publish) and GOTIFY_CLIENT_TOKEN (subscribe) belong in ~/.librefang/secrets.env — the dashboard's Channels page writes them there automatically when you fill the Gotify form.
Webhook (sidecar)
Webhook migrated to a Python sidecar (librefang.sidecar.adapters.webhook). The in-process [channels.webhook] block is no longer recognised. Declare as [[sidecar_channels]] instead — see Webhook in the channels configuration guide.
QQ Bot (sidecar)
QQ migrated from an in-process Rust adapter to an out-of-process Python sidecar (librefang.sidecar.adapters.qq). The sidecar talks to the QQ Open Platform via WebSocket (gateway) + REST (token + outbound). An existing [channels.qq] block is no longer recognised — re-declare as [[sidecar_channels]]:
[[sidecar_channels]]
name = "qq"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.qq"]
channel_type = "qq"
[sidecar_channels.env]
QQ_APP_ID = "your-app-id"
# QQ_ALLOWED_USERS = "openid-1,openid-2" # optional
# QQ_ACCOUNT_ID = "prod-bot" # optional
# QQ_INTENTS = "1073746435" # optional bitmask override
Secret via ~/.librefang/secrets.env: QQ_APP_SECRET (the bot's clientSecret from the QQ Open Platform console).
WeCom (sidecar)
WeCom (formerly WeChat Work / Enterprise WeChat) migrated from an in-process Rust adapter to an out-of-process Python sidecar (librefang.sidecar.adapters.wecom). The sidecar connects via WebSocket to wss://openws.work.weixin.qq.com using the intelligent-bot Bot ID and Secret. The legacy callback mode (HTTP webhook + AES-CBC-256 inbound decryption) is no longer supported — Python's stdlib has no AES, and the sidecar SDK is stdlib-only by policy. Operators who relied on callback mode must switch the bot to WebSocket mode in the WeCom admin console.
[[sidecar_channels]]
name = "wecom"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.wecom"]
channel_type = "wecom"
[sidecar_channels.env]
WECOM_BOT_ID = "aibxxxxxxx"
# WECOM_ALLOWED_USERS = "alice,bob"
# WECOM_ACCOUNT_ID = "prod-bot"
Secret via ~/.librefang/secrets.env: WECOM_BOT_SECRET (the bot secret from the WeCom admin console).
WeChat (sidecar)
WeChat (personal account via iLink) is provided by the Python sidecar adapter (librefang.sidecar.adapters.wechat). The in-process [channels.wechat] config block was removed in the sidecar migration. Declare WeChat as a [[sidecar_channels]] entry instead — see WeChat in the channels configuration guide.
[[mcp_servers]]
MCP (Model Context Protocol) server connections provide external tool integration. Each entry is a separate [[mcp_servers]] array element.
[[mcp_servers]]
name = "filesystem"
timeout_secs = 30
env = []
[mcp_servers.transport]
type = "stdio"
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem", "/home/user/docs"]
[[mcp_servers]]
name = "remote-api"
timeout_secs = 60
env = ["GITHUB_PERSONAL_ACCESS_TOKEN"]
[mcp_servers.transport]
type = "sse"
url = "https://mcp.example.com/sse"
[[mcp_servers]]
name = "my-http-backend"
timeout_secs = 30
[mcp_servers.transport]
type = "http_compat"
base_url = "https://tools.example.com"
headers = [{name = "Authorization", value_env = "MY_API_KEY"}]
[[mcp_servers.transport.tools]]
name = "search"
description = "Search documents"
path = "/search"
method = "post"
| Field | Type | Default | Description |
|---|---|---|---|
name | string | required | Display name for this MCP server. Tools are namespaced as mcp_{name}_{tool}. |
timeout_secs | u64 | 30 | Request timeout in seconds. |
env | list of strings | [] | Environment variable names to pass through to the subprocess (stdio transport only). |
Transport variants (tagged union on type):
type | Fields | Description |
|---|---|---|
stdio | command (string), args (list of strings, default []) | Spawn a subprocess, communicate via JSON-RPC over stdin/stdout. |
sse | url (string) | Connect to an HTTP Server-Sent Events endpoint. |
http_compat | base_url (string), headers (list of header configs), tools (list of tool configs) | Built-in compatibility adapter for plain HTTP/JSON tool backends without a native MCP server. Each tool maps to an HTTP endpoint. |
http_compat header config:
| Field | Type | Description |
|---|---|---|
name | string | HTTP header name (e.g., "Authorization"). |
value | string or null | Static header value. |
value_env | string or null | Env var name whose value is used as the header value (preferred for secrets). |
http_compat tool config:
| Field | Type | Default | Description |
|---|---|---|---|
name | string | required | Tool name exposed to the LLM. |
description | string | "" | Tool description shown to the LLM. |
path | string | required | HTTP path (e.g., "/search"). |
method | string | "post" | HTTP method: get, post, put, patch, delete. |
request_mode | string | "json_body" | How arguments are sent: json_body, query, none. |
response_mode | string | "json" | Response parsing: json, text. |
input_schema | object | {"type":"object"} | JSON Schema for the tool's input parameters. |
[a2a]
Agent-to-Agent protocol configuration, enabling inter-agent communication across LibreFang instances.
[a2a]
enabled = true
name = "LibreFang Agent OS"
description = "My production agent OS"
listen_path = "/a2a"
[[a2a.external_agents]]
name = "research-agent"
url = "https://agent.example.com/.well-known/agent.json"
[[a2a.external_agents]]
name = "code-reviewer"
url = "https://reviewer.example.com/.well-known/agent.json"
| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Whether A2A protocol is enabled. |
name | string | "LibreFang Agent OS" | Service-level display name shown in the well-known agent card. |
description | string | "" | Service-level description shown in the well-known agent card. |
listen_path | string | "/a2a" | URL path prefix for A2A endpoints. |
external_agents | list of objects | [] | External A2A agents to discover and interact with. |
external_agents entries:
| Field | Type | Description |
|---|---|---|
name | string | Display name for the external agent. |
url | string | Agent card endpoint URL (typically /.well-known/agent.json). |
[[fallback_providers]]
Fallback provider chain. When the primary LLM provider ([default_model]) fails, these are tried in order.
[[fallback_providers]]
provider = "ollama"
model = "llama3.2:latest"
api_key_env = ""
# base_url = "http://localhost:11434"
[[fallback_providers]]
provider = "groq"
model = "llama-3.3-70b-versatile"
api_key_env = "GROQ_API_KEY"
| Field | Type | Default | Description |
|---|---|---|---|
provider | string | "" | Provider name (e.g., "ollama", "groq", "openai"). |
model | string | "" | Model identifier for this provider. |
api_key_env | string | "" | Env var name for the API key. Empty for local providers (ollama, vllm, lmstudio). |
base_url | string or null | null | Base URL override. Uses catalog default if null. |
[[users]]
RBAC multi-user configuration. Users can be assigned roles and bound to channel platform identities.
[[users]]
name = "Alice"
role = "owner"
api_key_hash = "sha256_hash_of_api_key"
[users.channel_bindings]
telegram = "123456"
discord = "987654321"
slack = "U0ABCDEFG"
| Field | Type | Default | Description |
|---|---|---|---|
name | string | required | User display name. |
role | string | "user" | User role in the RBAC hierarchy. |
channel_bindings | map of string to string | {} | Maps channel platform names to platform-specific user IDs, binding this user identity across channels. |
api_key_hash | string or null | null | SHA256 hash of the user's personal API key for authenticated API access. |
Role hierarchy (highest to lowest privilege):
| Role | Description |
|---|---|
owner | Full administrative access. Can manage all agents, users, and configuration. |
admin | Can manage agents and most settings. Cannot modify owner accounts. |
user | Can interact with agents. Limited management capabilities. |
viewer | Read-only access. Can view agent responses but cannot send messages. |
Channel Overrides (legacy — removed)
The [channels.<name>.overrides] sub-table is no longer recognised — see the migration mapping in the channels configuration guide. The historical reference below documents the pre-migration shape only.
Channel Overrides (historical reference)
Every channel adapter (pre-migration) supported an [channels.<name>.overrides] sub-table that customized agent behavior per-channel.
[channels.discord.overrides]
model = "claude-haiku-4-5-20251001"
system_prompt = "You are a concise Discord assistant."
dm_policy = "respond"
group_policy = "mention_only"
rate_limit_per_minute = 0
rate_limit_per_user = 10
threading = true
output_format = "markdown"
usage_footer = "tokens"
typing_mode = "instant"
disable_commands = false
allowed_commands = []
blocked_commands = []
| Field | Type | Default | Description |
|---|---|---|---|
model | string or null | null | Model override for this channel. Uses the agent's default model when null. |
system_prompt | string or null | null | System prompt override for this channel. |
dm_policy | string | "respond" | How the bot handles direct messages. See below. |
group_policy | string | "mention_only" | How the bot handles group messages. See below. |
rate_limit_per_minute | u32 | 0 | Global rate limit for this channel (messages per minute). 0 = unlimited. |
rate_limit_per_user | u32 | 0 | Maximum messages per user per minute. 0 = unlimited. |
threading | bool | false | Enable thread replies (where supported by the platform). |
output_format | string or null | null | Override output formatting. See below. |
usage_footer | string or null | null | Override usage footer mode for this channel. Values: off, tokens, cost, full. |
typing_mode | string or null | null | Typing indicator behavior. See below. Defaults to instant. |
disable_commands | bool | false | Disable all built-in slash commands on this channel. Blocked commands are forwarded to the agent as plain text. |
allowed_commands | list of strings | [] | Whitelist of command names (no leading /). When non-empty, only these are allowed; others are forwarded to the agent. |
blocked_commands | list of strings | [] | Blacklist of command names (no leading /). Applied when allowed_commands is empty. |
dm_policy values:
| Value | Description |
|---|---|
respond | Respond to all direct messages (default). |
allowed_only | Only respond to DMs from users in the allowed list. |
ignore | Ignore all direct messages. |
group_policy values:
| Value | Description |
|---|---|
all | Respond to all messages in group chats. |
mention_only | Only respond when the bot is @mentioned (default). |
commands_only | Only respond to slash commands. |
ignore | Ignore all group messages. |
output_format values:
| Value | Description |
|---|---|
markdown | Standard Markdown (default). |
telegram_html | Telegram HTML subset (<b>, <i>, <code>, etc.). |
slack_mrkdwn | Slack mrkdwn format (*bold*, _italic_, `code`). |
plain_text | No formatting markup. |
typing_mode values:
| Value | Description |
|---|---|
instant | Send typing indicator immediately on message receipt (default). |
message | Send typing indicator only when the first text delta arrives from the LLM. |
thinking | Send typing indicator only during LLM reasoning/thinking phase. |
never | Never send typing indicators. |
[browser]
Configures the headless browser automation engine used by the browser_* agent tools.
[browser]
headless = true
viewport_width = 1280
viewport_height = 720
timeout_secs = 30
idle_timeout_secs = 300
max_sessions = 5
# chromium_path = "/usr/bin/chromium"
| Field | Type | Default | Description |
|---|---|---|---|
headless | bool | true | Run browser in headless mode (no visible window). |
viewport_width | u32 | 1280 | Browser viewport width in pixels. |
viewport_height | u32 | 720 | Browser viewport height in pixels. |
timeout_secs | u64 | 30 | Per-action timeout in seconds. |
idle_timeout_secs | u64 | 300 | Auto-close browser session after this many seconds of inactivity. |
max_sessions | usize | 5 | Maximum concurrent browser sessions. |
chromium_path | string or null | null | Path to the Chromium/Chrome binary. Auto-detected if null. |
[reload]
Controls automatic config file watching and hot-reloading.
[reload]
mode = "hybrid"
debounce_ms = 500
| Field | Type | Default | Description |
|---|---|---|---|
mode | string | "hybrid" | Reload mode. See below. |
debounce_ms | u64 | 500 | Debounce window in milliseconds before reloading after a file change is detected. |
mode values:
| Value | Description |
|---|---|
off | No automatic reloading. Changes require a manual restart. |
restart | Full daemon restart on any config change. |
hot | Hot-reload safe sections only (channels, skills, heartbeat). |
hybrid | Hot-reload where possible; flag restart-required for sections that need it (default). |
[exec_policy]
Controls which shell commands agents are allowed to execute via the exec and shell tools.
[exec_policy]
mode = "allowlist"
allowed_commands = ["git", "python3", "node"]
timeout_secs = 30
max_output_bytes = 102400
no_output_timeout_secs = 30
| Field | Type | Default | Description |
|---|---|---|---|
mode | string | "allowlist" | Security mode. See below. |
safe_bins | list of strings | ["sleep","true","false","cat","sort","uniq","cut","tr","head","tail","wc","date","echo","printf","basename","dirname","pwd","env"] | Commands that always bypass the allowlist check (stdin-only POSIX utilities). |
allowed_commands | list of strings | [] | Additional commands permitted when mode = "allowlist". |
timeout_secs | u64 | 30 | Maximum wall-clock execution time per command in seconds. |
max_output_bytes | usize | 102400 | Maximum combined stdout+stderr output size in bytes (100 KB default). |
no_output_timeout_secs | u64 | 30 | Kill processes that produce no output for this many seconds. 0 = disabled. |
mode values:
| Value | Aliases | Description |
|---|---|---|
deny | none, disabled | Block all shell execution. |
allowlist | restricted | Only allow commands in safe_bins or allowed_commands (default). |
full | allow, all, unrestricted | Allow all commands. Unsafe -- development use only. |
[approval]
Configures which tools require explicit human approval before execution. References the ApprovalPolicy type.
[approval]
require_approval = ["shell_exec"]
timeout_secs = 60
auto_approve_autonomous = false
auto_approve = false
| Field | Type | Default | Description |
|---|---|---|---|
require_approval | list of strings | ["shell_exec"] | List of tool names that pause execution and wait for human approval before proceeding. |
timeout_secs | u64 | 60 | Timeout for approval requests in seconds. |
auto_approve_autonomous | bool | false | Auto-approve tools when agent is in autonomous mode. |
auto_approve | bool | false | Auto-approve all tool executions (unsafe, dev only). |
[budget]
Sets global spending limits for LLM API costs. All limits default to 0.0 (unlimited).
[budget]
max_hourly_usd = 1.00
max_daily_usd = 10.00
max_monthly_usd = 50.00
alert_threshold = 0.8
default_max_llm_tokens_per_hour = 0
| Field | Type | Default | Description |
|---|---|---|---|
max_hourly_usd | f64 | 0.0 | Maximum total LLM cost in USD per hour across all agents. 0.0 = unlimited. |
max_daily_usd | f64 | 0.0 | Maximum total LLM cost in USD per day across all agents. 0.0 = unlimited. |
max_monthly_usd | f64 | 0.0 | Maximum total LLM cost in USD per month across all agents. 0.0 = unlimited. |
alert_threshold | f64 | 0.8 | Warning threshold as a fraction of each limit (0.0–1.0). At 0.8, warnings are logged when 80% of a limit is reached. |
default_max_llm_tokens_per_hour | u64 | 0 | Global override for per-agent hourly token budget. When > 0, overrides all agents' own token limits. 0 = keep each agent's own limit. |
[thinking]
Configures extended thinking (chain-of-thought reasoning) for models that support it (e.g., Claude 3.7 Sonnet with thinking mode).
[thinking]
budget_tokens = 10000
stream_thinking = false
| Field | Type | Default | Description |
|---|---|---|---|
budget_tokens | u32 | 10000 | Maximum tokens allocated for the thinking/reasoning phase. |
stream_thinking | bool | false | Whether to stream thinking tokens to the client (visible in the API response stream). |
[tts]
Configures text-to-speech synthesis for voice output.
[tts]
enabled = false
provider = "openai" # openai | elevenlabs
max_text_length = 4096
timeout_secs = 30
[tts.openai]
voice = "alloy"
model = "tts-1"
format = "mp3"
speed = 1.0
[tts.elevenlabs]
voice_id = "21m00Tcm4TlvDq8ikWAM"
model_id = "eleven_monolingual_v1"
stability = 0.5
similarity_boost = 0.75
[tts] fields:
| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Enable TTS synthesis. |
provider | string or null | null | Default TTS provider: "openai" or "elevenlabs". |
max_text_length | usize | 4096 | Maximum text length in characters for a single TTS request. |
timeout_secs | u64 | 30 | Request timeout per TTS call in seconds. |
[tts.openai] fields:
| Field | Type | Default | Description |
|---|---|---|---|
voice | string | "alloy" | Voice name. Options: alloy, echo, fable, onyx, nova, shimmer. |
model | string | "tts-1" | TTS model: "tts-1" (fast) or "tts-1-hd" (high quality). |
format | string | "mp3" | Output format: mp3, opus, aac, flac. |
speed | f32 | 1.0 | Speech speed multiplier (0.25 to 4.0). |
[tts.elevenlabs] fields:
| Field | Type | Default | Description |
|---|---|---|---|
voice_id | string | "21m00Tcm4TlvDq8ikWAM" | ElevenLabs voice ID (default: Rachel). |
model_id | string | "eleven_monolingual_v1" | ElevenLabs model ID. |
stability | f32 | 0.5 | Voice stability (0.0–1.0). Higher = more consistent, less expressive. |
similarity_boost | f32 | 0.75 | Voice similarity boost (0.0–1.0). |
[docker]
Configures the Docker container sandbox for isolated code execution.
[docker]
enabled = false
image = "python:3.12-slim"
container_prefix = "librefang-sandbox"
workdir = "/workspace"
network = "none"
memory_limit = "512m"
cpu_limit = 1.0
timeout_secs = 60
read_only_root = true
mode = "off"
scope = "session"
reuse_cool_secs = 300
idle_timeout_secs = 86400
max_age_secs = 604800
blocked_mounts = []
| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Enable Docker sandbox for code execution. |
image | string | "python:3.12-slim" | Docker image to use for the sandbox container. |
container_prefix | string | "librefang-sandbox" | Prefix for container names. |
workdir | string | "/workspace" | Working directory inside the container. |
network | string | "none" | Network mode: "none" (isolated), "bridge", or a custom network name. |
memory_limit | string | "512m" | Memory limit (e.g., "256m", "1g"). |
cpu_limit | f64 | 1.0 | CPU limit (e.g., 0.5, 1.0, 2.0). |
timeout_secs | u64 | 60 | Maximum execution time per command in seconds. |
read_only_root | bool | true | Mount the root filesystem as read-only. |
mode | string | "off" | Activation mode. See below. |
scope | string | "session" | Container lifecycle scope. See below. |
reuse_cool_secs | u64 | 300 | Cooldown in seconds before a released container can be reused. |
idle_timeout_secs | u64 | 86400 | Destroy containers after this many seconds of inactivity (24 hours default). |
max_age_secs | u64 | 604800 | Maximum container age before forced destruction (7 days default). |
blocked_mounts | list of strings | [] | Host paths blocked from bind mounting into containers. |
cap_add | list of strings | [] | Linux capabilities to add to the container (e.g., ["NET_ADMIN"]). Use with caution. |
tmpfs | list of strings | ["/tmp:size=64m"] | tmpfs mounts inside the container. Each entry is "path:options" (e.g., "/tmp:size=128m"). |
pids_limit | u32 | 100 | Maximum number of processes inside the container. Prevents fork bombs. |
mode values:
| Value | Description |
|---|---|
off | Docker sandbox disabled (default). |
non_main | Use Docker only for non-main (sub) agents. |
all | Use Docker for all agents. |
scope values:
| Value | Description |
|---|---|
session | One container per session, destroyed when the session ends (default). |
agent | One container per agent, reused across sessions. |
shared | Shared container pool across all agents. |
[canvas]
Configures the Canvas (Agent-to-UI) tool that allows agents to render HTML in the dashboard.
[canvas]
enabled = false
max_html_bytes = 524288
allowed_tags = []
| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Enable the canvas tool. |
max_html_bytes | usize | 524288 | Maximum HTML payload size in bytes (512 KB default). |
allowed_tags | list of strings | [] | Allowed HTML tag names for sanitization. Empty = all safe tags permitted. |
[auto_reply]
Configures the background auto-reply engine that can automatically respond to incoming messages without waiting for human interaction.
[auto_reply]
enabled = false
max_concurrent = 3
timeout_secs = 120
suppress_patterns = ["/stop", "/pause"]
| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Enable the auto-reply engine. |
max_concurrent | usize | 3 | Maximum number of concurrent auto-reply tasks. |
timeout_secs | u64 | 120 | Default timeout per auto-reply task in seconds. |
suppress_patterns | list of strings | ["/stop", "/pause"] | Incoming message patterns that suppress auto-reply. |
[broadcast]
Configures message broadcasting to route a single incoming message to multiple agents simultaneously.
[broadcast]
strategy = "parallel"
routes = { "announcement-channel" = ["agent-a", "agent-b", "agent-c"] }
| Field | Type | Default | Description |
|---|---|---|---|
strategy | string | "parallel" | Delivery strategy. "parallel" = send to all agents simultaneously; "sequential" = send one at a time in order. |
routes | map of string to list of strings | {} | Maps peer/channel identifiers to lists of agent names that receive the message. |
[inbox]
File-based input inbox for async external commands. Drop text files into a watched directory and they are dispatched as messages to agents. Processed files are moved to a processed/ subdirectory to avoid redelivery.
[inbox]
enabled = true
directory = "~/.librefang/inbox/"
poll_interval_secs = 5
default_agent = "assistant"
| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Enable the inbox directory watcher. |
directory | string or null | null | Directory to watch. Defaults to $HOME_DIR/inbox/. Supports ~ expansion. |
poll_interval_secs | u64 | 5 | How often (in seconds) to scan the directory for new files. Minimum 1. |
default_agent | string or null | null | Agent name to route files to when no agent: directive is found in the file. |
File format: Plain text files (.txt, .md, .json, .py, etc.). The first line may contain an agent:<name> directive to target a specific agent; the rest is sent as the message body. Files without the directive use default_agent.
Safety limits: Files larger than 1 MB are skipped. Binary files (non-text extensions) are skipped. Empty files are moved to processed/ without sending.
Usage examples:
Target a specific agent:
cat > ~/.librefang/inbox/task.txt << 'EOF'
agent:code-reviewer
Please review this code for security issues:
def login(user, password):
query = f"SELECT * FROM users WHERE name='{user}' AND pass='{password}'"
return db.execute(query)
EOF
Send to the default agent:
echo "Summarize today's system logs" > ~/.librefang/inbox/summarize.txt
Cron job:
# crontab -e
0 9 * * * grep ERROR /var/log/app.log > ~/.librefang/inbox/daily_errors.txt
CI/CD post-build:
echo "agent:devops
Build failed, please analyze:
$(tail -100 build.log)" > ~/.librefang/inbox/build_$(date +%s).txt
Batch processing:
for doc in ~/reports/*.md; do
cp "$doc" ~/.librefang/inbox/
done
Check inbox status:
curl -s http://127.0.0.1:4545/api/inbox/status
# {"enabled":true,"pending_count":3,"processed_count":12,...}
[[bindings]]
Agent bindings route specific channel/account/peer combinations to specific agents. More specific bindings (more non-null fields) take priority over less specific ones.
[[bindings]]
agent = "support-agent"
[bindings.match_rule]
channel = "telegram"
guild_id = "123456"
[[bindings]]
agent = "vip-agent"
[bindings.match_rule]
channel = "discord"
peer_id = "987654321"
roles = ["premium"]
Top-level fields:
| Field | Type | Description |
|---|---|---|
agent | string | Target agent name or ID to route matched messages to. |
match_rule | object | Match criteria. All specified (non-null) fields must match. |
match_rule fields:
| Field | Type | Default | Description |
|---|---|---|---|
channel | string or null | null | Channel type to match (e.g., "discord", "telegram", "slack"). |
account_id | string or null | null | Specific bot account ID within the channel (for multi-bot setups). |
peer_id | string or null | null | User/peer ID for DM routing. |
guild_id | string or null | null | Guild or server ID (Discord/Slack). |
roles | list of strings | [] | Role-based routing; user must have at least one of these roles. |
Specificity scoring (higher = matched first): peer_id (+8) > guild_id (+4) > roles (+2) = account_id (+2) > channel (+1).
[pairing]
Configures device pairing for the LibreFang mobile companion app and push notifications.
[pairing]
enabled = false
max_devices = 10
token_expiry_secs = 300
push_provider = "ntfy"
ntfy_url = "https://ntfy.sh"
ntfy_topic = "my-librefang-notifications"
| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Enable device pairing. |
max_devices | usize | 10 | Maximum number of paired devices. |
token_expiry_secs | u64 | 300 | Pairing token validity in seconds (5 minutes default). |
push_provider | string | "none" | Push notification provider: "none", "ntfy", or "gotify". |
ntfy_url | string or null | null | ntfy server URL (when push_provider = "ntfy"). |
ntfy_topic | string or null | null | ntfy topic for push notifications. |
[extensions]
Configures MCP server reconnection behavior and health monitoring.
[extensions]
auto_reconnect = true
reconnect_max_attempts = 10
reconnect_max_backoff_secs = 300
health_check_interval_secs = 60
| Field | Type | Default | Description |
|---|---|---|---|
auto_reconnect | bool | true | Automatically reconnect to MCP servers when they disconnect. |
reconnect_max_attempts | u32 | 10 | Maximum reconnect attempts before giving up permanently. |
reconnect_max_backoff_secs | u64 | 300 | Maximum backoff duration in seconds between reconnect attempts. |
health_check_interval_secs | u64 | 60 | Interval in seconds between health checks for connected extensions. |
[vault]
Configures the encrypted credential vault for storing sensitive secrets.
[vault]
enabled = true
# path = "~/.librefang/vault.enc"
| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | true | Enable the credential vault. Auto-detected if vault.enc already exists. |
path | path or null | null | Custom vault file path. Defaults to ~/.librefang/vault.enc. |
[webhook_triggers]
Enables external systems to trigger agent actions via authenticated HTTP webhooks at /hooks/wake and /hooks/agent.
[webhook_triggers]
enabled = true
token_env = "LIBREFANG_WEBHOOK_TOKEN"
max_payload_bytes = 65536
rate_limit_per_minute = 30
| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Enable webhook trigger endpoints. |
token_env | string | "LIBREFANG_WEBHOOK_TOKEN" | Env var name holding the bearer token (NOT the token itself). Token must be ≥ 32 characters. Required when enabled = true. |
max_payload_bytes | usize | 65536 | Maximum incoming payload size in bytes (64 KB default). |
rate_limit_per_minute | u32 | 30 | Maximum webhook requests per minute per source IP. |
[proxy]
Configures HTTP proxy for all outbound connections (LLM APIs, web search, MCP servers, etc.). Environment variables HTTP_PROXY, HTTPS_PROXY, and NO_PROXY are also respected as fallbacks.
[proxy]
http_proxy = "http://proxy.corp.example:8080"
https_proxy = "http://proxy.corp.example:8080"
no_proxy = "localhost,127.0.0.1,.internal.corp"
| Field | Type | Default | Description |
|---|---|---|---|
http_proxy | string or null | null | HTTP proxy URL. Falls back to HTTP_PROXY / http_proxy env var. Credentials in URLs are redacted in logs. |
https_proxy | string or null | null | HTTPS proxy URL. Falls back to HTTPS_PROXY / https_proxy env var. |
no_proxy | string or null | null | Comma-separated list of hosts/domains that bypass the proxy. Falls back to NO_PROXY / no_proxy env var. |
[[sidecar_channels]]
Sidecar channel adapters allow external processes (written in any language) to act as channel adapters. Communication uses newline-delimited JSON over stdin/stdout.
[[sidecar_channels]]
name = "my-custom-channel"
command = "python3"
args = ["adapters/my_adapter.py"]
channel_type = "custom_platform"
[sidecar_channels.env]
MY_API_TOKEN = "secret"
| Field | Type | Default | Description |
|---|---|---|---|
name | string | required | Display name for this adapter. |
command | string | required | Executable to run (e.g., "python3", "/usr/local/bin/my-adapter"). |
args | list of strings | [] | Arguments to pass to the command. |
env | map of string to string | {} | Extra environment variables to pass to the subprocess. |
channel_type | string or null | null | Channel type identifier. Defaults to Custom(<name>) if null. |
[session]
Configures automatic cleanup of idle or excess sessions.
[session]
retention_days = 30
max_sessions_per_agent = 100
cleanup_interval_hours = 24
| Field | Type | Default | Description |
|---|---|---|---|
retention_days | u32 | 0 | Maximum age in days for idle sessions before automatic cleanup. 0 = unlimited. |
max_sessions_per_agent | u32 | 0 | Maximum number of sessions per agent (oldest pruned first). 0 = unlimited. |
cleanup_interval_hours | u32 | 24 | How often the background cleanup job runs in hours. |
[terminal]
Configures access control for the interactive terminal WebSocket endpoint.
[terminal]
enabled = true
allow_remote = false
allowed_origins = ["https://dashboard.example.com"]
tmux_enabled = true
max_windows = 16
# tmux_binary_path = "/usr/local/bin/tmux"
| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | true | Master switch for the terminal feature. When false, the terminal WebSocket endpoint is disabled entirely. |
allow_remote | bool | false | Allows access from remote or proxied connections. When no auth is configured, allow_unauthenticated_remote must also be true or the connection is refused. Default behavior is local-only access without auth. |
allow_unauthenticated_remote | bool | false | Hard foot-gun guard. Must be explicitly set to true to expose an unauthenticated shell over the network when allow_remote = true and no auth is configured. Otherwise such connections are refused even if allow_remote = true. |
allowed_origins | list of strings | [] | Additional browser Origin values allowed for terminal WebSocket connections beyond localhost. Use this when the dashboard is served from a custom domain. [*] allows any HTTP/HTTPS origin and should only be used intentionally. |
require_proxy_headers | bool | false | When true, loopback connections without proxy headers (X-Forwarded-For, X-Real-IP) are rejected. Enable only when running behind a reverse proxy that injects these headers. (Old name: trust_proxy_headers, still accepted as alias.) |
tmux_enabled | bool | true | Enable tmux-backed multi-window terminal. Only effective when the tmux binary is available on the system. |
max_windows | u32 | 16 | Maximum number of tmux windows that may exist simultaneously. Guards against resource exhaustion. |
tmux_binary_path | string or null | null | Explicit path to the tmux binary. If null, resolved via PATH. |
Notes:
- Missing
Originis allowed for non-browser clients. allow_remote = truedoes not disable auth; if API keys or dashboard credentials are configured, remote clients still need valid auth.- Prefer explicit HTTPS origins over
"*"for browser access. - The
ws_terminal_messages_per_minuterate limit (default: 3600) in the[rate_limit]section controls per-connection WebSocket message throughput for interactive terminal sessions.
[queue]
Configures the agent command queue, including depth limits, TTL, and per-lane concurrency.
[queue]
max_depth_per_agent = 100
max_depth_global = 1000
task_ttl_secs = 3600
[queue.concurrency]
main_lane = 3
cron_lane = 2
subagent_lane = 3
[queue] fields:
| Field | Type | Default | Description |
|---|---|---|---|
max_depth_per_agent | u32 | 0 | Maximum queued tasks per agent. New tasks are rejected when full. 0 = unlimited. |
max_depth_global | u32 | 0 | Maximum total queued tasks across all agents. 0 = unlimited. |
task_ttl_secs | u64 | 3600 | Unprocessed tasks expire after this many seconds. 0 = unlimited. |
[queue.concurrency] fields:
| Field | Type | Default | Description |
|---|---|---|---|
main_lane | usize | 3 | Concurrent user message tasks. |
cron_lane | usize | 2 | Concurrent scheduled cron job tasks. |
subagent_lane | usize | 3 | Concurrent subagent invocation tasks. |
[external_auth]
Configures OAuth2/OIDC external authentication, allowing users to log in via identity providers like Google, GitHub, Okta, Auth0, or Keycloak.
[external_auth]
enabled = true
issuer_url = "https://accounts.google.com"
client_id = "your-client-id.apps.googleusercontent.com"
client_secret_env = "LIBREFANG_OAUTH_CLIENT_SECRET"
redirect_url = "http://127.0.0.1:4545/api/auth/callback"
scopes = ["openid", "profile", "email"]
allowed_domains = ["example.com"]
session_ttl_secs = 86400
| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Enable external authentication. |
issuer_url | string | "" | OIDC issuer URL for provider discovery at {issuer_url}/.well-known/openid-configuration. |
client_id | string | "" | OAuth2 client ID registered with the identity provider. |
client_secret_env | string | "LIBREFANG_OAUTH_CLIENT_SECRET" | Env var name holding the OAuth2 client secret. |
redirect_url | string | "http://127.0.0.1:4545/api/auth/callback" | OAuth2 authorization code flow callback URL. |
scopes | list of strings | ["openid","profile","email"] | OAuth2 scopes to request. |
allowed_domains | list of strings | [] | Restrict login to these email domains. Empty = allow all. |
audience | string | "" | JWT audience claim to validate. Defaults to client_id if empty. |
session_ttl_secs | u64 | 86400 | Session token lifetime in seconds (24 hours default). |
providers | list of objects | [] | Multiple OIDC/OAuth2 providers. When configured, takes precedence over the single-provider fields above. |
For multi-provider setups, use [[external_auth.providers]] with fields: id, display_name, issuer_url, auth_url, token_url, userinfo_url, jwks_uri, client_id, client_secret_env, redirect_url, scopes, allowed_domains, audience.
[vertex_ai]
Configures Google Cloud Vertex AI as an LLM provider.
[vertex_ai]
project_id = "my-gcp-project"
region = "us-central1"
credentials_path = "/path/to/service-account.json"
Credentials are resolved in this order:
credentials_pathin config (JSON string or file path)VERTEX_AI_SERVICE_ACCOUNT_JSONenv varGOOGLE_APPLICATION_CREDENTIALSenv var (file path)gcloud auth print-access-tokenCLI fallback
| Field | Type | Default | Description |
|---|---|---|---|
project_id | string or null | null | GCP project ID. Falls back to VERTEX_AI_PROJECT_ID, GOOGLE_CLOUD_PROJECT, or the project_id field in the service account JSON. |
region | string or null | null | GCP region for the Vertex AI endpoint. Falls back to VERTEX_AI_REGION or GOOGLE_CLOUD_REGION env var. Default: "us-central1". |
credentials_path | string or null | null | Path to a GCP service account JSON key file, or the raw JSON string. |
[oauth]
Configures OAuth client IDs for PKCE (Proof Key for Code Exchange) flows used by the dashboard.
[oauth]
google_client_id = "your-google-client-id.apps.googleusercontent.com"
github_client_id = "your-github-app-client-id"
microsoft_client_id = "your-azure-app-client-id"
slack_client_id = "your-slack-app-client-id"
| Field | Type | Default | Description |
|---|---|---|---|
google_client_id | string or null | null | Google OAuth2 client ID for PKCE flow. |
github_client_id | string or null | null | GitHub OAuth app client ID for PKCE flow. |
microsoft_client_id | string or null | null | Microsoft (Entra ID / Azure AD) OAuth application client ID. |
slack_client_id | string or null | null | Slack OAuth app client ID. |
[auth_profiles]
Configures multiple API key profiles per provider to enable key rotation when one key is rate-limited or exhausted.
[auth_profiles]
anthropic = [
{name = "primary", api_key_env = "ANTHROPIC_API_KEY_1", priority = 0},
{name = "secondary", api_key_env = "ANTHROPIC_API_KEY_2", priority = 1},
]
openai = [
{name = "main", api_key_env = "OPENAI_API_KEY", priority = 0},
]
The value is a map from provider name to a list of AuthProfile objects:
| Field | Type | Default | Description |
|---|---|---|---|
name | string | required | Profile name (e.g., "primary", "secondary"). |
api_key_env | string | required | Env var name holding the API key for this profile. |
priority | u32 | 0 | Priority for key selection. Lower value = preferred. |
[tool_policy]
Configures global tool access rules, groups, and recursion depth limits. References the ToolPolicy type.
[tool_policy]
subagent_max_depth = 10
subagent_max_concurrent = 5
[[tool_policy.global_rules]]
pattern = "shell_*"
effect = "deny"
[[tool_policy.groups]]
name = "web_tools"
tools = ["web_search", "web_fetch"]
| Field | Type | Default | Description |
|---|---|---|---|
agent_rules | list of ToolPolicyRule | [] | Per-agent tool rules (highest priority, checked first). |
global_rules | list of ToolPolicyRule | [] | Global tool rules applied to all agents (checked after agent rules). |
groups | list of ToolGroup | [] | Named tool groups for reuse in rules. |
subagent_max_depth | u32 | 10 | Maximum subagent spawning depth. |
subagent_max_concurrent | u32 | 5 | Maximum concurrent subagents. |
ToolPolicyRule fields:
| Field | Type | Description |
|---|---|---|
pattern | string | Glob pattern to match tool names (e.g., "shell_*", "web_*", "mcp_github_*"). |
effect | string | "allow" or "deny". Deny-wins: if any deny rule matches, the tool is blocked regardless of allow rules. |
ToolGroup fields:
| Field | Type | Description |
|---|---|---|
name | string | Group name (e.g., "web_tools", "code_tools"). |
tools | list of strings | Tool name patterns included in this group. |
[tool_timeouts]
Configures per-tool execution timeouts. The global default timeout applies to all tools unless overridden by exact name or glob pattern.
# Global default timeout for all tools (seconds)
tool_timeout_secs = 300
# Per-tool timeout overrides (exact match or glob pattern)
[tool_timeouts]
agent_send = 600
agent_spawn = 600
"mcp_browser_*" = 900
shell_exec = 300
| Field | Type | Default | Description |
|---|---|---|---|
tool_timeout_secs | u64 | 300 | Global timeout in seconds for all tool executions. Increase for browser automation or long-running builds. |
tool_timeouts | map of string→u64 | {} | Per-tool timeout overrides. Keys are exact tool names or glob patterns (e.g., "mcp_browser_*"). Exact matches take priority over glob patterns; among globs, the longest matching pattern wins (most specific first). Falls back to tool_timeout_secs when no entry matches. |
Resolution order:
- Exact tool name match in
tool_timeouts - Longest matching glob pattern in
tool_timeouts - Global
tool_timeout_secs
[proactive_memory]
Configures proactive memory extraction (mem0-style automatic memory management). References the ProactiveMemoryConfig type.
[proactive_memory]
enabled = true
auto_memorize = true
auto_retrieve = true
max_retrieve = 10
extraction_threshold = 0.7
# extraction_model = "gpt-4o-mini" # uses default provider
# extraction_model = "anthropic/claude-haiku-4" # targets a specific provider
# extraction_model = "anthropic:claude-haiku-4" # colon form also works
extract_categories = ["user_preference", "important_fact", "task_context", "relationship"]
session_ttl_hours = 24
duplicate_threshold = 0.5
confidence_decay_rate = 0.01
max_memories_per_agent = 1000
| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | true | Master toggle — when false, the entire proactive memory subsystem is disabled. |
auto_memorize | bool | true | Automatically extract and store memories after each agent execution. |
auto_retrieve | bool | true | Automatically retrieve relevant memories before each agent execution. |
max_retrieve | usize | 10 | Maximum number of memories to retrieve per query. |
extraction_threshold | f32 | 0.7 | Confidence threshold for near-duplicate detection (0.0–1.0). |
extraction_model | string or null | null | LLM model for extraction. Supports provider/model (e.g. "anthropic/claude-haiku-4"), provider:model, or bare model name (uses default provider). If null, uses rule-based extraction. There is no separate extraction_provider field. |
extract_categories | list of strings | ["user_preference", "important_fact", "task_context", "relationship"] | Categories to extract from conversations. |
session_ttl_hours | u32 | 24 | Session memory TTL in hours. Memories older than this are cleaned up before each agent execution. |
duplicate_threshold | f32 | 0.5 | Similarity threshold for duplicate detection (0.0–1.0). Uses vector cosine similarity when embeddings are available, otherwise falls back to Jaccard word overlap. |
confidence_decay_rate | f64 | 0.01 | Confidence decay rate per day. Follows exponential decay: conf × e^(−rate × days). Default of 0.01 takes ~70 days to halve. |
max_memories_per_agent | usize | 1000 | Maximum memories per agent. When exceeded, oldest/lowest-confidence entries are evicted. 0 = no cap. |
Per-agent overrides
Each agent's agent.toml may carry a [proactive_memory] block that overrides selected fields of the kernel-global config above for that agent only. Unset fields inherit the global. All four override fields are optional:
# agent.toml
[proactive_memory]
# enabled = false # opt this agent out of proactive memory entirely
# auto_memorize = false # skip the after-turn extraction call
# auto_retrieve = false # skip the before-turn retrieval injection
# extraction_model = "openai/gpt-4o-mini" # use a different extractor model for this agent
| Field | Notes |
|---|---|
enabled | Some(false) disables proactive memory for this agent regardless of the global. Some(true) is documented but not load-bearing — the global enabled = false short-circuits store construction at boot. |
auto_memorize | Some(false) skips the after-turn extraction. Useful for cron sub-agents whose tool-only turns produce extraction noise. |
auto_retrieve | Some(false) skips the before-turn memory injection. Useful when an agent's prompt is already memory-heavy. |
extraction_model | (#5475) Per-agent extraction model. Same shape as the global extraction_model (provider/model, provider:model, or bare). Lets multi-provider deployments give each agent the extractor its provider supports natively. Driver is reused from the kernel-global extraction driver — full per-agent driver switching is a follow-up. |
[context_engine]
Configures the pluggable context assembly engine that controls how agent memory is recalled and assembled into prompts.
[context_engine]
engine = "default"
# plugin = "qdrant-recall" # resolves to ~/.librefang/plugins/qdrant-recall/
[context_engine.hooks]
# ingest = "~/.librefang/scripts/my_recall.py"
# after_turn = "~/.librefang/scripts/my_indexer.py"
# runtime = "python" # python (default) | native | v | node | deno | go | ruby | bash | bun | php | lua
[[context_engine.plugin_registries]]
name = "Official"
github_repo = "librefang/librefang-registry"
| Field | Type | Default | Description |
|---|---|---|---|
engine | string | "default" | Built-in engine name. Currently only "default" is supported. |
plugin | string or null | null | Plugin name. Resolves to ~/.librefang/plugins/<name>/plugin.toml. Takes precedence over manual hooks if set. |
hooks.ingest | string or null | null | Script path for the ingest hook (called on new user message). |
hooks.after_turn | string or null | null | Script path for the after_turn hook (called after each completed turn). |
hooks.runtime | string or null | "python" | Which launcher runs the hook scripts. Supported: python, native (exec a pre-compiled binary), v, node, deno, go, ruby, bash, bun, php, lua. Unknown values fall back to python with a warning. |
plugin_registries | list of objects | Official registry | Plugin registries (GitHub owner/repo) to browse for installable plugins. |
Hooks speak a language-agnostic JSON-over-stdin/stdout protocol — pick the runtime that matches your script:
# Go plugin
[context_engine.hooks]
ingest = "~/.librefang/plugins/my-go-recall/hooks/ingest.go"
runtime = "go"
# V plugin compiled to a native binary
[context_engine.hooks]
ingest = "~/.librefang/plugins/fast-recall/hooks/ingest"
runtime = "native"
Checking runtime availability
Call GET /api/plugins/doctor to probe every runtime on the host and see which ones are missing — the response includes each runtime's detected version and an install hint. It also cross-references installed plugins and flags any whose declared runtime is not usable on this host.
Runtimes in the official Docker image
The official image (node:lts-bookworm-slim base) ships with python, node, bash, native ready to go. Other runtimes are not bundled to keep the image small — if you need them, extend the image with one of these snippets:
# V
RUN git clone --depth 1 https://github.com/vlang/v /opt/v \
&& make -C /opt/v && ln -s /opt/v/v /usr/local/bin/v
# Deno
RUN curl -fsSL https://deno.land/install.sh | DENO_INSTALL=/usr/local sh
# Go
RUN curl -fsSL https://go.dev/dl/go1.23.0.linux-amd64.tar.gz | tar -C /usr/local -xz \
&& ln -s /usr/local/go/bin/go /usr/local/bin/go
# Ruby
RUN apt-get update && apt-get install -y --no-install-recommends ruby && rm -rf /var/lib/apt/lists/*
# Bun
RUN curl -fsSL https://bun.sh/install | BUN_INSTALL=/usr/local bash
# PHP
RUN apt-get update && apt-get install -y --no-install-recommends php-cli && rm -rf /var/lib/apt/lists/*
# Lua (with dkjson for the Lua scaffold template)
RUN apt-get update && apt-get install -y --no-install-recommends lua5.4 lua-dkjson && rm -rf /var/lib/apt/lists/*
If a plugin's declared runtime is not on PATH, the hook returns a LauncherNotFound error with the install hint — existing plugins keep running, only the misconfigured one is affected.
[audit]
Configures audit log retention.
[audit]
retention_days = 90
| Field | Type | Default | Description |
|---|---|---|---|
retention_days | u32 | 90 | Number of days to retain audit log entries. 0 = unlimited retention. |
[health_check]
Configures periodic health checks for LLM providers.
[health_check]
health_check_interval_secs = 60
| Field | Type | Default | Description |
|---|---|---|---|
health_check_interval_secs | u64 | 60 | Interval in seconds between provider health checks. |
[plugins]
Configures additional plugin registries to search for installable context engine plugins.
[plugins]
plugin_registries = ["acme-corp/librefang-plugins"]
| Field | Type | Default | Description |
|---|---|---|---|
plugin_registries | list of strings | [] | Additional GitHub owner/repo plugin registries. Merged with context_engine.plugin_registries. |
[prompt_intelligence]
Configures prompt versioning and A/B experiment support. When enabled, LibreFang automatically tracks prompt version history and supports running A/B experiments to compare prompt variants. See the Prompt Intelligence guide for full documentation.
[prompt_intelligence]
enabled = false
hash_prompts = true
max_versions_per_agent = 50
| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Master toggle. When false, no prompt versions are tracked and experiments are skipped. |
hash_prompts | bool | true | Compute content hashes for prompt versions. |
max_versions_per_agent | u32 | 50 | Maximum prompt versions per agent. Oldest inactive versions are pruned when exceeded. |
Environment Variables
Complete table of all environment variables referenced by the configuration. None of these are read by the config file itself -- they are read at runtime by the kernel and channel adapters.
LLM Provider Keys
| Variable | Used By | Description |
|---|---|---|
ANTHROPIC_API_KEY | [default_model] | Anthropic API key (Claude models). |
GEMINI_API_KEY | Gemini driver | Google Gemini API key. Alias: GOOGLE_API_KEY. |
OPENAI_API_KEY | OpenAI-compat driver | OpenAI API key. |
GROQ_API_KEY | Groq provider | Groq API key (fast Llama inference). |
DEEPSEEK_API_KEY | DeepSeek provider | DeepSeek API key. |
PERPLEXITY_API_KEY | Perplexity provider / web search | Perplexity API key. |
OPENROUTER_API_KEY | OpenRouter provider | OpenRouter API key. |
TOGETHER_API_KEY | Together AI provider | Together AI API key. |
MISTRAL_API_KEY | Mistral provider | Mistral AI API key. |
FIREWORKS_API_KEY | Fireworks provider | Fireworks AI API key. |
COHERE_API_KEY | Cohere provider | Cohere API key. |
AI21_API_KEY | AI21 provider | AI21 Labs API key. |
CEREBRAS_API_KEY | Cerebras provider | Cerebras API key. |
SAMBANOVA_API_KEY | SambaNova provider | SambaNova API key. |
HUGGINGFACE_API_KEY | Hugging Face provider | Hugging Face Inference API key. |
XAI_API_KEY | xAI provider | xAI (Grok) API key. |
REPLICATE_API_KEY | Replicate provider | Replicate API key. |
Web Search Keys
| Variable | Used By | Description |
|---|---|---|
BRAVE_API_KEY | [web.brave] | Brave Search API key. |
JINA_API_KEY | [web.jina] | Jina AI Search API key. |
TAVILY_API_KEY | [web.tavily] | Tavily Search API key. |
PERPLEXITY_API_KEY | [web.perplexity] | Perplexity Search API key (shared with LLM provider). |
Channel Tokens
| Variable | Channel | Description |
|---|---|---|
TELEGRAM_BOT_TOKEN | Telegram | Bot API token from @BotFather. |
DISCORD_BOT_TOKEN | Discord | Discord bot token. |
SLACK_APP_TOKEN | Slack | Slack app-level token (xapp-) for Socket Mode. |
SLACK_BOT_TOKEN | Slack | Slack bot token (xoxb-) for REST API. |
WHATSAPP_ACCESS_TOKEN | WhatsApp Cloud API access token. | |
WHATSAPP_VERIFY_TOKEN | Webhook verification token. | |
MATRIX_ACCESS_TOKEN | Matrix | Matrix homeserver access token. |
EMAIL_PASSWORD | Email account password or app password. | |
TEAMS_APP_PASSWORD | Teams | Azure Bot Framework app password. |
MATTERMOST_TOKEN | Mattermost (sidecar) | Mattermost bot token (sidecar adapter — set in ~/.librefang/secrets.env). |
TWITCH_OAUTH_TOKEN | Twitch | Twitch OAuth token. |
ROCKETCHAT_TOKEN | Rocket.Chat (sidecar) | Rocket.Chat personal access token (sidecar adapter — set in ~/.librefang/secrets.env). |
ZULIP_API_KEY | Zulip (sidecar) | Zulip bot API key (sidecar adapter — set in ~/.librefang/secrets.env). |
XMPP_PASSWORD | XMPP | XMPP account password. |
GOOGLE_CHAT_SERVICE_ACCOUNT | Google Chat | Service account JSON key. |
LINE_CHANNEL_SECRET | LINE (sidecar) | LINE channel secret (sidecar adapter — set in ~/.librefang/secrets.env). |
LINE_CHANNEL_ACCESS_TOKEN | LINE (sidecar) | LINE channel access token (librefang.sidecar.adapters.line). |
REDDIT_CLIENT_SECRET | Reddit app client secret. | |
REDDIT_PASSWORD | Reddit bot account password. | |
MASTODON_ACCESS_TOKEN | Mastodon | Mastodon access token. |
BLUESKY_APP_PASSWORD | Bluesky | Bluesky app password. |
FEISHU_APP_SECRET | Feishu | Feishu/Lark app secret. |
REVOLT_BOT_TOKEN | Revolt | Revolt bot token. |
NEXTCLOUD_TOKEN | Nextcloud (sidecar) | Nextcloud Talk app password / OAuth bearer (librefang.sidecar.adapters.nextcloud). |
GUILDED_BOT_TOKEN | Guilded | Guilded bot token. |
KEYBASE_PAPERKEY | Keybase | Keybase paper key. |
THREEMA_SECRET | Threema | Threema Gateway API secret. |
WEBEX_BOT_TOKEN | Webex (sidecar) | Webex bot Bearer token (librefang.sidecar.adapters.webex). |
PUMBLE_BOT_TOKEN | Pumble | Pumble bot token. |
FLOCK_BOT_TOKEN | Flock | Flock bot token. |
TWIST_TOKEN | Twist | Twist API token. |
MUMBLE_PASSWORD | Mumble | Mumble server password. |
DINGTALK_APP_KEY | DingTalk | DingTalk App Key / Client ID (sidecar — librefang.sidecar.adapters.dingtalk, stream mode only). |
DINGTALK_APP_SECRET | DingTalk | DingTalk App Secret / Client Secret (sidecar — librefang.sidecar.adapters.dingtalk, stream mode only). |
GITTER_TOKEN | Gitter | Gitter auth token. |
NTFY_TOKEN | ntfy | ntfy auth token (optional for public topics). |
GOTIFY_APP_TOKEN | Gotify | Gotify app token (sending). |
GOTIFY_CLIENT_TOKEN | Gotify | Gotify client token (receiving). |
WEBHOOK_SECRET | Webhook | HMAC signing secret for webhook verification. |
Validation
KernelConfig::validate() runs at boot time and returns a list of warnings (non-fatal). The kernel still starts, but logs each warning.
What is validated
For every enabled channel (i.e., its config section is present in the TOML), the validator checks that the corresponding environment variable(s) are set and non-empty:
| Channel | Env vars checked |
|---|---|
| Telegram | bot_token_env |
| Discord | bot_token_env |
| Slack | app_token_env, bot_token_env (both checked) |
access_token_env | |
| Matrix | access_token_env |
password_env | |
| Teams | app_password_env |
| Mattermost | token_env |
| Google Chat | service_account_env |
| XMPP | password_env |
client_secret_env | |
| Mastodon | access_token_env |
| Bluesky | app_password_env |
| Feishu | app_secret_env |
| Revolt | bot_token_env |
| Guilded | bot_token_env |
| Keybase | paperkey_env |
| Threema | secret_env |
| Pumble | bot_token_env |
| Flock | bot_token_env |
| Twist | token_env |
| Mumble | password_env |
| DingTalk | sidecar — validation handled by librefang.sidecar.adapters.dingtalk's DINGTALK_APP_KEY / DINGTALK_APP_SECRET env check |
| Gitter | token_env |
| ntfy | token_env (only if token_env is non-empty; public topics are OK without auth) |
| Webhook | secret_env |
For web search providers, the validator checks:
| Provider | Env var checked |
|---|---|
brave | web.brave.api_key_env |
jina | web.jina.api_key_env |
tavily | web.tavily.api_key_env |
perplexity | web.perplexity.api_key_env |
duckduckgo | (no check -- no API key needed) |
auto | (no check -- cascading fallback handles missing keys) |
What is NOT validated
- The
api_key_envin[default_model]is not checked byvalidate(). Missing LLM keys cause errors at runtime when the driver is first used. - The
shared_secretin[network]is not validated againstnetwork_enabled. If networking is enabled with an empty secret, authentication will fail at connection time. - MCP server configurations are not validated at config load time. Connection errors surface during the background MCP connect phase.
- Agent manifests have their own separate validation.
Related Configuration
Some subsystems have their own configuration that is not part of config.toml but is worth noting:
Session Compaction
Configured via the [compaction] section in config.toml:
[compaction]
threshold_messages = 30 # Compact when message count exceeds this
keep_recent = 10 # Recent messages preserved verbatim
max_summary_tokens = 1024 # Max tokens for the LLM summary
token_threshold_ratio = 0.7 # Trigger at this fraction of context window
max_chunk_chars = 80000 # Max chars per summarization chunk
max_retries = 3 # Max LLM summarization retries
| Field | Default | Description |
|---|---|---|
threshold_messages | 30 | Compact when session message count exceeds this. |
keep_recent | 10 | Number of recent messages preserved verbatim after compaction. |
max_summary_tokens | 1024 | Maximum tokens for the LLM summary of compacted messages. |
token_threshold_ratio | 0.7 | Trigger compaction when estimated tokens exceed this fraction of the context window. |
max_chunk_chars | 80000 | Maximum characters per summarization chunk. |
max_retries | 3 | Maximum retries for LLM summarization. |
WASM Sandbox (runtime)
Configured internally via SandboxConfig (not currently exposed in config.toml):
| Field | Default | Description |
|---|---|---|
fuel_limit | 1000000 | Maximum CPU instruction budget. 0 = unlimited. |
max_memory_bytes | 16777216 (16 MB) | Maximum WASM linear memory. |
timeout_secs | null (30s fallback) | Wall-clock timeout for epoch-based interruption. |
Model Routing (per-agent manifest)
Configured in agent manifests via ModelRoutingConfig:
| Field | Default | Description |
|---|---|---|
simple_model | "claude-haiku-4-5-20251001" | Model for simple queries. |
medium_model | "claude-sonnet-4-20250514" | Model for medium-complexity queries. |
complex_model | "claude-sonnet-4-20250514" | Model for complex queries. |
simple_threshold | 100 | Token count below which a query is classified as simple. |
complex_threshold | 500 | Token count above which a query is classified as complex. |
Autonomous Guardrails (per-agent manifest)
Configured in agent manifests via AutonomousConfig:
| Field | Default | Description |
|---|---|---|
quiet_hours | null | Cron expression for quiet hours (agent pauses during this window). |
max_iterations | 50 | Maximum tool-use iterations per invocation. |
max_restarts | 10 | Maximum automatic restarts before permanent stop. |
heartbeat_interval_secs | 30 | Seconds between heartbeat health checks. |
heartbeat_channel | null | Channel to send heartbeat status to (e.g., "telegram"). |