LibreFang Configuration Reference

Complete reference for config.toml, covering every configurable field in the LibreFang Agent OS.


Table of Contents


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.) are Option<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_env and bot_token_env hold 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 include arrays (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]).

FieldTypeDefaultDescription
home_dirpath~/.librefangLibreFang home directory. Stores config, agents, skills.
data_dirpath~/.librefang/dataDirectory for SQLite databases and persistent data.
log_levelstring"info"Log verbosity. One of: trace, debug, info, warn, error.
api_listenstring"127.0.0.1:4545"Bind address for the HTTP/WebSocket/SSE API server. Alias: listen_addr.
network_enabledboolfalseEnable the OFP peer-to-peer network layer.
api_keystring"" (empty)API authentication key. When set, all endpoints except /api/health require Authorization: Bearer <key>. Empty means unauthenticated (local development only).
cors_originlist of strings[]CORS allowed origins added to the allow list (in addition to localhost). E.g., ["https://dash.example.com"].
modestring"default"Kernel operating mode. See below.
languagestring"en"Language/locale code for CLI output and system messages.
usage_footerstring"full"Controls usage info appended to responses. See below.
prompt_cachingbooltrueEnable LLM provider prompt caching. Adds cache hints to system prompts (Anthropic: cache_control, OpenAI: automatic prefix caching).
stable_prefix_modeboolfalseWhen enabled, avoids volatile system-prompt additions (recalled memory, canonical context) that change every turn, improving provider-side prompt cache hit rates.
max_cron_jobsusize500Global maximum number of cron jobs across all agents.
workspaces_dirpath or nullnullRoot directory for agent workspaces. Defaults to ~/.librefang/workspaces. Contains agent working directories and the hands/ subdirectory for user custom hands.
includelist of strings[]Config file includes (relative paths). See Config Include Mechanism.
provider_urlsmap 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_keysmap 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_regionsmap 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_secsu64300Global timeout in seconds for all tool executions. Can be overridden per-tool via [tool_timeouts] section.
tool_timeoutsmap of string→u64{}Per-tool timeout overrides. Keys are exact tool names or glob patterns. See [tool_timeouts] section for details.

mode values:

ValueBehavior
stableConservative: no auto-updates, pinned models, frozen skill registry. Uses FallbackDriver.
defaultBalanced: standard operation.
devDeveloper: experimental features enabled.

usage_footer values:

