Configuration Reference

⚙️ Complete reference for all PiSovereign configuration options

This document covers every configuration option available in config.toml.

Table of Contents


Overview

PiSovereign uses a layered configuration system:

  1. Default values - Built into the application
  2. Configuration file - config.toml in the working directory
  3. Environment variables - Override config file values (prefix: PISOVEREIGN_)

Configuration File Location

The application loads config.toml from the current working directory:

# Default location (relative to working directory)
./config.toml

Environment Variable Mapping

Config values can be overridden using environment variables:

[server]
port = 3000

# Becomes:
PISOVEREIGN_SERVER_PORT=3000

Nested values use double underscores:

[speech.local_stt]
threads = 4

# Becomes:
PISOVEREIGN_SPEECH_LOCAL_STT__THREADS=4

Environment Settings

# Application environment: "development" or "production"
# In production:
#   - JSON logging is enforced
#   - Security warnings block startup (unless PISOVEREIGN_ALLOW_INSECURE_CONFIG=true)
#   - TLS verification is enforced
environment = "development"
ValueDescription
developmentRelaxed security, human-readable logs
productionStrict security, JSON logs, TLS enforced

Server Settings

[server]
# Network interface to bind to
# "127.0.0.1" = localhost only (recommended for security)
# "0.0.0.0" = all interfaces (use behind reverse proxy)
host = "127.0.0.1"

# HTTP port
port = 3000

# Enable CORS (Cross-Origin Resource Sharing)
cors_enabled = true

# Allowed CORS origins
# Empty array = allow all (WARNING in production)
# Example: ["https://app.example.com", "https://admin.example.com"]
allowed_origins = []

# Graceful shutdown timeout (seconds)
# Time to wait for active requests to complete
shutdown_timeout_secs = 30

# Log format: "json" or "text"
# In production mode, defaults to "json" even if set to "text"
log_format = "text"

# Secure session cookies (requires HTTPS)
# Set to false for local HTTP development
secure_cookies = false

# Maximum request body size for JSON payloads (optional, bytes)
# max_body_size_json_bytes = 1048576  # 1MB

# Maximum request body size for audio uploads (optional, bytes)
# max_body_size_audio_bytes = 10485760  # 10MB
OptionTypeDefaultDescription
hostString127.0.0.1Bind address
portInteger3000HTTP port
cors_enabledBooleantrueEnable CORS
allowed_originsArray[]CORS allowed origins
shutdown_timeout_secsInteger30Shutdown grace period
log_formatStringtextLog output format
secure_cookiesBooleanfalseSecure cookie mode (HTTPS)
max_body_size_json_bytesInteger1048576(Optional) Max JSON payload size
max_body_size_audio_bytesInteger10485760(Optional) Max audio upload size

Inference Engine

[inference]
# Ollama-compatible server URL
# Works with both hailo-ollama (Raspberry Pi) and standard Ollama (macOS)
base_url = "http://localhost:11434"

# Default model for inference
default_model = "qwen2.5:1.5b"

# Request timeout (milliseconds)
timeout_ms = 60000

# Maximum tokens to generate
max_tokens = 2048

# Sampling temperature (0.0 = deterministic, 2.0 = creative)
temperature = 0.7

# Top-p (nucleus) sampling (0.0-1.0)
top_p = 0.9

# System prompt (optional)
# system_prompt = "You are a helpful AI assistant."
OptionTypeDefaultRangeDescription
base_urlStringhttp://localhost:11434-Inference server URL
default_modelStringqwen2.5:1.5b-Model identifier
timeout_msInteger600001000-300000Request timeout
max_tokensInteger20481-8192Max generation length
temperatureFloat0.70.0-2.0Randomness
top_pFloat0.90.0-1.0Nucleus sampling
system_promptStringNone-(Optional) System prompt

Security Settings

[security]
# Whitelisted phone numbers for WhatsApp
# Empty = allow all, Example: ["+491234567890", "+491234567891"]
whitelisted_phones = []

# API Keys (hashed with Argon2id)
# Generate hashed keys using: pisovereign-cli hash-api-key <your-key>
# Migrate existing plaintext keys: pisovereign-cli migrate-keys --input config.toml --dry-run
#
# [[security.api_keys]]
# hash = "$argon2id$v=19$m=19456,t=2,p=1$..."
# user_id = "550e8400-e29b-41d4-a716-446655440000"
#
# [[security.api_keys]]
# hash = "$argon2id$v=19$m=19456,t=2,p=1$..."
# user_id = "6ba7b810-9dad-11d1-80b4-00c04fd430c8"

# Trusted reverse proxies (IP addresses) - optional
# Add your proxy IPs here if behind a reverse proxy
# trusted_proxies = ["127.0.0.1", "::1"]

# Rate limiting
rate_limit_enabled = true
rate_limit_rpm = 120  # Requests per minute per IP

