# Example coral config. Copy to config.toml and tweak. # Secrets belong in secrets.toml.env, not here. # # Launch with: # coral --config /path/to/config.toml --secrets /path/to/secrets.toml.env # ── Model presets ──────────────────────────────────────────────────────────── # Define named model configurations once, then reference them from [model], # [fallback], [summary], and [embeddings] via `preset = "name"`. # Secrets (api_key) are deep-merged from the secrets file into the same # [models.] table, so they don't need to be repeated here. [models.umans] provider = "openai" model = "umans-glm-5.2" base_url = "https://api.code.umans.ai/v1" tool_choice = "required" enable_thinking = true thinking_effort = "low" [models.glm_openrouter] provider = "openrouter" model = "zai-org/glm-4.7-flash" base_url = "" tool_choice = "required" referer = "" title = "" n_ctx = 4096 context_margin = 256 hard_overflow_tokens = 1024 # enforce_context_limit = false [models.gemini_embedding] provider = "openai" model = "google/gemini-embedding-2-preview" base_url = "https://openrouter.ai/api/v1" dimensions = 3072 referer = "" title = "" # ── LLM bridge (our own OpenRouter) ────────────────────────────────────────── # Optional unified inference proxy. Accepts OpenAI-format requests and routes to # the best provider (pi-ai registry + custom providers below). Claude models are # discovered dynamically from Anthropic's /v1/models — nothing is hardcoded. # [llm_bridge] # enabled = true # host = "127.0.0.1" # port = 4040 # # [[llm_bridge.providers]] # name = "umans" # api_key = "sk-..." # or deep-merged from secrets.toml.env # base_url = "https://api.code.umans.ai/v1" # # [[llm_bridge.providers]] # name = "anthropic-oauth" # # pi-ai auth.json: { "anthropic": { "type": "oauth", "refresh", "access", "expires" } } # # Tokens refresh + rotate back in place, just like codex's ~/.codex/auth.json. # auth_path = "~/.pi/auth.json" [server] port = 3000 [agent] # Display name used in summarizer prompt and grounding context. name = "niri" # Set to "local" to skip the primary model and route everything through the fallback. env = "default" # Boredom wake: if > 0, wake the agent after this many minutes of inactivity. # Any real wake (DM, heartbeat, etc.) resets the timer. 0 disables. # boredom_wake_min = 0 # Git upstream check: when true, runs `git fetch` on every file read/write # and nags the agent if the remote has changed. Throttled to 10 min per repo. # git_upstream_nag = false # Optional: route shell tools through a Docker container instead of the local shell. # Set both to enable. Mutually exclusive with [ssh]. [container] name = "" user = "" # Optional: route shell tools through SSH instead. Mutually exclusive with [container]. [ssh] target = "" identity = "" [model] # Reference a preset defined under [models.], or set fields directly. preset = "umans" # Any field here overrides the preset. [fallback] # Used when the primary endpoint is unreachable or rate-limited (429/5xx). preset = "glm_openrouter" [summary] # Optional dedicated summarizer model. Leave empty to reuse primary/fallback. preset = "glm_openrouter" [embeddings] # Reference a preset, or set fields directly. preset = "gemini_embedding" [context] compact_target_tokens = 65000 compact_trigger_tokens = 90000 compact_recent_messages = 80 compact_chunk_messages = 32 compact_summary_max_chars = 16000 # 0 disables idle compaction. idle_compaction_minutes = 45 # When true, no summaries are produced — the conversation stays raw to # preserve prompt cache. When false, old large tool results are proactively # summarized using the [summary] model (or primary model if no [summary] is set). respect_cache = true # Minimum number of turns (assistant messages) that must pass after a tool # result before it's eligible for proactive compaction. Only applies when # respect_cache = false. compaction_turn_age = 5 # Minimum character length for a tool result to be considered "large" enough # to summarize. Only applies when respect_cache = false. compaction_min_chars = 2000 [runner] # Hard cap on assistant turns in a single wake cycle. max_turns = 120 # Stop if the same assistant tool call + result pattern repeats. max_identical_tool_turns = 6 [image_tool] # Max bytes accepted before rejecting an image attachment. max_bytes = 150000 # Absolute image root exposed to the agent. Defaults based on container/ssh mode. root = "" [discord] gateway_enabled = true gateway_trace = false gateway_raw_fallback = true gateway_raw_fallback_all = false # Channel ids scanned by discord_scan when no channel_ids arg is passed. scan_channel_ids = [] wake_on_event = false wake_on_dm = true batch_interval_ms = 60000 batch_only_configured = true pending_auto_seen_minutes = 10 batch_scan = true batch_max_messages = 40 rest_max_attempts = 3 rest_retry_base_ms = 1000 # Auto-detected from the gateway READY payload if left blank. bot_user_id = "" [metrics] retention_days = 3 # ── Tool filtering (main agent) ─────────────────────────────────────────────── # Control which tools the main agent can use. # - enabled_tools: if empty (default), ALL tools are enabled. # If non-empty, only the listed tools are available. # - blacklisted_tools: always removed from the available set, regardless of # enabled_tools. # # [tools] # enabled_tools = ["shell", "read_file", "memory_search"] # blacklisted_tools = ["rest"] # ── Subagents ───────────────────────────────────────────────────────────────── # Define named subagents the main agent can spawn via the `subagent` tool. # Each [[subagents]] entry creates a new subagent with its own system prompt, # tool set, and turn limit. Subagents run a simplified loop — no event waiting, # no compaction, no Discord access, no nesting. # # [[subagents]] # name = "researcher" # model = "" # blank = inherit main agent's model # system_prompt = "You are a research assistant. Be thorough and cite sources." # enabled_tools = ["shell", "read_file", "memory_search"] # blank = all (minus auto-blacklisted) # blacklisted_tools = ["edit_file"] # additional exclusions # max_turns = 30 # # [[subagents]] # name = "writer" # model = "" # system_prompt = "You are a technical writer. Produce clean, concise prose." # enabled_tools = [] # blacklisted_tools = [] # max_turns = 20