ValueBehavior
offNo usage information shown.
tokensShow token counts only.
costShow estimated cost only.
fullShow 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"
FieldTypeDefaultDescription
providerstring"anthropic"Provider name. Supported: anthropic, gemini, openai, groq, openrouter, deepseek, together, mistral, fireworks, ollama, vllm, lmstudio, perplexity, cohere, ai21, cerebras, sambanova, huggingface, xai, replicate.
modelstring"claude-sonnet-4-20250514"Model identifier. Aliases like sonnet, haiku, gpt-4o, gemini-flash are resolved by the model catalog.
api_key_envstring"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_urlstring or nullnullOverride 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
FieldTypeDefaultDescription
sqlite_pathpath or nullnullExplicit path to the SQLite database file. When null, defaults to {data_dir}/librefang.db.
embedding_modelstring"all-MiniLM-L6-v2"Model name used for generating vector embeddings for semantic memory search.
embedding_providerstring or nullnullEmbedding provider (e.g., "openai", "ollama"). Auto-detected if null.
embedding_api_key_envstring or nullnullEnvironment variable name holding the API key for the embedding provider.
consolidation_thresholdu6410000Number of stored memories before automatic consolidation is triggered to merge and prune old entries.
consolidation_interval_hoursu6424How often memory consolidation runs (hours). 0 = disabled.
decay_ratef320.1Memory 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/
FieldTypeDefaultDescription
enabledboolfalseMaster toggle. When false, no dream fires regardless of per-agent opt-in.
min_hoursf6424.0Minimum hours since that agent's last consolidation before the next one fires.
min_sessionsu325Minimum sessions touched since that agent's last consolidation before the next one fires. Set to 0 to disable the session-count gate.
check_interval_secsu6486400Backstop scheduler cadence, in seconds. Primary trigger is the AgentLoopEnd hook; this only controls the fallback for agents that never turn.
timeout_secsu64600Timeout for a single dream invocation in seconds.
lock_dirstring""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"
FieldTypeDefaultDescription
listen_addresseslist of strings["/ip4/0.0.0.0/tcp/0"]libp2p multiaddresses to listen on. Port 0 means auto-assign.
bootstrap_peerslist of strings[]Multiaddresses of bootstrap peers for DHT discovery.
mdns_enabledbooltrueEnable mDNS for automatic local network peer discovery.
max_peersu3250Maximum number of simultaneously connected peers.
shared_secretstring"" (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
FieldTypeDefaultDescription
search_providerstring"auto"Which search engine to use. See values below.
cache_ttl_minutesu6415Cache duration for search/fetch results in minutes. 0 = caching disabled.
timeout_secsu6415HTTP timeout in seconds for all web search requests. Recommended: 15 for most providers, 30+ for Jina.

search_provider values:

ValueDescription
autoCascading fallback: tries Tavily, then Brave, then Jina, then Perplexity, then DuckDuckGo, based on which API keys are available.
braveBrave Search API. Requires BRAVE_API_KEY.
jinaJina AI search and grounding. Requires JINA_API_KEY.
tavilyTavily AI-native search. Requires TAVILY_API_KEY.
perplexityPerplexity AI search. Requires PERPLEXITY_API_KEY.
duckduckgoDuckDuckGo HTML scraping. No API key needed.

[web.brave]

[web.brave]
api_key_env = "BRAVE_API_KEY"
max_results = 5
country = ""
search_lang = ""
freshness = ""
FieldTypeDefaultDescription
api_key_envstring"BRAVE_API_KEY"Environment variable name holding the Brave Search API key.
max_resultsusize5Maximum number of search results to return.
countrystring""Country code for localized results (e.g., "US", "GB"). Empty = no filter.
search_langstring""Language code (e.g., "en", "fr"). Empty = no filter.
freshnessstring""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
FieldTypeDefaultDescription
api_key_envstring"TAVILY_API_KEY"Environment variable name holding the Tavily API key.
search_depthstring"basic"Search depth: "basic" for fast results, "advanced" for deeper analysis.
max_resultsusize5Maximum number of search results to return.
include_answerbooltrueWhether to include Tavily's AI-generated answer summary in results.

[web.jina]

[web.jina]
api_key_env = "JINA_API_KEY"
max_results = 5
FieldTypeDefaultDescription
api_key_envstring"JINA_API_KEY"Environment variable name holding the Jina AI API key.
max_resultsusize5Maximum number of search results to return.

[web.perplexity]

[web.perplexity]
api_key_env = "PERPLEXITY_API_KEY"
model = "sonar"
FieldTypeDefaultDescription
api_key_envstring"PERPLEXITY_API_KEY"Environment variable name holding the Perplexity API key.
modelstring"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
# ]
FieldTypeDefaultDescription
max_charsusize50000Maximum characters returned in fetched content. Content exceeding this is truncated.
max_response_bytesusize10485760 (10 MB)Maximum HTTP response body size in bytes.
timeout_secsu6430HTTP request timeout in seconds.
readabilitybooltrueEnable HTML-to-Markdown readability extraction. When true, fetched HTML is converted to clean Markdown.
ssrf_allowed_hostslist 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
FieldTypeDefaultDescription
image_descriptionbooltrueEnable automatic image description for incoming image attachments.
audio_transcriptionbooltrueEnable automatic audio transcription for incoming audio attachments.
video_descriptionboolfalseEnable video description. Disabled by default (expensive and slow).
max_concurrencyusize2Maximum number of concurrent media processing tasks.
image_providerstring or nullnullPreferred provider for image description. Auto-detected from available providers if null.
audio_providerstring or nullnullPreferred provider for audio transcription. Auto-detected from available providers if null.

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
FieldTypeDefaultDescription
enabledboolfalseEnable automatic link understanding. When true, URLs in messages are fetched and their content is summarized before the agent processes the message.
max_linksusize3Maximum number of links to process per message. Additional links are ignored.
max_content_bytesusize102400 (100 KB)Maximum content size to fetch per link in bytes. Content exceeding this is truncated.
timeout_secsu6410Per-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:

FieldTypeDefaultDescription
default_agentstring or nullnullAgent name to route messages to by default.
account_idstring or nullnullUnique identifier for this bot instance. Used for multi-bot routing via [[bindings]] match rules.
overridesobject(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

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"
FieldTypeDefaultDescription
namestringrequiredDisplay name for this MCP server. Tools are namespaced as mcp_{name}_{tool}.
timeout_secsu6430Request timeout in seconds.
envlist of strings[]Environment variable names to pass through to the subprocess (stdio transport only).

Transport variants (tagged union on type):

typeFieldsDescription
stdiocommand (string), args (list of strings, default [])Spawn a subprocess, communicate via JSON-RPC over stdin/stdout.
sseurl (string)Connect to an HTTP Server-Sent Events endpoint.
http_compatbase_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:

FieldTypeDescription
namestringHTTP header name (e.g., "Authorization").
valuestring or nullStatic header value.
value_envstring or nullEnv var name whose value is used as the header value (preferred for secrets).

http_compat tool config:

FieldTypeDefaultDescription
namestringrequiredTool name exposed to the LLM.
descriptionstring""Tool description shown to the LLM.
pathstringrequiredHTTP path (e.g., "/search").
methodstring"post"HTTP method: get, post, put, patch, delete.
request_modestring"json_body"How arguments are sent: json_body, query, none.
response_modestring"json"Response parsing: json, text.
input_schemaobject{"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"
FieldTypeDefaultDescription
enabledboolfalseWhether A2A protocol is enabled.
namestring"LibreFang Agent OS"Service-level display name shown in the well-known agent card.
descriptionstring""Service-level description shown in the well-known agent card.
listen_pathstring"/a2a"URL path prefix for A2A endpoints.
external_agentslist of objects[]External A2A agents to discover and interact with.

external_agents entries:

FieldTypeDescription
namestringDisplay name for the external agent.
urlstringAgent 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"
FieldTypeDefaultDescription
providerstring""Provider name (e.g., "ollama", "groq", "openai").
modelstring""Model identifier for this provider.
api_key_envstring""Env var name for the API key. Empty for local providers (ollama, vllm, lmstudio).
base_urlstring or nullnullBase 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"
FieldTypeDefaultDescription
namestringrequiredUser display name.
rolestring"user"User role in the RBAC hierarchy.
channel_bindingsmap of string to string{}Maps channel platform names to platform-specific user IDs, binding this user identity across channels.
api_key_hashstring or nullnullSHA256 hash of the user's personal API key for authenticated API access.

Role hierarchy (highest to lowest privilege):

RoleDescription
ownerFull administrative access. Can manage all agents, users, and configuration.
adminCan manage agents and most settings. Cannot modify owner accounts.
userCan interact with agents. Limited management capabilities.
viewerRead-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 = []
FieldTypeDefaultDescription
modelstring or nullnullModel override for this channel. Uses the agent's default model when null.
system_promptstring or nullnullSystem prompt override for this channel.
dm_policystring"respond"How the bot handles direct messages. See below.
group_policystring"mention_only"How the bot handles group messages. See below.
rate_limit_per_minuteu320Global rate limit for this channel (messages per minute). 0 = unlimited.
rate_limit_per_useru320Maximum messages per user per minute. 0 = unlimited.
threadingboolfalseEnable thread replies (where supported by the platform).
output_formatstring or nullnullOverride output formatting. See below.
usage_footerstring or nullnullOverride usage footer mode for this channel. Values: off, tokens, cost, full.
typing_modestring or nullnullTyping indicator behavior. See below. Defaults to instant.
disable_commandsboolfalseDisable all built-in slash commands on this channel. Blocked commands are forwarded to the agent as plain text.
allowed_commandslist of strings[]Whitelist of command names (no leading /). When non-empty, only these are allowed; others are forwarded to the agent.
blocked_commandslist of strings[]Blacklist of command names (no leading /). Applied when allowed_commands is empty.

dm_policy values:

ValueDescription
respondRespond to all direct messages (default).
allowed_onlyOnly respond to DMs from users in the allowed list.
ignoreIgnore all direct messages.

group_policy values:

ValueDescription
allRespond to all messages in group chats.
mention_onlyOnly respond when the bot is @mentioned (default).
commands_onlyOnly respond to slash commands.
ignoreIgnore all group messages.

output_format values:

ValueDescription
markdownStandard Markdown (default).
telegram_htmlTelegram HTML subset (<b>, <i>, <code>, etc.).
slack_mrkdwnSlack mrkdwn format (*bold*, _italic_, `code`).
plain_textNo formatting markup.

typing_mode values:

ValueDescription
instantSend typing indicator immediately on message receipt (default).
messageSend typing indicator only when the first text delta arrives from the LLM.
thinkingSend typing indicator only during LLM reasoning/thinking phase.
neverNever 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"
FieldTypeDefaultDescription
headlessbooltrueRun browser in headless mode (no visible window).
viewport_widthu321280Browser viewport width in pixels.
viewport_heightu32720Browser viewport height in pixels.
timeout_secsu6430Per-action timeout in seconds.
idle_timeout_secsu64300Auto-close browser session after this many seconds of inactivity.
max_sessionsusize5Maximum concurrent browser sessions.
chromium_pathstring or nullnullPath to the Chromium/Chrome binary. Auto-detected if null.

[reload]

Controls automatic config file watching and hot-reloading.

[reload]
mode = "hybrid"
debounce_ms = 500
FieldTypeDefaultDescription
modestring"hybrid"Reload mode. See below.
debounce_msu64500Debounce window in milliseconds before reloading after a file change is detected.

mode values:

ValueDescription
offNo automatic reloading. Changes require a manual restart.
restartFull daemon restart on any config change.
hotHot-reload safe sections only (channels, skills, heartbeat).
hybridHot-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
FieldTypeDefaultDescription
modestring"allowlist"Security mode. See below.
safe_binslist 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_commandslist of strings[]Additional commands permitted when mode = "allowlist".
timeout_secsu6430Maximum wall-clock execution time per command in seconds.
max_output_bytesusize102400Maximum combined stdout+stderr output size in bytes (100 KB default).
no_output_timeout_secsu6430Kill processes that produce no output for this many seconds. 0 = disabled.

mode values:

ValueAliasesDescription
denynone, disabledBlock all shell execution.
allowlistrestrictedOnly allow commands in safe_bins or allowed_commands (default).
fullallow, all, unrestrictedAllow 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
FieldTypeDefaultDescription
require_approvallist of strings["shell_exec"]List of tool names that pause execution and wait for human approval before proceeding.
timeout_secsu6460Timeout for approval requests in seconds.
auto_approve_autonomousboolfalseAuto-approve tools when agent is in autonomous mode.
auto_approveboolfalseAuto-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
FieldTypeDefaultDescription
max_hourly_usdf640.0Maximum total LLM cost in USD per hour across all agents. 0.0 = unlimited.
max_daily_usdf640.0Maximum total LLM cost in USD per day across all agents. 0.0 = unlimited.
max_monthly_usdf640.0Maximum total LLM cost in USD per month across all agents. 0.0 = unlimited.
alert_thresholdf640.8Warning 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_houru640Global 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
FieldTypeDefaultDescription
budget_tokensu3210000Maximum tokens allocated for the thinking/reasoning phase.
stream_thinkingboolfalseWhether 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:

FieldTypeDefaultDescription
enabledboolfalseEnable TTS synthesis.
providerstring or nullnullDefault TTS provider: "openai" or "elevenlabs".
max_text_lengthusize4096Maximum text length in characters for a single TTS request.
timeout_secsu6430Request timeout per TTS call in seconds.

[tts.openai] fields:

FieldTypeDefaultDescription
voicestring"alloy"Voice name. Options: alloy, echo, fable, onyx, nova, shimmer.
modelstring"tts-1"TTS model: "tts-1" (fast) or "tts-1-hd" (high quality).
formatstring"mp3"Output format: mp3, opus, aac, flac.
speedf321.0Speech speed multiplier (0.25 to 4.0).

[tts.elevenlabs] fields:

FieldTypeDefaultDescription
voice_idstring"21m00Tcm4TlvDq8ikWAM"ElevenLabs voice ID (default: Rachel).
model_idstring"eleven_monolingual_v1"ElevenLabs model ID.
stabilityf320.5Voice stability (0.0–1.0). Higher = more consistent, less expressive.
similarity_boostf320.75Voice 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 = []
FieldTypeDefaultDescription
enabledboolfalseEnable Docker sandbox for code execution.
imagestring"python:3.12-slim"Docker image to use for the sandbox container.
container_prefixstring"librefang-sandbox"Prefix for container names.
workdirstring"/workspace"Working directory inside the container.
networkstring"none"Network mode: "none" (isolated), "bridge", or a custom network name.
memory_limitstring"512m"Memory limit (e.g., "256m", "1g").
cpu_limitf641.0CPU limit (e.g., 0.5, 1.0, 2.0).
timeout_secsu6460Maximum execution time per command in seconds.
read_only_rootbooltrueMount the root filesystem as read-only.
modestring"off"Activation mode. See below.
scopestring"session"Container lifecycle scope. See below.
reuse_cool_secsu64300Cooldown in seconds before a released container can be reused.
idle_timeout_secsu6486400Destroy containers after this many seconds of inactivity (24 hours default).
max_age_secsu64604800Maximum container age before forced destruction (7 days default).
blocked_mountslist of strings[]Host paths blocked from bind mounting into containers.
cap_addlist of strings[]Linux capabilities to add to the container (e.g., ["NET_ADMIN"]). Use with caution.
tmpfslist of strings["/tmp:size=64m"]tmpfs mounts inside the container. Each entry is "path:options" (e.g., "/tmp:size=128m").
pids_limitu32100Maximum number of processes inside the container. Prevents fork bombs.

mode values:

ValueDescription
offDocker sandbox disabled (default).
non_mainUse Docker only for non-main (sub) agents.
allUse Docker for all agents.

scope values:

ValueDescription
sessionOne container per session, destroyed when the session ends (default).
agentOne container per agent, reused across sessions.
sharedShared 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 = []
FieldTypeDefaultDescription
enabledboolfalseEnable the canvas tool.
max_html_bytesusize524288Maximum HTML payload size in bytes (512 KB default).
allowed_tagslist 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"]
FieldTypeDefaultDescription
enabledboolfalseEnable the auto-reply engine.
max_concurrentusize3Maximum number of concurrent auto-reply tasks.
timeout_secsu64120Default timeout per auto-reply task in seconds.
suppress_patternslist 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"] }
FieldTypeDefaultDescription
strategystring"parallel"Delivery strategy. "parallel" = send to all agents simultaneously; "sequential" = send one at a time in order.
routesmap 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"
FieldTypeDefaultDescription
enabledboolfalseEnable the inbox directory watcher.
directorystring or nullnullDirectory to watch. Defaults to $HOME_DIR/inbox/. Supports ~ expansion.
poll_interval_secsu645How often (in seconds) to scan the directory for new files. Minimum 1.
default_agentstring or nullnullAgent 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:

FieldTypeDescription
agentstringTarget agent name or ID to route matched messages to.
match_ruleobjectMatch criteria. All specified (non-null) fields must match.

match_rule fields:

FieldTypeDefaultDescription
channelstring or nullnullChannel type to match (e.g., "discord", "telegram", "slack").
account_idstring or nullnullSpecific bot account ID within the channel (for multi-bot setups).
peer_idstring or nullnullUser/peer ID for DM routing.
guild_idstring or nullnullGuild or server ID (Discord/Slack).
roleslist 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"
FieldTypeDefaultDescription
enabledboolfalseEnable device pairing.
max_devicesusize10Maximum number of paired devices.
token_expiry_secsu64300Pairing token validity in seconds (5 minutes default).
push_providerstring"none"Push notification provider: "none", "ntfy", or "gotify".
ntfy_urlstring or nullnullntfy server URL (when push_provider = "ntfy").
ntfy_topicstring or nullnullntfy 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
FieldTypeDefaultDescription
auto_reconnectbooltrueAutomatically reconnect to MCP servers when they disconnect.
reconnect_max_attemptsu3210Maximum reconnect attempts before giving up permanently.
reconnect_max_backoff_secsu64300Maximum backoff duration in seconds between reconnect attempts.
health_check_interval_secsu6460Interval 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"
FieldTypeDefaultDescription
enabledbooltrueEnable the credential vault. Auto-detected if vault.enc already exists.
pathpath or nullnullCustom 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
FieldTypeDefaultDescription
enabledboolfalseEnable webhook trigger endpoints.
token_envstring"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_bytesusize65536Maximum incoming payload size in bytes (64 KB default).
rate_limit_per_minuteu3230Maximum 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"
FieldTypeDefaultDescription
http_proxystring or nullnullHTTP proxy URL. Falls back to HTTP_PROXY / http_proxy env var. Credentials in URLs are redacted in logs.
https_proxystring or nullnullHTTPS proxy URL. Falls back to HTTPS_PROXY / https_proxy env var.
no_proxystring or nullnullComma-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"
FieldTypeDefaultDescription
namestringrequiredDisplay name for this adapter.
commandstringrequiredExecutable to run (e.g., "python3", "/usr/local/bin/my-adapter").
argslist of strings[]Arguments to pass to the command.
envmap of string to string{}Extra environment variables to pass to the subprocess.
channel_typestring or nullnullChannel 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
FieldTypeDefaultDescription
retention_daysu320Maximum age in days for idle sessions before automatic cleanup. 0 = unlimited.
max_sessions_per_agentu320Maximum number of sessions per agent (oldest pruned first). 0 = unlimited.
cleanup_interval_hoursu3224How 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"
FieldTypeDefaultDescription
enabledbooltrueMaster switch for the terminal feature. When false, the terminal WebSocket endpoint is disabled entirely.
allow_remoteboolfalseAllows 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_remoteboolfalseHard 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_originslist 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_headersboolfalseWhen 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_enabledbooltrueEnable tmux-backed multi-window terminal. Only effective when the tmux binary is available on the system.
max_windowsu3216Maximum number of tmux windows that may exist simultaneously. Guards against resource exhaustion.
tmux_binary_pathstring or nullnullExplicit path to the tmux binary. If null, resolved via PATH.

Notes:

  • Missing Origin is allowed for non-browser clients.
  • allow_remote = true does 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_minute rate 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:

FieldTypeDefaultDescription
max_depth_per_agentu320Maximum queued tasks per agent. New tasks are rejected when full. 0 = unlimited.
max_depth_globalu320Maximum total queued tasks across all agents. 0 = unlimited.
task_ttl_secsu643600Unprocessed tasks expire after this many seconds. 0 = unlimited.

[queue.concurrency] fields:

FieldTypeDefaultDescription
main_laneusize3Concurrent user message tasks.
cron_laneusize2Concurrent scheduled cron job tasks.
subagent_laneusize3Concurrent 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
FieldTypeDefaultDescription
enabledboolfalseEnable external authentication.
issuer_urlstring""OIDC issuer URL for provider discovery at {issuer_url}/.well-known/openid-configuration.
client_idstring""OAuth2 client ID registered with the identity provider.
client_secret_envstring"LIBREFANG_OAUTH_CLIENT_SECRET"Env var name holding the OAuth2 client secret.
redirect_urlstring"http://127.0.0.1:4545/api/auth/callback"OAuth2 authorization code flow callback URL.
scopeslist of strings["openid","profile","email"]OAuth2 scopes to request.
allowed_domainslist of strings[]Restrict login to these email domains. Empty = allow all.
audiencestring""JWT audience claim to validate. Defaults to client_id if empty.
session_ttl_secsu6486400Session token lifetime in seconds (24 hours default).
providerslist 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:

  1. credentials_path in config (JSON string or file path)
  2. VERTEX_AI_SERVICE_ACCOUNT_JSON env var
  3. GOOGLE_APPLICATION_CREDENTIALS env var (file path)
  4. gcloud auth print-access-token CLI fallback
FieldTypeDefaultDescription
project_idstring or nullnullGCP project ID. Falls back to VERTEX_AI_PROJECT_ID, GOOGLE_CLOUD_PROJECT, or the project_id field in the service account JSON.
regionstring or nullnullGCP region for the Vertex AI endpoint. Falls back to VERTEX_AI_REGION or GOOGLE_CLOUD_REGION env var. Default: "us-central1".
credentials_pathstring or nullnullPath 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"
FieldTypeDefaultDescription
google_client_idstring or nullnullGoogle OAuth2 client ID for PKCE flow.
github_client_idstring or nullnullGitHub OAuth app client ID for PKCE flow.
microsoft_client_idstring or nullnullMicrosoft (Entra ID / Azure AD) OAuth application client ID.
slack_client_idstring or nullnullSlack 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:

FieldTypeDefaultDescription
namestringrequiredProfile name (e.g., "primary", "secondary").
api_key_envstringrequiredEnv var name holding the API key for this profile.
priorityu320Priority 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"]
FieldTypeDefaultDescription
agent_ruleslist of ToolPolicyRule[]Per-agent tool rules (highest priority, checked first).
global_ruleslist of ToolPolicyRule[]Global tool rules applied to all agents (checked after agent rules).
groupslist of ToolGroup[]Named tool groups for reuse in rules.
subagent_max_depthu3210Maximum subagent spawning depth.
subagent_max_concurrentu325Maximum concurrent subagents.

ToolPolicyRule fields:

FieldTypeDescription
patternstringGlob pattern to match tool names (e.g., "shell_*", "web_*", "mcp_github_*").
effectstring"allow" or "deny". Deny-wins: if any deny rule matches, the tool is blocked regardless of allow rules.

ToolGroup fields:

FieldTypeDescription
namestringGroup name (e.g., "web_tools", "code_tools").
toolslist of stringsTool 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
FieldTypeDefaultDescription
tool_timeout_secsu64300Global timeout in seconds for all tool executions. Increase for browser automation or long-running builds.
tool_timeoutsmap 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:

  1. Exact tool name match in tool_timeouts
  2. Longest matching glob pattern in tool_timeouts
  3. 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
FieldTypeDefaultDescription
enabledbooltrueMaster toggle — when false, the entire proactive memory subsystem is disabled.
auto_memorizebooltrueAutomatically extract and store memories after each agent execution.
auto_retrievebooltrueAutomatically retrieve relevant memories before each agent execution.
max_retrieveusize10Maximum number of memories to retrieve per query.
extraction_thresholdf320.7Confidence threshold for near-duplicate detection (0.0–1.0).
extraction_modelstring or nullnullLLM 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_categorieslist of strings["user_preference", "important_fact", "task_context", "relationship"]Categories to extract from conversations.
session_ttl_hoursu3224Session memory TTL in hours. Memories older than this are cleaned up before each agent execution.
duplicate_thresholdf320.5Similarity 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_ratef640.01Confidence decay rate per day. Follows exponential decay: conf × e^(−rate × days). Default of 0.01 takes ~70 days to halve.
max_memories_per_agentusize1000Maximum 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
FieldNotes
enabledSome(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_memorizeSome(false) skips the after-turn extraction. Useful for cron sub-agents whose tool-only turns produce extraction noise.
auto_retrieveSome(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"
FieldTypeDefaultDescription
enginestring"default"Built-in engine name. Currently only "default" is supported.
pluginstring or nullnullPlugin name. Resolves to ~/.librefang/plugins/<name>/plugin.toml. Takes precedence over manual hooks if set.
hooks.ingeststring or nullnullScript path for the ingest hook (called on new user message).
hooks.after_turnstring or nullnullScript path for the after_turn hook (called after each completed turn).
hooks.runtimestring 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_registrieslist of objectsOfficial registryPlugin 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
FieldTypeDefaultDescription
retention_daysu3290Number 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
FieldTypeDefaultDescription
health_check_interval_secsu6460Interval 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"]
FieldTypeDefaultDescription
plugin_registrieslist 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
FieldTypeDefaultDescription
enabledboolfalseMaster toggle. When false, no prompt versions are tracked and experiments are skipped.
hash_promptsbooltrueCompute content hashes for prompt versions.
max_versions_per_agentu3250Maximum 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

VariableUsed ByDescription
ANTHROPIC_API_KEY[default_model]Anthropic API key (Claude models).
GEMINI_API_KEYGemini driverGoogle Gemini API key. Alias: GOOGLE_API_KEY.
OPENAI_API_KEYOpenAI-compat driverOpenAI API key.
GROQ_API_KEYGroq providerGroq API key (fast Llama inference).
DEEPSEEK_API_KEYDeepSeek providerDeepSeek API key.
PERPLEXITY_API_KEYPerplexity provider / web searchPerplexity API key.
OPENROUTER_API_KEYOpenRouter providerOpenRouter API key.
TOGETHER_API_KEYTogether AI providerTogether AI API key.
MISTRAL_API_KEYMistral providerMistral AI API key.
FIREWORKS_API_KEYFireworks providerFireworks AI API key.
COHERE_API_KEYCohere providerCohere API key.
AI21_API_KEYAI21 providerAI21 Labs API key.
CEREBRAS_API_KEYCerebras providerCerebras API key.
SAMBANOVA_API_KEYSambaNova providerSambaNova API key.
HUGGINGFACE_API_KEYHugging Face providerHugging Face Inference API key.
XAI_API_KEYxAI providerxAI (Grok) API key.
REPLICATE_API_KEYReplicate providerReplicate API key.

Web Search Keys

VariableUsed ByDescription
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

VariableChannelDescription
TELEGRAM_BOT_TOKENTelegramBot API token from @BotFather.
DISCORD_BOT_TOKENDiscordDiscord bot token.
SLACK_APP_TOKENSlackSlack app-level token (xapp-) for Socket Mode.
SLACK_BOT_TOKENSlackSlack bot token (xoxb-) for REST API.
WHATSAPP_ACCESS_TOKENWhatsAppWhatsApp Cloud API access token.
WHATSAPP_VERIFY_TOKENWhatsAppWebhook verification token.
MATRIX_ACCESS_TOKENMatrixMatrix homeserver access token.
EMAIL_PASSWORDEmailEmail account password or app password.
TEAMS_APP_PASSWORDTeamsAzure Bot Framework app password.
MATTERMOST_TOKENMattermost (sidecar)Mattermost bot token (sidecar adapter — set in ~/.librefang/secrets.env).
TWITCH_OAUTH_TOKENTwitchTwitch OAuth token.
ROCKETCHAT_TOKENRocket.Chat (sidecar)Rocket.Chat personal access token (sidecar adapter — set in ~/.librefang/secrets.env).
ZULIP_API_KEYZulip (sidecar)Zulip bot API key (sidecar adapter — set in ~/.librefang/secrets.env).
XMPP_PASSWORDXMPPXMPP account password.
GOOGLE_CHAT_SERVICE_ACCOUNTGoogle ChatService account JSON key.
LINE_CHANNEL_SECRETLINE (sidecar)LINE channel secret (sidecar adapter — set in ~/.librefang/secrets.env).
LINE_CHANNEL_ACCESS_TOKENLINE (sidecar)LINE channel access token (librefang.sidecar.adapters.line).
REDDIT_CLIENT_SECRETRedditReddit app client secret.
REDDIT_PASSWORDRedditReddit bot account password.
MASTODON_ACCESS_TOKENMastodonMastodon access token.
BLUESKY_APP_PASSWORDBlueskyBluesky app password.
FEISHU_APP_SECRETFeishuFeishu/Lark app secret.
REVOLT_BOT_TOKENRevoltRevolt bot token.
NEXTCLOUD_TOKENNextcloud (sidecar)Nextcloud Talk app password / OAuth bearer (librefang.sidecar.adapters.nextcloud).
GUILDED_BOT_TOKENGuildedGuilded bot token.
KEYBASE_PAPERKEYKeybaseKeybase paper key.
THREEMA_SECRETThreemaThreema Gateway API secret.
WEBEX_BOT_TOKENWebex (sidecar)Webex bot Bearer token (librefang.sidecar.adapters.webex).
PUMBLE_BOT_TOKENPumblePumble bot token.
FLOCK_BOT_TOKENFlockFlock bot token.
TWIST_TOKENTwistTwist API token.
MUMBLE_PASSWORDMumbleMumble server password.
DINGTALK_APP_KEYDingTalkDingTalk App Key / Client ID (sidecar — librefang.sidecar.adapters.dingtalk, stream mode only).
DINGTALK_APP_SECRETDingTalkDingTalk App Secret / Client Secret (sidecar — librefang.sidecar.adapters.dingtalk, stream mode only).
GITTER_TOKENGitterGitter auth token.
NTFY_TOKENntfyntfy auth token (optional for public topics).
GOTIFY_APP_TOKENGotifyGotify app token (sending).
GOTIFY_CLIENT_TOKENGotifyGotify client token (receiving).
WEBHOOK_SECRETWebhookHMAC 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:

ChannelEnv vars checked
Telegrambot_token_env
Discordbot_token_env
Slackapp_token_env, bot_token_env (both checked)
WhatsAppaccess_token_env
Matrixaccess_token_env
Emailpassword_env
Teamsapp_password_env
Mattermosttoken_env
Google Chatservice_account_env
XMPPpassword_env
Redditclient_secret_env
Mastodonaccess_token_env
Blueskyapp_password_env
Feishuapp_secret_env
Revoltbot_token_env
Guildedbot_token_env
Keybasepaperkey_env
Threemasecret_env
Pumblebot_token_env
Flockbot_token_env
Twisttoken_env
Mumblepassword_env
DingTalksidecar — validation handled by librefang.sidecar.adapters.dingtalk's DINGTALK_APP_KEY / DINGTALK_APP_SECRET env check
Gittertoken_env
ntfytoken_env (only if token_env is non-empty; public topics are OK without auth)
Webhooksecret_env

For web search providers, the validator checks:

ProviderEnv var checked
braveweb.brave.api_key_env
jinaweb.jina.api_key_env
tavilyweb.tavily.api_key_env
perplexityweb.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_env in [default_model] is not checked by validate(). Missing LLM keys cause errors at runtime when the driver is first used.
  • The shared_secret in [network] is not validated against network_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.

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
FieldDefaultDescription
threshold_messages30Compact when session message count exceeds this.
keep_recent10Number of recent messages preserved verbatim after compaction.
max_summary_tokens1024Maximum tokens for the LLM summary of compacted messages.
token_threshold_ratio0.7Trigger compaction when estimated tokens exceed this fraction of the context window.
max_chunk_chars80000Maximum characters per summarization chunk.
max_retries3Maximum retries for LLM summarization.

WASM Sandbox (runtime)

Configured internally via SandboxConfig (not currently exposed in config.toml):

FieldDefaultDescription
fuel_limit1000000Maximum CPU instruction budget. 0 = unlimited.
max_memory_bytes16777216 (16 MB)Maximum WASM linear memory.
timeout_secsnull (30s fallback)Wall-clock timeout for epoch-based interruption.

Model Routing (per-agent manifest)

Configured in agent manifests via ModelRoutingConfig:

FieldDefaultDescription
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_threshold100Token count below which a query is classified as simple.
complex_threshold500Token count above which a query is classified as complex.

Autonomous Guardrails (per-agent manifest)

Configured in agent manifests via AutonomousConfig:

FieldDefaultDescription
quiet_hoursnullCron expression for quiet hours (agent pauses during this window).
max_iterations50Maximum tool-use iterations per invocation.
max_restarts10Maximum automatic restarts before permanent stop.
heartbeat_interval_secs30Seconds between heartbeat health checks.
heartbeat_channelnullChannel to send heartbeat status to (e.g., "telegram").