# TLS settings for outbound connections
tls_verify_certs = true
connection_timeout_secs = 30
min_tls_version = "1.2"  # "1.2" or "1.3"
OptionTypeDefaultDescription
whitelisted_phonesArray[](Optional) Allowed phone numbers
api_keysArray[]API key definitions with Argon2id hash
trusted_proxiesArray-(Optional) Trusted reverse proxy IPs
rate_limit_enabledBooleantrueEnable rate limiting
rate_limit_rpmInteger120Requests/minute/IP
tls_verify_certsBooleantrueVerify TLS certificates for outbound connections
connection_timeout_secsInteger30Connection timeout for external services
min_tls_versionString1.2Minimum TLS version (“1.2” or “1.3”)

Prompt Security

Protects against prompt injection and other AI security threats.

[prompt_security]
# Enable prompt security analysis
enabled = true

# Sensitivity level: "low", "medium", or "high"
# - low: Only block high-confidence threats
# - medium: Block medium and high confidence threats (recommended)
# - high: Block all detected threats including low confidence
sensitivity = "medium"

# Block requests when security threats are detected
block_on_detection = true

# Maximum violations before auto-blocking an IP
max_violations_before_block = 3

# Time window for counting violations (seconds)
violation_window_secs = 3600  # 1 hour

# How long to block an IP after exceeding max violations (seconds)
block_duration_secs = 86400  # 24 hours

# Immediately block IPs that send critical-level threats
auto_block_on_critical = true

# Custom patterns to detect (in addition to built-in patterns) - optional
# custom_patterns = ["DROP TABLE", "eval("]
OptionTypeDefaultDescription
enabledBooleantrueEnable prompt security analysis
sensitivityStringmediumDetection level: “low”, “medium”, or “high”
block_on_detectionBooleantrueBlock requests when threats detected
max_violations_before_blockInteger3Violations before IP auto-block
violation_window_secsInteger3600Time window for counting violations
block_duration_secsInteger86400IP block duration after violations
auto_block_on_criticalBooleantrueAuto-block critical threats immediately
custom_patternsArray-(Optional) Custom threat detection patterns

API Key Authentication

API keys are now securely hashed using Argon2id. Use the CLI tools to generate and migrate keys.

Generate a new hashed key:

pisovereign-cli hash-api-key <your-api-key>

Migrate existing plaintext keys:

pisovereign-cli migrate-keys --input config.toml --dry-run
pisovereign-cli migrate-keys --input config.toml --output config-new.toml

Configuration:

[[security.api_keys]]
hash = "$argon2id$v=19$m=19456,t=2,p=1$..."
user_id = "550e8400-e29b-41d4-a716-446655440000"

Usage:

curl -H "Authorization: Bearer <your-api-key>" http://localhost:3000/v1/chat

Memory & Knowledge Storage

Persistent AI memory for RAG-based context retrieval. Stores interactions, facts, preferences, and corrections using embeddings for semantic similarity search.

[memory]
# Enable memory storage (default: true)
# enabled = true

# Enable RAG context retrieval (default: true)
# enable_rag = true

# Enable automatic learning from interactions (default: true)
# enable_learning = true

# Number of memories to retrieve for RAG context (default: 5)
# rag_limit = 5

# Minimum similarity threshold for RAG retrieval (0.0-1.0, default: 0.5)
# rag_threshold = 0.5

# Similarity threshold for memory deduplication (0.0-1.0, default: 0.85)
# merge_threshold = 0.85

# Minimum importance score to keep memories (default: 0.1)
# min_importance = 0.1

# Decay factor for memory importance over time (default: 0.95)
# decay_factor = 0.95

# Enable content encryption (default: true)
# enable_encryption = true

# Path to encryption key file (generated if not exists)
# encryption_key_path = "memory_encryption.key"

[memory.embedding]
# Embedding model name (default: nomic-embed-text)
# model = "nomic-embed-text"

# Embedding dimension (default: 384 for nomic-embed-text)
# dimension = 384

# Request timeout in milliseconds (default: 30000)
# timeout_ms = 30000
OptionTypeDefaultDescription
enabledBooleantrue(Optional) Enable memory storage
enable_ragBooleantrue(Optional) Enable RAG context retrieval
enable_learningBooleantrue(Optional) Auto-learn from interactions
rag_limitInteger5(Optional) Number of memories for RAG
rag_thresholdFloat0.5(Optional) Min similarity for RAG (0.0-1.0)
merge_thresholdFloat0.85(Optional) Similarity for deduplication (0.0-1.0)
min_importanceFloat0.1(Optional) Min importance to keep memories
decay_factorFloat0.95(Optional) Importance decay over time
enable_encryptionBooleantrue(Optional) Encrypt stored content
encryption_key_pathStringmemory_encryption.key(Optional) Encryption key file path

Embedding Settings:

OptionTypeDefaultDescription
embedding.modelStringnomic-embed-text(Optional) Embedding model name
embedding.dimensionInteger384(Optional) Embedding vector dimension
embedding.timeout_msInteger30000(Optional) Request timeout

Database & Cache

Database

[database]
# SQLite database file path
path = "pisovereign.db"

