All configuration lives in a single TOML file. Default location: ~/.denkeeper/denkeeper.toml.

[telegram]

KeyTypeDefaultDescription
tokenstringrequiredBot token from @BotFather
allowed_usersint[]requiredTelegram user IDs allowed to interact

[discord]

KeyTypeDefaultDescription
tokenstringrequiredDiscord bot token
allowed_usersstring[]requiredDiscord user snowflake IDs

[llm]

KeyTypeDefaultDescription
default_providerstring"openrouter"Name of the provider instance to use by default (must match a configured instance name)
default_modelstringModel identifier (format depends on provider)
cost_limit_softfloat0Soft cost limit per session in USD (warns but continues)
cost_limit_hardfloat1.0Hard cost limit per session in USD (stops generation)

[[llm.providers]]

Named provider instances. Multiple entries of the same type are allowed, enabling e.g. OpenAI + a local LM Studio endpoint simultaneously. Each instance is addressable by its unique name.

KeyTypeDescription
namestringUnique instance name (used in default_provider and per-agent llm_provider)
typestringProvider type: "anthropic", "openai", "openrouter", or "ollama"
api_keystringAPI key (required for all types except ollama)
base_urlstringAPI endpoint override (useful for Azure, vLLM, LM Studio, etc.)
organizationstringOpenAI organization ID (openai type only)
[[llm.providers]]
name = "openai"
type = "openai"
api_key = "sk-..."

[[llm.providers]]
name = "lmstudio"
type = "openai"
base_url = "http://localhost:1234/v1"
api_key = "lm-studio"

Legacy single-slot syntax ([llm.openai], [llm.anthropic], etc.) is still supported and auto-converted at startup. The two styles can coexist; an explicit [[llm.providers]] entry with the same name takes precedence.

[llm.openrouter] (legacy)

KeyTypeDefaultDescription
api_keystringrequiredOpenRouter API key

[llm.anthropic] (legacy)

KeyTypeDefaultDescription
api_keystringrequiredAnthropic API key (sk-ant-...)
base_urlstring"https://api.anthropic.com"API endpoint override

[llm.ollama] (legacy)

KeyTypeDefaultDescription
base_urlstring"http://localhost:11434"Ollama server URL

[llm.openai] (legacy)

KeyTypeDefaultDescription
api_keystringrequiredOpenAI API key
base_urlstring"https://api.openai.com/v1"API endpoint override (for Azure OpenAI, vLLM, LiteLLM, etc.)
organizationstringOpenAI organization ID (optional)

Compatible with any endpoint that speaks the OpenAI Chat Completions API format.

[[llm.fallback]]

KeyTypeDescription
triggerstring"cost_limit", "rate_limit", or "error"
actionstring"switch_provider", "switch_model", or "wait_and_retry"
providerstringTarget provider (for switch_provider)
modelstringTarget model (for switch_model)
scopestring"soft" or "hard" (for cost_limit) — which agent cost limit triggers the swap
max_retriesintMax retry count (for wait_and_retry)
backoffstring"exponential" (default) or "constant"

cost_limit rules consume the agent’s cost_limit_soft / cost_limit_hard (resolved via [[agents]] overrides or the global [llm] defaults). Legacy low_funds rules with a threshold field auto-migrate to cost_limit + scope = "soft" on load.

[session]

KeyTypeDefaultDescription
tierstring"supervised"Default permission tier: "autonomous", "supervised", "restricted"

[[agents]]

KeyTypeDefaultDescription
namestringrequiredUnique agent name (one must be "default")
descriptionstringAgent description
persona_dirstringPath to persona files
adaptersstring[]Adapter bindings (e.g., ["telegram"], ["telegram:12345"])
llm_providerstringOverride default provider (must match a configured provider instance name)
llm_modelstringOverride default model
session_tierstringOverride default permission tier
cost_limit_softfloatPer-agent soft cost limit in USD (overrides global)
cost_limit_hardfloatPer-agent hard cost limit in USD (overrides global)
supervisorstringName of another agent that auto-reviews tool calls before they reach you (supervised tier only; supervisor must be autonomous or restricted, not itself supervised)
supervisor_timeoutstring"30s"Max wait for the supervisor’s LLM review. Go duration format (30s, 1m, 90s). On timeout, falls through to human approval.
supervisor_context_messagesint5Number of recent conversation messages passed to the supervisor as context.

