aidaemon 0.11.3

A personal AI agent that runs as a background daemon, accessible via Telegram, Slack, or Discord, with tool use, MCP integration, and persistent memory
Documentation
# aidaemon configuration
#
# Secrets resolution order:
#   1. "keychain"    → reads from OS credential store (macOS Keychain, etc.)
#   2. "${ENV_VAR}"  → reads from environment variable (for Docker/CI)
#   3. "plain-value" → used as-is (not recommended for production)
#
# The setup wizard stores secrets in the OS keychain automatically.
# Set AIDAEMON_NO_KEYCHAIN=1 to use env-file-backed secrets instead of OS keychain.
# Optional: set AIDAEMON_ENV_FILE=/absolute/path/.env to choose the env file path.
# In no-keychain mode, secret updates (including OAuth token refresh rotation) are written to that env file.

[provider]
# Provider type: openai_compatible, xai_native, google_genai, or anthropic
api_key = "keychain"
# reasoning_effort = "medium"   # extended thinking: "low" | "medium" | "high" | "off".
#   OpenAI-compatible providers send it as `reasoning`; the native Anthropic
#   provider maps it to extended-thinking budget_tokens (2k/8k/16k).
# streaming = false             # opt-in SSE streaming transport (OpenAI-compatible).
#   Same response contract as buffered calls; a stream that dies after partial
#   text is recovered through truncation recovery instead of failing the call.
# api_key = "${AIDAEMON_API_KEY}"
base_url = "https://openrouter.ai/api/v1"
# Optional: Cloudflare AI Gateway authenticated mode
# gateway_token = "keychain"
# # gateway_token = "${AIDAEMON_GATEWAY_TOKEN}"
#
# Moonshot OpenAI-compatible base URL:
# base_url = "https://api.moonshot.ai/v1"
#
# xAI (Grok) native base URL:
# base_url = "https://api.x.ai/v1"
#
# MiniMax OpenAI-compatible base URL:
# base_url = "https://api.minimax.io/v1"
#
# Cloudflare AI Gateway base URL format:
# base_url = "https://gateway.ai.cloudflare.com/v1/<ACCOUNT_ID>/<GATEWAY_ID>/compat"

# Optional: llama.cpp slot routing (local OpenAI-compatible servers only).
# Pins the interactive conversation to a dedicated KV-cache slot so always-on
# background tasks (memory consolidation, summarization, etc.) can't evict it
# between turns — keeping prompt-cache hits warm. Requires llama-server started
# with --parallel >= 2. Leave disabled for cloud APIs (they reject unknown params).
#
# IMPORTANT for sliding-window-attention models (e.g. Gemma): also start
# llama-server with --swa-full, or cross-turn KV reuse won't happen at all —
# llama re-processes the full prompt every turn regardless of prefix stability.
# --swa-full keeps a full-size KV cache for the SWA layers (more KV memory; size
# -c accordingly). Slot routing alone does not fix this; the two work together.
# [provider.slot_routing]
# enabled = false          # opt-in; no id_slot is sent when false
# interactive_slot = 0     # slot the interactive conversation pins to
# background_slot = 1      # slot all background:* calls are routed to

[provider.models]
primary = "openai/gpt-4o"
fast = "openai/gpt-4o-mini"
smart = "anthropic/claude-sonnet-4"

# Optional direct-provider fallback chain. These entries are tried in order
# after the primary provider's own model fallback list is exhausted.
# Keychain secret names use `provider_fallback_<index>_...`, for example:
# `provider_fallback_0_api_key`, `provider_fallback_0_gateway_token`.
#
# [[provider.fallbacks]]
# kind = "anthropic"
# api_key = "keychain"
# # api_key = "${ANTHROPIC_API_KEY}"
# [provider.fallbacks.models]
# default = "claude-sonnet-4-20250514"
#
# [[provider.fallbacks]]
# kind = "xai_native"
# api_key = "keychain"
# # api_key = "${XAI_API_KEY}"
# [provider.fallbacks.models]
# default = "grok-4"

[telegram]
bot_token = "keychain"
# bot_token = "${TELOXIDE_TOKEN}"
allowed_user_ids = [123456789]
# Optional webhook mode for lower delivery latency in production.
# Long polling remains the default unless explicitly enabled.
# Multi-bot note: if multiple bots enable webhooks on one aidaemon instance,
# each bot needs a unique `listen_addr` (port).
# [telegram.webhook]
# enabled = false
# public_url = "https://bot.example.com"
# listen_addr = "0.0.0.0:8443"
# # Optional custom path (defaults to "/telegram/{bot_username}").
# # path = "/telegram"
# # Optional 1..100, Telegram defaults to 40.
# # max_connections = 40
# # Optional: clear queued Telegram updates when setting the webhook.
# # drop_pending_updates = false