# Connection pool size
max_connections = 5

# Auto-run migrations on startup
run_migrations = true
OptionTypeDefaultDescription
pathStringpisovereign.dbDatabase file path
max_connectionsInteger5Pool size
run_migrationsBooleantrueAuto-migrate

Cache

PiSovereign uses a 3-layer caching architecture:

  1. L1 (Moka) - In-memory cache for fastest access
  2. L2 (Redb) - Persistent disk cache for exact-match lookups
  3. L3 (Semantic) - pgvector-based similarity cache for semantically equivalent queries
[cache]
# Enable caching (disable for debugging)
enabled = true

# TTL values (seconds)
ttl_short_secs = 300       # 5 minutes - frequently changing
ttl_medium_secs = 3600     # 1 hour - moderately stable
ttl_long_secs = 86400      # 24 hours - stable data

# LLM response caching
ttl_llm_dynamic_secs = 3600   # Dynamic content (briefings)
ttl_llm_stable_secs = 86400   # Stable content (help text)

# L1 (in-memory) cache size
l1_max_entries = 10000
OptionTypeDefaultDescription
enabledBooleantrueEnable caching
ttl_short_secsInteger300Short TTL
ttl_medium_secsInteger3600Medium TTL
ttl_long_secsInteger86400Long TTL
ttl_llm_dynamic_secsInteger3600Dynamic LLM TTL
ttl_llm_stable_secsInteger86400Stable LLM TTL
l1_max_entriesInteger10000Max memory cache entries

Semantic Cache

The semantic cache provides an additional layer that matches queries based on embedding similarity rather than exact string matching. This enables cache hits for semantically equivalent queries like:

  • “What’s the weather?” ≈ “How’s the weather today?”
  • “Tell me about the capital of France” ≈ “What is Paris?”
[cache.semantic]
# Enable semantic caching
enabled = true

# Minimum cosine similarity for cache hit (0.0-1.0)
# Higher = stricter matching, lower = more cache hits
similarity_threshold = 0.92

# TTL for cached entries (hours)
ttl_hours = 48

# Maximum cached entries
max_entries = 10000

# Patterns that bypass semantic cache (time-sensitive queries)
bypass_patterns = ["weather", "time", "date", "today", "tomorrow", "now", "latest", "current", "recent"]

# How often to evict expired entries (minutes)
eviction_interval_minutes = 60
OptionTypeDefaultDescription
enabledBooleantrueEnable semantic caching
similarity_thresholdFloat0.92Minimum cosine similarity (0.0-1.0)
ttl_hoursInteger48Time-to-live in hours
max_entriesInteger10000Maximum cache entries
bypass_patternsArraySee aboveQueries containing these words skip cache
eviction_interval_minutesInteger60Expired entry cleanup interval

Integrations

Messenger Selection

PiSovereign supports one messenger at a time:

# Choose one: "whatsapp", "signal", or "none"
messenger = "whatsapp"
ValueDescription
whatsappUse WhatsApp Business API (webhooks)
signalUse Signal via signal-cli (polling)
noneDisable messenger integration

WhatsApp Business

[whatsapp]
# Meta Graph API access token (store in Vault)
# access_token = "your-access-token"

# Phone number ID from WhatsApp Business
# phone_number_id = "your-phone-number-id"

# App secret for webhook signature verification
# app_secret = "your-app-secret"

# Verify token for webhook setup
# verify_token = "your-verify-token"

# Require webhook signature verification
signature_required = true

# Meta Graph API version
api_version = "v18.0"

# Phone numbers allowed to send messages (empty = allow all)
# whitelist = ["+1234567890"]

# Conversation Persistence Settings
[whatsapp.persistence]
# Enable conversation persistence (default: true)
# enabled = true

# Enable encryption for stored messages (default: true)
# enable_encryption = true

# Enable RAG context retrieval from memory system (default: true)
# enable_rag = true

# Enable automatic learning from interactions (default: true)
# enable_learning = true

# Maximum days to retain conversations (optional, unlimited if not set)
# retention_days = 90

# Maximum messages per conversation before FIFO truncation (optional)
# max_messages_per_conversation = 1000

# Number of recent messages to use as context (default: 50)
# context_window = 50
OptionTypeDefaultDescription
access_tokenString-(Optional) Meta Graph API token (store in Vault)
phone_number_idString-(Optional) WhatsApp Business phone number ID
app_secretString-(Optional) Webhook signature secret
verify_tokenString-(Optional) Webhook verification token
signature_requiredBooleantrueRequire webhook signature verification
api_versionStringv18.0Meta Graph API version
whitelistArray[](Optional) Allowed phone numbers

Persistence Options:

OptionTypeDefaultDescription
persistence.enabledBooleantrue(Optional) Store conversations in database
persistence.enable_encryptionBooleantrue(Optional) Encrypt stored messages
persistence.enable_ragBooleantrue(Optional) Enable RAG context retrieval
persistence.enable_learningBooleantrue(Optional) Auto-learn from interactions
persistence.retention_daysInteger-(Optional) Max retention days (unlimited if not set)
persistence.max_messages_per_conversationInteger-(Optional) Max messages before truncation
persistence.context_windowInteger50(Optional) Recent messages for context

