Skip to content

Configuration reference

Complete reference for config/config.toml. See config/config.example.toml in the repository for a commented example.

Top-level settings

Setting Default Description
bot_name "Istota" User-facing name (chat, emails, folder names)
emissaries_enabled true Include emissaries.md in system prompts
model "" Claude model override (empty = CLI default). Pin to a version like "claude-opus-4-8".
effort "" Effort level: low, medium, high, xhigh, or max (empty = model default)
custom_system_prompt false Use config/system-prompt.md instead of Claude Code default
db_path "data/istota.db" SQLite database path
rclone_remote "nextcloud" rclone remote name
nextcloud_mount_path not set Local mount path (enables mount mode when set)
skills_dir "config/skills" Operator skill overrides directory
disabled_skills [] Instance-wide skills to exclude
temp_dir "/tmp/istota" Temporary directory for task execution
max_memory_chars 0 Cap total memory in prompts (0 = unlimited)
max_knowledge_facts 50 Cap knowledge graph facts per prompt (0 = unlimited)

[nextcloud]

Setting Default Description
url "" Nextcloud server URL
username "" Bot's Nextcloud username
app_password "" Nextcloud app password

[talk]

Setting Default Description
enabled true Enable Talk polling
bot_username "istota" Bot's username (to filter own messages)

[email]

Setting Default Description
enabled false Enable email
imap_host "" IMAP server
imap_port 993 IMAP port
imap_user "" IMAP username
imap_password "" IMAP password
smtp_host "" SMTP server
smtp_port 587 SMTP port
smtp_user "" SMTP username (defaults to imap_user)
smtp_password "" SMTP password (defaults to imap_password)
poll_folder "INBOX" Folder to poll
bot_email "" Bot's email address
confirm_sender_match true Require confirmation for sender-match routed emails

[conversation]

Setting Default Description
enabled true Enable conversation context
lookback_count 25 Messages to consider
skip_selection_threshold 3 Include all if history <= this
selection_model "fast" Role alias for relevance matching (resolves to Haiku by default)
selection_timeout 30.0 Timeout for selection
use_selection true Use LLM selection
always_include_recent 5 Always include this many recent
context_truncation 0 Max chars per bot response (0 = no limit)
context_recency_hours 0 Exclude old messages (0 = disabled)
context_min_messages 10 Min messages when recency filtering
previous_tasks_count 3 Unfiltered tasks to inject
talk_context_limit 100 Messages from Talk API

[logging]

Setting Default Description
level "INFO" Log level (INFO or DEBUG)
output "console" Destination: console, file, or both
file "" Log file path
rotate true Enable log rotation
max_size_mb 10 Max log file size
backup_count 5 Rotated files to keep

[scheduler]

Polling intervals

Setting Default Description
poll_interval 2 Seconds between queue checks
dispatch_interval 0.5 Sub-tick cadence for pool.dispatch() within a poll tick — bounds cold pending-task pickup latency. 0 or ≥ poll_interval = legacy one-dispatch-per-tick
talk_poll_interval 10 Seconds between Talk polls
talk_poll_timeout 30 Talk long-poll timeout
talk_poll_wait 2.0 Max wait before processing available rooms
email_poll_interval 60 Seconds between email polls
briefing_check_interval 60 Seconds between briefing/job/cleanup checks
tasks_file_poll_interval 30 Seconds between TASKS.md polls
shared_file_check_interval 120 Seconds between shared file checks
heartbeat_check_interval 60 Seconds between heartbeat checks
db_health_check_interval 86400 Seconds between SQLite quick_check + self-heal REINDEX sweeps over framework + per-user DBs (24h)
scheduler_stats_interval 60 Seconds between scheduler_stats health-line emits (threads / fds / rss / running-tasks / active-workers) — one key=value INFO line per interval on the istota.scheduler.stats logger, for catching resource leaks early. 0 disables
loop_stall_alert_seconds 180 Defense-in-depth: a watchdog thread logs an ERROR and fires one operator alert if the single-threaded main dispatch loop hasn't ticked in this long (a slow call that slipped onto the loop thread, a wedged check), then re-arms when the loop recovers. Suspended around known multi-minute in-loop work (sleep cycles, DB-health sweep) to avoid false pages. 0 disables

Progress & event streaming

One persisted, typed event stream per task (the task_events table) feeds Talk, the web SSE endpoint, the log channel, and push notifications.

Setting Default Description
progress_updates true Master toggle for Talk progress updates
progress_show_tool_use true Emit tool_start / tool_end events
progress_show_text false Emit progress_text events (intermediate text; noisy)
event_log_enabled true Write events to the task_events table (kill-switch for task-event-streaming)
stream_text_gate_chars 280 Narration gate for streamed answer text on stream surfaces (web/REPL). A text run emits no text_delta until it crosses this many chars without an intervening tool call, so short lead-in narration ("Let me check…") is discarded at the tool boundary instead of leaking into the answer area. Never loses text — only animation. 0 disables
push_notification_threshold_seconds 30 Min task duration before an ntfy completion push fires
push_notification_sources [] Source types that trigger a completion push; empty = ntfy opt-in only (never a default surface)