# Global webhook defaults for ALL Telegram bots (config-based and dynamic).
# When enabled, any bot without an explicit [webhook] section with enabled = true
# will auto-derive webhook settings from these defaults at startup.
# Per-bot [telegram.webhook] with enabled = true always takes precedence.
# [telegram_webhook_defaults]
# enabled = false
# base_domain = "bots.example.com"   # Each bot gets <slug>.bots.example.com
# port_start = 8443                  # First auto-assigned port; increments for each bot
# max_connections = 40               # Telegram max_connections (1..100)
# drop_pending_updates = false       # Clear queued updates when setting webhook
# bind_local_only = true             # true → 127.0.0.1, false → 0.0.0.0

[state]
db_path = "aidaemon.db"
working_memory_cap = 50
# consolidation_interval_hours = 6  # How often to run memory consolidation (extract durable facts from conversations)
# encryption_key = "keychain"       # Optional override; default startup uses AIDAEMON_ENCRYPTION_KEY from .env
# encryption_key = "${AIDAEMON_ENCRYPTION_KEY}"

# Optional per-model total context windows (input plus output tokens).
# Use the exact model identifier configured under [provider.models].
# [state.context_window.model_budgets]
# local-model-name = 16384

[terminal]
# Set to ["*"] to allow all commands (only if you trust the LLM fully)
allowed_prefixes = ["ls", "cat", "head", "tail", "echo", "date", "whoami", "pwd", "find", "wc", "grep", "tree", "file", "stat", "uname", "df", "du", "ps", "which", "env", "printenv"]
# Telegram /terminal Mini App URL (must be HTTPS).
# Runtime override: AIDAEMON_TERMINAL_WEB_APP_URL
web_app_url = "https://terminal.aidaemon.ai"
# Enable local terminal bridge websocket worker.
# Runtime override: AIDAEMON_TERMINAL_BRIDGE_ENABLED
bridge_enabled = true
# Rust bridge endpoint for local daemon <-> broker websocket (must be WSS).
# Runtime override: AIDAEMON_TERMINAL_BROKER_URL
daemon_ws_url = "wss://terminal.aidaemon.ai/v1/ws/daemon"
# Optional static daemon connector token.
# If unset, aidaemon auto-mints short-lived connector tokens using your Telegram bot secret proof.
# Runtime override: AIDAEMON_TERMINAL_DAEMON_TOKEN
# daemon_connect_token = "keychain"
# Optional insecure fallback: if true, static daemon_connect_token is used when secure minting fails.
# Default false (recommended). Runtime override: AIDAEMON_TERMINAL_ALLOW_STATIC_FALLBACK
# allow_static_token_fallback = false
# Optional explicit Telegram user ID; defaults to first telegram owner.
# Runtime override: AIDAEMON_TERMINAL_USER_ID
# daemon_user_id = 123456789
# Optional overrides for bridge device id / shell.
# daemon_device_id = "macbook-pro"
# daemon_shell = "/bin/zsh"

# Optional path alias roots for relative "projects/..." requests.
# Precedence for scoped path resolution:
# 1) explicit absolute/anchored paths
# 2) existing relative paths in current working directory
# 3) alias roots below
# [path_aliases]
# projects = ["~/projects"]

[daemon]
health_port = 8080

# [daemon.queue_policy]
# # Capacity (bounded channels).
# approval_capacity = 16
# media_capacity = 16
# trigger_event_capacity = 64
#
# # Depth thresholds as queue fill ratios (0.0-1.0).
# warning_ratio = 0.75
# overload_ratio = 0.90
#
# # Overload handling.
# # Lane-specific overload and fairness settings.
# [daemon.queue_policy.lanes.approval]
# adaptive_shedding = true
# fair_sessions = true
# fair_session_window_secs = 60
# fair_max_events_per_session = 4
#
# [daemon.queue_policy.lanes.media]
# adaptive_shedding = true
# fair_sessions = true
# fair_session_window_secs = 60
# fair_max_events_per_session = 4
#
# [daemon.queue_policy.lanes.trigger]
# adaptive_shedding = true
# fair_sessions = true
# fair_session_window_secs = 60
# fair_max_events_per_session = 4
#
# # Legacy shared knobs (still accepted; applied to all lanes when lane settings are default).
# # adaptive_shedding = true
# # fair_trigger_sessions = true
# # fair_trigger_session_window_secs = 60
# # fair_trigger_max_events_per_session = 4