Signal Messenger

[signal]
# Your phone number registered with Signal (E.164 format)
phone_number = "+1234567890"

# Path to signal-cli JSON-RPC socket
socket_path = "/var/run/signal-cli/socket"

# Path to signal-cli data directory (optional)
# data_path = "/var/lib/signal-cli"

# Connection timeout in milliseconds
timeout_ms = 30000

# Phone numbers allowed to send messages (empty = allow all)
# whitelist = ["+1234567890", "+0987654321"]

# Conversation Persistence Settings
[signal.persistence]
# Enable conversation persistence (default: true)
# enabled = true

# Enable encryption for stored messages (default: true)
# enable_encryption = true

# Enable RAG context retrieval from memory system (default: true)
# enable_rag = true

# Enable automatic learning from interactions (default: true)
# enable_learning = true

# Maximum days to retain conversations (optional, unlimited if not set)
# retention_days = 90

# Maximum messages per conversation before FIFO truncation (optional)
# max_messages_per_conversation = 1000

# Number of recent messages to use as context (default: 50)
# context_window = 50
OptionTypeDefaultDescription
phone_numberString-Your Signal phone number (E.164)
socket_pathString/var/run/signal-cli/socketsignal-cli daemon socket
data_pathString-(Optional) signal-cli data directory
timeout_msInteger30000Connection timeout
whitelistArray[](Optional) Allowed phone numbers

Persistence Options:

OptionTypeDefaultDescription
persistence.enabledBooleantrue(Optional) Store conversations in database
persistence.enable_encryptionBooleantrue(Optional) Encrypt stored messages
persistence.enable_ragBooleantrue(Optional) Enable RAG context retrieval
persistence.enable_learningBooleantrue(Optional) Auto-learn from interactions
persistence.retention_daysInteger-(Optional) Max retention days (unlimited if not set)
persistence.max_messages_per_conversationInteger-(Optional) Max messages before truncation
persistence.context_windowInteger50(Optional) Recent messages for context

📖 See Signal Setup Guide for installation instructions.

Speech Processing

Voice message support for speech-to-text (STT) and text-to-speech (TTS).

Cloud Provider (OpenAI):

  • Works on all platforms
  • Requires API key

Local Provider (whisper.cpp + Piper):

  • Raspberry Pi: Models in /usr/local/share/{whisper,piper}/
  • macOS: Models in ~/Library/Application Support/{whisper,piper}/
  • Install whisper.cpp: brew install whisper-cpp (Mac) or build from source (Pi)
  • Install Piper: Download from https://github.com/rhasspy/piper/releases
[speech]
# Speech provider: "openai" (cloud) or "local" (whisper.cpp + Piper)
# provider = "openai"

# OpenAI API key for Whisper (STT) and TTS
# openai_api_key = "sk-..."

# OpenAI API base URL (for custom endpoints)
# openai_base_url = "https://api.openai.com/v1"

# Speech-to-text model (OpenAI Whisper)
# stt_model = "whisper-1"

# Text-to-speech model
# tts_model = "tts-1"

# Default TTS voice: alloy, echo, fable, onyx, nova, shimmer
# default_voice = "nova"

# Output audio format: opus, ogg, mp3, wav
# output_format = "opus"

# Request timeout in milliseconds
# timeout_ms = 60000

# Maximum audio duration in milliseconds (25 min for Whisper)
# max_audio_duration_ms = 1500000

# Response format preference: mirror, text, voice
# response_format = "mirror"

# TTS speaking speed (0.25 to 4.0)
# speed = 1.0
OptionTypeDefaultDescription
providerStringopenai(Optional) Speech provider: “openai” or “local”
openai_api_keyString-(Optional) OpenAI API key (store in Vault)
openai_base_urlStringhttps://api.openai.com/v1(Optional) OpenAI API base URL
stt_modelStringwhisper-1(Optional) Speech-to-text model
tts_modelStringtts-1(Optional) Text-to-speech model
default_voiceStringnova(Optional) TTS voice (alloy, echo, fable, onyx, nova, shimmer)
output_formatStringopus(Optional) Audio format (opus, ogg, mp3, wav)
timeout_msInteger60000(Optional) Request timeout
max_audio_duration_msInteger1500000(Optional) Max audio duration (25 minutes)
response_formatStringmirror(Optional) Response format (mirror, text, voice)
speedFloat1.0(Optional) TTS speaking speed (0.25 to 4.0)

Weather

[weather]
# Open-Meteo API (free, no key required)
# base_url = "https://api.open-meteo.com/v1"

# Connection timeout in seconds
# timeout_secs = 30

# Number of forecast days (1-16)
# forecast_days = 7

# Cache TTL in minutes
# cache_ttl_minutes = 30

