Zenii Configuration Reference
Table of Contents
File Location
Zenii uses the directories crate with the reverse-domain identifier com.sprklai.zenii to determine platform-correct paths:
| Platform | Config File Path |
|---|
| Linux | ~/.config/zenii/config.toml |
| macOS | ~/Library/Application Support/com.sprklai.zenii/config.toml |
| Windows | %APPDATA%\sprklai\zenii\config\config.toml |
Data files (databases, identity, skills) default to:
| Platform | Data Directory |
|---|
| Linux | ~/.local/share/zenii/ |
| macOS | ~/Library/Application Support/com.sprklai.zenii/ |
| Windows | %APPDATA%\sprklai\zenii\data\ |
If the config file does not exist on startup, Zenii uses all default values.
Configuration Sections
All fields use serde(default), so any field can be omitted to use its default value. The config file format is TOML.
Gateway
| Field | Type | Default | Description |
|---|
gateway_host | String | "127.0.0.1" | IP address the gateway listens on |
gateway_port | u16 | 18981 | Port the gateway listens on |
gateway_auth_token | Option<String> | null | Bearer token for API authentication. If unset, auth is disabled |
gateway_cors_origins | Vec<String> | ["http://localhost:18971", "tauri://localhost", "https://tauri.localhost"] | Allowed CORS origins. ["*"] enables permissive CORS; [] denies all cross-origin requests |
ws_max_connections | usize | 32 | Maximum concurrent WebSocket connections |
event_bus_capacity | usize | 256 | Capacity of the tokio broadcast event bus channel |
gateway_host = "127.0.0.1"
gateway_port = 18981
gateway_auth_token = "my-secret-token"
gateway_cors_origins = ["http://localhost:18971"]
ws_max_connections = 32
event_bus_capacity = 256
Database
| Field | Type | Default | Description |
|---|
data_dir | Option<String> | Platform default (see above) | Root directory for all data files |
db_path | Option<String> | {data_dir}/zenii.db | Path to main SQLite database (app + FTS5) |
memory_db_path | Option<String> | {data_dir}/memory_vec.db | Path to vector memory SQLite database (sqlite-vec) |
session_max_age_days | u32 | 90 | Days before old sessions are automatically cleaned up on boot |
data_dir = "/home/user/.zenii"
db_path = "/home/user/.zenii/zenii.db"
memory_db_path = "/home/user/.zenii/memory_vec.db"
session_max_age_days = 90
Memory
| Field | Type | Default | Description |
|---|
memory_fts_weight | f32 | 0.4 | Weight for FTS5 full-text search scoring (0.0-1.0) |
memory_vector_weight | f32 | 0.6 | Weight for vector similarity scoring (0.0-1.0) |
memory_default_limit | usize | 10 | Default number of results for memory recall queries |
embedding_dim | usize | 384 | Dimensionality of embedding vectors |
embedding_cache_size | usize | 1000 | Number of embeddings to cache in memory |
memory_bm25_key_weight | f64 | 2.0 | BM25 field weight for the key column in FTS scoring |
memory_bm25_content_weight | f64 | 1.0 | BM25 field weight for the content column in FTS scoring |
memory_bm25_category_weight | f64 | 0.5 | BM25 field weight for the category column in FTS scoring |
memory_decay_enabled | bool | true | Apply temporal decay to recall scores |
memory_decay_lambda | f32 | 0.01 | Decay rate λ in exp(-λ × days_since_update); default ≈ 70-day half-life |
memory_dedup_enabled | bool | true | Check for near-duplicate memories before storing (requires embedding provider) |
memory_dedup_threshold | f32 | 0.92 | Cosine similarity threshold above which an incoming store is treated as a duplicate |
memory_fts_weight = 0.4
memory_vector_weight = 0.6
memory_default_limit = 10
embedding_dim = 384
embedding_cache_size = 1000
memory_bm25_key_weight = 2.0
memory_bm25_content_weight = 1.0
memory_bm25_category_weight = 0.5
memory_decay_enabled = true
memory_decay_lambda = 0.01
memory_dedup_enabled = true
memory_dedup_threshold = 0.92
Security
| Field | Type | Default | Description |
|---|
security_autonomy_level | String | "full" | Agent autonomy level (supervised, semi-autonomous, full) |
max_tool_retries | u32 | 3 | Maximum retry attempts for failed tool executions |
security_rate_limit_max | u32 | 60 | Maximum requests per rate limit window |
security_rate_limit_window_secs | u64 | 60 | Rate limit window duration in seconds |
security_audit_log_capacity | usize | 1000 | Maximum number of audit log entries in memory |
security_autonomy_level = "full"
max_tool_retries = 3
security_rate_limit_max = 60
security_rate_limit_window_secs = 60
security_audit_log_capacity = 1000
AI Agent
| Field | Type | Default | Description |
|---|
provider_name | String | "anthropic" | Default AI provider name. Alias: default_provider |
provider_type | String | "anthropic" | Provider type (used for API compatibility) |
provider_base_url | Option<String> | null | Custom base URL for the provider API |
provider_model_id | String | "claude-sonnet-4-6" | Default model ID. Alias: default_model |
provider_api_key_env | Option<String> | null | Environment variable name for the API key |
agent_max_turns | usize | 8 | Maximum agent turns (tool call loops) per request. Range: 1-32 |
agent_max_tokens | usize | 4096 | Maximum tokens for agent responses |
agent_timeout_secs | u64 | 300 | Maximum seconds for agent execution before timeout. WebSocket chat aborts the agent task and returns an error on timeout |
agent_system_prompt | Option<String> | null | Additional system prompt appended to identity (never replaces it) |
provider_name = "anthropic"
provider_type = "anthropic"
provider_model_id = "claude-sonnet-4-6"
provider_api_key_env = "ANTHROPIC_API_KEY"
agent_max_turns = 8
agent_max_tokens = 4096
agent_timeout_secs = 300
agent_system_prompt = "Always respond concisely."
Identity
| Field | Type | Default | Description |
|---|
identity_name | String | "Zenii" | Display name of the AI assistant |
identity_description | String | "AI-powered assistant" | Short description of the assistant |
identity_dir | Option<String> | {data_dir}/identity/ | Directory containing identity/persona markdown files |
identity_name = "Zenii"
identity_description = "AI-powered assistant"
identity_dir = "/home/user/.zenii/identity"
Skills
| Field | Type | Default | Description |
|---|
skills_dir | Option<String> | {data_dir}/skills/ | Directory containing skill definition files |
skill_max_content_size | usize | 100000 | Maximum size in bytes for a skill's content |
skill_proposal_expiry_days | u32 | 7 | Days before pending skill proposals expire |
skills_dir = "/home/user/.zenii/skills"
skill_max_content_size = 100000
skill_proposal_expiry_days = 7
User Learning
| Field | Type | Default | Description |
|---|
learning_enabled | bool | true | Whether the user learning system is active |
learning_denied_categories | Vec<String> | [] | Categories of observations the system must not learn |
learning_max_observations | usize | 10000 | Maximum number of stored user observations |
learning_observation_ttl_days | u32 | 365 | Days before observations expire |
learning_min_confidence | f32 | 0.5 | Minimum confidence threshold to store an observation |
learning_enabled = true
learning_denied_categories = ["medical", "financial"]
learning_max_observations = 10000
learning_observation_ttl_days = 365
learning_min_confidence = 0.5
| Field | Type | Default | Description |
|---|
tool_shell_timeout_secs | u64 | 30 | Timeout in seconds for shell command execution |
tool_file_read_max_lines | usize | 10000 | Maximum lines to read from a file |
tool_file_search_max_results | usize | 100 | Maximum results for file search operations |
tool_process_list_limit | usize | 200 | Maximum number of processes to list |
tool_shell_timeout_secs = 30
tool_file_read_max_lines = 10000
tool_file_search_max_results = 100
tool_process_list_limit = 200
Web Search
| Field | Type | Default | Description |
|---|
web_search_timeout_secs | u64 | 30 | Timeout for web search requests |
web_search_max_results | usize | 20 | Maximum number of web search results |
web_search_timeout_secs = 30
web_search_max_results = 20
Context Injection
| Field | Type | Default | Description |
|---|
context_injection_enabled | bool | true | Whether context injection into agent prompts is active |
context_summary_model_id | String | "gpt-4o-mini" | Model used for generating conversation summaries |
context_summary_provider_id | String | "openai" | Provider used for summary generation |
context_reinject_gap_minutes | u32 | 30 | Minutes of inactivity before reinjecting full context |
context_reinject_message_count | u32 | 20 | Number of messages before triggering context reinjection |
context_injection_enabled = true
context_summary_model_id = "gpt-4o-mini"
context_summary_provider_id = "openai"
context_reinject_gap_minutes = 30
context_reinject_message_count = 20
Prompt Strategy
| Field | Type | Default | Description |
|---|
prompt_compact_identity | bool | true | Use compact axiom-based preamble instead of verbose prose. Reduces token usage by ~60-80% while maintaining response quality |
prompt_max_preamble_tokens | usize | 1500 | Token budget for system preamble. Overflow trims lowest-priority dynamic context |
prompt_compact_identity = true
prompt_max_preamble_tokens = 1500
When prompt_compact_identity is true (default), Zenii uses a 4-layer compact format:
- Layer 0: Core identity (~80 tokens) -- name, version, location, OS, capabilities
- Layer 1: Runtime state (~60 tokens) -- date, model, session, compact reasoning axioms
- Layer 2: Dynamic context (variable) -- memories, user observations, skills, domain-specific details
- Layer 3: Overrides -- custom system prompt, conversation summary
When false, the legacy verbose prose mode is used (PromptComposer + ContextEngine).
The token budget (prompt_max_preamble_tokens) acts as overflow protection. When the assembled preamble exceeds the budget, lowest-priority dynamic context fragments are trimmed first.
Context Management
| Field | Type | Default | Description |
|---|
context_strategy | String | "balanced" | Context assembly strategy (minimal, balanced, full) |
context_max_history_messages | usize | 20 | Maximum conversation history messages to include in context |
context_max_memory_results | usize | 5 | Maximum memory recall results to include in context |
context_auto_extract | bool | true | Whether to automatically extract key facts from conversations |
context_extract_interval | usize | 3 | Extract facts every N messages |
context_summary_model | String | "" | Override model for context summarization (empty uses default) |
context_strategy = "balanced"
context_max_history_messages = 20
context_max_memory_results = 5
context_auto_extract = true
context_extract_interval = 3
context_summary_model = ""
Embeddings
| Field | Type | Default | Description |
|---|
embedding_provider | String | "none" | Embedding provider type: none (FTS5 only), openai, or local (FastEmbed) |
embedding_model | String | "BAAI/bge-small-en-v1.5" | Model ID for embedding generation |
embedding_download_dir | Option<String> | null | Directory for local embedding model downloads (defaults to data dir) |
embedding_provider = "local"
embedding_model = "BAAI/bge-small-en-v1.5"
Reasoning
| Field | Type | Default | Description |
|---|
agent_max_continuations | usize | 1 | Maximum autonomous continuation turns for the reasoning engine |
tool_dedup_enabled | bool | true | Deduplicate identical tool calls within a single request. Uses a per-request cache keyed by hash(tool_name + args) |
agent_reasoning_guidance | Option<String> | null | Custom reasoning instructions appended to agent system prompt |
agent_max_continuations = 1
tool_dedup_enabled = true
agent_reasoning_guidance = "Think step by step before taking actions."
Plugins
| Field | Type | Default | Description |
|---|
plugins_dir | Option<String> | {data_dir}/plugins/ | Directory containing installed plugins |
plugin_idle_timeout_secs | u64 | 300 | Seconds before idle plugin processes are stopped |
plugin_max_restart_attempts | u32 | 3 | Maximum restart attempts for crashed plugin processes |
plugin_execute_timeout_secs | u64 | 60 | Timeout for plugin tool execution |
plugin_auto_update | bool | false | Whether to auto-update plugins on boot |
plugin_idle_timeout_secs = 300
plugin_max_restart_attempts = 3
plugin_execute_timeout_secs = 60
plugin_auto_update = false
Risk-based, per-surface tool permission system. See Architecture: Tool Permission System for details.
| Field | Type | Default | Description |
|---|
tool_permissions.low_risk_default | String | "allowed" | Default permission for low-risk tools |
tool_permissions.medium_risk_default | String | "allowed" | Default permission for medium-risk tools |
tool_permissions.high_risk_default | String | "denied" | Default permission for high-risk tools |
tool_permissions.overrides | HashMap | desktop/cli/tui: all high-risk allowed | Per-surface, per-tool overrides |
[tool_permissions]
low_risk_default = "allowed"
medium_risk_default = "allowed"
high_risk_default = "denied"
[tool_permissions.overrides.telegram]
memory = "denied"
web_search = "allowed"
[tool_permissions.overrides.desktop]
shell = "allowed"
file_read = "allowed"
file_write = "allowed"
Permission states: allowed, denied, ask_once (future), ask_always (future).
Channels
| Field | Type | Default | Description |
|---|
channels_enabled | Vec<String> | [] | List of channel names to enable on startup |
channel_tool_policy | HashMap<String, Vec<String>> | {} | Legacy per-channel tool allowlists (superseded by tool_permissions) |
telegram_polling_timeout_secs | u32 | 30 | Telegram long-polling timeout |
telegram_dm_policy | String | "allowlist" | Telegram DM policy (allowlist, open, deny) |
telegram_retry_min_ms | u64 | 1000 | Minimum retry delay for Telegram API errors (milliseconds) |
telegram_retry_max_ms | u64 | 60000 | Maximum retry delay for Telegram API errors (milliseconds) |
telegram_require_group_mention | bool | true | Whether the bot must be @mentioned in group chats to respond |
channels_enabled = ["telegram", "slack"]
telegram_polling_timeout_secs = 30
telegram_dm_policy = "allowlist"
telegram_retry_min_ms = 1000
telegram_retry_max_ms = 60000
telegram_require_group_mention = true
Scheduler
| Field | Type | Default | Description |
|---|
scheduler_tick_interval_secs | u64 | 1 | How often the scheduler checks for due jobs (seconds) |
scheduler_stuck_threshold_secs | u64 | 120 | Seconds before a running job is considered stuck |
scheduler_error_backoff_secs | Vec<u64> | [30, 60, 300, 900, 3600] | Exponential backoff delays for failed jobs (seconds) |
scheduler_max_history_per_job | usize | 100 | Maximum execution history entries per job |
scheduler_agent_turn_timeout_secs | u64 | 120 | Timeout for agent turns within scheduled jobs |
scheduler_heartbeat_file | Option<String> | null | Path to heartbeat file (updated each tick for external monitoring) |
scheduler_tick_interval_secs = 1
scheduler_stuck_threshold_secs = 120
scheduler_error_backoff_secs = [30, 60, 300, 900, 3600]
scheduler_max_history_per_job = 100
scheduler_agent_turn_timeout_secs = 120
scheduler_heartbeat_file = "/tmp/zenii-heartbeat"
Credentials
| Field | Type | Default | Description |
|---|
keyring_service_id | String | "com.sprklai.zenii" | OS keyring service identifier for credential storage |
credential_file_path | Optional<String> | None (auto: {data_dir}/credentials.enc) | Override path for the encrypted credential file (used when OS keyring is unavailable) |
keyring_service_id = "com.sprklai.zenii"
Self-Evolution
| Field | Type | Default | Description |
|---|
self_evolution_enabled | bool | true | Whether the self-evolution system (skill proposals) is active |
learning_archive_threshold | f64 | 0.3 | Confidence threshold below which observations are archived |
learning_archive_after_days | u32 | 30 | Days before low-confidence observations are archived |
self_evolution_enabled = true
learning_archive_threshold = 0.3
learning_archive_after_days = 30
User Profile
| Field | Type | Default | Description |
|---|
user_name | Option<String> | null | User's display name (e.g., "John"). Used in greetings and personalization |
user_timezone | Option<String> | null | IANA timezone (e.g., "America/New_York"). Auto-detected on first run |
user_location | Option<String> | null | Location/region description (e.g., "New York, US"). Used for context injection |
user_name = "John"
user_timezone = "America/New_York"
user_location = "New York, US"
MCP Client
Configure external MCP servers whose tools are automatically registered into the agent's ToolRegistry at startup. Requires --features mcp-client.
Each entry in mcp_client_servers uses an array-of-tables ([[mcp_client_servers]]) and supports two transport types: stdio (launch a local subprocess) and http (connect to a remote HTTP/SSE endpoint).
| Field | Type | Default | Description |
|---|
id | String | — | Unique identifier for this MCP server |
transport | McpTransport | — | How to connect: stdio or http (see below) |
tools_prefix | Option<String> | null | Optional prefix prepended to every tool name (e.g. "github/") |
enabled | bool | true | Whether this server is connected at startup |
Stdio transport fields (type = "stdio"):
| Field | Type | Description |
|---|
command | String | Executable to launch |
args | Vec<String> | Arguments passed to the command |
env | HashMap<String, String> | Additional environment variables |
HTTP transport fields (type = "http"):
| Field | Type | Description |
|---|
url | String | URL of the remote MCP server |
headers | HashMap<String, String> | Additional HTTP headers (e.g. auth) |
[[mcp_client_servers]]
id = "github"
tools_prefix = "github/"
enabled = true
[mcp_client_servers.transport]
type = "stdio"
command = "npx"
args = ["-y", "@modelcontextprotocol/server-github"]
[[mcp_client_servers]]
id = "my-remote"
enabled = true
[mcp_client_servers.transport]
type = "http"
url = "https://example.com/mcp"
Logging
| Field | Type | Default | Description |
|---|
log_level | String | "info" | Log level for the tracing framework (trace, debug, info, warn, error) |
log_dir | String | "" (platform default) | Override log directory. Empty uses {data_dir}/logs/ |
log_keep_days | u32 | 30 | Days to retain log files before automatic cleanup |
usage_tracking_enabled | bool | true | Enable date-rotated JSONL usage tracking |
All binaries write daily-rotated diagnostic logs to the OS-appropriate data directory:
| OS | Log Directory |
|---|
| Linux | ~/.local/share/zenii/logs/ |
| macOS | ~/Library/Application Support/com.sprklai.zenii/logs/ |
| Windows | C:\Users\{user}\AppData\Roaming\sprklai\zenii\logs\ |
Log files per binary: daemon.log.YYYY-MM-DD, desktop.log.YYYY-MM-DD, cli.log.YYYY-MM-DD, tui.log.YYYY-MM-DD. Old files are automatically cleaned up based on log_keep_days.
Environment Variable Overrides
| Variable | Description | Maps To |
|---|
ZENII_TOKEN | Gateway authentication token | gateway_auth_token |
ZENII_GATEWAY_URL | Gateway URL override (used by CLI and desktop app to connect to an external daemon instead of starting an embedded one) | N/A (runtime override, not a config field) |
Environment variables take precedence over config file values when supported.
Feature Flag Impact
Some configuration fields are only relevant when specific feature flags are enabled at compile time:
| Feature Flag | Relevant Config Fields |
|---|
local-embeddings | embedding_provider (when set to "local"), embedding_model, embedding_download_dir |
channels | channels_enabled, tool_permissions (channel surface overrides) |
channels-telegram | telegram_polling_timeout_secs, telegram_dm_policy, telegram_retry_min_ms, telegram_retry_max_ms, telegram_require_group_mention |
channels-slack | (uses tool_permissions for Slack surface overrides) |
channels-discord | (uses tool_permissions for Discord surface overrides) |
scheduler | scheduler_tick_interval_secs, scheduler_stuck_threshold_secs, scheduler_error_backoff_secs, scheduler_max_history_per_job, scheduler_agent_turn_timeout_secs, scheduler_heartbeat_file |
Fields can always be set in the config file regardless of feature flags -- they are simply ignored at runtime if the corresponding feature is not compiled in.
Example Full Config
gateway_host = "127.0.0.1"
gateway_port = 18981
gateway_auth_token = "my-secret-token"
gateway_cors_origins = ["http://localhost:18971"]
ws_max_connections = 32
event_bus_capacity = 256
log_level = "info"
session_max_age_days = 90
provider_name = "anthropic"
provider_model_id = "claude-sonnet-4-6"
agent_max_turns = 8
agent_max_tokens = 4096
agent_timeout_secs = 300
identity_name = "Zenii"
identity_description = "AI-powered assistant"
memory_fts_weight = 0.4
memory_vector_weight = 0.6
memory_default_limit = 10
embedding_dim = 384
memory_bm25_key_weight = 2.0
memory_bm25_content_weight = 1.0
memory_bm25_category_weight = 0.5
memory_decay_enabled = true
memory_decay_lambda = 0.01
memory_dedup_enabled = true
memory_dedup_threshold = 0.92
security_autonomy_level = "full"
max_tool_retries = 3
security_rate_limit_max = 60
security_rate_limit_window_secs = 60
tool_shell_timeout_secs = 30
tool_file_read_max_lines = 10000
web_search_timeout_secs = 30
web_search_max_results = 20
embedding_provider = "none"
embedding_model = "BAAI/bge-small-en-v1.5"
agent_max_continuations = 1
tool_dedup_enabled = true
context_injection_enabled = true
context_strategy = "balanced"
context_max_history_messages = 20
context_auto_extract = true
learning_enabled = true
learning_max_observations = 10000
learning_min_confidence = 0.5
[tool_permissions]
low_risk_default = "allowed"
medium_risk_default = "allowed"
high_risk_default = "denied"
channels_enabled = []
telegram_dm_policy = "allowlist"
scheduler_tick_interval_secs = 1
keyring_service_id = "com.sprklai.zenii"
self_evolution_enabled = true
skill_proposal_expiry_days = 7