# [skills]
# enabled = true
# dir = "skills"    # relative to config.toml location

# [browser]
# enabled = true
# headless = true
# screenshot_width = 1280
# screenshot_height = 720
# # Bounded wait timeouts (seconds). These replace fixed sleeps with real,
# # capped waits so navigation/clicks aren't flaky or needlessly slow.
# nav_timeout_secs = 30       # navigation / DOM-ready wait after goto (clamp 1..=120)
# element_timeout_secs = 10   # default 'wait' action element-poll timeout (clamp 1..=120)
# action_timeout_secs = 30    # overall per-action ceiling for the click nav-race (clamp 1..=300)
# # Use an existing Chrome profile to inherit cookies/sessions (e.g. Cloudflare, GitHub, AWS)
# user_data_dir = "~/Library/Application Support/Google/Chrome"
# profile = "Default"    # or "Profile 1", "Profile 2", etc.
# # How browser sessions are isolated from each other (cookies/cache):
# #   "page"            - all sessions SHARE one browser context (shared cookies).
# #   "browser_context" - each session gets its own incognito context
# #                       (per-session isolated cookies). Requires dedicated
# #                       ephemeral browsing: NO user_data_dir and NO
# #                       remote_debugging_port (a persistent profile or attached
# #                       Chrome shares one cookie jar and cannot isolate cookies).
# # Omit (default = auto): ephemeral browsing -> "browser_context"; a configured
# # user_data_dir or remote_debugging_port -> "page". An explicit
# # "browser_context" combined with user_data_dir/remote_debugging_port is
# # REJECTED at startup rather than silently claiming isolation it can't deliver.
# session_isolation = "page"

# [computer_use]
# Native macOS desktop control: the agent inspects and operates GUI apps via the
# accessibility tree + screenshots (click, type, scroll). macOS-only and
# feature-gated — build with `--features computer_use-macos`. Requires a one-time
# Accessibility + Screen Recording grant; SETUP: COMPUTER_USE_MACOS.md
# (run scripts/create-signing-identity.sh then scripts/package-macos-app.sh so
# the permission grants survive rebuilds).
# enabled = true
# screenshot_max_width = 1280
# screenshot_max_height = 800
# screenshot_max_bytes = 2000000
# ax_max_depth = 12
# ax_max_nodes = 500
# action_timeout_secs = 10
# max_mutating_actions = 40
# max_consecutive_observations = 3
# max_wall_clock_secs = 600
# always_allowed_apps = []
# mirror_screenshots_to_channel = false

# [triggers.email]
# host = "imap.gmail.com"
# port = 993
# username = "you@gmail.com"
# password = "keychain"
# # password = "${AIDAEMON_EMAIL_PASSWORD}"
# folder = "INBOX"

# [search]
# backend = "brave"   # duckduckgo | brave | searxng
# api_key = "keychain"
# # api_key = "${AIDAEMON_SEARCH_API_KEY}"
# # searxng_url = "http://localhost:8080"  # self-hosted SearxNG (enable JSON in search.formats)
# # parallel = true     # fan out to every configured backend and merge (default: true)

# [subagents]
# enabled = true
# max_depth = 3            # max nesting levels for sub-agents
# max_iterations = 10      # agentic loop iterations per sub-agent
# max_response_chars = 8000
# timeout_secs = 300       # 5 minute timeout per sub-agent

# [files]
# Channel file inbox/outbox and LLM vision for uploaded images.
# enabled = true
# inbox_dir = "~/.aidaemon/files/inbox"
# max_file_size_mb = 10
# retention_hours = 24
# vision_enabled = true
# max_vision_image_mb = 4
# vision_mime_types = ["image/jpeg", "image/png", "image/gif", "image/webp"]
# vision_model_patterns = ["gpt-4o", "gemini", "claude-3", "gemma"]
# audio_enabled = true
# max_audio_mb = 10
# audio_mime_types = ["audio/ogg", "audio/mpeg", "audio/mp3", "audio/wav", "audio/flac", "audio/aac"]
# audio_model_patterns = ["audio", "gemini-2", "gemini-3"]
# Native audio requires audio-capable models (e.g. gpt-4o-audio-preview, gpt-audio-1.5, gemini-2.x flash).
#
# [files.stt]
# enabled = false
# cli_path = "/opt/homebrew/bin/whisper-cli"
# model_path = "~/models/whisper/ggml-medium.en.bin"
# ffmpeg_path = "ffmpeg"
# language = "en"
# max_audio_mb = 25
# timeout_secs = 120
# Fallback only: runs when native input_audio is skipped (e.g. gemma-4-26b + voice notes).