# Default location (when user has no profile)
# default_location = { latitude = 52.52, longitude = 13.405 }  # Berlin
OptionTypeDefaultDescription
base_urlStringhttps://api.open-meteo.com/v1(Optional) Open-Meteo API URL
timeout_secsInteger30(Optional) Request timeout
forecast_daysInteger7(Optional) Forecast days (1-16)
cache_ttl_minutesInteger30(Optional) Cache TTL
default_locationObject-(Optional) Default location { latitude, longitude }

CalDAV Calendar

[caldav]
# CalDAV server URL (Baïkal, Radicale, Nextcloud)
# server_url = "https://cal.example.com"
# When using Baïkal via Docker (setup --baikal):
# server_url = "http://baikal:80/dav.php"

# Authentication (store in Vault)
# username = "your-username"
# password = "your-password"

# Default calendar path (optional)
# calendar_path = "/calendars/user/default"

# TLS verification
# verify_certs = true

# Connection timeout in seconds
# timeout_secs = 30
OptionTypeDefaultDescription
server_urlString-(Optional) CalDAV server URL
usernameString-(Optional) Username for authentication (store in Vault)
passwordString-(Optional) Password for authentication (store in Vault)
calendar_pathString/calendars/user/default(Optional) Default calendar path
verify_certsBooleantrue(Optional) Verify TLS certificates
timeout_secsInteger30(Optional) Connection timeout

Email (IMAP/SMTP)

PiSovereign supports any email provider that offers IMAP/SMTP access, including Gmail, Outlook, Proton Mail (via Bridge), and custom servers. Authentication is supported via password or OAuth2 (XOAUTH2).

Migration note: The config section was previously named [proton]. The old name still works (via a serde alias) but [email] is the canonical name going forward.

Quick setup with provider presets:

The easiest way to configure email is using the provider field, which automatically sets sensible defaults for IMAP/SMTP hosts and ports:

[email]
provider = "gmail"    # or "proton" or "custom"
email = "user@gmail.com"
password = "app-password"

Available providers:

ProviderIMAP HostIMAP PortSMTP HostSMTP Port
proton127.0.0.11143127.0.0.11025
gmailimap.gmail.com993smtp.gmail.com465
custom(must specify)(must specify)(must specify)(must specify)

Explicit imap_host, imap_port, smtp_host, smtp_port values always override provider presets.

Full configuration:

[email]
# Provider preset: "proton" (default), "gmail", or "custom"
# provider = "proton"

# IMAP server host (overrides provider preset)
# imap_host = "imap.gmail.com"       # Gmail
# imap_host = "outlook.office365.com" # Outlook
# imap_host = "127.0.0.1"            # Proton Bridge

# IMAP server port (993 for TLS, 1143 for Proton Bridge STARTTLS)
# imap_port = 993

# SMTP server host
# smtp_host = "smtp.gmail.com"       # Gmail
# smtp_host = "smtp.office365.com"   # Outlook
# smtp_host = "127.0.0.1"           # Proton Bridge

# SMTP server port (465 for TLS, 587 for STARTTLS, 1025 for Proton Bridge)
# smtp_port = 465

# Email address
# email = "user@gmail.com"

# Authentication: password or OAuth2
# For password-based auth (app passwords, Bridge passwords):
# password = "app-password"

# For OAuth2 (Gmail, Outlook):
# [email.auth]
# type = "oauth2"
# access_token = "ya29.your-token"

# TLS configuration
[email.tls]
# Verify TLS certificates (set false for self-signed certs like Proton Bridge)
# verify_certificates = true

# Minimum TLS version
# min_tls_version = "1.2"

# Custom CA certificate path (optional)
# ca_cert_path = "/path/to/ca.pem"
OptionTypeDefaultDescription
providerStringproton(Optional) Provider preset: proton, gmail, or custom. Sets default host/port values.
imap_hostString127.0.0.1(Optional) IMAP server host (overrides provider preset)
imap_portInteger1143(Optional) IMAP server port (overrides provider preset)
smtp_hostString127.0.0.1(Optional) SMTP server host (overrides provider preset)
smtp_portInteger1025(Optional) SMTP server port (overrides provider preset)
emailString-(Optional) Email address (store in Vault)
passwordString-(Optional) Password (store in Vault)
auth.typeStringpassword(Optional) Auth method: password or oauth2
auth.access_tokenString-(Optional) OAuth2 access token (store in Vault)
tls.verify_certificatesBooleantrue(Optional) Verify TLS certificates
tls.min_tls_versionString1.2(Optional) Minimum TLS version
tls.ca_cert_pathString-(Optional) Custom CA certificate path

Provider-specific examples:

Gmail
[email]
provider = "gmail"
email = "user@gmail.com"
# Use an App Password (not your Google account password)
# Generate at: https://myaccount.google.com/apppasswords
password = "xxxx xxxx xxxx xxxx"
Outlook / Microsoft 365
[email]
provider = "custom"
imap_host = "outlook.office365.com"
imap_port = 993
smtp_host = "smtp.office365.com"
smtp_port = 587
email = "user@outlook.com"
password = "your-app-password"
Proton Mail (via Bridge)
[email]
provider = "proton"  # default — uses Bridge at 127.0.0.1
email = "user@proton.me"
# Use the Bridge password (from Bridge UI), NOT your Proton account password
password = "bridge-password"

