Files
opensquilla--opensquilla/opensquilla.toml.example
T
2026-07-13 13:12:33 +08:00

573 lines
27 KiB
TOML

# OpenSquilla Configuration
# Copy to opensquilla.toml and edit as needed:
# cp opensquilla.toml.example opensquilla.toml
#
# Precedence: env vars > opensquilla.toml > defaults
# Also searched: ~/.opensquilla/config.toml (global user config)
#
# Applying changes: edits made through the RPC/Web UI (config.set/patch/apply)
# hot-apply immediately. Hand-edits to this file are only read at boot — run
# `opensquilla gateway reload` (or restart) to pick them up. Channel, memory
# embedding/retrieval-mode, and sandbox/permissions changes always need a full
# `opensquilla gateway restart`; auth, host/port, file logging, and the search
# provider are also read only at boot (reload updates the stored values but
# the running components keep the boot-time ones).
# Workspace/state defaults
# Defaults:
# workspace_dir = "~/.opensquilla/workspace"
# state_dir = "~/.opensquilla/state"
# memory.source = "workspace"
# workspace_dir = "/path/to/workspace"
# state_dir = "/path/to/state"
# workspace_strict = true # true restricts read-side file tools to workspace
# Realtime feedback timing. Heartbeats are non-persistent UI/CLI liveness
# events while a run is active; stream idle timeout is the real upstream stall
# detector. Keep browser grace above stream idle so server terminal errors win.
# agent_stream_heartbeat_interval_seconds = 15.0
# agent_stream_idle_timeout_seconds = 180.0
# webui_stream_idle_grace_seconds = 210.0
# WebSocket per-connection outbound writer queue. When enabled (default),
# each WS connection gets a bounded asyncio queue + dedicated writer task.
# Producers enqueue and return immediately; slow clients trigger a fast
# 1011 close instead of back-pressuring the gateway. Disable to fall back
# to the legacy direct-`ws.send_text` path under a per-connection lock.
# Kill switch affects new connections only; existing connections retain
# their startup-time behavior.
# Env override: OPENSQUILLA_WS_WRITER_QUEUE_ENABLED=true|false
# ws_writer_queue_enabled = true
# Env override: OPENSQUILLA_WS_WRITER_QUEUE_MAXSIZE=512
# ws_writer_queue_maxsize = 512
# Gateway debug file logging. Raw prompt/tool call capture is separate from
# debug.log and standard diagnostics; it is opt-in through
# OPENSQUILLA_TURN_CALL_LOG=1 or `opensquilla diagnostics on --raw`.
# log_file_enabled = true
# log_level = "DEBUG" # CRITICAL/FATAL/ERROR/WARNING/WARN/INFO/DEBUG/TRACE
# log_file_max_bytes = 5000000
# log_file_backup_count = 3
# Developer diagnostics. debug is security-sensitive; keep it false in shared
# deployments. diagnostics_enabled enables standard diagnostics at startup.
# Raw turn-call capture remains a separate explicit runtime/env switch.
# debug = false
# diagnostics_enabled = false
[control_ui]
# Vue is the product UI. Use "legacy" only as a maintainer rollback fallback
# for the frozen vanilla-JS frontend; changing this requires a gateway restart.
# Env override: OPENSQUILLA_CONTROL_UI_FRONTEND=vue|legacy
frontend = "vue"
# Web search settings
# Use DuckDuckGo for the no-key path, or configure Bocha, Brave, IQS, Tavily, or Exa.
# search_provider = "duckduckgo" # "duckduckgo", "bocha", "brave", "iqs", "tavily", or "exa"
# search_api_key = "" # one-time pasted key; prefer search_api_key_env
# search_api_key_env = "" # BOCHA_SEARCH_API_KEY, BRAVE_SEARCH_API_KEY, IQS_SEARCH_API_KEY, TAVILY_API_KEY, or EXA_API_KEY
# search_max_results = 5
# search_proxy = "" # e.g. "http://127.0.0.1:7890"
# search_use_env_proxy = false # true = allow HTTP_PROXY/HTTPS_PROXY if search_proxy is empty
# search_fallback_policy = "off" # "off" or "network" retry via DuckDuckGo
# search_diagnostics = false # true = include provider-attempt diagnostics
# Tool safety settings.
# Some local proxy/fake-IP DNS setups resolve public domains through RFC 2544
# test-network addresses (198.18.0.0/15). Leave this empty unless you trust
# that fake-IP resolver; other private/internal ranges remain hard-blocked.
# [tools]
# trusted_fake_ip_cidrs = ["198.18.0.0/15"]
# Attachment ingestion. Any file type is accepted by default: rendered
# families (images, PDF, text, Office, email) are extracted or inlined for
# the model; everything else stages as an opaque agent-workspace file whose
# bytes are never parsed or inlined into a prompt.
# Env prefix: OPENSQUILLA_ATTACHMENTS_
# [attachments]
# accept_opaque = true # false = legacy rendered-types-only admission
# opaque_max_bytes = 31457280 # 30 MiB per-file ceiling for opaque types
# upload_store_max_total_bytes = 314572800 # 300 MiB staged-upload RAM ceiling
# # (raise-only; <=0 = default)
# workspace_attachment_disk_budget_bytes = 1073741824 # 1 GiB workspace copies
# # (<=0 = unbounded)
# persist_transcripts = true
# transcript_disk_budget_bytes = 2147483648
# artifact_max_bytes = 31457280
# artifact_disk_budget_bytes = 536870912
[llm]
provider = "tokenrhythm"
model = "deepseek-v4-pro"
# api_key = "" # TokenRhythm API key; env TOKENRHYTHM_API_KEY also works
base_url = "https://tokenrhythm.studio/v1"
# proxy = "" # e.g. http://127.0.0.1:7890
# Provider quick reference. Support levels marked compat_mock_verified are
# verified by local mocked-contract tests, not by live vendor calls.
#
# provider env var default base_url
# tokenrhythm TOKENRHYTHM_API_KEY https://tokenrhythm.studio/v1
# openrouter OPENROUTER_API_KEY https://openrouter.ai/api/v1
# openai OPENAI_API_KEY https://api.openai.com/v1
# openai_responses OPENAI_API_KEY https://api.openai.com/v1 (native Responses-API shape; chat+responses)
# anthropic ANTHROPIC_API_KEY https://api.anthropic.com
# ollama none http://localhost:11434
# deepseek DEEPSEEK_API_KEY https://api.deepseek.com
# gemini GEMINI_API_KEY https://generativelanguage.googleapis.com/v1beta/openai
# dashscope DASHSCOPE_API_KEY https://dashscope.aliyuncs.com/compatible-mode/v1
# moonshot MOONSHOT_API_KEY https://api.moonshot.ai/v1
# mistral MISTRAL_API_KEY https://api.mistral.ai/v1
# groq GROQ_API_KEY https://api.groq.com/openai/v1
# zhipu ZAI_API_KEY https://open.bigmodel.cn/api/paas/v4
# siliconflow SILICONFLOW_API_KEY https://api.siliconflow.cn/v1
# volcengine VOLCENGINE_API_KEY https://ark.cn-beijing.volces.com/api/v3
# volcengine_coding_plan VOLCENGINE_API_KEY https://ark.cn-beijing.volces.com/api/coding/v3
# byteplus BYTEPLUS_API_KEY https://ark.ap-southeast.bytepluses.com/api/v3
# vllm none explicit base_url required
# custom CUSTOM_LLM_API_KEY (optional) explicit base_url required
# lm_studio none http://localhost:1234/v1
# ovms none http://localhost:8000/v3
# qianfan QIANFAN_API_KEY https://qianfan.baidubce.com/v2
# aihubmix AIHUBMIX_API_KEY https://aihubmix.com/v1
# minimax MINIMAX_API_KEY https://api.minimaxi.com/anthropic (Anthropic-shape backend)
# minimax_openai MINIMAX_API_KEY https://api.minimax.io/v1 (OpenAI-compatible variant)
# azure provider-specific unsupported_for_A
#
# Example direct DeepSeek config:
# provider = "deepseek"
# model = "deepseek-v4-flash"
# api_key = "" # env DEEPSEEK_API_KEY also works
# base_url = "https://api.deepseek.com"
#
# Example direct TokenRhythm config (aggregator: DeepSeek/GLM/MiniMax/Kimi/
# MiMo/Qwen families on one key). Every served model streams
# reasoning_content, and reasoning tokens count against max_tokens — keep
# max_tokens = 0 (auto) rather than setting small caps:
# provider = "tokenrhythm"
# model = "deepseek-v4-pro"
# api_key = "" # env TOKENRHYTHM_API_KEY also works
# base_url = "https://tokenrhythm.studio/v1"
#
# Example self-hosted / custom OpenAI-compatible endpoint (vLLM, SGLang, TGI,
# llama.cpp server, or any proxy speaking the Chat Completions API):
# provider = "custom"
# base_url = "http://127.0.0.1:8000/v1" # required — no default endpoint
# model = "qwen3-32b-awq"
# # api_key is optional: set env CUSTOM_LLM_API_KEY (or api_key here) only if
# # your endpoint enforces one. Declare the endpoint's real context window via
# # [models.custom."qwen3-32b-awq"] — see the per-model overrides section below.
# Pin each model to its native upstream provider on OpenRouter.
# Sends provider.only=[slug] + allow_fallbacks=true per request.
[llm.provider_routing]
"anthropic/claude-opus-4.8" = "anthropic"
"anthropic/claude-sonnet-4.6" = "anthropic"
"deepseek/deepseek-v4-flash" = "deepseek"
"google/gemini-3.5-flash" = "google"
"moonshotai/kimi-k2.6" = "moonshotai"
"openai/gpt-5.4-mini" = "openai"
"openai/gpt-5.5" = "openai"
"qwen/qwen3-coder-plus" = "qwen"
"x-ai/grok-4.3" = "x-ai"
"z-ai/glm-4.6" = "z-ai"
"z-ai/glm-5.1" = "z-ai"
"z-ai/glm-5.2" = "z-ai"
# LLM ensemble routing — the DEFAULT routing surface.
# A fresh install ships enabled = true with selection_mode =
# "static_openrouter_b5": each turn fans out to a fixed five-member
# OpenRouter proposer set and fuses their candidates into one answer.
#
# Upgrade note (0.5.0rc1 → current):
# * static_openrouter_b5 is the default for FRESH installs only. Configs
# saved by earlier versions keep their stored `enabled` value.
# * Enabling the ensemble now selects "static_openrouter_b5", where
# 0.5.0rc1's enable toggle gave "router_dynamic". Set
# selection_mode = "router_dynamic" to restore the rc1 behavior.
#
# Credential requirement: static_openrouter_b5 runs its members on
# OpenRouter, so it needs a resolvable OpenRouter credential — the [llm]
# api_key when the active provider is "openrouter", or OPENROUTER_API_KEY
# in the environment otherwise. Without one the ensemble is inactive and
# every turn falls back to the single configured provider —
# `opensquilla doctor` reports this as the llm_ensemble finding
# "LLM ensemble is enabled but cannot run".
# "static_tokenrhythm_b5" is the TokenRhythm mirror of the same lineup
# (deepseek-v4-pro, glm-5.2, kimi-k2.7-code, qwen3.7-max + glm-5.2
# aggregator) and needs a TokenRhythm credential the same way (the [llm]
# api_key when the active provider is "tokenrhythm", or
# TOKENRHYTHM_API_KEY in the environment).
#
# The keys below are also writable at runtime via the Web UI / the
# `onboarding.ensemble.configure` RPC; omitted keys keep their current
# values. Changes take effect on the next turn without a gateway restart.
[llm_ensemble]
enabled = true
# "static_openrouter_b5": fixed five-member OpenRouter proposer set (default).
# "static_tokenrhythm_b5": the same lineup served through TokenRhythm.
# "custom_b5": an explicit user-authored lineup from [[llm_ensemble.candidates]]
# — 2-6 enabled proposer rows plus at most one role = "aggregator" row (the
# member that fuses drafts into the final answer; omitted = the current chat
# model fuses). Proposer roles ("primary", "contrast", "fast_check",
# "critic") are advisory labels shown in the UI and decision trace.
# "router_dynamic": legacy automatic selection; kept readable for existing
# configs but no longer offered in the Web UI.
selection_mode = "static_openrouter_b5"
# Explicit lineup for custom_b5 (ignored by the static profiles):
# [[llm_ensemble.candidates]]
# provider = "volcengine"
# model = "doubao-2.0-pro"
# role = "primary"
# [[llm_ensemble.candidates]]
# provider = "volcengine"
# model = "deepseek-v4-pro"
# role = "aggregator"
#
# Candidate pool for the legacy router_dynamic mode (ignored otherwise).
model_options = [
"deepseek/deepseek-v4-pro",
"z-ai/glm-5.2",
"qwen/qwen3.7-plus",
"deepseek/deepseek-v4-flash",
"qwen/qwen3.7-max",
"moonshotai/kimi-k2.6",
"moonshotai/kimi-k2.7-code",
"minimax/minimax-m3",
]
# Minimum proposers that must succeed before aggregation (>= 1). The fixed
# lineups (static profiles and custom_b5) replace the untouched default of 1
# with their own quorum at runtime: 3-of-4 for the static profiles, N-1 for a
# custom lineup of N proposers.
min_successful_proposers = 1
# When aggregation cannot proceed: "fallback_single" (single-provider turn) or "error".
all_failed_policy = "fallback_single"
# Advanced knobs — TOML-only (not on the configure RPC surface). Defaults:
# mode = "b5_fusion" # only supported ensemble mode
# proposer_tools = false # allow tool calls inside proposer turns
# candidate_max_chars = 24000 # per-candidate transcript budget (0 = unlimited)
# proposer_timeout_seconds = 3600.0
# aggregator_timeout_seconds = 3600.0
# shuffle_candidates = true # shuffle candidate order before aggregation
# record_candidates = false # persist per-proposer candidates for replay/debug
# Model metadata catalog (added in 0.5.x; schema only — wiring lands in a
# follow-up release). Offline-first: with refresh = "off" (the default) the
# gateway never fetches model metadata (context windows, output caps, pricing,
# capability flags) from the network. Today's OpenRouter live model-list fetch
# is a separate, existing mechanism that this flag does NOT govern yet.
# Downgrade note: model_catalog and the models tables below are top-level
# sections written to disk only when a config persist re-materializes the full
# file. 0.5.0rc1 and older reject unknown top-level keys, so delete both
# sections before downgrading (same class as config_version; see the 0.5.x
# release notes).
# [model_catalog]
# refresh = "off" # "off" | "startup" (fetch once at gateway boot)
# pin_path = "" # local JSON/TOML catalog override for air-gapped deploys
# stale_after_days = 45 # advisory doctor threshold for metadata age (days)
# Per-model metadata overrides (added in 0.5.x), keyed
# [models.<provider_id>."<model_id>"]. Quote model ids that contain dots or
# slashes. Exact ids only — globs are not supported in user config. Every
# field is optional; anything unset keeps resolving from catalog/registry
# metadata as before.
#
# Self-hosted example: a vLLM endpoint declaring its real context window so
# context budgeting and compaction stop assuming a conservative default:
# [models.vllm."qwen3-32b-awq"]
# context_window = 131072
# max_output_tokens = 8192
# supports_tools = true
#
# The generic `custom` provider (any self-hosted OpenAI-compatible endpoint;
# see the [llm] example above) uses the same override table, keyed by its
# served model id:
# [models.custom."qwen3-32b-awq"]
# context_window = 131072
# max_output_tokens = 8192
# supports_tools = true
#
# [models.openrouter."z-ai/glm-5.2"]
# # reasoning_format: openrouter | openai | deepseek | gemini | zai |
# # dashscope | moonshot | volcengine | none
# reasoning_format = "openrouter"
# supports_reasoning = true
# input_cost_per_mtok = 0.5 # USD per million input tokens (example value)
# output_cost_per_mtok = 2.0 # USD per million output tokens (example value)
# cache_read_cost_per_mtok = 0.05 # USD per million cached-prompt-read tokens (example value)
# cache_write_cost_per_mtok = 0.6 # USD per million cached-prompt-write tokens (example value)
# thinking_level_map = { high = "high", medium = "medium" }
[memory]
# "workspace" stores MEMORY.md and memory/*.md under workspace_dir.
# SQLite indexes still live under state_dir.
source = "workspace"
# Long-term memory vector indexing defaults to provider="auto": it tries the
# bundled local BGE-small ONNX model first, then a memory-specific remote key
# if one is configured, then FTS-only. Chat LLM/OpenRouter credentials are not
# used for memory embeddings unless explicitly configured below.
# retrieval_mode = "hybrid" # "hybrid" | "fts_only"
#
# [memory.embedding]
# provider = "auto" # "auto" | "none" | "local" | "openai" | "openai-compatible" | "ollama"
#
# [memory.embedding.local]
# onnx_dir = "" # optional; empty uses bundled BGE; supports absolute, ~, or process-relative paths
#
# [memory.embedding.remote]
# api_key = ""
# base_url = "https://api.openai.com/v1"
# model = "text-embedding-3-small"
# headers = {}
#
# [memory.embedding.ollama]
# base_url = "http://localhost:11434"
# model = "nomic-embed-text"
# Turn capture writes memory/archive/** audit transcripts by default.
# These archives are not searchable memory unless explicitly opted in.
# capture_user = true
# capture_assistant = false
# index_captured_turns = false
# capture_excluded_run_kinds = ["recall", "session_recall"]
# capture_excluded_provenance_kinds = ["recall", "tool_result", "memory_injected"]
[memory.dream]
# Dream consolidation is safety-first by default. Background scheduling and
# curated MEMORY.md writes require explicit opt-in.
enabled = false
preview_mode = true
auto_schedule = false
# interval_h = 24
# cron = "0 3 * * *"
[skills]
# Experimental skill relevance filtering. Default is off; deterministic
# skill gating still runs for visibility, platform, and tool availability.
filter_enabled = false
filter_top_k = 5
max_skills_prompt_chars = 8000
# injection_mode = "system" # "system", "user_context", or "user_message"
# The default lexical strategy is dependency-free. "semantic" and "hybrid"
# are legacy experimental modes that need the bundled BGE ONNX backend
# (onnxruntime + transformers tokenizer + the int8 ONNX export shipped under
# squilla_router/models/v4.2_phase3_inference/bge_onnx/). Install via
# `uv sync --extra recommended`. If unavailable, they degrade to lexical-only.
# filter_strategy = "lexical" # "lexical" | "semantic" | "hybrid"
# filter_lexical_top_n = 20
# filter_semantic_top_n = 20
# filter_rrf_k = 60
# filter_embedding_model = "BAAI/bge-small-zh-v1.5"
[task_runtime]
# Server-side agent turn queue. Same-session tasks are serialized;
# different sessions can run concurrently up to this limit.
max_concurrency = 4
# Waiting tasks per session before new follow-up work is rejected.
max_pending_per_session = 64
[squilla_router]
enabled = true # V4 model router
auto_thinking = true
rollout_phase = "full"
strategy = "v4_phase3"
# Optional provider tier profile. Leave unset to preserve the built-in
# OpenRouter defaults below. If set, it must match [llm].provider; the router
# does not switch providers at runtime.
# tier_profile = "dashscope" # openrouter | dashscope | deepseek | gemini | volcengine | openai | zhipu | moonshot
# Bundled router prerequisites:
# 1. install with `uv sync --extra model-router` or `uv sync --extra recommended`
# 2. hydrate model assets via:
# git lfs pull --include="src/opensquilla/squilla_router/models/**"
default_tier = "c1"
confidence_threshold = 0.5
v4_use_aux_head = true
kv_cache_anti_downgrade_enabled = true
kv_cache_anti_downgrade_window_seconds = 600
complaint_upgrade_enabled = true
complaint_upgrade_steps = 1
complaint_upgrade_max_chars = 160
require_router_runtime = true
estimated_output_savings_pct = 0.03
upgrade_to_c3_compaction_enabled = true
# What routing does when a tier names a provider other than [llm].provider
# while cross_provider_tiers is off. "route" (default) preserves the historical
# behavior: the mismatch is logged but the tier's model runs on the active
# provider's credentials. "veto" instead rebinds the turn to the nearest tier
# that executes on the active provider (or the default tier).
# tier_provider_mismatch = "route" # route | veto
# TokenRhythm serves every model in reasoning-streaming mode and rejects
# thinking-toggle request fields, so these tiers set no thinking_level.
[squilla_router.tiers.c0]
provider = "tokenrhythm"
model = "deepseek-v4-flash"
description = "Fast DeepSeek V4 Flash route for trivial chat, short rewrites, extraction, and low-risk simple Q&A"
supports_image = false
[squilla_router.tiers.c1]
provider = "tokenrhythm"
model = "deepseek-v4-pro"
description = "Default balanced text model for normal agent work, coding assistance, debugging, and moderate analysis"
supports_image = false
[squilla_router.tiers.c2]
provider = "tokenrhythm"
model = "kimi-k2.7-code"
description = "Stronger Kimi K2.7 Code route for multi-step coding, structured reasoning, larger context synthesis, and harder analysis"
supports_image = false
[squilla_router.tiers.c3]
provider = "tokenrhythm"
model = "glm-5.2"
description = "Highest-tier GLM 5.2 route for difficult planning, deep review, complex debugging, and high-stakes synthesis"
supports_image = false
[squilla_router.tiers.image_model]
provider = "tokenrhythm"
model = "kimi-k2.6"
description = "Image model: vision-capable route for user-supplied image attachments, screenshots, diagrams, and visual question answering"
supports_image = true
image_only = true
# Example: DashScope profile requires:
# [llm]
# provider = "dashscope"
# model = "qwen3.6-plus"
# api_key = "${DASHSCOPE_API_KEY}"
# base_url = "https://dashscope.aliyuncs.com/compatible-mode/v1"
#
# [squilla_router]
# tier_profile = "dashscope"
# ─────────────────────────────────────────────────────────────────────────────
# Tool policy
# ─────────────────────────────────────────────────────────────────────────────
#
# Optional coding/repo-coding narrowed tool surface. Keep commented unless you
# are intentionally narrowing the tool surface for a scripted run.
#
# [tools]
# profile = "coding"
# also_allow = ["retrieve_tool_result"]
# deny = ["execute_code", "background_process", "process"]
# file_edit_requires_fresh_read = true
# file_edit_flexible_recovery = true # default; records used/rejected recovery events
# workspace_write_deny_globs = []
[agent_token_saving]
# Project fresh tool results with the built-in tokenjuice reducer before they
# are fed back into the model. Raw tool responses remain available through the
# tool-result store when configured by the gateway runtime.
tool_result_projection_max_inline_chars = 60000
tool_result_store_max_bytes = 8388608
tool_result_store_disk_budget_bytes = 268435456
tool_result_store_retention_seconds = 604800
[compaction]
enabled = true
# model = "" # None = use session model
# timeout_seconds = 30.0
[sandbox]
# Per-command ephemeral sandbox + security grading.
#
# sandbox on -> processes run under namespace/profile isolation
# off -> host execution is allowed (logs a WARNING per run)
# security_grading on -> action_kind drives the selected SecurityLevel
# off -> a fixed STANDARD policy is used, no approval flow
#
# Both default to false for the out-of-box bypass posture. Turning sandbox off
# while grading stays on is silently coerced to grading=false with a warning.
sandbox = false
security_grading = false
# default_level = "STANDARD" # DISABLED | STANDARD | STRICT | LOCKED
# backend = "auto" # auto | bubblewrap | seatbelt | windows_default | noop
# allow_legacy_mode = false # required for default_level = DISABLED
# network_default = "proxy_allowlist" # none | proxy_allowlist
# denial_threshold = 3 # pause autonomous runs after N denials
# extra_ro_mounts = []
# extra_rw_mounts = []
# cpu_seconds = 30
# memory_mb = 1024
# wall_seconds = 60
[permissions]
# Owner/operator default permission mode. The shipped default is "bypass", which
# runs local/operator tool execution on the host while still blocking sensitive
# paths. Use `opensquilla sandbox on|bypass|full|reset` to update this together
# with the sandbox section.
default_mode = "bypass" # off | on | bypass | full
# [auth]
# mode = "none" # none | token | password
# token = ""
# [cors]
# Cross-origin browser access to the gateway HTTP API. Off by default: the
# Web UI is served same-origin from the gateway itself and non-browser
# clients (CLI, desktop app, curl) never need CORS. Only list origins here
# if you host a separate frontend on another origin; avoid "*", especially
# together with allow_credentials.
# allowed_origins = []
# allow_credentials = true
# [channels]
# [[channels.channels]]
# name = "my-slack"
# type = "slack"
# token = "xoxb-..."
# connection_mode = "socket" # socket = no public URL; webhook = Events API
# app_token = "xapp-..." # required for Slack Socket Mode
# signing_secret = "" # required for Slack webhook mode
# slack_channel_id = "C12345"
# reply_in_thread = false
# ─────────────────────────────────────────────────────────────────────────────
# Meta-Skill subsystem
# ─────────────────────────────────────────────────────────────────────────────
[meta_skill]
# Master gate. When false the meta-skill subsystem is fully off: meta-skills are
# not injected into prompts, meta_invoke is not surfaced, and the /meta command
# refuses (both list and run). Default: true.
# enabled = true
# Automatic activation. When false (the default), meta-skills are MANUAL-ONLY:
# no system-prompt guidance, no keyword/semantic auto-trigger, meta_invoke is not
# offered for automatic invocation, and meta-skills are hidden from automatic
# skill listings.
# They run only via the explicit `/meta <name>` command. Set true to restore the
# previous automatic behavior. Default: false.
# auto_trigger = false
[meta_skill.persistence]
# Whether to write meta-skill execution traces to SQLite (G4 traceable audit).
# Disabling drops the writer to no-op; the CLI `skills meta runs ...` will
# report no rows. Default: true.
# enabled = true
# How long (seconds) a 'running' row may live before boot cleanup marks it
# 'failed' as an orphan from a crashed prior process. Only applies to rows
# whose owner_pid differs from the current process pid. Default: 3600 (1h).
# orphan_cleanup_age_seconds = 3600
# ─────────────────────────────────────────────────────────────────────────────
# Meta-Skill auto-propose: unattended synthesis from co-occurrence patterns
#
# Two independent triggers feed the same library function
# (skills.creator.auto_propose):
# * `enabled` — schedule a recurring cron job (Path 1)
# * `on_dream_complete` — piggyback on memory-consolidation dreams (Path 2)
# Both default OFF. Operators turn them on after reviewing how
# meta-skill-creator's gated output looks once.
# ─────────────────────────────────────────────────────────────────────────────
[meta_skill.auto_propose]
# enabled = false # Path 1 cron toggle
# cron = "0 5 * * *" # 5-field local-time cron; daily 05:00
# window_days = 30 # log-history window for co-occurrence
# min_freq = 3 # drop chains observed fewer than N times
# top_k = 5 # consider at most N patterns per fire
# on_dream_complete = false # Path 2 dream-hook toggle (independent)
# agent_ids = ["main"] # empty/omitted = all configured agents