# [cli_agents]
# Delegate to external CLI coding agents (claude, codex, gemini, …). Off by default.
# enabled = true
# timeout_secs = 600           # default timeout per invocation
# max_output_chars = 16000     # max output before truncation
# # If cli_agent action=run omits "tool", aidaemon auto-picks:
# # claude -> gemini -> codex -> copilot -> aider (first installed)
#
# # Pre-configured tools (auto-discovered on startup via `which`)
# # Override or add your own:
# [cli_agents.tools.claude]
# command = "claude"
# args = ["-p", "--output-format", "json"]
#
# [cli_agents.tools.gemini]
# command = "gemini"
# args = ["--sandbox=false", "--yolo", "--output-format", "json"]
#
# [cli_agents.tools.codex]
# command = "codex"
# args = ["exec", "--json", "--dangerously-bypass-approvals-and-sandbox"]

# [mcp.filesystem]
# command = "npx"
# args = ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
# Prefer built-in read_file/write_file/search_files instead of MCP filesystem.

# [health]
# Scheduled uptime probes (http/port/command/file) with channel alerts. Off by default.
# enabled = true
# probes = [
#   { name = "my-site", type = "http", target = "https://example.com", schedule = "every 5 minutes" },
# ]

# [tools]
# Omit built-in tools at startup by machine name (requires /restart).
# Off by default: git_info, git_commit, policy_metrics, check_environment, service_status,
# project_inspect, read_channel_history (Slack builds only), tool_trace (use goal_trace action=tool_trace).
# Re-enable any with: disabled = []  (or remove names from the list below)
# disabled = [
#   "git_info",
#   "git_commit",
#   "policy_metrics",
#   "check_environment",
#   "service_status",
#   "project_inspect",
#   "read_channel_history",
#   "tool_trace",
#   "goal_trace",
# ]

# [policy]
# All policy features are enabled by default. Uncomment to override.
# Policy profiles (`cheap`, `balanced`, `strong`) are execution presets, not separate model tiers.
# They all use the configured default model; the differences are context/tool budgets, verification,
# and approval behavior. The open-source default auto-routing floors at `balanced`.
# policy_shadow_mode = true          # log policy shadow comparisons and rollout diagnostics
# policy_enforce = true              # enforce policy context budget cap (vs model max only)
# tool_filter_enforce = true         # risk-based tool filtering
# uncertainty_clarify_enforce = true  # ask for clarification on ambiguous requests
# context_refresh_enforce = true      # mid-loop context budget refresh
# learning_evidence_gate_enforce = true  # stricter evidence thresholds for auto-learning
# autotune_shadow = true             # log auto-tune adjustments
# autotune_enforce = true            # apply auto-tune adjustments
# uncertainty_clarify_threshold = 0.55
# trust_tier = "auto"                # "auto" | "guided" | "autonomous"
#   Model trust tier. "auto" classifies by model id: frontier families
#   (claude-*, gpt-4/5, o-series, gemini-2.5+, grok-3/4) run with a thin loop —
#   supervision gates (narration/evidence/planning/critique/uncertainty,
#   deferred-action blocking) become telemetry-only and per-tool budgets
#   scale 3x — while small/local models (gemma, llama, qwen, ...) keep the
#   full supervision scaffolding. Hard safety caps (iteration/token/wall-clock,
#   repetition guards, anti-fabrication checks) apply on every tier.
#   Gate fires are logged under the `heuristic_telemetry` tracing target.
#
# [policy.write_consistency]
# # Guardrail thresholds for dual-write drift checks.
# max_abs_global_delta = 3
# max_session_mismatch_count = 0
# max_stale_task_starts = 0
# max_missing_message_id_events = 0

[diagnostics]
# Off by default: self_diagnose tool (~560 schema tokens). Event/decision recording stays on.
# Set enabled = true when you want in-chat task failure forensics from Telegram/Slack.
enabled = false
record_decision_points = true
max_events = 200
include_raw_tool_args = false