[email.tls]
verify_certificates = false  # Bridge uses self-signed certs
[websearch]
# Brave Search API key (required for primary provider)
# Get your key at: https://brave.com/search/api/
# api_key = "BSA-your-brave-api-key"

# Maximum results per search query (default: 5)
max_results = 5

# Request timeout in seconds (default: 30)
timeout_secs = 30

# Enable DuckDuckGo fallback if Brave fails (default: true)
fallback_enabled = true

# Safe search: "off", "moderate", "strict" (default: "moderate")
safe_search = "moderate"

# Country code for localized results (e.g., "US", "DE", "GB")
country = "DE"

# Language code for results (e.g., "en", "de", "fr")
language = "de"

# Rate limit: requests per minute (default: 60)
rate_limit_rpm = 60

# Cache TTL in minutes (default: 30)
cache_ttl_minutes = 30
OptionTypeDefaultDescription
api_keyString-(Optional) Brave Search API key (store in Vault)
max_resultsInteger5(Optional) Max search results (1-10)
timeout_secsInteger30(Optional) Request timeout
fallback_enabledBooleantrue(Optional) Enable DuckDuckGo fallback
safe_searchStringmoderate(Optional) Safe search: “off”, “moderate”, “strict”
countryStringDE(Optional) Country code for results
languageStringde(Optional) Language code for results
rate_limit_rpmInteger60(Optional) Rate limit (requests/minute)
cache_ttl_minutesInteger30(Optional) Cache time-to-live

Security Note: Store the Brave API key in Vault rather than config.toml:

vault kv put secret/pisovereign/websearch brave_api_key="BSA-..."

Public Transit (ÖPNV)

Provides public transit routing for German transport networks via transport.rest API. Used for “How do I get to X?” queries and location-based reminders.

[transit]
# Base URL for transport.rest API (default: v6.db.transport.rest)
# base_url = "https://v6.db.transport.rest"

# Request timeout in seconds
# timeout_secs = 10

# Maximum number of journey results
# max_results = 3

# Cache TTL in minutes
# cache_ttl_minutes = 5

# Include transit info in location-based reminders
# include_in_reminders = true

# Transport modes to include:
# products_bus = true
# products_suburban = true  # S-Bahn
# products_subway = true    # U-Bahn
# products_tram = true
# products_regional = true  # RB/RE
# products_national = false # ICE/IC

# User's home location for route calculations
# home_location = { latitude = 52.52, longitude = 13.405 }  # Berlin
OptionTypeDefaultDescription
base_urlStringhttps://v6.db.transport.rest(Optional) transport.rest API URL
timeout_secsInteger10(Optional) Request timeout
max_resultsInteger3(Optional) Max journey results
cache_ttl_minutesInteger5(Optional) Cache TTL
include_in_remindersBooleantrue(Optional) Include in location reminders
products_busBooleantrue(Optional) Include bus routes
products_suburbanBooleantrue(Optional) Include S-Bahn
products_subwayBooleantrue(Optional) Include U-Bahn
products_tramBooleantrue(Optional) Include tram
products_regionalBooleantrue(Optional) Include regional trains (RB/RE)
products_nationalBooleanfalse(Optional) Include national trains (ICE/IC)
home_locationObject-(Optional) Home location { latitude, longitude }

Reminder System

Configures the proactive reminder system including CalDAV sync, custom reminders, and scheduling settings.

[reminder]
# Maximum number of snoozes per reminder
# max_snooze = 5

# Default snooze duration in minutes
# default_snooze_minutes = 15

# How far in advance to create reminders from CalDAV events (minutes)
# caldav_reminder_lead_time_minutes = 30

# Interval for checking due reminders (seconds)
# check_interval_secs = 60

# CalDAV sync interval (minutes)
# caldav_sync_interval_minutes = 15

# Morning briefing time (HH:MM format)
# morning_briefing_time = "07:00"

# Enable morning briefing
# morning_briefing_enabled = true
OptionTypeDefaultDescription
max_snoozeInteger5(Optional) Max snoozes per reminder
default_snooze_minutesInteger15(Optional) Default snooze duration
caldav_reminder_lead_time_minutesInteger30(Optional) CalDAV event advance notice
check_interval_secsInteger60(Optional) How often to check for due reminders
caldav_sync_interval_minutesInteger15(Optional) CalDAV sync frequency
morning_briefing_timeString07:00(Optional) Morning briefing time (HH:MM)
morning_briefing_enabledBooleantrue(Optional) Enable daily morning briefing

Model Selector (Deprecated)

Deprecated since v0.6.0: Use [model_routing] instead. See Adaptive Model Routing.

The old [model_selector] section with small_model / large_model is still accepted but will be removed in a future release.