Worker pool

Setting Default Description
max_foreground_workers 5 Instance-level fg worker cap
max_background_workers 3 Instance-level bg worker cap
user_max_foreground_workers 2 Global per-user fg default
user_max_background_workers 1 Global per-user bg default
worker_idle_timeout 10 Seconds before idle worker exits

Robustness

Setting Default Description
task_timeout_minutes 30 Claude Code execution timeout
confirmation_timeout_minutes 120 Auto-cancel confirmations after
stale_pending_warn_minutes 30 Warn for long-pending tasks
stale_pending_fail_hours 2 Auto-fail ancient tasks
worker_heartbeat_seconds 60 How often a running worker pings liveness (0 disables). Stuck-task reclaim uses the heartbeat to tell a slow-but-alive worker from a dead one.
worker_stuck_minutes 10 Reclaim a heartbeating worker's task after this much heartbeat silence. Independent of task_timeout_minutes.
task_retention_days 7 Delete old completed tasks
email_retention_days 7 Delete old IMAP emails (0 = disable)
talk_cache_max_per_conversation 200 Max cached Talk messages
scheduled_job_max_consecutive_failures 5 Auto-disable threshold
cron_max_staleness_minutes 60 Skip cron-driven catch-up fires older than this (jobs + briefings). After a long daemon outage, fires missed by more than N minutes are skipped and last_run_at is bumped so the schedule resumes from the next future fire. 0 = legacy unconditional catch-up.
log_channel_show_skills true Include selected skills in log channel messages

[security]

Setting Default Description
sandbox_enabled true Bubblewrap filesystem isolation (Linux only)
sandbox_admin_db_write false Allow admin DB writes in sandbox
skill_proxy_enabled true Credential proxy via Unix socket
skill_proxy_timeout 300 Proxy command timeout (seconds)
passthrough_env_vars ["LANG", "LC_ALL", "LC_CTYPE", "TZ"] Extra env vars for subprocess

[security.network]

Setting Default Description
enabled true Network isolation via CONNECT proxy
allow_pypi true Allow PyPI access
extra_hosts [] Additional allowed hosts

[skills]

There is no [skills] config section. Skill disclosure is single-axis (a skill is either eager or a menu entry the model loads on demand) with no config knobs. A stale [skills] block only logs a warning at load time.

[models.roles]

Provider-agnostic role aliases that map to brain-specific model identifiers. Used by !model <role> <prompt> in Talk and by internal subsystems (fast for triage/classification, general for sleep cycle, smart is user-facing only).

Defaults (when no override is set):

Role Default target
fast Haiku
general Sonnet
smart Opus

Override in config to rebind:

[models.roles]
smart = "opus-46-high"    # pin smart to Opus 4.6
deep  = "opus-max"        # define a custom role

Role aliases never carry effort — smart = "opus-46-high" resolves the model to claude-opus-4-6 only; effort tracks the top-level effort field (or the per-task override) unless the user types the provider alias directly via !model opus-46-high <prompt>. Invalid override targets (neither a known alias nor a canonical claude-* ID) are warned at config-load time via Brain.validate_role_override.

[brain]

Selects which model-invocation backend the executor uses. See architecture/brain for the protocol and the native brain runbook for the full [brain.native] settings.

Setting Default Description
kind "claude_code" Brain implementation. "claude_code" (default) wraps the headless claude -p CLI subprocess; "native" runs Istota's own in-process agent loop against any OpenAI-compatible model (configured under [brain.native]); "tmux_claude" drives the interactive claude TUI in a detached tmux session to keep traffic on subscription billing (configured under [brain.tmux], with automatic fallback to claude_code).
source_type_overrides {} Per-source_type brain override (e.g. route scheduled to native while interactive tasks stay on claude_code).

[brain.native] (used when kind = "native" or a source_type_overrides entry routes to it): provider (only "openai_compat"), model (explicit id), base_url, extra_headers, context_window, max_turns, max_tokens, prompt_caching. The API key comes from ISTOTA_BRAIN_NATIVE_API_KEY, never the TOML file.

[brain.tmux] (used when kind = "tmux_claude" or routed-to): every field defaults in code to the prototype's pinned values, so an absent block is behavioral parity. Knobs include fallback_trip_threshold, fallback_cooldown_seconds, ready_timeout_seconds, tmux_command_timeout, cli_version_pin, and the pane-text marker lists (ready_markers, trust_markers, theme_markers, bypass_warning_marker, bypass_accept_marker, error_markers) — heuristics pinned to a claude CLI version, so a CLI reword that breaks readiness detection is a config hotfix, not a code release. See config.example.toml for the full annotated block.

[sleep_cycle]