[memory]

KeyTypeDefaultDescription
db_pathstring"~/.denkeeper/data/memory.db"SQLite database path

[log]

KeyTypeDefaultDescription
levelstring"info""debug", "info", "warn", "error"
formatstring"text""text" or "json"

[voice]

KeyTypeDefaultDescription
stt_providerstringSpeech-to-text provider ("openai")
tts_providerstringText-to-speech provider ("openai")
tts_voicestring"alloy"Voice name
auto_voice_replyboolfalseReply with voice when user sends voice

[voice.openai]

KeyTypeDefaultDescription
api_keystringrequiredOpenAI API key for STT/TTS

[api]

KeyTypeDefaultDescription
enabledbooltrueEnable the REST API server and web dashboard
listenstring":8080"Bind address
tlsboolfalseEnable HTTPS
cert_filestringTLS certificate path
key_filestringTLS private key path
cors_originsstring[]Allowed CORS origins
rate_limitfloat0Max requests/sec per API key
websocket_enabledbooltrueEnable the WebSocket endpoint (GET /api/v1/ws)
websocket_max_connectionsint0Maximum concurrent WebSocket connections (0 = unlimited)
websocket_replay_buffer_ttlstring"5m"How long to buffer events for replay after a client disconnects
external_urlstringPublicly-reachable base URL (used for OAuth callback URLs; defaults to http(s)://<listen>)

[[schedules]]

KeyTypeDefaultDescription
namestringrequiredUnique schedule name
typestringrequired"system" or "agent"
schedulestringrequiredCron expression, interval, or named schedule
skillstringSkill to invoke
agentstring"default"Target agent
session_tierstring"supervised"Permission tier for this schedule
channelstringDelivery channel (e.g., "telegram:12345")
tagsstring[]Freeform labels
enabledbooltrueEnable/disable without removing

[plugins.*]

KeyTypeDefaultDescription
typestringrequired"subprocess" or "docker"
commandstringrequiredPlugin binary path (subprocess) or Docker image (docker)
argsstring[]Command-line arguments
envmapEnvironment variable overrides
capabilitiesstring[]required["tools"]
memory_limitstringDocker container memory limit (e.g., "256m")
cpu_limitstringDocker container CPU limit (e.g., "0.5")
networkstring"none"Docker network mode ("none", "bridge", etc.)
volumesstring[]Docker bind mounts

Subprocess plugins run as child processes with direct MCP stdio. Docker plugins run in hardened containers with --cap-drop ALL, --read-only, --security-opt no-new-privileges, and --network none by default.

[security]

KeyTypeDefaultDescription
trusted_keysstring[]Paths to PEM-encoded Ed25519 public key files
allow_unsignedbooltrueAllow unsigned subprocess plugin binaries

When allow_unsigned = false, all subprocess plugin binaries must have a valid Ed25519 signature from one of the trusted keys.

[kv]

KeyTypeDefaultDescription
max_keys_per_agentint1000Maximum keys per agent
max_value_bytesint65536Maximum value size in bytes (64 KB)
cleanup_intervalstring"1h"Background cleanup interval for expired keys

Per-agent key-value storage with optional TTL. Exposed as Config MCP tools (kv_get, kv_set, kv_delete, kv_list, kv_set_nx). Useful for locks, counters, caches, and cross-session coordination.

[sandbox]

KeyTypeDefaultDescription
runtimestring"docker"Sandbox backend: "docker" or "kubernetes"

Selects the runtime backend for sandboxed (Docker-type) plugins.

[sandbox.kubernetes]

KeyTypeDefaultDescription
namespacestring"denkeeper-sandboxes"Kubernetes namespace for sandbox Pods
kubeconfigstringPath to kubeconfig file (empty uses in-cluster config)
runtime_classstringRuntimeClassName for gVisor or Kata Containers

The Kubernetes backend creates ephemeral Pods with init-container network isolation, dropped capabilities, read-only root filesystem, and Pod Security Admission labels. Supports both in-cluster (ServiceAccount) and out-of-cluster (kubeconfig) authentication.

[mcp]

Global settings that apply to all MCP tool servers.

KeyTypeDefaultDescription
request_timeout_secsint30Per-request timeout for MCP calls (0 = no timeout)
auto_restartbooltrueAutomatically restart crashed stdio servers
max_restart_attemptsint3Consecutive failures before disabling a server
restart_cooldownstring"5m"Duration a server must stay connected to reset the failure counter
url_allowliststring[]Allowed hostnames/wildcards for SSE tool server URLs (empty = all non-blocked hosts)

[tools.*]

KeyTypeDefaultDescription
transportstring"stdio"Transport type: "stdio" (subprocess) or "sse" (remote HTTP/SSE)
commandstringrequired for stdioMCP server command (stdio only)
argsstring[]Command arguments (stdio only)
envmapEnvironment variables; supports ${NAME} placeholder expansion (stdio only)
urlstringrequired for sseRemote server URL (SSE only, must be http/https)
headersmapHTTP headers sent with SSE requests (SSE only)
request_timeout_secsint0Per-server timeout override (0 = use global [mcp] value)
authstring""Authentication method: "" (none) or "oauth" (OAuth 2.1, SSE only)
client_idstringOAuth2 client ID (optional; some servers use dynamic registration)
client_secretstringOAuth2 client secret (optional; must be set together with client_id)
scopesstring[]OAuth2 scopes to request (optional)

SSE security: SSRF protection blocks localhost, link-local (169.254.x.x), and cloud metadata endpoints. ${NAME} placeholders in url and headers are resolved from environment but secrets matching DENKEEPER_*_SECRET, DENKEEPER_*_PASSWORD*, and related patterns are denied. URL and header values are redacted in API responses.

Tools can also be added and removed at runtime via the REST API (tools:write scope) or the Config MCP server (tool_add/tool_remove). Runtime changes are persisted to the TOML config file.

[otel]

KeyTypeDefaultDescription
enabledboolfalseEnable OpenTelemetry instrumentation
traces_endpointstringOTLP HTTP endpoint for trace export (e.g. "http://localhost:4318")
service_namestring"denkeeper"Service name for the OTel resource

Env overrides: DENKEEPER_OTEL_ENABLED sets enabled, DENKEEPER_OTEL_TRACES_ENDPOINT sets traces_endpoint.

When enabled, Prometheus metrics are exposed at GET /metrics (no auth required). Traces are only exported when traces_endpoint is set.

[api.auth]

KeyTypeDefaultDescription
password_hashstringbcrypt hash from denkeeper passwd CLI
session_secretstringHex-encoded AES-256 key (64 hex chars). Generate with openssl rand -hex 32
session_max_agestring"24h"Session cookie lifetime

Env override: DENKEEPER_API_AUTH_SESSION_SECRET sets session_secret.

[api.auth.oidc]

KeyTypeDefaultDescription
enabledboolfalseEnable OIDC SSO
issuerstringOIDC provider issuer URL (e.g. "https://accounts.google.com")
client_idstringOAuth2 client ID
client_secretstringOAuth2 client secret
redirect_urlstringCallback URL (e.g. "https://denkeeper.example.com/auth/callback")
scopesstring[]["openid","email","profile"]OAuth2 scopes
allowed_emailsstring[]Email allowlist. Required non-empty when enabled. Case-insensitive.

Env overrides: DENKEEPER_OIDC_CLIENT_ID sets client_id, DENKEEPER_OIDC_CLIENT_SECRET sets client_secret.

Requires email_verified: true claim from the OIDC provider. Uses Authorization Code flow with PKCE (S256).