Adaptive Model Routing

Routes requests to different LLM models based on complexity. See the dedicated Adaptive Model Routing page for full documentation.

[model_routing]
enabled = true

[model_routing.models]
trivial = "template"
simple = "gemma3:1b"
moderate = "gemma3:4b"
complex = "gemma3:12b"
OptionTypeDefaultDescription
enabledBooleanfalseEnable adaptive routing
models.trivialString"template"Model for trivial tier (usually "template")
models.simpleString"gemma3:1b"Small model for simple queries
models.moderateString"gemma3:4b"Medium model for moderate queries
models.complexString"gemma3:12b"Large model for complex queries
classification.confidence_thresholdFloat0.6Below this, upgrade tier

Telemetry

[telemetry]
# Enable OpenTelemetry export
enabled = false

# OTLP endpoint (Tempo, Jaeger)
# otlp_endpoint = "http://localhost:4317"

# Sampling ratio (0.0-1.0, 1.0 = all traces)
# sample_ratio = 1.0

# Service name for traces
# service_name = "pisovereign"

# Log level filter (e.g., "info", "debug", "pisovereign=debug,tower_http=info")
# log_filter = "pisovereign=info,tower_http=info"

# Batch export timeout in seconds
# export_timeout_secs = 30

# Maximum batch size for trace export
# max_batch_size = 512

# Graceful fallback to console-only logging if OTLP collector is unavailable.
# When true (default), the application starts with console logging if the collector
# cannot be reached. Set to false to require a working collector in production.
# graceful_fallback = true
OptionTypeDefaultDescription
enabledBooleanfalseEnable OpenTelemetry export
otlp_endpointStringhttp://localhost:4317(Optional) OTLP collector endpoint
sample_ratioFloat1.0(Optional) Trace sampling ratio (0.0-1.0)
service_nameStringpisovereign(Optional) Service name for traces
log_filterStringpisovereign=info,tower_http=info(Optional) Log level filter
export_timeout_secsInteger30(Optional) Batch export timeout
max_batch_sizeInteger512(Optional) Max batch size for export
graceful_fallbackBooleantrue(Optional) Fallback to console logging if collector unavailable

Resilience

Degraded Mode

[degraded_mode]
# Enable fallback when backend unavailable
enabled = true

# Message returned during degraded mode
unavailable_message = "I'm currently experiencing technical difficulties. Please try again in a moment."

# Cooldown before retrying primary backend (seconds)
retry_cooldown_secs = 30

# Number of failures before entering degraded mode
failure_threshold = 3

# Number of successes required to exit degraded mode
success_threshold = 2
OptionTypeDefaultDescription
enabledBooleantrueEnable degraded mode fallback
unavailable_messageStringSee aboveMessage returned during degraded mode
retry_cooldown_secsInteger30Cooldown before retrying primary backend
failure_thresholdInteger3Failures before entering degraded mode
success_thresholdInteger2Successes to exit degraded mode

Retry Configuration

Exponential backoff for retrying failed requests.

[retry]
# Initial delay before first retry in milliseconds
initial_delay_ms = 100

# Maximum delay between retries in milliseconds
max_delay_ms = 10000

# Multiplier for exponential backoff (delay = initial * multiplier^attempt)
multiplier = 2.0

# Maximum number of retry attempts
max_retries = 3
OptionTypeDefaultDescription
initial_delay_msInteger100Initial retry delay (milliseconds)
max_delay_msInteger10000Maximum retry delay (milliseconds)
multiplierFloat2.0Exponential backoff multiplier
max_retriesInteger3Maximum retry attempts

Formula: delay = min(initial_delay * multiplier^attempt, max_delay)


Health Checks

[health]
# Global timeout for all health checks in seconds
global_timeout_secs = 5

# Service-specific timeout overrides (uncomment to customize):
# inference_timeout_secs = 10
# email_timeout_secs = 5
# calendar_timeout_secs = 5
# weather_timeout_secs = 5
OptionTypeDefaultDescription
global_timeout_secsInteger5Global timeout for all health checks
inference_timeout_secsInteger5(Optional) Inference service timeout override
email_timeout_secsInteger5(Optional) Email service timeout override
calendar_timeout_secsInteger5(Optional) Calendar service timeout override
weather_timeout_secsInteger5(Optional) Weather service timeout override

Event Bus

The in-process event bus decouples post-processing from the user-facing response path. When enabled, background handlers asynchronously handle fact extraction, audit logging, conversation persistence verification, and metrics collection — reducing perceived latency by 100–500 ms per request.

[events]
# Enable or disable the event bus (default: true)
enabled = true

# Broadcast channel buffer capacity (default: 1024)
# Increase if handlers can't keep up under high load.
channel_capacity = 1024

# Error handling policy: "log" or "retry" (default: "log", reserved for future use)
# handler_error_policy = "log"