Setting Default Description
enabled true Enable nightly memory extraction
cron "0 2 * * *" Schedule (user's timezone)
lookback_hours 24 How far back to gather day data
memory_retention_days 0 Prune dated memory files and ephemeral memory_chunks rows (conversation, memory_file, channel_memory) older than N days. Durable user_memory chunks are not touched. 0 = unlimited
auto_load_dated_days 3 Days of dated memories injected into prompts; 0 disables
curate_user_memory false Run op-based USER.md curation after extraction
curation_log_summary true Post a one-line summary to the user's log_channel after applied curation ops
knowledge_graph_audit_retention_days 365 Prune knowledge_facts_audit rows older than N days. Independent of memory_retention_days. 0 = unlimited

[channel_sleep_cycle]

Setting Default Description
enabled true Enable channel memory extraction
cron "0 3 * * *" Schedule (UTC)
lookback_hours 24 How far back to gather channel data
memory_retention_days 0 Prune dated channel files and channel_memory chunks older than N days. 0 = unlimited
Setting Default Description
enabled true Enable memory search
auto_index_conversations true Index after task completion
auto_index_memory_files true Index after sleep cycle
auto_recall false BM25 auto-recall in prompts
auto_recall_limit 5 Max recall results

[briefing_defaults]

Admin-level defaults expanded when users set markets = true or news = true:

[briefing_defaults.news]
lookback_hours = 12
sources = [
    { type = "domain", value = "semafor.com" },
    { type = "email", value = "briefing@nytimes.com" },
]

[briefing_defaults.headlines]
sources = ["ap", "reuters", "guardian", "ft", "aljazeera", "lemonde", "spiegel"]

[developer]

Setting Default Description
enabled false Enable developer skill
repos_dir "" Base directory for clones + worktrees
gitlab_url "https://gitlab.com" GitLab instance URL
gitlab_token "" API token
gitlab_username "" GitLab username for HTTPS auth
gitlab_default_namespace "" Default namespace for short repo names
gitlab_reviewer_id "" User ID for MR reviewer
github_url "https://github.com" GitHub instance URL
github_token "" Personal access token
github_username "" GitHub username
github_default_owner "" Default org/user for short repo names
github_reviewer "" PR reviewer username

ntfy push notifications

ntfy is a per-user connected service — there is no [ntfy] config block. Each user supplies their own server URL, topic, and (optional) auth via the encrypted secrets table (see credentials for the full per-user credential inventory):

istota secret ensure --user alice --service ntfy --key topic --value alice-alerts
istota secret ensure --user alice --service ntfy --key server_url --value https://ntfy.example.com
istota secret ensure --user alice --service ntfy --key token --value tk_…

Or via the web UI at /istota/settings (Connected services → ntfy push). Priority is hardcoded to 3 (the ntfy default).

What it IS: a one-way push channel (bot → device) used by heartbeat alerts and scheduled-job output (output_target = "ntfy"). What it ISN'T: two-way (no replies), a Talk replacement, operator-shared infrastructure, or required.

Money

There is no instance-level [money] config section. Money is a module (on by default; opt out per user via disabled_modules = ["money"]). The bot auto-discovers *.beancount files at the top level of {user_workspace}/ledgers/ — no per-resource path is required. Monarch credentials are a cookie pair in the encrypted secrets table — provision both keys (session_id and csrftoken) via the CLI or the web settings UI:

istota secret ensure --user alice --service monarch --key session_id --value istota secret ensure --user alice --service monarch --key csrftoken --value 

[google_workspace]

Setting Default Description
enabled false Enable Google Workspace skill
client_id "" Google OAuth client ID
client_secret "" Google OAuth client secret (or ISTOTA_GOOGLE_WORKSPACE_CLIENT_SECRET env var)
scopes Drive, Gmail, Calendar, Sheets, Docs OAuth scopes to request

See Google Workspace for setup instructions.

[site]

Setting Default Description
enabled false Enable static site hosting
hostname "" Public hostname
base_path "" Local directory for site files

[web]

Setting Default Description
enabled false Enable web interface
port 8766 Web app port
oauth2_provider "" Public Nextcloud URL (browser-facing), no trailing slash
oauth2_client_id "" NC OAuth 2.0 client ID
oauth2_client_secret "" NC OAuth 2.0 client secret (or ISTOTA_WEB_OAUTH2_CLIENT_SECRET env)
oauth2_token_endpoint "" Optional server-to-server token URL override
oauth2_userinfo_endpoint "" Optional server-to-server userinfo URL override
oauth2_redirect_uri "" Explicit redirect URI override; otherwise derived from request
session_secret_key "" Session signing key (or ISTOTA_WEB_SESSION_SECRET_KEY env)

[web.chat]

Knobs for the in-app web chat surface (the "Chat" tab). The surface is always enabled when the web UI is on; these tune limits and streaming cadence.

Setting Default Description
max_prompt_chars 32000 Max characters accepted per chat message
max_attachment_mb 25 Max attachment size, in MB
attachment_extensions (image/pdf/text set) Allowed attachment file extensions
rate_limit_messages 30 Messages allowed per user per window
rate_limit_window_seconds 300 Rate-limit window (5 minutes)
sse_poll_interval_ms 200 Server-side task_events poll cadence for the SSE stream
client_poll_interval_ms 1500 Client fallback poll cadence when SSE is unavailable

[location]

Setting Default Description
enabled false Enable GPS webhook receiver
webhooks_port 8765 Receiver port