# Retry settings (reserved for future use)
# max_retry_attempts = 3
# retry_delay_ms = 500
OptionTypeDefaultDescription
enabledBooleantrueEnable or disable the event bus
channel_capacityInteger1024Broadcast channel buffer size. Values 256–4096 suit most workloads
handler_error_policyString"log"(Reserved) "log" = log-and-continue, "retry" = retry with backoff
max_retry_attemptsInteger3(Reserved) Max retries when policy is "retry"
retry_delay_msInteger500(Reserved) Base delay between retries in milliseconds

Background handlers spawned automatically:

HandlerRequiresPurpose
FactExtractionHandlerMemory contextExtracts structured facts from conversations via LLM
AuditLogHandlerDatabaseRecords audit trail entries for chat/command/security events
ConversationPersistenceHandlerConversation storeVerifies conversation integrity after each interaction
MetricsHandler(always)Feeds event data into the metrics collector

Tip: Set enabled = false to disable all background processing and fall back to synchronous inline behavior.


Agentic Mode

Multi-agent orchestration for complex tasks. When enabled, the system decomposes complex user requests into parallel sub-tasks, each handled by an independent AI agent.

Note: Requires [agent.tool_calling] enabled = true to be set.

[agentic]
# Enable agentic mode (default: false)
enabled = false

# Maximum concurrent sub-agents running in parallel
max_concurrent_sub_agents = 4

# Maximum sub-agents spawned per task
max_sub_agents_per_task = 10

# Total timeout for the entire agentic task (minutes)
total_timeout_minutes = 30

# Timeout for each individual sub-agent (minutes)
sub_agent_timeout_minutes = 10

# Operations that require user approval before execution
# Example: ["send_email", "delete_contact", "execute_code"]
require_approval_for = []
OptionTypeDefaultDescription
enabledBooleanfalseEnable agentic multi-agent orchestration
max_concurrent_sub_agentsInteger4Max sub-agents running in parallel
max_sub_agents_per_taskInteger10Max sub-agents per task
total_timeout_minutesInteger30Total task timeout (minutes)
sub_agent_timeout_minutesInteger10Per sub-agent timeout (minutes)
require_approval_forArray[]Operations requiring user approval

Vault Integration

[vault]
# Vault server address
# address = "http://127.0.0.1:8200"

# AppRole authentication (recommended)
# role_id = "your-role-id"
# secret_id = "your-secret-id"

# Or token authentication
# token = "hvs.your-token"

# KV engine mount path
# mount_path = "secret"

# Request timeout in seconds
# timeout_secs = 5

# Vault Enterprise namespace (optional)
# namespace = "admin/pisovereign"
OptionTypeDefaultDescription
addressStringhttp://127.0.0.1:8200(Optional) Vault server address
role_idString-(Optional) AppRole role ID (recommended)
secret_idString-(Optional) AppRole secret ID
tokenString-(Optional) Vault token (alternative to AppRole)
mount_pathStringsecret(Optional) KV engine mount path
timeout_secsInteger5(Optional) Request timeout
namespaceString-(Optional) Vault Enterprise namespace

Environment Variables

All configuration options can be set via environment variables. Use __ (double underscore) as the nesting separator to avoid conflicts with field names containing underscores (e.g., phone_number):

Config PathEnvironment Variable
server.portPISOVEREIGN_SERVER__PORT
inference.base_urlPISOVEREIGN_INFERENCE__BASE_URL
signal.phone_numberPISOVEREIGN_SIGNAL__PHONE_NUMBER
database.pathPISOVEREIGN_DATABASE__PATH
vault.addressPISOVEREIGN_VAULT__ADDRESS

Special variables:

VariableDescription
PISOVEREIGN_ALLOW_INSECURE_CONFIGAllow insecure settings in production
RUST_LOGLog level override

Example Configurations

Development

environment = "development"

[server]
host = "127.0.0.1"
port = 3000
log_format = "text"

[inference]
base_url = "http://localhost:11434"
default_model = "qwen2.5:1.5b"

[database]
path = "./dev.db"

[cache]
enabled = false  # Disable for debugging

[security]
rate_limit_enabled = false
tls_verify_certs = false

Production

environment = "production"

[server]
host = "127.0.0.1"  # Behind reverse proxy
port = 3000
log_format = "json"
cors_enabled = true
allowed_origins = ["https://app.example.com"]

[inference]
base_url = "http://localhost:11434"
default_model = "qwen2.5:1.5b"
timeout_ms = 120000

[database]
path = "/var/lib/pisovereign/pisovereign.db"
max_connections = 10

[security]
rate_limit_enabled = true
rate_limit_rpm = 30
min_tls_version = "1.3"

[prompt_security]
enabled = true
sensitivity = "high"
block_on_detection = true

[vault]
address = "https://vault.internal:8200"
role_id = "..."
mount_path = "secret"

[telemetry]
enabled = true
otlp_endpoint = "http://tempo:4317"
sample_ratio = 0.1

Minimal (Quick Start)

environment = "development"

[server]
port = 3000

[inference]
base_url = "http://localhost:11434"
default_model = "qwen2.5:1.5b"

[database]
path = "pisovereign.db"