> ## Documentation Index
> Fetch the complete documentation index at: https://comis-fix-skill-import-vetting-gate.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Config YAML Reference

> Complete reference for all configuration options with types, defaults, and validation rules

**What this is for:** the single source of truth for everything you can put in `~/.comis/config.yaml`. **Who it's for:** anyone tuning agent behavior, wiring channels, hardening the gateway, or composing layered configs across environments.

Comis uses a single `config.yaml` file (or multiple layered files) to configure every aspect of the system. The configuration contains 41 top-level sections with 54+ nested schemas defining agent behavior, channel adapters, security policies, gateway settings, and more. All schemas use Zod strict validation -- unknown keys are rejected at startup.

Config files are specified via the `COMIS_CONFIG_PATHS` environment variable (colon-separated, like the shell `PATH`). See the [Configuration Guide](/installation/configuration) for a step-by-step setup walkthrough.

## Config Loading

Comis loads configuration through a layered resolution process:

1. **Defaults** -- Every schema field has a sensible default. An empty config file produces a valid configuration.
2. **YAML files** -- Files listed in `COMIS_CONFIG_PATHS` are merged in order (later files override earlier ones).
3. **Environment overrides** -- Env vars override YAML values at runtime.

### Special Directives

| Directive      | Description                                                              | Example                          |
| -------------- | ------------------------------------------------------------------------ | -------------------------------- |
| `$include`     | Reference another YAML file for modular composition (max 10 levels deep) | `$include: ./channels.yaml`      |
| `${VAR_NAME}`  | Substitute an environment variable or stored secret via SecretManager    | `token: "${TELEGRAM_BOT_TOKEN}"` |
| `$${VAR_NAME}` | Escape syntax -- produces literal `${VAR_NAME}` in the output            | `example: "$${NOT_SUBSTITUTED}"` |
| `$VAR_NAME`    | Bare reference -- auto-corrected to `${VAR_NAME}` with a warning         | `token: $MY_TOKEN` (corrected)   |

<Warning>
  Strict validation (`z.strictObject`) means any unrecognized key in your config file causes a startup error. Check key names carefully against this reference.
</Warning>

### Minimal Example

```yaml theme={}
# ~/.comis/config.yaml
tenantId: my-project
logLevel: info

agents:
  assistant:
    provider: anthropic
    model: claude-sonnet-4-5-20250929

channels:
  telegram:
    enabled: true
    token: "${TELEGRAM_BOT_TOKEN}"

gateway:
  enabled: true
  port: 4766
  tokens:
    - id: default
      secret: "${COMIS_GATEWAY_TOKEN}"
      scopes: ["*"]
```

### Complete Annotated Example

A realistic single-tenant deployment running one Telegram-connected research assistant with persistent memory, gateway auth, scheduled heartbeat, and approval gates for sensitive actions. Copy this as a starting point and trim anything you don't need — defaults take care of the rest.

```yaml theme={}
# ~/.comis/config.yaml — annotated reference deployment
tenantId: research-lab            # Used to scope memory and sessions
logLevel: info                    # trace/debug/info/warn/error/fatal
dataDir: ""                       # Empty resolves to ~/.comis

# -- One agent: research assistant on Anthropic Sonnet 4.5 --------------------
agents:
  research-bot:
    name: "Research Bot"
    provider: anthropic
    model: claude-sonnet-4-5-20250929
    maxSteps: 50                  # Reasoning steps per execution
    cacheRetention: long          # Anthropic prompt cache: long beats short
    budgets:
      perExecution: 1000000       # 1M tokens per single run
      perHour:      5000000
      perDay:       50000000
    rag:
      enabled: true               # Auto-retrieve relevant memory before LLM call
      maxResults: 8
      minScore: 0.15
    skills:
      toolPolicy:
        profile: full             # minimal | coding | messaging | supervisor | full
        deny: ["exec"]            # No shell exec for this agent
    session:
      resetPolicy:
        mode: hybrid              # daily + idle whichever fires first
        dailyResetHour: 4
        idleTimeoutMs: 14400000   # 4 hours
    scheduler:
      heartbeat:
        enabled: true             # Proactive check-ins
        intervalMs: 1800000       # 30 min

# -- One inbound channel ------------------------------------------------------
channels:
  telegram:
    enabled: true
    botToken: "${TELEGRAM_BOT_TOKEN}"
    allowFrom: ["123456789"]      # Only this Telegram user can reach the agent
    mediaProcessing:
      transcribeAudio: true
      analyzeImages:   true

# -- Memory: SQLite + local embeddings ---------------------------------------
memory:
  dbPath: "memory.db"
  walMode: true
  embeddingModel: "text-embedding-3-small"
  embeddingDimensions: 1536
  retention:
    maxAgeDays: 365               # Keep one year of memory entries (0 = no age limit)

# -- Gateway: HTTP + WebSocket on loopback only ------------------------------
gateway:
  enabled: true
  host: "127.0.0.1"               # 0.0.0.0 only with TLS in front
  port: 4766
  tokens:
    - id: cli-default
      secret: "${COMIS_GATEWAY_TOKEN}"
      scopes: ["*"]
  rateLimit:
    windowMs: 60000
    maxRequests: 200

# -- Approvals: gate destructive actions -------------------------------------
approvals:
  enabled: true
  defaultPolicy: prompt           # prompt | allow | deny
  rules:
    - match: { tool: "exec", argMatches: ["rm ", "DROP ", "delete from"] }
      action: deny
    - match: { tool: "exec" }
      action: prompt

# -- Scheduler: cron + heartbeat ---------------------------------------------
scheduler:
  cron:
    enabled: true
    defaultTimezone: "America/New_York"
    # wakeGate: true          # optional: run scheduler pre-run wake-gates (default: each agent's `script` surface)
  heartbeat:
    enabled: true
    intervalMs: 600000            # 10 min global heartbeat coalescer
```

The values shown are real (not placeholders). Save it as `~/.comis/config.yaml`, populate `~/.comis/.env` with `TELEGRAM_BOT_TOKEN` and `COMIS_GATEWAY_TOKEN`, and `comis daemon start` will boot a working deployment.

## Quick Reference

All 42 top-level configuration sections at a glance:

| Section                | Type                             | Description                                                                                                            |
| ---------------------- | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `tenantId`             | `string`                         | Tenant identifier for multi-tenancy                                                                                    |
| `logLevel`             | `enum`                           | Global log level (trace/debug/info/warn/error/fatal)                                                                   |
| `dataDir`              | `string`                         | Base data directory for persistent storage                                                                             |
| `agentDir`             | `string`                         | SDK agent directory for persistent settings                                                                            |
| `agents`               | `Record<string, PerAgentConfig>` | Multi-agent configuration map                                                                                          |
| `channels`             | `ChannelConfig`                  | Channel adapter configuration                                                                                          |
| `memory`               | `MemoryConfig`                   | Memory system configuration                                                                                            |
| `security`             | `SecurityConfig`                 | Security, audit, and secrets configuration                                                                             |
| `routing`              | `RoutingConfig`                  | Multi-agent routing dispatch                                                                                           |
| `daemon`               | `DaemonConfig`                   | Daemon process, logging, and watchdog                                                                                  |
| `scheduler`            | `SchedulerConfig`                | Cron, heartbeat, and task automation                                                                                   |
| `gateway`              | `GatewayConfig`                  | HTTPS server, tokens, TLS, rate limiting                                                                               |
| `integrations`         | `IntegrationsConfig`             | External services (MCP, search, media)                                                                                 |
| `monitoring`           | `MonitoringConfig`               | System health monitoring                                                                                               |
| `observability`        | `ObservabilityConfig`            | Observability persistence (retention, snapshots)                                                                       |
| `diagnostics`          | `DiagnosticsConfig`              | Trajectory JSONL recording, config-audit log path, cache-trace enable/disable knobs                                    |
| `plugins`              | `PluginsConfig`                  | Plugin system configuration                                                                                            |
| `queue`                | `QueueConfig`                    | Command queue and concurrency control                                                                                  |
| `streaming`            | `StreamingConfig`                | Block streaming and delivery                                                                                           |
| `autoReplyEngine`      | `AutoReplyEngineConfig`          | Agent activation for inbound messages                                                                                  |
| `sendPolicy`           | `SendPolicyConfig`               | Outbound message gating rules                                                                                          |
| `embedding`            | `EmbeddingConfig`                | Embedding provider (local GGUF or OpenAI)                                                                              |
| `envelope`             | `EnvelopeConfig`                 | Message metadata enrichment                                                                                            |
| `browser`              | `BrowserConfig`                  | Browser automation (CDP, headless Chrome)                                                                              |
| `models`               | `ModelsConfig`                   | Model catalog and alias configuration                                                                                  |
| `providers`            | `ProvidersConfig`                | LLM provider entries (API keys, endpoints)                                                                             |
| `messages`             | `MessagesConfig`                 | Messaging UX (splitting, typing, receipts)                                                                             |
| `approvals`            | `ApprovalsConfig`                | Action approval workflow                                                                                               |
| `webhooks`             | `WebhooksConfig`                 | Webhook subsystem (path routing, HMAC)                                                                                 |
| `lifecycleReactions`   | `LifecycleReactionsConfig`       | Processing phase emoji reactions                                                                                       |
| `responsePrefix`       | `ResponsePrefixConfig`           | Response prefix/suffix template                                                                                        |
| `deliveryQueue`        | `DeliveryQueueConfig`            | Crash-safe outbound delivery queue                                                                                     |
| `deliveryMirror`       | `DeliveryMirrorConfig`           | Session delivery mirroring and deduplication                                                                           |
| `outputRetention`      | `OutputRetentionConfig`          | Output retention housekeeper configuration -- per-class retention window in milliseconds                               |
| `deliveryTiming`       | `DeliveryTimingConfig`           | Inter-block delivery pacing                                                                                            |
| `coalescer`            | `CoalescerConfig`                | Block coalescer for streaming                                                                                          |
| `senderTrustDisplay`   | `SenderTrustDisplayConfig`       | Sender identity display in envelope                                                                                    |
| `documentation`        | `DocumentationConfig`            | Documentation links for system prompt                                                                                  |
| `telegramFileRefGuard` | `TelegramFileRefGuardConfig`     | Telegram file reference guard                                                                                          |
| `tooling`              | `ToolingConfig`                  | Tool-first capability layer -- MCP/skill hints, install-detour mode                                                    |
| `orchestration`        | `OrchestrationConfig`            | Small-model DAG-authoring gates (all default `true` — full authoring out of the box; set a flag `false` to opt it out) |
| `executor`             | `ExecutorConfig`                 | Credential-broker executor configuration (optional; omit unless using the broker)                                      |

## Core Settings

<AccordionGroup>
  <Accordion title="tenantId, logLevel, dataDir, agentDir">
    Scalar fields at the root of the configuration.

    | Key        | Type     | Default                       | Description                                                                          |
    | ---------- | -------- | ----------------------------- | ------------------------------------------------------------------------------------ |
    | `tenantId` | `string` | `"default"`                   | Tenant identifier for SaaS multi-tenancy. Used to scope memory and sessions.         |
    | `logLevel` | `enum`   | `"debug"`                     | Global log level. Values: `trace`, `debug`, `info`, `warn`, `error`, `fatal`.        |
    | `dataDir`  | `string` | `""` (resolves to `~/.comis`) | Base data directory for all persistent storage. Empty string resolves to `~/.comis`. |
    | `agentDir` | `string` | `"~/.pi/agent"`               | SDK agent directory for persistent settings.                                         |

    ```yaml theme={}
    tenantId: my-project
    logLevel: info
    dataDir: /opt/comis/data
    agentDir: ~/.pi/agent
    ```
  </Accordion>
</AccordionGroup>

## Agents

<AccordionGroup>
  <Accordion title="agents">
    Multi-agent configuration map. Each key is an agent ID, and the value is a `PerAgentConfig` object that extends `AgentConfig` with skills, scheduler, session, concurrency, and broadcast settings.

    **Type:** `Record<string, PerAgentConfig>`

    **Default:** `{ default: PerAgentConfig.parse({}) }` (one default agent with all defaults)

    #### AgentConfig Fields

    | Key                  | Type                     | Default     | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
    | -------------------- | ------------------------ | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `name`               | `string`                 | `"Comis"`   | Display name for the agent                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
    | `model`              | `string`                 | `"default"` | LLM model identifier. `"default"` resolves via `models.defaultModel`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
    | `provider`           | `string`                 | `"default"` | LLM provider. `"default"` resolves via `models.defaultProvider`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
    | `capabilityClass`    | `enum`                   | *(unset)*   | Pin the model's capability class: `frontier`, `mid`, `small`, `nano`. When unset, the class is derived from the provider family (openai/anthropic/google → `frontier`). When set, it overrides that heuristic so the reduced prompt, small/nano tool deferral, and the `contextEngine.budget.effectiveContextCap{Small,Nano}` caps apply on **any** provider/model — e.g. `capabilityClass: nano` + `effectiveContextCapNano: 8192` forces a small-window nano treatment on a large-window model (gpt-5-nano, claude-haiku, gemini-flash). Takes precedence over the provider-level `providers.<id>.capabilityClass`.                                                                                                                                                        |
    | `maxSteps`           | `number`                 | `150`       | Maximum reasoning steps per execution                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
    | `thinkingLevel`      | `enum`                   | *(unset)*   | SDK thinking level override: `off`, `minimal`, `low`, `medium`, `high`, `xhigh`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
    | `maxTokens`          | `number`                 | *(unset)*   | SDK max tokens override                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
    | `temperature`        | `number`                 | *(unset)*   | SDK temperature override (0-2)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
    | `cacheRetention`     | `enum`                   | `"long"`    | Anthropic prompt cache retention: `none`, `short`, `long`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
    | `maxContextChars`    | `number`                 | `100000`    | Maximum total characters for context window (\~25k tokens)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
    | `maxToolResultChars` | `number`                 | `50000`     | Maximum characters per tool result before truncation. Small/nano-class models default lower (small `12000`, nano `8000`) so a single large result can't fill the window; set this explicitly (or use `capabilityClassOverride: frontier`) to keep the full `50000`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
    | `preserveRecent`     | `number`                 | `4`         | Minimum recent messages to always preserve during compaction                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
    | `workspacePath`      | `string`                 | *(unset)*   | Path to agent workspace directory containing identity files                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
    | `reactionLevel`      | `enum`                   | *(unset)*   | Reaction frequency: `minimal` (1 per 5-10 exchanges) or `extensive` (react freely)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
    | `language`           | `string`                 | *(unset)*   | Explicit response locale, consumed by the deterministic degraded replies (context-exhausted / output-starved notices) and locale-quality observability. Must be a canonical BCP-47 tag (`"he"`, `"pt-BR"`) — validation rejects anything else. When omitted, Comis resolves the turn's locale from the channel-supplied request locale (e.g. the Telegram client's `language_code` — ignored for the turn when the message's script contradicts it), then the dominant non-Latin script of the current message (as a script-only locale such as `und-Hebr`), else leaves it unset. A resolved locale also governs the live reply: a script-mismatched final response triggers at most one bounded tools-disabled repair turn (see [Multilingual](/operations/multilingual)). |
    | `enforceFinalTag`    | `boolean`                | `false`     | When enabled, only content inside `<final>` blocks reaches users. Suppresses all content outside final tags on both streaming and non-streaming paths.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
    | `fastMode`           | `boolean`                | `false`     | Enables fast mode for the LLM provider (provider-specific behavior).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
    | `storeCompletions`   | `boolean`                | `false`     | When enabled, sends `store: true` to OpenAI-compatible providers for completion storage.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
    | `oauthProfiles`      | `Record<string, string>` | `{}`        | Per-provider OAuth profile preference. Keys are provider ids; values are profile ids in `<provider>:<identity>` form (e.g., `openai-codex:user@example.com`). The daemon resolves at LLM-call time as: this map -> `lastGood` per provider -> first available profile in the store. See [OAuth concepts -> Multi-account profiles](/security/oauth#multi-account-profiles).                                                                                                                                                                                                                                                                                                                                                                                                  |

    **Multi-account example** -- two agents, two ChatGPT accounts:

    ```yaml title="~/.comis/config.yaml" theme={}
    security:
      storage: file

    agents:
      default:
        provider: openai-codex
        model: gpt-5.1
        oauthProfiles:
          openai-codex: "openai-codex:user@example.com"

      work-agent:
        provider: openai-codex
        model: gpt-5.1-codex-max
        oauthProfiles:
          openai-codex: "openai-codex:work@company.com"
    ```

    #### Autonomy (agents.\*.autonomy)

    How much the agent may do on its own. Set `profile` to pick a named posture;
    omit the whole block to get `standard` (the zero-config default). Any explicit
    field overrides the profile (progressive disclosure). See the
    [Autonomy](/agents/autonomy) page for the full model and the legible-degrade
    behavior.

    | Key                             | Type       | Default                                    | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
    | ------------------------------- | ---------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
    | `profile`                       | `enum`     | `"standard"`                               | `assistant`, `standard`, `unattended`, or `max`. `standard` is the zero-config default. `unattended`'s never-hang mode behaviors (deny+escalate, the denial breaker, operator evict) are **active**; its capability set stays `standard`-equivalent (no over-grant). `max` still resolves to `standard`-equivalent in this release plus an "available later" notice.                                                                                                                                                                                                                                                                                                                                                                 |
    | `role`                          | `enum`     | `"worker"`                                 | `worker` (default) or `coordinator`. `worker` does its work inline -- no change to existing agents. `coordinator` strips a long-running lead to the orchestration surface (`sessions_spawn`/`pipeline`/`cron`/`message` + the read-only drill-in tools `read`/`grep`/`find`/`ls` + the `obs_query` observability tool) under a delegate-then-synthesize doctrine -- heavy work always routes to a fresh-window child (`exec`/`edit`/`write`/`browser` are not on the coordinator surface). **Narrows the tool surface only; never widens a capability** -- the resolved `orch:*` set is byte-identical with and without the role. An explicit `tool_groups`/`full` overrides it. See [Long-Running Coordinator](/agents/efficiency). |
    | `enabled`                       | `boolean`  | *(from profile)*                           | Whether autonomy surfaces are on. Defaults from the profile (`standard` -> true, `assistant` -> false); an explicit value overrides it.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
    | `capabilities`                  | `string[]` | *(from profile)*                           | Explicit `orch:*` capability list. Overrides the profile's base set (still bounded by the structural floor).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
    | `mode`                          | `enum`     | *(from profile)*                           | Autonomy mode: `default`, `accept-reversible`, `unattended`, `max`. Operator-set; the agent cannot self-raise it.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
    | `denialBreakerN`                | `number`   | `5`                                        | Consecutive floor blocks (capability/outward-quota denials) on a single run that trip the denial breaker -> the run aborts (`execution:aborted` reason `denial_breaker`) and escalates, rather than retry-looping and burning the budget. An allowed step in between resets the count. Positive integer. The never-hang dial for the [`unattended`](/agents/autonomy#the-unattended-profile) profile (applies to every profile).                                                                                                                                                                                                                                                                                                     |
    | `evictOnPolicyUnreachable`      | `boolean`  | `true`                                     | Fail-closed posture: if the run's autonomy mode cannot be resolved, treat it as `default` (the safe profile), never broader. This is the same safe target the operator [`autonomy.evict`](/reference/json-rpc#autonomy-evict) demotion lands on.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
    | `aggregateBudgetUsd`            | `number`   | `200`                                      | Hard \$ ceiling across the whole spawn tree, per root run. Alias of `budget.aggregateUsd` (either form resolves to the same bound). A generous runaway backstop, not a normal-use limit — lower it per-agent for a tighter guard.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
    | `maxConcurrentSelfAgents`       | `number`   | `4`                                        | Tree-wide concurrent self-spawned-agent ceiling. Alias of `spawn.maxConcurrentSelfAgents`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
    | `maxSelfSpawnRatePerMin`        | `number`   | `30`                                       | Self-spawn rate limit per minute (distinct from the concurrency cap).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
    | `cronSelfMax`                   | `number`   | `8`                                        | Maximum agent-authored cron jobs.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
    | `lease.leaseMaxTtlMin`          | `number`   | `60`                                       | Capability-lease renewal ceiling, in minutes. A jailed-surface lease renews on a shorter per-renew TTL but never past this maximum, so revoking a lease actually stops its renewal (no unbounded re-lease). Positive integer.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
    | `budget.aggregateUsd`           | `number`   | `200`                                      | Per-root-run priced \$ ceiling across the whole spawn tree (the `aggregateBudgetUsd` alias resolves here). A generous runaway backstop, not a normal-use limit. Positive.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
    | `budget.tokens`                 | `number`   | `200000000`                                | Per-root-run token ceiling (200M). Bites even on an unknown-priced (subscription / $0) model that the $ limb counts as free, so a runaway loop is bounded regardless of pricing knowledge. Positive integer.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
    | `budget.wallClockMs`            | `number`   | `172800000`                                | Per-root-run wall-clock ceiling in milliseconds (48 h). Backstops a stuck/looping tree that burns neither \$ nor tokens fast. Positive integer.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
    | `rate.perRootCallsPerSec`       | `number`   | `20`                                       | Calls/second across the whole spawn tree (per root run). Bounds a `for(;;) spawn()` call storm. Positive integer.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
    | `rate.perSocketCallsPerSec`     | `number`   | `10`                                       | Calls/second on a single orchestration socket. Positive integer.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
    | `rate.connectionChurnPerMin`    | `number`   | `60`                                       | New-connection churn per minute (a reconnect-storm cap). Positive integer.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
    | `spawn.maxConcurrentSelfAgents` | `number`   | `4`                                        | Tree-wide concurrent self-agent ceiling (the `maxConcurrentSelfAgents` alias resolves here). Positive integer.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
    | `spawn.maxSpawnDepth`           | `number`   | `3`                                        | Delegation-tree depth cap. Positive integer.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
    | `spawn.maxChildrenPerAgent`     | `number`   | `5`                                        | Per-caller fan-out cap (children one agent may spawn). Positive integer.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
    | `outward.originOnly`            | `boolean`  | `true`                                     | When true, only the agent's own origin channel is an auto-allowable outward target; a new target needs an explicit grant.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
    | `outward.perTargetGrants`       | `string[]` | `[]`                                       | Explicit per-target grant list. A send to a non-origin target is denied unless its channel id is listed here.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
    | `outward.volumeCap`             | `number`   | `4000`                                     | Per-send volume bound (a mass-recipient / high-volume send trips this gate even when reversible). Positive integer.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
    | `durability.enabled`            | `boolean`  | `true`                                     | Durable runs (on by default — full capability out of the box): the daemon persists a per-root checkpoint as the spawn tree advances, emits a keep-alive heartbeat, and on restart re-mints the run from its persisted attenuated capabilities. A retained committed outward operation short-circuits; any interrupted or otherwise ambiguous send is parked `unresolved` and escalated without a channel query or replay. This is duplicate suppression for one stable operation identity, not universal exactly-once delivery. Set `false` to opt out (no durable stores or watchdog). Only takes effect for an autonomy-bearing agent.                                                                                             |
    | `durability.staleHeartbeatMs`   | `number`   | `120000`                                   | Lapsed-heartbeat threshold in milliseconds. A running run whose last heartbeat is older than this is treated as crashed and orphan-swept by the daemon-wide watchdog (which also fires at this cadence). Default is 4x `keepAliveMs` (the conservative ratio: a transiently-slow run must not be falsely failed and its work duplicated). Positive integer.                                                                                                                                                                                                                                                                                                                                                                          |
    | `durability.keepAliveMs`        | `number`   | `30000`                                    | Keep-alive write cadence in milliseconds — how often a live run stamps its heartbeat, independent of step/spawn completion so a long-running child never looks stale. Positive integer.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
    | `durability.recoveryBudgetMs`   | `number`   | `30000`                                    | Wall-clock recovery budget in milliseconds for one boot/watchdog pass. A backlog larger than the budget is partially recovered and the remainder deferred to the next pass (no thundering herd on a large crash backlog). Positive integer.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
    | `durability.orchestrateResume`  | `boolean`  | `true`                                     | Preserve a timed-out `orchestrate` run's pinned script and last checkpoint so an explicit `orchestrate({resumeRunId})` can restart it from verified artifacts. Set `false` to kill and clean timed-out runs without retaining resumable artifacts.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
    | `message`                       | `object`   | `{ channels: ["origin"], maxPerHour: 20 }` | Outward-message posture. `channels` lists allowed targets (`"origin"` = the agent's own); `maxPerHour` is the rolling-hour send quota. A non-origin target still requires the exact endpoint already bound to a trusted run; this grant never discovers or mints routing authority.                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
    | `web`                           | `boolean`  | *(from profile)*                           | Toggle for `orch:web` (untrusted external content).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
    | `analyze`                       | `boolean`  | *(from profile)*                           | Toggle for `orch:analyze` (cost-bearing media analysis).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
    | `write`                         | `boolean`  | *(from profile)*                           | Toggle for `orch:write` (jailed workspace mutation).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
    | `browse`                        | `boolean`  | `false`                                    | Toggle for `orch:browse` (a real browser). Off in every default profile; even when on, it always escalates for approval.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |

    ```yaml title="~/.comis/config.yaml -- standard with a tighter budget" theme={}
    agents:
      default:
        autonomy:
          profile: standard
          aggregateBudgetUsd: 5
          browse: true            # opt into orch:browse (still always escalates)
    ```

    ```yaml title="~/.comis/config.yaml -- a long-running coordinator lead" theme={}
    agents:
      orchestrator:
        autonomy:
          profile: standard
          role: coordinator       # narrow to the orchestration surface; delegate heavy work
    ```

    #### Budget (agents.\*.budgets)

    | Key            | Type     | Default     | Description                          |
    | -------------- | -------- | ----------- | ------------------------------------ |
    | `perExecution` | `number` | `2000000`   | Max tokens per single execution      |
    | `perHour`      | `number` | `10000000`  | Max tokens per hour (rolling window) |
    | `perDay`       | `number` | `100000000` | Max tokens per day (rolling window)  |

    #### Circuit Breaker (agents.\*.circuitBreaker)

    | Key                 | Type     | Default | Description                                 |
    | ------------------- | -------- | ------- | ------------------------------------------- |
    | `failureThreshold`  | `number` | `5`     | Consecutive failures before opening circuit |
    | `resetTimeoutMs`    | `number` | `60000` | Milliseconds before attempting recovery     |
    | `halfOpenTimeoutMs` | `number` | `30000` | Milliseconds for half-open probe timeout    |

    #### Model Routes (agents.\*.modelRoutes)

    Extensible key-value map of task type to model identifier. Any string key maps to a model ID string.

    | Key         | Type     | Default   | Description                                                    |
    | ----------- | -------- | --------- | -------------------------------------------------------------- |
    | `default`   | `string` | *(unset)* | Default model for unrouted tasks (falls back to `agent.model`) |
    | *(any key)* | `string` | --        | Custom route name to model ID                                  |

    #### Operation Model Overrides (agents.\*.operationModels)

    Override the model and timeout for specific internal operation types. Each entry is an `OperationModelEntry` with optional `model` (string `"provider:modelId"`) and `timeout` (number) fields. The key is `timeout` (milliseconds) — `timeoutMs` is rejected by the strict config parser. Omit any type to use the agent's primary model.

    | Operation type   | Default timeout        | When it fires                                                                                                                                          | Recommended model                                                                                                                   |
    | ---------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- |
    | `cron`           | `600000` (10 min)      | Scheduled cron task execution                                                                                                                          | —                                                                                                                                   |
    | `heartbeat`      | `60000` (1 min)        | Periodic heartbeat check                                                                                                                               | —                                                                                                                                   |
    | `subagent`       | `120000` (2 min)       | Sub-agent task delegation                                                                                                                              | —                                                                                                                                   |
    | `compaction`     | `60000` (1 min)        | Context compaction summarization                                                                                                                       | Capable model or `contextEngine.compaction.strongerSummarizerModel`                                                                 |
    | `taskExtraction` | `30000` (30 s)         | Task extraction from conversation                                                                                                                      | —                                                                                                                                   |
    | `condensation`   | `30000` (30 s)         | Memory condensation                                                                                                                                    | Capable model                                                                                                                       |
    | `verification`   | `120000` (2 min)       | Pre-delivery critic. Fires when `agents.<id>.verification.enabled=true` and the response is a completion-claiming response meeting `minResponseChars`. | Cheap model: `"anthropic:claude-haiku-4-5-20250929"` or `"ollama:qwen3.6:27b"` for local self-check. Omit to use the primary model. |
    | `planning`       | Primary prompt timeout | Pre-execution planner.                                                                                                                                 | Capable model for checklist generation. Omit to use the primary model.                                                              |
    | `outcomeJudge`   | `30000` (30 s)         | Post-execution classification for conversational turns without a deterministic tool or pipeline outcome.                                               | A cheap classification model. The override is agent-scoped and verdict provenance records the resolved model.                       |
    | `skillSynthesis` | `150000` (2.5 min)     | Offline procedural-skill synthesis from successful trajectories.                                                                                       | A mid-tier model capable of structured synthesis.                                                                                   |

    ```yaml theme={}
    agents:
      default:
        operationModels:
          verification:
            model: "anthropic:claude-haiku-4-5-20250929"
            timeout: 30000
          compaction:
            model: "anthropic:claude-haiku-4-5-20250929"
    ```

    #### Model Failover (agents.\*.modelFailover)

    | Key                  | Type              | Default   | Description                                        |
    | -------------------- | ----------------- | --------- | -------------------------------------------------- |
    | `fallbackModels`     | `FallbackModel[]` | `[]`      | Ordered fallback models (provider + modelId pairs) |
    | `authProfiles`       | `AuthProfile[]`   | `[]`      | Per-provider API key profiles for auth rotation    |
    | `allowedModels`      | `string[]`        | `[]`      | Model allowlist (empty = allow all)                |
    | `maxAttempts`        | `number`          | `6`       | Maximum total attempts across all models/keys      |
    | `cooldownInitialMs`  | `number`          | `60000`   | Initial cooldown duration in ms                    |
    | `cooldownMultiplier` | `number`          | `5`       | Exponential cooldown multiplier                    |
    | `cooldownCapMs`      | `number`          | `3600000` | Maximum cooldown duration in ms (1 hour)           |

    #### SDK Retry (agents.\*.sdkRetry)

    | Key           | Type      | Default | Description                                                |
    | ------------- | --------- | ------- | ---------------------------------------------------------- |
    | `enabled`     | `boolean` | `true`  | Enable SDK-native retry with exponential backoff           |
    | `maxRetries`  | `number`  | `5`     | Maximum retry attempts for transient errors                |
    | `baseDelayMs` | `number`  | `4000`  | Base delay in ms before first retry (doubles each attempt) |
    | `maxDelayMs`  | `number`  | `60000` | Maximum delay cap in ms between retries                    |

    #### Prompt Timeout (agents.\*.promptTimeout)

    | Key                      | Type     | Default  | Description                                                                                                                                                                                                                                                                                                                                                              |
    | ------------------------ | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
    | `promptTimeoutMs`        | `number` | `180000` | Stall budget for primary prompt calls (3 minutes): the deadline resets on activity — stream text/thinking deltas (throttled \~1/s), tool progress, and tool completions. A turn dies only when NO activity occurs for this long. Stall semantics apply to all providers.                                                                                                 |
    | `retryPromptTimeoutMs`   | `number` | `60000`  | Wall-clock timeout for retry and fallback prompt calls (1 minute). Used during auth rotation and model fallback attempts. Whole-turn (not stall-based) — retry and fallback prompts do not reset on activity.                                                                                                                                                            |
    | `stallCeilingMultiplier` | `number` | `10`     | Makespan ceiling: a turn is aborted at `promptTimeoutMs × stallCeilingMultiplier` even while still streaming — bounds runaway generations that would otherwise reset the stall budget forever. Valid range 1–100 (a value below 1 would fire the ceiling before the stall budget; the product is additionally capped at Node's \~24.8-day timer limit). Runtime-tunable. |

    See [Resilience](/agents/resilience) for how prompt timeouts fit into the full resilience stack.

    #### Background Tasks (agents.\*.backgroundTasks)

    Controls automatic promotion of long-running tool calls into the daemon's
    durable background-task manager. The manager is shared across agents. Every
    field except `maxTotal` resolves from the task owner's agent configuration.
    `maxTotal` is the daemon-wide ceiling and resolves once from the agent selected
    by `routing.defaultAgentId`; setting it on another agent does not change the
    shared cap.

    | Key                       | Type       | Default  | Description                                                                                                                                   |
    | ------------------------- | ---------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
    | `enabled`                 | `boolean`  | `true`   | Enable automatic background promotion.                                                                                                        |
    | `autoBackgroundMs`        | `number`   | `10000`  | Promote an eligible tool call after this many milliseconds.                                                                                   |
    | `maxPerAgent`             | `number`   | `5`      | Maximum concurrent background tasks for this agent.                                                                                           |
    | `maxTotal`                | `number`   | `20`     | Maximum concurrent background tasks across the shared manager. Only the `routing.defaultAgentId` agent's value supplies this daemon-wide cap. |
    | `maxBackgroundDurationMs` | `number`   | `300000` | Per-owner-agent hard runtime limit for a background task. When it expires, the task is aborted and finalized as failed.                       |
    | `excludeTools`            | `string[]` | `[]`     | Additional tool names that must never be auto-promoted.                                                                                       |
    | `maxBackgroundHops`       | `number`   | `3`      | Per-owner-agent continuation re-entry depth, preventing a completion turn from creating an unbounded background chain.                        |

    #### RAG (agents.\*.rag)

    | Key                  | Type           | Default                                                        | Description                                                                                                                                                                                                                                                                                                                                                                                                                                 |
    | -------------------- | -------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `enabled`            | `boolean`      | `true`                                                         | Enable automatic memory retrieval before LLM calls                                                                                                                                                                                                                                                                                                                                                                                          |
    | `maxResults`         | `number`       | `5`                                                            | Maximum memory results to retrieve                                                                                                                                                                                                                                                                                                                                                                                                          |
    | `maxContextChars`    | `number`       | `4000`                                                         | Maximum characters of memory context injected                                                                                                                                                                                                                                                                                                                                                                                               |
    | `minScore`           | `number`       | `0.1`                                                          | Minimum RRF score threshold (0-1)                                                                                                                                                                                                                                                                                                                                                                                                           |
    | `includeTrustLevels` | `string[]`     | `["system", "learned"]`                                        | Trust levels to include in retrieval                                                                                                                                                                                                                                                                                                                                                                                                        |
    | `baseFloor`          | `number` (0–1) | `0` for frontier/mid; `0.15` for small/nano (capability-gated) | Minimum BASE relevance score (pre-boost) for memory injection. Boosts cannot resurrect a memory whose base score falls below this threshold. Gates on `ScoreBreakdown.base` (un-boosted cosine/RRF score), applied after `scoreWithBreakdown()`. Setting `baseFloor: 0` is the unset sentinel — the capability default applies (0.15 for small/nano). Any value greater than 0 is treated as explicit and wins over the capability default. |

    > **Security:** A weaker ModelProfile cannot lower this below the operator-set value. `FROZEN_TRUST_PATHS` and `MemoryWriteValidator` remain enforced for all `capabilityClass`es regardless of this setting.

    > **Capability-gated default:** For `small`/`nano` agents, `rag.baseFloor` defaults
    > to `0.15` — dropping memories with a base relevance score below 0.15 (a mitigation
    > against low-relevance poison memories). For `frontier`/`mid`, the default remains `0` (no filter). Setting
    > `rag.baseFloor: 0` explicitly is treated the same as "unset" — the capability default applies.
    > To disable the relevance floor on a small/nano agent, set
    > `providers.<id>.capabilityClass: "frontier"` to override the capability class, or set
    > `rag.baseFloor: 0.01` (any value greater than 0 is treated as explicit and wins over the capability default).

    **Cross-encoder rerank (agents.\*.rag.rerank)** — opt-in (default off)

    A cross-encoder re-scores the top fusion candidates with the local reranker model (see `memory.rerankerModel`). Disabled by default; on timeout or unavailability it falls back to the fusion-ranked order, and the reranker GGUF is never downloaded while disabled.

    | Key             | Type      | Default | Description                                                                      |
    | --------------- | --------- | ------- | -------------------------------------------------------------------------------- |
    | `enabled`       | `boolean` | `false` | Enable cross-encoder reranking of fused candidates (opt-in)                      |
    | `maxCandidates` | `number`  | `40`    | Candidate cap bounding worst-case rerank latency (positive)                      |
    | `minResults`    | `number`  | `1`     | Skip reranking when fewer than this many candidates are present (nonnegative)    |
    | `timeoutMs`     | `number`  | `800`   | Rerank wall-clock timeout in ms; on timeout fall back to fusion order (positive) |

    **Scoring boosts (agents.\*.rag.scoring)** — all `number`, range 0-1

    Multiplicative boosts applied to the reranked-or-fused score before the trust filter.

    | Key             | Type     | Default | Description                                                                                   |
    | --------------- | -------- | ------- | --------------------------------------------------------------------------------------------- |
    | `recencyAlpha`  | `number` | `0.2`   | Recency boost weight (applied via `createdAt`)                                                |
    | `temporalAlpha` | `number` | `0.2`   | Event-time proximity boost weight (applies when `occurredAt` is present, neutral when absent) |
    | `proofAlpha`    | `number` | `0.1`   | Proof-count boost weight (neutral until `proofCount` exists)                                  |
    | `trustAlpha`    | `number` | `0.1`   | Trust-level boost weight and tie-break                                                        |

    **Entity associative lane (agents.\*.rag.entityLane)** — opt-in (default off)

    A one-hop entity-associative fusion lane. When disabled (the default), RRF fusion is unchanged.

    | Key            | Type      | Default | Description                                                   |
    | -------------- | --------- | ------- | ------------------------------------------------------------- |
    | `enabled`      | `boolean` | `false` | Enable the one-hop entity-associative lane (opt-in)           |
    | `seedCount`    | `number`  | `5`     | How many top search hits seed the entity self-join (positive) |
    | `perEntityCap` | `number`  | `200`   | Max shared-entity neighbour rows the lane returns (positive)  |
    | `weight`       | `number`  | `1.0`   | RRF weight for the entity lane (≥ 0)                          |

    **Enabling the opt-in recall features**

    Reranking and the entity lane both ship off. The reranker model and the recall trace live under the top-level `memory` and `diagnostics` keys (not under `agents`). A schema-valid block that turns them on:

    ```yaml theme={}
    agents:
      default:
        rag:
          enabled: true                 # always-on recall (default true)
          rerank:
            enabled: true               # opt-in (default false) -- cross-encoder re-scoring
            maxCandidates: 40
            timeoutMs: 800
          scoring:                      # multiplicative boosts (defaults shown)
            recencyAlpha: 0.2
            temporalAlpha: 0.2
            proofAlpha: 0.1
            trustAlpha: 0.1
          entityLane:
            enabled: true               # opt-in (default false) -- one-hop associative lane
        contextEngine:
          version: dag                  # the default lossless LCD engine; set "pipeline" for the simpler engine

    memory:
      rerankerModel: "hf:gpustack/bge-reranker-v2-m3-GGUF:bge-reranker-v2-m3-Q8_0.gguf"
      rerankerThreads: 4
      rerankerGpu: auto

    diagnostics:
      recallTrace:
        enabled: true                   # opt-in (default false) -- full-sanitized, bounded JSONL
    ```

    #### Bootstrap (agents.\*.bootstrap)

    | Key                  | Type      | Default                                                            | Description                                                                                                                                                                                                                                                                                                                                                                                                       |
    | -------------------- | --------- | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `maxChars`           | `number`  | `20000` for frontier/mid; `3500` for small/nano (capability-gated) | Per-file character limit for workspace files injected into system prompt. For `capabilityClass` in `{small, nano}`, the effective default drops to `3_500` chars per file. Setting `maxChars: 20000` explicitly is treated the same as "unset" — the capability default of `3_500` still applies for small/nano. Use `capabilityClassOverride: frontier` or set an explicit value other than `20000` to override. |
    | `promptMode`         | `enum`    | `"full"`                                                           | Verbosity: `full`, `minimal` (sub-agents), `none` (identity only)                                                                                                                                                                                                                                                                                                                                                 |
    | `groupChatFiltering` | `boolean` | `true`                                                             | Exclude USER.md from bootstrap in group chats (privacy)                                                                                                                                                                                                                                                                                                                                                           |

    #### Workspace (agents.\*.workspace)

    | Key       | Type   | Default  | Description                                                                                                                                                                             |
    | --------- | ------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `profile` | `enum` | `"full"` | Workspace profile: `full` (all platform instructions, \~9K tokens) or `specialist` (minimal safety + workspace reference, \~800 tokens). Use `specialist` for purpose-built sub-agents. |

    #### Concurrency (agents.\*.concurrency)

    | Key                   | Type     | Default | Description                                         |
    | --------------------- | -------- | ------- | --------------------------------------------------- |
    | `maxConcurrentRuns`   | `number` | `4`     | Maximum concurrent agent runs                       |
    | `maxQueuedPerSession` | `number` | `50`    | Maximum queued messages per session before overflow |

    #### Broadcast Groups (agents.\*.broadcastGroups)

    Array of broadcast group objects for simultaneous multi-channel message delivery.

    | Key       | Type                | Default      | Description                                      |
    | --------- | ------------------- | ------------ | ------------------------------------------------ |
    | `id`      | `string`            | *(required)* | Unique group identifier                          |
    | `name`    | `string`            | `""`         | Human-readable group name                        |
    | `targets` | `BroadcastTarget[]` | `[]`         | Channel targets (channelType, channelId, chatId) |
    | `enabled` | `boolean`           | `true`       | Whether this broadcast group is active           |

    #### Elevated Reply (agents.\*.elevatedReply)

    | Key                    | Type                     | Default      | Description                                  |
    | ---------------------- | ------------------------ | ------------ | -------------------------------------------- |
    | `enabled`              | `boolean`                | `false`      | Enable trust-based model/prompt routing      |
    | `trustModelRoutes`     | `Record<string, string>` | `{}`         | Map of trust level to model route name       |
    | `trustPromptOverrides` | `Record<string, string>` | `{}`         | Map of trust level to system prompt override |
    | `defaultTrustLevel`    | `string`                 | `"external"` | Default trust level for unknown senders      |
    | `senderTrustMap`       | `Record<string, string>` | `{}`         | Per-sender trust level overrides             |

    #### Tracing (agents.\*.tracing)

    | Key         | Type      | Default             | Description                            |
    | ----------- | --------- | ------------------- | -------------------------------------- |
    | `enabled`   | `boolean` | `false`             | Enable per-LLM-call JSONL trace files  |
    | `outputDir` | `string`  | `"~/.comis/traces"` | Output directory for JSONL trace files |

    #### Gemini Cache (agents.\*.geminiCache)

    Per-agent configuration for Gemini explicit CachedContent caching. On by default for Gemini agents: Comis creates server-side cached content on Google AI Studio for a guaranteed discount on cached input tokens (system instruction + tools).

    | Key               | Type      | Default | Description                                                                                                                                                                                                                         |
    | ----------------- | --------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `enabled`         | `boolean` | `true`  | Enable Gemini explicit CachedContent caching (on by default; set `false` to opt out). Only activates for Google AI Studio providers (not Vertex AI), and only above the per-model minimum (\~4096 tokens for gemini-3-pro/3.1-pro). |
    | `maxActiveCaches` | `number`  | `20`    | Maximum active cached contents per agent. Must be a positive integer. Oldest entries are evicted (LRU) when this limit is reached.                                                                                                  |

    <Info>
      Gemini cache TTL is hardcoded at 3600 seconds (1 hour) and is not configurable per-agent. Caches are automatically refreshed when more than 50% of the TTL has elapsed. This setting is independent of the Anthropic `cacheRetention` field -- they control different provider caching mechanisms.
    </Info>

    #### Context Guard (agents.\*.contextGuard)

    | Key            | Type      | Default | Description                                                     |
    | -------------- | --------- | ------- | --------------------------------------------------------------- |
    | `enabled`      | `boolean` | `true`  | Enable context window guard checks                              |
    | `warnPercent`  | `number`  | `80`    | Warn when context usage reaches this percent (0-100)            |
    | `blockPercent` | `number`  | `95`    | Block execution when context usage reaches this percent (0-100) |

    #### Context Engine (agents.\*.contextEngine)

    Context engine configuration. Controls the system that manages what your agent sees each turn. The default is **dag** (the LCD engine). The **pipeline** value (the simpler sequential layered engine) is the first-class **opt-in** — set `version: "pipeline"` to use it. DAG keeps canonical messages and tool results recoverable while assembling a budget-bounded active prompt with a verbatim fresh tail, provider-valid transcript repair, selected older material, and explicitly lossy summaries. The in-session `ctx_*` expansion tools recover underlying detail on demand. See [Compaction](/agents/compaction) for a user-friendly explanation.

    **Core fields**

    | Key       | Type      | Default | Description                                                                                                        |
    | --------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------------ |
    | `enabled` | `boolean` | `true`  | Master toggle for the context engine                                                                               |
    | `version` | `string`  | `"dag"` | Operating mode: `"dag"` (default, the lossless LCD engine) or `"pipeline"` (opt-in, the sequential layered system) |

    **Shared fields** (both modes)

    | Key                 | Type     | Default | Description                                                                                                                                                                |
    | ------------------- | -------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `thinkingKeepTurns` | `number` | `10`    | Recent assistant turns that retain thinking blocks (1-50)                                                                                                                  |
    | `compactionModel`   | `string` | `""`    | Model used for summarization. Empty (the default) means runtime-resolved against the agent's primary provider. Override with a specific cheaper or faster model if needed. |
    | `evictionMinAge`    | `number` | `15`    | Minimum turn age before stale errors are evicted by the dead content evictor (3-50)                                                                                        |

    **Pipeline mode fields** (`version: "pipeline"`)

    | Key                                   | Type                     | Default  | Description                                                                                                                                                                                                                                                                                       |
    | ------------------------------------- | ------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `historyTurns`                        | `number`                 | `15`     | Recent user turns to keep in context (3-100)                                                                                                                                                                                                                                                      |
    | `historyTurnOverrides`                | `Record<string, number>` | --       | Per-channel-type turn count overrides (e.g., `{ dm: 10, group: 5 }`)                                                                                                                                                                                                                              |
    | `observationKeepWindow`               | `number`                 | `25`     | Recent tool uses that retain full results -- the "last K". Read-time observation masking keeps this many, and the [Tier-0 micro-compaction pass](/agents/compaction#tier-0-micro-compaction-every-turn) runs **every turn** keeping this many before evicting older read-only tool results (1-50) |
    | `observationTriggerChars`             | `number`                 | `120000` | Character threshold before observation masking activates (50K-1M)                                                                                                                                                                                                                                 |
    | `compactionCooldownTurns`             | `number`                 | `5`      | Turns to wait before re-triggering LLM compaction (1-50)                                                                                                                                                                                                                                          |
    | `compactionPrefixAnchorTurns`         | `number`                 | `2`      | User turns preserved at conversation head for cache prefix stability (0-10)                                                                                                                                                                                                                       |
    | `outputEscalation.enabled`            | `boolean`                | `true`   | Allow escalating output token budget when context is compacted                                                                                                                                                                                                                                    |
    | `outputEscalation.escalatedMaxTokens` | `number`                 | `32768`  | Maximum output tokens after escalation (4096-128000)                                                                                                                                                                                                                                              |
    | `observationDeactivationChars`        | `number`                 | `80000`  | Character threshold to deactivate observation masking entirely (20K-500K)                                                                                                                                                                                                                         |
    | `ephemeralKeepWindow`                 | `number`                 | `10`     | Recent ephemeral tool results to keep unmasked (1-50)                                                                                                                                                                                                                                             |

    **DAG mode fields** (`version: "dag"`) — `freshTailTurns`, the leaf/condense summarization fields, and budget-bounded eviction are **active**. Canonical records remain recoverable while the active prompt uses threshold-triggered leaf summarization, multi-tier condensation, and budget eviction. The on-demand `ctx_*` recall keys (`maxExpandTokens`, `maxRecallsPerDay`, `recallTimeoutMs`) are **reserved** and not yet consumed by those tools:

    | Key                       | Type     | Default  | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
    | ------------------------- | -------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `freshTailTurns`          | `number` | `8`      | Recent **steps** (assistant + tool round-trips, not user-turns) always kept verbatim and never evicted (1-50)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
    | `contextThreshold`        | `number` | `0.75`   | Budget utilization ratio that triggers the end-of-turn leaf-summarization pass and the condense pass's hard-fanout pressure gate (0.1-0.95). The ratio is computed against the turn's effective budget window (min of the reconciled context window and the capability-class cap) — not the model's configured contextWindow — so capped or served-bound small models compact at the real window.                                                                                                                                                                                                                                                                                                                                   |
    | `leafMinFanout`           | `number` | `8`      | Minimum raw messages before creating a leaf summary (2-20)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
    | `condensedMinFanout`      | `number` | `4`      | Minimum leaf summaries before creating a condensed summary (2-20)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
    | `condensedMinFanoutHard`  | `number` | `2`      | Absolute minimum fanout for condensed summaries under pressure (2-10)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
    | `incrementalMaxDepth`     | `number` | `0`      | Maximum DAG depth for incremental compaction. -1 disables depth limit. (-1 to 10)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
    | `leafChunkTokens`         | `number` | `20000`  | Maximum source tokens per leaf summary chunk (1K-100K). Clamped at runtime to the resolved summarizer model's context window — the smaller of its configured window and the probed served window when the summarizer runs on the served-bound provider — minus the summary target, prompt-template overhead, and the threaded previous-summary size, so a small compaction summarizer (e.g. an 8K operationModels.compaction model, or a served-bound primary) is never fed an over-window chunk; oversized backlogs drain across multiple bounded passes, and a single message larger than the clamped cap is replaced by a bounded deterministic extraction (no LLM call) — its full content stays in the lossless message store. |
    | `leafTargetTokens`        | `number` | `1200`   | Target token count for each leaf summary output (96-5K)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
    | `condensedTargetTokens`   | `number` | `2000`   | Target token count for each condensed summary output (256-10K)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
    | `maxExpandTokens`         | `number` | `4000`   | Maximum tokens a recall sub-agent can read per expansion (500-50K)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
    | `maxRecallsPerDay`        | `number` | `10`     | Daily limit on recall sub-agent spawns per agent (1-100)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
    | `recallTimeoutMs`         | `number` | `120000` | Timeout for recall sub-agent execution in milliseconds (10K-600K)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
    | `largeFileTokenThreshold` | `number` | `25000`  | File token count above which content is stored as a large file reference (1K-200K)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
    | `annotationKeepWindow`    | `number` | `15`     | Recent tool results protected from annotation replacement in DAG assembly (1-50)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
    | `annotationTriggerChars`  | `number` | `200000` | Character threshold before old tool results are annotated with placeholders (10K-1M)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
    | `summaryModel`            | `string` | --       | Optional model override for DAG summarization (falls back to `compactionModel`)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
    | `summaryProvider`         | `string` | --       | Optional provider override for DAG summarization                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |

    **DAG robustness / spend / deferred-compaction fields** (`version: "dag"`) — all **active** in the current release:

    | Key                                         | Type      | Default   | Description                                                                                                                                                                                                 |
    | ------------------------------------------- | --------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `deferCompaction`                           | `boolean` | `true`    | Run the afterTurn leaf + condense passes in the background on the per-conversation single-flight serializer (never blocking the turn). `false` runs them inline at end-of-turn (deterministic, for tests)   |
    | `summarizerSpend.maxTokensPerTenantPerHour` | `number`  | `500000`  | Per-tenant rolling-hour ceiling on summarizer (input+output) tokens; over the cap the summarizer is bypassed → truncation-only assembly (no LLM call, no turn failure). `0` disables the hourly cap (min 0) |
    | `summarizerSpend.maxTokensPerTenantPerDay`  | `number`  | `5000000` | Per-tenant rolling-day summarizer token ceiling. `0` disables the daily cap (min 0)                                                                                                                         |
    | `summarizerBreaker.failureThreshold`        | `number`  | `5`       | Consecutive summarizer failures before the breaker opens → truncation-only assembly (min 1)                                                                                                                 |
    | `summarizerBreaker.resetTimeoutMs`          | `number`  | `60000`   | How long the summarizer breaker stays open before a half-open trial, in milliseconds                                                                                                                        |
    | `summarizerBreaker.halfOpenTimeoutMs`       | `number`  | `30000`   | Half-open trial window for the summarizer breaker, in milliseconds                                                                                                                                          |

    <Note>
      DAG mode fields are validated by Zod even when `version` is `"pipeline"`. This means you can pre-configure DAG settings before switching modes -- invalid values will be caught at startup regardless of the active mode.
    </Note>

    <Note>
      The DAG cross-agent isolation adds an `agent_id` column to the full-text search index, created once when the database is first initialized — a fresh install needs nothing. See [Compaction](/agents/compaction#per-tenant-agent-session-isolation).
    </Note>

    <Note>
      **Engine scope — served-window honesty and the viable floor (the recorded pipeline-parity verdict).**
      The turn-time pre-flight fit check, output-headroom enforcement, and the served/cap
      `context_exhausted` provenance (the exhaustion text that names `OLLAMA_CONTEXT_LENGTH` /
      `PARAMETER num_ctx` / `contextEngine.budget.effectiveContextCapSmall` — see the
      [served-window section](#local-model-context-window)) apply to the DEFAULT
      `version: "dag"` engine only. The `"pipeline"` engine's fit guard is its budget-aware
      compaction trigger — it compacts when the estimated context exceeds 85% of the budget
      window, computed against the UNCAPPED configured `contextWindow` (no served reconcile, no
      capability-class cap) — plus reactive provider-side `context_too_long` classification when
      the provider rejects an oversized request. (The trigger behavior is test-pinned.)
      Pipeline compaction likewise summarizes only the longest oldest-first span that fits the
      summarizer's window (reserving a summarizer-sized output allowance — at most a quarter of
      that window — for the summary itself) and keeps the un-summarized remainder in context
      (never dropped). When even the oldest message alone exceeds that budget, that single
      message is escalated through the compaction fallback ladder (worst case a bounded
      count-only note), so every evaluation shrinks the backlog and the 85% trigger re-fires on
      later turns until it drains.

      Recorded decision: the boot viable-floor WARN (the `minViable` equation) is deliberately
      ENGINE-AGNOSTIC — it fires for dag AND pipeline agents alike, because the minViable
      arithmetic (bootstrap + tool schemas + output headroom + fresh-tail reserve + safety margin
      vs the effective window) holds regardless of engine. What differs per engine is which
      TURN-TIME guard backs it up — dag: the pre-flight with knob-named exhaustion; pipeline: the
      85% compaction trigger + reactive classification. An operator running a pipeline agent
      should read the boot WARN as applying to them, while the per-turn preflight surfaces do not.
    </Note>

    #### Capacity Cap (agents.\*.contextEngine.budget)

    Prevents 256K context-window models from over-provisioning when running a small executive (e.g., qwen3.6:27b). Applied in `computeTokenBudgetForProfile` before history budget computation. `frontier` and `mid` classes always receive the full `contextWindow`.

    | Key                                                         | Type                        | Default | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
    | ----------------------------------------------------------- | --------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `agents.<id>.contextEngine.budget.effectiveContextCapSmall` | `number` (non-negative int) | `32000` | Maximum effective context tokens for `capabilityClass="small"`. `0` = no cap (use raw contextWindow). Applied to prevent 256K overfill degrading a 27–35B executive.                                                                                                                                                                                                                                                                                                                                                                                                   |
    | `agents.<id>.contextEngine.budget.effectiveContextCapNano`  | `number` (non-negative int) | `16000` | Maximum effective context tokens for `capabilityClass="nano"`. `0` = no cap. `frontier` and `mid` classes always receive the full `contextWindow`.                                                                                                                                                                                                                                                                                                                                                                                                                     |
    | `agents.<id>.contextEngine.budget.minVisibleOutputTokens`   | `number` (int, 256–8192)    | `768`   | Minimum visible output tokens guaranteed on every LLM dispatch — the non-reasoning floor (answer or tool-call body) that must remain after the model's thinking block. The total output headroom is `thinkingReserve(reasoningStyle, thinkingLevel) + minVisibleOutputTokens`. Raising this value increases the safety margin but reduces available history tokens. Applies to all capability classes; the thinking reserve on top of this floor varies by `thinkingLevel` (`high` adds 2,048 tokens; `xhigh` adds 4,096 tokens; models with no thinking block add 0). |

    #### Thinking-Effort Governor (agents.\*.thinking)

    Controls whether the thinking-effort governor may automatically adjust the active `thinkingLevel` when the remaining context window after eviction is tight.

    | Key                                           | Type      | Default | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
    | --------------------------------------------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
    | `agents.<id>.thinking.downshiftOnTightWindow` | `boolean` | `true`  | When `true`, the thinking-effort governor may automatically lower `thinkingLevel` (high → medium → low) for a dispatch when the remaining context room after eviction cannot cover `thinkingReserve(thinkingLevel) + minVisibleOutputTokens`. This prevents the model's thinking block from consuming the entire output budget and silently truncating the answer or tool call. For frontier and mid-capability models the governor is always a no-op regardless of this setting (their effective windows are large enough that the threshold is never reached). Set to `false` to disable down-shifting and always preserve the configured `thinkingLevel` (may result in `context_exhausted` degradation on tight windows instead of graceful down-shift). |

    #### Compaction Routing (agents.\*.contextEngine.compaction)

    Controls how the context engine handles LLM summarization for low-capability models. Applied in both pipeline `llm-compaction` and DAG leaf-summarizer layers.

    | Key                                                               | Type      | Default | Description                                                                                                                                                                                                                                                                                                                    |
    | ----------------------------------------------------------------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
    | `agents.<id>.contextEngine.compaction.preferEvictionByCapability` | `boolean` | `true`  | When `true`: `small`/`nano` capabilityClass → eviction-first (or `strongerSummarizerModel` if set) instead of same-model LLM summarization. Prevents degraded summaries from a weak model. A `context:compaction_routed` event fires when routing occurs.                                                                      |
    | `agents.<id>.contextEngine.compaction.strongerSummarizerModel`    | `string`  | `""`    | Optional `"provider:modelId"` for a stronger summarizer when `small`/`nano` models are detected. Empty string = pure eviction/deterministic fallback. Example: `"anthropic:claude-haiku-4-5-20250929"`. A keyless local provider (Ollama / LM Studio) is also valid (e.g. `"qwen36-local:qwen3.6:35b"`) — no API key required. |

    > **Security:** Eviction never drops security-relevant context (sender-trust, safety reinforcement, untrusted-content markers, canary token). The `security-context-pinner` enforces this fail-closed.

    #### Compact Prompt (agents.\*.contextEngine.compactPrompt)

    Controls the compact-secure promptMode for `small`/`nano` models. This mode assembles the system prompt to a bounded target while **always** retaining the full safety core, sender-trust, and config-secret sections.

    | Key                                                    | Type                | Default | Description                                                                                                                                                                                                                                         |
    | ------------------------------------------------------ | ------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `agents.<id>.contextEngine.compactPrompt.enabled`      | `boolean`           | `true`  | Enable compact-secure promptMode for `small`/`nano` capabilityClass. When `true`, retains the FULL safety core, sender-trust, and config-secret sections — never falls back to minimal mode's empty safety. `frontier`/`mid` agents are unaffected. |
    | `agents.<id>.contextEngine.compactPrompt.targetTokens` | `number` (500–8000) | `3000`  | Soft token target for the compact prompt (\~chars/3.5). At 3000 ≈ 10,500 chars.                                                                                                                                                                     |

    > **Security warning:** Setting `enabled=false` does **not** make the prompt smaller — it restores the full-size `full` promptMode. The compact prompt is safe for all deployments because it retains the security core. It is **not safe** to use `minimal` promptMode (which drops the safety block) for any security-sensitive deployment.

    #### Relevance Policy (agents.\*.contextEngine.relevance)

    Controls whether within-conversation history is assembled **relevance-first** (the margin arbiter allocates the contended history budget across tiers by fused rank, with the fresh-tail and security-pinned floors guaranteed) or **recency-first** (the existing newest-kept eviction). The decision is **capability-gated**: `small`/`nano` models on a **non-caching** provider default relevance-first (reordering is free when there is no prompt cache to break); `frontier`/`mid` and any prompt-caching model default recency-first (the arbiter does not run for them, so their assembly is untouched). Precedence: **explicit per-agent config > capability default > off**.

    | Key                                                  | Type                 | Default                                                                                                                 | Description                                                                                                                                                                                                                                                                                                                                                                                                                           |
    | ---------------------------------------------------- | -------------------- | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `agents.<id>.contextEngine.relevance.firstByDefault` | `boolean` (optional) | unset → capability default (`small`/`nano` + non-caching → relevance-first; `frontier`/`mid` + caching → recency-first) | Force the relevance policy. `true` runs the margin arbiter at the eviction seam (relevance-first); `false` keeps recency-first. The field is **optional with no default** — omit it to let the capability gate decide. An explicit value (either direction) always wins. Setting `true` on a `frontier`/`mid` agent opts that agent into relevance-first; setting `false` on a non-caching `small`/`nano` agent forces recency-first. |

    > **Capability-gated default:** The `small`/`nano` relevance-first default is gated on
    > `supportsPromptCache=false` — a non-caching local model (typical Ollama) reorders history for
    > free, while a caching model stays recency-first below the cache fence (reordering would break the
    > prefix cache). `frontier`/`mid` are **byte-identical** by default (the arbiter never runs for them).
    > The `small`/`nano` default-on flip is **measurement-gated** (validated by the outcome harness before
    > being relied upon in production); the mechanism ships behind this flag. Precedence:
    > **explicit per-agent config > capability default > off**.

    > **Security:** The arbiter never demotes security-relevant history (canary token,
    > untrusted-content delimiters, safety reinforcement, sender-trust markers) — those items are
    > **unconditional floors**, excluded from relevance candidacy and always kept, exactly as the
    > recency path's pre-flight already protects them. A content-free `context:arbitrated` event
    > (per-tier kept counts; the discretionary pool **offered** and **consumed** plus the
    > unconditional floor-token weight; the kept LTM/KG ids; and a `relevanceFirst` boolean)
    > fires only on the relevance-first path; it carries no message, memory, or query content.

    #### Learning Outcome (agents.\*.learningOutcome)

    The learning outcome signal is a durable record of configured observations for a
    finished trajectory (`success` / `failure` / `corrected` / `unknown`). Reflection
    and outcome-aware forgetting consume the fused result instead of relying on text
    overlap alone. It is **default-ON** and gated by the master cost switch
    (`memory.enabled`); setting the master switch to `false` force-disables it.
    Deterministic tool and pipeline observations take precedence. The cost-gated LLM
    judge is the fallback when deterministic resolution returns `unknown`. Capture is
    daemon-side and content-free: ids, closed enums, counts, and confidence only.

    The fused result is evidence, not independent verification of the overall task.
    Mixed observations can resolve to `success` for a session that failed overall.
    Operators should inspect the source labels and use their own workload evaluation
    before treating learned guidance as reliable.

    | Key                    | Type           | Default                                          | Description                                                                                                                                                                                                                                                                                                                                                                                                               |
    | ---------------------- | -------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `enabled`              | `boolean`      | `true`                                           | Enable outcome capture for this agent (opt-out; force-disabled when `memory.enabled` is `false`)                                                                                                                                                                                                                                                                                                                          |
    | `sources`              | `string[]`     | `["tool", "pipeline"]`                           | Which signal sources may contribute observations (closed set: `tool` / `pipeline` / `correction` / `judge` / `reaction` / `explicit`)                                                                                                                                                                                                                                                                                     |
    | `reactionMap`          | `object`       | `{ success: ["👍", "✅"], failure: ["👎", "❌"] }` | Maps an inbound-reaction emoji to an outcome: a reaction added to one of this agent's own outbound messages on Discord/Slack/Telegram is matched against this map and recorded as a `reaction`-source observation (see the note below). Slack short names (`thumbsup`/`white_check_mark`/`thumbsdown`/`x` and the `+1`/`-1` aliases) reconcile to the Unicode defaults automatically.                                     |
    | `judge.enabled`        | `boolean`      | `true`                                           | Cost-gated LLM judge as the **fallback** outcome source (opt-out). Runs ONE cheap-model pass ONLY when the deterministic resolve is `unknown` — i.e. a conversational turn with no tool/pipeline signal — and records a `judge`-source observation (reward capped at 0.7, always out-ranked by a deterministic signal). Routed via the `outcomeJudge` model tier (below); force-disabled when `memory.enabled` is `false` |
    | `correction.enabled`   | `boolean`      | `true`                                           | Enable the cost-gated correction detector. A classified follow-up emits a `corrected` soft-failure for the prior trajectory. This signal does not automatically demote a learned skill; attribution, confidence, corroboration, and trend gates still apply. Force-disabled by `memory.enabled: false`                                                                                                                    |
    | `minConfidenceToLearn` | `number` (0–1) | `0.6`                                            | Minimum fused confidence required before a resolved outcome drives learning                                                                                                                                                                                                                                                                                                                                               |
    | `retentionDays`        | `number`       | `90`                                             | Age horizon for the `outcome_events` ledger; rows older than this are pruned at daemon startup (anti-DoS, runs regardless of the enable flag). Best-out-of-box `90` (larger outcome corpus for reflection; was `30`)                                                                                                                                                                                                      |

    > **Inbound reactions as a corroborating source.** When `learningOutcome.enabled` is on, Comis captures emoji reactions added to the agent's **own outbound messages** on Discord, Slack, and Telegram and records them through the `reaction` source. This is **corroboration, not control**: a reaction can only *weight* an outcome the deterministic tool/pipeline signal already resolved — it never overrides it. Three safeguards bound the signal:
    >
    > * **Fail-closed scope.** A reaction is observed only when its `message_id` maps to an agent-authored outbound trajectory. A reaction on a user's message, or on an id the daemon never sent, is silently ignored.
    > * **Reactor trust-weighting.** The confidence is scaled by the reactor's channel-sender trust (`elevatedReply.senderTrustMap[reactorId]`, falling back to `elevatedReply.defaultTrustLevel`). An **unknown / external** reactor is capped near zero (\~0.05) — a stranger's 👍 barely moves the needle.
    > * **Per-sender rate limit.** Reaction-spam from one reactor is throttled so it cannot flood the ledger.
    >
    > No reaction-specific config keys exist beyond `reactionMap`: the trust map and rate limiter are reused/daemon-constructed. Capturing reactions on the silent platforms needs operator setup (Slack `reactions:read` scope; Telegram `allowed_updates`) — see [Reactions and Corrections in messaging](/agent-tools/messaging#inbound-reactions-corroborating-learning-signal).

    #### Learning (agents.\*.learning)

    The governed reflection engine maintains named **Mental Model** docs
    (`kind: skill | profile | topic`) at `trust=learned` via byte-stable delta
    operations, plus outcome-aware soft eviction. Skill, profile, and topic learning
    share this layer. **One flag**, `enabled`, gates the whole layer. It is
    **default-ON (opt-out)** and force-disabled when `memory.enabled` is `false`.
    Skill reflection consumes the fused [`learningOutcome`](#learning-outcome-agents-learningoutcome)
    signal. The `learned` trust value is a policy label and ceiling, not a guarantee
    that a document is correct. Recall ranking stays on the fixed `rag.scoring`
    fusion; learning tunes no recall weight.

    A read-only, non-evicted doc in `candidate` **or** `active` state can surface to
    the agent in `<available_skills>` and as
    `.learned-skills/<name>/SKILL.md`. Candidates surface before promotion so they
    can be tried and receive reuse feedback; `active` is the higher proof tier.
    Mutating, stale, archived, and evicted docs do not surface. Attributed reuse
    labeled `success` can promote a candidate at `reflect.promoteAtProofCount`.
    Corroborated failure plus a weakening trend can demote it. A recorded correction
    does not guarantee demotion or durable behavior change.

    Learned docs are advisory Markdown with no executable column. Procedure docs
    record a deterministic tool-name footprint from `orchestrate` runs resolved as
    successful, but the resolved label is not proof that the overall task succeeded.
    The model must re-author any action through its existing permissioned tools. The
    document adds no execution path or authority. Static validation rejects critical
    secret and poison patterns, but warning-level patterns, including some
    jailbreak-like language, can pass. All learned-doc store operations are scoped to
    `(tenant, agent)`, not to a chat or sender.

    | Key                                     | Type                                    | Default          | Description                                                                                                                                                                                                                                                                                                                                                                                                                                      |
    | --------------------------------------- | --------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
    | `enabled`                               | `boolean`                               | `true`           | The SINGLE master gate for the whole learning layer (opt-out; force-disabled when `memory.enabled` is `false`). When off, reflection registers no cron and the soft-eviction sweep evicts nothing — recall is byte-identical                                                                                                                                                                                                                     |
    | `reflect.schedule`                      | `string` (cron)                         | `"0 */3 * * *"`  | The `__REFLECT__` cron expression. Best-out-of-box every 3 hours (near-real-time learning — a skill corroborated mid-day is usable within hours, not next-night; was daily 03:00 UTC). Reflects skill + profile + topic docs in one pass                                                                                                                                                                                                         |
    | `reflect.minConfidence`                 | `number` (0–1)                          | `0.6`            | The reflection-side confidence floor a candidate doc must clear before admission. Distinct from `learningOutcome.minConfidenceToLearn` (the separate outcome-resolution floor)                                                                                                                                                                                                                                                                   |
    | `reflect.promoteAtProofCount`           | `number` (positive int)                 | `3`              | The attributed reuse-success count at which an admitted `candidate` doc promotes to `active`. Candidates may surface before reaching this bar                                                                                                                                                                                                                                                                                                    |
    | `reflect.maxDocsPerRun`                 | `number` (positive int)                 | `100`            | Per-run admitted-doc cap — a finite DoS bound (kept finite for safety even with cost ignored). Best-out-of-box `100` (don't throttle a burst of corroborated learning; was `25`)                                                                                                                                                                                                                                                                 |
    | `reflect.maxProcedureDocsSurfaced`      | `number` (positive int)                 | `10`             | Per-agent surface cap on **procedure** docs (the `orchestrate`-derived subset that records a tool footprint) listed in `<available_skills>` — the scaling guard, since there is no ranked top-K at surface time. When the cap is exceeded the **most-corroborated** procedure docs (highest proof count) keep a slot, surfaced in a stable listing order; user-intent **skill** docs and **topic** docs are unaffected (separate, uncapped path) |
    | `reflect.corroboration.mode`            | `"single_owner" \| "distinct_sessions"` | `"single_owner"` | Admission gate for a topic. `single_owner` counts repeated success-labeled observations from exactly one operator-named sender. This is same-owner repetition, not independent corroboration. `distinct_sessions` requires at least two distinct `(session, sender)` observations and is the stricter anti-domination posture                                                                                                                    |
    | `reflect.corroboration.minObservations` | `number` (int >= 2)                     | `2`              | In `single_owner` mode, the minimum repeated success-labeled observations from the owner. Ignored in `distinct_sessions` mode                                                                                                                                                                                                                                                                                                                    |
    | `forget.maxDormantDays`                 | `number` (positive int)                 | `365`            | Dormancy window (days): the keyless lifecycle sweep soft-evicts a memory dormant (un-recalled) longer than this. Best-out-of-box `365` (remember \~a year; forget pure-disuse far less aggressively; was `90`). The anti-poison failure-eviction belt is UNAFFECTED                                                                                                                                                                              |
    | `forget.failureEvictionFloor`           | `number` (positive int)                 | `3`              | `failure_count` at or above which a non-exempt memory is soft-evicted regardless of decayed strength. Each increment requires two distinct-session observations or one deterministic source                                                                                                                                                                                                                                                      |
    | `forget.highProofFloor`                 | `number` (positive int)                 | `5`              | The high-proof exemption — a memory at/above this proof count is **exempt** from failure eviction, so a poisoner cannot evict a well-corroborated memory no matter how many failures it induces                                                                                                                                                                                                                                                  |

    > **Admission boundary.** In the default `single_owner` mode, repetition counts
    > only for a sender explicitly named by the operator. Unknown senders and senders
    > trusted only by `defaultTrustLevel` cannot use this path. A deployment with two
    > or more explicitly named senders falls back to `distinct_sessions`. These are
    > origin and repetition controls, not independent verification of truth. External
    > trust sources are excluded in every mode. A `small` or `nano` agent below the
    > synthesis capability gate abstains. Counts and ids are visible through
    > [`comis explain`](/reference/cli#comis-explain) and
    > [`comis memory skills`](/reference/cli#comis-memory).

    > **Gate composition.** `learning.enabled` is the single switch for the whole layer: the outcome-reward write (`recordUsage` on success / `recordFailure` on failure-or-corrected — the `memory_usefulness.failure_count` source), the reflection cron, and the wrongness-based soft eviction (`forget.*`). The soft eviction **composes with** — does not replace — the `memoryLifecycle` cron (the schedule that runs the sweep) and the `rag.forget` recall-score decay. Eviction is **soft and reversible** — a memory un-evicts on renewed usefulness, is never hard-deleted, and stays resolvable via `asOf`/inspect — and emits counts-only `learning:memory_demoted` / `learning:memory_evicted` signals for `comis explain` / `comis system-health`. See the `memory_usefulness.failure_count` / `memories.evicted_at` columns in [data-directory.mdx](/operations/data-directory).

    > **Model tier `outcomeJudge`.** The optional outcome judge runs on the `fast` model operation tier (`outcomeJudge`), like the other lightweight classification operations -- a cheap model resolved by name and injected daemon-side. It is dormant while `learningOutcome.judge.enabled` is `false`. (The reflection cron itself runs on the `mid` `skillSynthesis` operation tier — an offline batch op, capability-routed like the other tiers.)

    #### Source Gate (agents.\*.sourceGate)

    | Key                | Type      | Default   | Description                                    |
    | ------------------ | --------- | --------- | ---------------------------------------------- |
    | `maxResponseBytes` | `number`  | `2000000` | Default byte cap for HTTP responses            |
    | `stripHiddenHtml`  | `boolean` | `true`    | Whether to strip hidden HTML before extraction |

    #### Goal Anchor (agents.\*.goalAnchor)

    Injects the current execution objective and uncompleted step checklist at the context tail every turn. Helps weak models stay on task across multi-turn executions. **Default-ON** for scaffolded (`small`/`nano`) tiers; **off** by default for `frontier`/`mid`, but injected when `enabled: true` is set explicitly. Precedence: **explicit per-agent config > capability default > off**.

    | Key                               | Type                | Default                                                             | Description                                                                                                                                                                                                                                                                                                                                                         |
    | --------------------------------- | ------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `agents.<id>.goalAnchor.enabled`  | `boolean`           | automatic for small/nano (no config needed); false for frontier/mid | Enable GoalAnchor tail injection. Default-ON for `small`/`nano` (no config needed); off for `frontier`/`mid` unless `enabled: true` is set explicitly (which injects on `frontier`/`mid` too). When effective, the execution objective and uncompleted step checklist is tail-appended each turn. Precedence: explicit per-agent config > capability default > off. |
    | `agents.<id>.goalAnchor.maxChars` | `number` (100–2000) | `500`                                                               | Maximum characters for the injected GoalAnchor block. \~5–10 steps at \~50 chars/step.                                                                                                                                                                                                                                                                              |

    > **Capability-gated default:** For `capabilityClass` in `{small, nano}` (e.g. any Ollama
    > qwen3.6 deployment), `goalAnchor` is **default-ON** — no `enabled: true` required. Set
    > `agents.<id>.goalAnchor.enabled: false` explicitly to disable it on a small/nano agent.
    > For `frontier`/`mid` agents, behavior is unchanged (default off). Precedence:
    > **explicit per-agent config > capability default > off**.

    #### Verification Critic (agents.\*.verification)

    A pre-delivery critic that checks the terminal response against the GoalAnchor checklist before delivery. Unmet requirements redirect the executor; exhausted retries deliver an honest unmet-list. Meaningful only for `scaffoldLevel=max` (small/nano) agents.

    | Key                                         | Type               | Default                                                                                         | Description                                                                                                                                                                                                                       |
    | ------------------------------------------- | ------------------ | ----------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `agents.<id>.verification.enabled`          | `boolean`          | cost-gated automatic for small/nano when a distinct cheap critic is configured; false otherwise | Enable pre-delivery verification critic. Fires only when a completion-claiming response meets `minResponseChars`. Default `false` for frontier/mid and for small/nano when no distinct cheap critic model is configured = opt-in. |
    | `agents.<id>.verification.minResponseChars` | `number` (50–2000) | `200`                                                                                           | Minimum response length in characters before the critic is invoked. Prevents firing on short acks, clarifying questions, and non-completion replies.                                                                              |

    > **Security:** The critic treats the output-under-review as untrusted (`wrapExternalContent`), inherits the safety core, embeds the canary, fails closed (uncertain → not-verified, never auto-approve), and re-validates any implied tool calls through the same exec gates. Use `agents.<id>.operationModels.verification` to run the critic on a cheaper or faster model.

    > **Capability-gated default + cost-gate:** For `capabilityClass` in `{small, nano}`,
    > `verification` is **default-ON** — but **only when `agents.<id>.operationModels.verification`
    > resolves to a distinct cheaper model** (i.e. `operationModels.verification.model` is explicitly
    > configured to a different, faster model). If no distinct critic model is configured, the default
    > stays **OFF** — the critic never silently doubles local-CPU inference latency. Set
    > `agents.<id>.verification.enabled: false` explicitly to force-off the critic on a small/nano
    > agent even when a cheap critic is configured. Set `agents.<id>.verification.enabled: true` to
    > force-on the critic regardless of class (including frontier/mid, if a critic model is configured).
    > Precedence: **explicit per-agent config > capability default > off**.

    #### Honesty Guardrail (agents.\*.honesty)

    | Key                                    | Type           | Default | Description                                                                                                                                                                                                                   |
    | -------------------------------------- | -------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `agents.<id>.honesty.maxCriticRetries` | `number` (0–5) | `2`     | Maximum critic retry redirects before delivering an honest unmet-list. After this many not-verified verdicts, the executor delivers an honest unmet-list instead of an unqualified "done". Prevents infinite re-prompt loops. |

    #### Skills (agents.\*.skills)

    | Key               | Type       | Default        | Description                                                                                                                                                                                                              |
    | ----------------- | ---------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
    | `discoveryPaths`  | `string[]` | `["./skills"]` | Directories to scan for SKILL.md files. For named agents, Comis automatically prepends the agent's workspace skills directory (`~/.comis/workspace-{agentId}/skills/`) at startup -- you do not need to include it here. |
    | `watchEnabled`    | `boolean`  | `true`         | Enable file watching for automatic skill reload                                                                                                                                                                          |
    | `watchDebounceMs` | `number`   | `400`          | Debounce interval in ms (100-5000)                                                                                                                                                                                       |

    **Built-in Tools (agents.\*.skills.builtinTools)**

    | Key         | Type      | Default | Description                                |
    | ----------- | --------- | ------- | ------------------------------------------ |
    | `read`      | `boolean` | `true`  | Read file contents                         |
    | `write`     | `boolean` | `true`  | Write or overwrite files                   |
    | `edit`      | `boolean` | `true`  | Surgical search-and-replace on files       |
    | `grep`      | `boolean` | `true`  | Regex search across files (requires `rg`)  |
    | `find`      | `boolean` | `true`  | Find files by glob pattern (requires `fd`) |
    | `ls`        | `boolean` | `true`  | List directory contents                    |
    | `exec`      | `boolean` | `true`  | Shell command execution                    |
    | `process`   | `boolean` | `true`  | Background process management              |
    | `webSearch` | `boolean` | `true`  | Web search API integration                 |
    | `webFetch`  | `boolean` | `true`  | URL content fetching                       |
    | `browser`   | `boolean` | `true`  | Headless browser control                   |

    **Tool Policy (agents.\*.skills.toolPolicy)**

    | Key       | Type       | Default  | Description                                                               |
    | --------- | ---------- | -------- | ------------------------------------------------------------------------- |
    | `profile` | `enum`     | `"full"` | Baseline tool set: `minimal`, `coding`, `messaging`, `supervisor`, `full` |
    | `allow`   | `string[]` | `[]`     | Additional tools to allow beyond the profile                              |
    | `deny`    | `string[]` | `[]`     | Tools to deny even if in the profile                                      |

    **Prompt Skills (agents.\*.skills.promptSkills)**

    | Key                    | Type       | Default | Description                                            |
    | ---------------------- | ---------- | ------- | ------------------------------------------------------ |
    | `maxBodyLength`        | `number`   | `20000` | Maximum skill body length in characters                |
    | `enableDynamicContext` | `boolean`  | `false` | Enable shell command execution in skill bodies         |
    | `maxAutoInject`        | `number`   | `3`     | Maximum prompt skills auto-injected per request (0-20) |
    | `allowedSkills`        | `string[]` | `[]`    | Skill names allowed (empty = allow all)                |
    | `deniedSkills`         | `string[]` | `[]`    | Skill names denied (applied after allowedSkills)       |

    **Runtime Eligibility (agents.\*.skills.runtimeEligibility)**

    | Key       | Type      | Default | Description                                                                         |
    | --------- | --------- | ------- | ----------------------------------------------------------------------------------- |
    | `enabled` | `boolean` | `true`  | Enable runtime eligibility filtering based on OS, binary, and env var prerequisites |

    **Content Scanning (agents.\*.skills.contentScanning)**

    Applies to **load-time** scanning only. It does not affect the install-time
    vetting gate, which cannot be disabled -- see
    [Security Scanning](/skills/security-scanning).

    | Key               | Type      | Default | Description                                            |
    | ----------------- | --------- | ------- | ------------------------------------------------------ |
    | `enabled`         | `boolean` | `true`  | Enable content scanning at skill load time             |
    | `blockOnCritical` | `boolean` | `true`  | Block skill loading when CRITICAL findings are present |

    **Install Vetting (agents.\*.skills.installVetting)**

    Bounds for the install-time vetting gate, which inspects every file in a skill
    bundle before anything is written to disk. These tune the gate's limits; there
    is no key to turn the gate off.

    | Key              | Type     | Default    | Description                                               |
    | ---------------- | -------- | ---------- | --------------------------------------------------------- |
    | `maxEntries`     | `number` | `200`      | Maximum members per bundle (ignore-file matches excluded) |
    | `maxEntryBytes`  | `number` | `4194304`  | Maximum bytes for a single member (4 MiB)                 |
    | `maxBundleBytes` | `number` | `33554432` | Maximum total bytes across the bundle (32 MiB)            |
    | `maxPathDepth`   | `number` | `10`       | Maximum path depth for any member                         |

    WARN findings are reported but do not block loading under the default policy.
    The learned-document validator follows the same critical-only rejection boundary,
    so warning-level jailbreak-like language can be admitted.

    **Exec Sandbox (agents.\*.skills.execSandbox)**

    | Key                  | Type       | Default    | Description                                                                                                                                                                                                                                                                                                                                      |
    | -------------------- | ---------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
    | `enabled`            | `enum`     | `"always"` | Requested OS-level sandbox posture for ordinary `exec`. With `"always"`, Comis uses the detected provider, but if no supported provider is available it logs a warning and ordinary `exec` runs on the host. `"never"` disables the sandbox. Use the Linux jailed autonomy or terminal surfaces when unavailable isolation must refuse execution |
    | `readOnlyAllowPaths` | `string[]` | `[]`       | Additional filesystem paths exposed read-only inside the sandbox (e.g., `/opt/data`)                                                                                                                                                                                                                                                             |

    #### Secrets (agents.\*.secrets)

    | Key     | Type       | Default | Description                                                          |
    | ------- | ---------- | ------- | -------------------------------------------------------------------- |
    | `allow` | `string[]` | `[]`    | Glob patterns for allowed secret names. Empty = unrestricted access. |

    #### Session (agents.\*.session)

    Optional session configuration containing reset policy, DM scope, pruning, and compaction.

    **Reset Policy (agents.\*.session.resetPolicy)**

    | Key                  | Type                  | Default    | Description                                                          |
    | -------------------- | --------------------- | ---------- | -------------------------------------------------------------------- |
    | `mode`               | `enum`                | `"none"`   | Reset mode: `daily`, `idle`, `hybrid`, `none`                        |
    | `dailyResetHour`     | `number`              | `4`        | Hour of day for daily reset (0-23)                                   |
    | `dailyResetTimezone` | `string`              | `""`       | IANA timezone (empty = system local)                                 |
    | `idleTimeoutMs`      | `number`              | `14400000` | Idle timeout in ms (default 4 hours)                                 |
    | `sweepIntervalMs`    | `number`              | `300000`   | How often to check sessions (default 5 min)                          |
    | `resetTriggers`      | `string[]`            | `[]`       | Phrases that trigger immediate session reset                         |
    | `perType.dm`         | `ResetPolicyOverride` | *(unset)*  | DM-specific override (mode, dailyResetHour, timezone, idleTimeoutMs) |
    | `perType.group`      | `ResetPolicyOverride` | *(unset)*  | Group-specific override                                              |
    | `perType.thread`     | `ResetPolicyOverride` | *(unset)*  | Thread-specific override                                             |

    **DM Scope (agents.\*.session.dmScope)**

    | Key               | Type      | Default              | Description                                                                   |
    | ----------------- | --------- | -------------------- | ----------------------------------------------------------------------------- |
    | `mode`            | `enum`    | `"per-channel-peer"` | Isolation: `main`, `per-peer`, `per-channel-peer`, `per-account-channel-peer` |
    | `threadIsolation` | `boolean` | `false`              | Append `:thread:<threadId>` to session keys                                   |

    **Pruning (agents.\*.session.pruning)**

    | Key                       | Type       | Default | Description                                                |
    | ------------------------- | ---------- | ------- | ---------------------------------------------------------- |
    | `enabled`                 | `boolean`  | `true`  | Enable session pruning of oversized tool results           |
    | `softTrimThresholdChars`  | `number`   | `8000`  | Chars above which tool results are soft-trimmed            |
    | `hardClearThresholdChars` | `number`   | `30000` | Chars above which tool results are hard-cleared            |
    | `preserveHeadChars`       | `number`   | `500`   | Characters to preserve at the start of soft-trimmed result |
    | `preserveTailChars`       | `number`   | `500`   | Characters to preserve at the end of soft-trimmed result   |
    | `pruneableTools`          | `string[]` | `[]`    | Tools eligible for pruning (empty = all)                   |
    | `protectedTools`          | `string[]` | `[]`    | Tools never pruned (takes precedence)                      |
    | `protectImageBlocks`      | `boolean`  | `true`  | Protect tool results containing image content blocks       |
    | `preserveRecentCount`     | `number`   | `6`     | Recent messages exempt from pruning                        |

    **Compaction (agents.\*.session.compaction)**

    | Key                      | Type       | Default                            | Description                                                                 |
    | ------------------------ | ---------- | ---------------------------------- | --------------------------------------------------------------------------- |
    | `softThresholdRatio`     | `number`   | `0.75`                             | Context fraction at which soft flush triggers (0-1)                         |
    | `hardThresholdRatio`     | `number`   | `0.90`                             | Context fraction at which hard compaction triggers (0-1)                    |
    | `flushModel`             | `string`   | *(unset)*                          | Model for memory extraction during flush                                    |
    | `chunkMaxChars`          | `number`   | `50000`                            | Max characters per summarization chunk                                      |
    | `chunkOverlapMessages`   | `number`   | `2`                                | Overlap messages between chunks                                             |
    | `chunkMergeSummaries`    | `boolean`  | `true`                             | Whether to merge chunk summaries via LLM                                    |
    | `reserveTokens`          | `number`   | `16384`                            | Tokens reserved for summary during auto-compaction                          |
    | `keepRecentTokens`       | `number`   | `32768`                            | Recent message tokens to keep after auto-compaction                         |
    | `postCompactionSections` | `string[]` | `["Session Startup", "Red Lines"]` | AGENTS.md sections to re-inject after compaction (from read-only AGENTS.md) |

    #### Scheduler (agents.\*.scheduler)

    **Cron (agents.\*.scheduler.cron)**

    Setting **any** field here replaces the global `scheduler.cron` block **wholesale** -- unlike `heartbeat` below, per-agent cron fields do **not** merge with the global block field-by-field. An agent that overrides one cron field (e.g. `maxJobs`) therefore does **not** inherit the global `scheduler.cron.wakeGate` toggle; re-declare `wakeGate` in the agent's block to keep it.

    | Key                              | Type      | Default   | Description                                                                                                                                                                                                                                                                  |
    | -------------------------------- | --------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `enabled`                        | `boolean` | `true`    | Enable cron job scheduling                                                                                                                                                                                                                                                   |
    | `maxRunsPerTick`                 | `number`  | `3`       | Maximum due cron occurrences admitted by one scheduler tick                                                                                                                                                                                                                  |
    | `defaultTimezone`                | `string`  | `"UTC"`   | IANA authoring timezone used when a cron expression omits an explicit zone                                                                                                                                                                                                   |
    | `maxJobs`                        | `number`  | `100`     | Positive authored-job cap; config-owned jobs and retained terminal records are separate                                                                                                                                                                                      |
    | `maxConsecutiveDependencyErrors` | `number`  | `5`       | Consecutive scheduled dependency failures before suspension; `0` disables suspension                                                                                                                                                                                         |
    | `staggerWindowMs`                | `number`  | `0`       | Stable eligibility spread applied only to recurring scheduled fires                                                                                                                                                                                                          |
    | `wakeGate`                       | `boolean` | *(unset)* | Per-agent [wake-gate](/agent-tools/scheduling#wake-gate-run-the-model-only-when-it-matters) toggle (same semantics as the global `scheduler.cron.wakeGate`). Because the block is whole-replace, this is **not** inherited from the global toggle -- set it here explicitly. |

    **Heartbeat (agents.\*.scheduler.heartbeat)**

    All fields are optional and inherit from the global `scheduler.heartbeat` when omitted.

    | Key               | Type              | Default   | Description                                                                                                                 |
    | ----------------- | ----------------- | --------- | --------------------------------------------------------------------------------------------------------------------------- |
    | `enabled`         | `boolean`         | *(unset)* | Override heartbeat enabled state                                                                                            |
    | `intervalMs`      | `number`          | *(unset)* | Override heartbeat interval in ms                                                                                           |
    | `showOk`          | `boolean`         | *(unset)* | Override show OK status                                                                                                     |
    | `showAlerts`      | `boolean`         | *(unset)* | Override show alerts                                                                                                        |
    | `target`          | `ChannelEndpoint` | *(unset)* | Exact delivery endpoint (`channelType`, `channelInstanceId`, `conversationId`, optional `threadId`, and `conversationKind`) |
    | `prompt`          | `string`          | *(unset)* | Custom heartbeat prompt                                                                                                     |
    | `allowDm`         | `boolean`         | *(unset)* | Allow heartbeat delivery to direct conversations                                                                            |
    | `lightContext`    | `boolean`         | *(unset)* | Include ONLY HEARTBEAT.md in bootstrap context                                                                              |
    | `ackMaxChars`     | `number`          | *(unset)* | Max chars for soft acknowledgment threshold                                                                                 |
    | `responsePrefix`  | `string`          | *(unset)* | Prefix to strip from LLM responses before delivery                                                                          |
    | `alertThreshold`  | `number`          | *(unset)* | Consecutive failure threshold for alerting                                                                                  |
    | `alertCooldownMs` | `number`          | *(unset)* | Alert cooldown period in ms                                                                                                 |
    | `staleMs`         | `number`          | *(unset)* | Stuck detection timeout in ms                                                                                               |
    | `toolPolicy`      | `ToolPolicy`      | *(unset)* | Additional heartbeat restriction applied after the agent policy; it cannot restore a denied tool or capability              |

    ```yaml theme={}
    agents:
      assistant:
        name: "My Assistant"
        provider: anthropic
        model: claude-sonnet-4-5-20250929
        maxSteps: 30
        budgets:
          perExecution: 1000000
          perHour: 5000000
        rag:
          enabled: true
          maxResults: 10
        skills:
          toolPolicy:
            profile: coding
            allow: ["memory_search"]
        session:
          resetPolicy:
            mode: hybrid
            idleTimeoutMs: 7200000
    ```

    <Info>See [Agent Identity](/agents/identity) and [Agent Lifecycle](/agents/lifecycle) for how these settings affect agent behavior.</Info>
  </Accordion>
</AccordionGroup>

## Channels

<AccordionGroup>
  <Accordion title="channels">
    Channel adapter configuration. Each key is a channel platform name.

    **Type:** `ChannelConfig` (strict object with per-platform entries)

    #### Base Channel Entry (shared by all channels)

    | Key               | Type                  | Default      | Description                            |
    | ----------------- | --------------------- | ------------ | -------------------------------------- |
    | `enabled`         | `boolean`             | `false`      | Whether this channel is active         |
    | `apiKey`          | `string \| SecretRef` | *(unset)*    | API key for the channel service        |
    | `botToken`        | `string \| SecretRef` | *(unset)*    | Bot token for the channel service      |
    | `webhookUrl`      | `string (URL)`        | *(unset)*    | Webhook URL for receiving events       |
    | `allowFrom`       | `string[]`            | `[]`         | Allowed sender IDs (empty = allow all) |
    | `mediaProcessing` | `MediaProcessing`     | *(all true)* | Per-channel media processing overrides |

    `webhookUrl` is platform-dependent. Telegram currently supports polling only and
    fails adapter startup when this field is set.

    **Media Processing (channels.\*.mediaProcessing)**

    | Key                | Type      | Default | Description                      |
    | ------------------ | --------- | ------- | -------------------------------- |
    | `transcribeAudio`  | `boolean` | `true`  | Enable voice transcription (STT) |
    | `analyzeImages`    | `boolean` | `true`  | Enable image analysis (Vision)   |
    | `describeVideos`   | `boolean` | `true`  | Enable video description         |
    | `extractDocuments` | `boolean` | `true`  | Enable document text extraction  |
    | `understandLinks`  | `boolean` | `true`  | Enable link content fetching     |

    #### Ack Reactions (channels.\*.ackReaction -- global schema)

    | Key       | Type      | Default  | Description                                        |
    | --------- | --------- | -------- | -------------------------------------------------- |
    | `enabled` | `boolean` | `false`  | Send ack reaction when processing starts           |
    | `emoji`   | `string`  | `"eyes"` | Emoji to react with (Unicode or platform-specific) |

    #### Platform-Specific Fields

    **Slack (channels.slack)**

    | Key             | Type                  | Default   | Description                                  |
    | --------------- | --------------------- | --------- | -------------------------------------------- |
    | `appToken`      | `string \| SecretRef` | *(unset)* | App-level token for Socket Mode (`xapp-...`) |
    | `signingSecret` | `string \| SecretRef` | *(unset)* | Signing secret for HTTP request verification |
    | `mode`          | `enum`                | *(unset)* | Connection mode: `socket` or `http`          |

    **WhatsApp (channels.whatsapp)**

    | Key       | Type      | Default   | Description                                 |
    | --------- | --------- | --------- | ------------------------------------------- |
    | `authDir` | `string`  | *(unset)* | Directory for multi-device auth state files |
    | `printQR` | `boolean` | *(unset)* | Print QR code to terminal for pairing       |

    **Signal (channels.signal)**

    | Key       | Type     | Default                   | Description                              |
    | --------- | -------- | ------------------------- | ---------------------------------------- |
    | `baseUrl` | `string` | `"http://127.0.0.1:8080"` | signal-cli REST API base URL             |
    | `account` | `string` | *(unset)*                 | Phone number registered with Signal      |
    | `cliPath` | `string` | *(unset)*                 | Path to signal-cli binary for auto-spawn |

    **iMessage (channels.imessage)**

    | Key          | Type     | Default   | Description                   |
    | ------------ | -------- | --------- | ----------------------------- |
    | `binaryPath` | `string` | *(unset)* | Path to imsg binary           |
    | `account`    | `string` | *(unset)* | Apple ID for iMessage account |

    **LINE (channels.line)**

    | Key             | Type                  | Default            | Description                                       |
    | --------------- | --------------------- | ------------------ | ------------------------------------------------- |
    | `channelSecret` | `string \| SecretRef` | *(unset)*          | Channel secret for webhook signature verification |
    | `webhookPath`   | `string`              | `"/webhooks/line"` | Webhook path for LINE events                      |

    **IRC (channels.irc)**

    | Key                | Type                  | Default   | Description                          |
    | ------------------ | --------------------- | --------- | ------------------------------------ |
    | `host`             | `string`              | *(unset)* | IRC server hostname                  |
    | `port`             | `number`              | *(unset)* | IRC server port                      |
    | `nick`             | `string`              | *(unset)* | Bot nickname                         |
    | `tls`              | `boolean`             | `true`    | Whether to use TLS                   |
    | `channels`         | `string[]`            | *(unset)* | Channels to auto-join on connect     |
    | `nickservPassword` | `string \| SecretRef` | *(unset)* | NickServ password for identification |

    **Microsoft Teams (channels.msteams)**

    Teams authenticates with an app registration rather than a single bot token. Inbound activities arrive as authenticated HTTPS POSTs on the gateway, so enabling this channel also requires the gateway to be enabled (`gateway.enabled: true`) — it rides the gateway's existing host/port and TLS surface; no separate port is opened. While `enabled` is `false` no inbound route is mounted.

    | Key                        | Type                  | Default             | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                |
    | -------------------------- | --------------------- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `enabled`                  | `boolean`             | `false`             | Whether this channel is active (opt-in)                                                                                                                                                                                                                                                                                                                                                                                                                    |
    | `authMode`                 | `enum`                | `"secret"`          | Credential mode: `secret`, `certificate`, or `managedIdentity`                                                                                                                                                                                                                                                                                                                                                                                             |
    | `appId`                    | `string`              | *(unset)*           | Bot application (client) ID                                                                                                                                                                                                                                                                                                                                                                                                                                |
    | `appPassword`              | `string \| SecretRef` | *(unset)*           | App password / client secret. A secret — supply a `SecretRef` or a plain string, or set it via the `MSTEAMS_APP_PASSWORD` environment variable                                                                                                                                                                                                                                                                                                             |
    | `tenantId`                 | `string`              | *(unset)*           | Directory (tenant) ID; single-tenant registration, required when enabled                                                                                                                                                                                                                                                                                                                                                                                   |
    | `certPath`                 | `string`              | *(unset)*           | Client certificate path (certificate credential mode)                                                                                                                                                                                                                                                                                                                                                                                                      |
    | `managedIdentityClientId`  | `string`              | *(unset)*           | Managed-identity client ID (managed-identity credential mode)                                                                                                                                                                                                                                                                                                                                                                                              |
    | `allowFrom`                | `string[]`            | `[]`                | Allowed sender IDs — each an `aadObjectId` or a `conversation.id`                                                                                                                                                                                                                                                                                                                                                                                          |
    | `allowMode`                | `enum`                | `"allowlist"`       | Sender gate: `allowlist` (default, blocks all unless listed) or `open`                                                                                                                                                                                                                                                                                                                                                                                     |
    | `mediaAuthAllowHosts`      | `string[]`            | *(Connector hosts)* | Hosts allowed to receive the bot's auth token on an inbound media fetch. When unset, the token rides only the built-in Bot Framework Connector attachment hosts and is dropped on any cross-host redirect; set this to override that built-in list                                                                                                                                                                                                         |
    | `cloud`                    | `enum`                | `"public"`          | Target cloud environment                                                                                                                                                                                                                                                                                                                                                                                                                                   |
    | `missedInboundThresholdMs` | `number`              | `21600000` (6h)     | Silence window before a missed-inbound alert fires. Webhook channels are exempt from the health monitor's stale-reap, so a dead ingress reports healthy indefinitely; a dedicated liveness timer compares the last inbound-activity timestamp to this threshold and, on breach, emits a `channel:inbound_silent` event + a WARN that surface as a `comis system-health` health signal (`health_signal:channel_ingress_silent`). Must be a positive integer |
    | `mediaProcessing`          | `MediaProcessing`     | *(all true)*        | Per-channel media processing overrides                                                                                                                                                                                                                                                                                                                                                                                                                     |
    | `ackReaction`              | `AckReaction`         | *(disabled)*        | Ack reaction sent when the agent starts processing                                                                                                                                                                                                                                                                                                                                                                                                         |

    ```yaml theme={}
    channels:
      telegram:
        enabled: true
        botToken: "${TELEGRAM_BOT_TOKEN}"
        allowFrom: ["123456789"]
        mediaProcessing:
          transcribeAudio: true
          analyzeImages: true
      discord:
        enabled: true
        botToken: "${DISCORD_BOT_TOKEN}"
      slack:
        enabled: true
        botToken: "${SLACK_BOT_TOKEN}"
        appToken: "${SLACK_APP_TOKEN}"
        mode: socket
      msteams:
        enabled: true
        appId: "00000000-0000-0000-0000-000000000000"
        appPassword: "${MSTEAMS_APP_PASSWORD}"
        tenantId: "00000000-0000-0000-0000-000000000000"
        allowFrom: ["29:1abcAADObjectId"]
    ```

    <Info>See the [Channels](/channels/index) section for platform-specific setup guides.</Info>
  </Accordion>
</AccordionGroup>

## Memory & Search

<AccordionGroup>
  <Accordion title="memory">
    SQLite-backed memory system configuration.

    | Key                   | Type      | Default                    | Description                                        |
    | --------------------- | --------- | -------------------------- | -------------------------------------------------- |
    | `dbPath`              | `string`  | `"memory.db"`              | Path to SQLite database file (relative to dataDir) |
    | `walMode`             | `boolean` | `true`                     | Enable WAL mode for concurrent reads               |
    | `embeddingModel`      | `string`  | `"text-embedding-3-small"` | Embedding model identifier                         |
    | `embeddingDimensions` | `number`  | `1536`                     | Embedding vector dimensions                        |

    **Reranker model (memory.reranker\*)**

    The cross-encoder model used when `agents.*.rag.rerank.enabled` is `true`. The `hf:` URI auto-downloads on first enable; nothing is downloaded while reranking is off. This is a distinct model from the bi-encoder embedder (see the `embedding` accordion) -- do not conflate the two.

    | Key                 | Type     | Default                                                              | Description                                                        |
    | ------------------- | -------- | -------------------------------------------------------------------- | ------------------------------------------------------------------ |
    | `rerankerModel`     | `string` | `"hf:gpustack/bge-reranker-v2-m3-GGUF:bge-reranker-v2-m3-Q8_0.gguf"` | Reranker GGUF model URI (HuggingFace `hf:` ref or local path)      |
    | `rerankerModelsDir` | `string` | `"models"`                                                           | Directory (relative to dataDir) to store/resolve the reranker GGUF |
    | `rerankerGpu`       | `enum`   | `"auto"`                                                             | GPU acceleration: `auto`, `metal`, `cuda`, `vulkan`, `false`       |
    | `rerankerThreads`   | `number` | `4`                                                                  | Thread count for the reranker ranking context (positive)           |

    **Compaction (memory.compaction)**

    | Key          | Type      | Default | Description                                |
    | ------------ | --------- | ------- | ------------------------------------------ |
    | `enabled`    | `boolean` | `true`  | Whether automatic compaction is enabled    |
    | `threshold`  | `number`  | `1000`  | Minimum entries before compaction triggers |
    | `targetSize` | `number`  | `500`   | Maximum entries after compaction           |

    **Retention (memory.retention)**

    | Key          | Type     | Default | Description                        |
    | ------------ | -------- | ------- | ---------------------------------- |
    | `maxAgeDays` | `number` | `0`     | Maximum age in days (0 = no limit) |

    <Info>See [Memory](/agents/memory) and [Search](/agents/search) for how these settings affect agent memory behavior.</Info>
  </Accordion>

  <Accordion title="embedding">
    Embedding provider configuration for vector search. Supports local GGUF models via node-llama-cpp or remote OpenAI.

    | Key            | Type      | Default   | Description                                                                                                                                                                                                                                                                                                                                    |
    | -------------- | --------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `enabled`      | `boolean` | `true`    | Enable embedding generation. When false, only FTS5 search is used.                                                                                                                                                                                                                                                                             |
    | `provider`     | `enum`    | `"auto"`  | Provider preference: `auto` (tries local then remote), `local`, `openai`                                                                                                                                                                                                                                                                       |
    | `autoReindex`  | `boolean` | `true`    | Auto-reindex when provider model changes                                                                                                                                                                                                                                                                                                       |
    | `multilingual` | `boolean` | *(unset)* | Advisory: declare the embedder multilingual for the `comis system-health` model-health line. Omitted: inferred from the model id (`bge-m3` / `multilingual-e5` / LaBSE / E5 read as multilingual; otherwise `unknown`). Does not gate search — the FTS5 trigram floor carries recall regardless. See [Multilingual](/operations/multilingual). |

    **Local (embedding.local)**

    | Key           | Type     | Default                                                                    | Description                                                                                                                                                    |
    | ------------- | -------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `modelUri`    | `string` | `"hf:nomic-ai/nomic-embed-text-v1.5-GGUF:nomic-embed-text-v1.5.Q8_0.gguf"` | HuggingFace model URI or path to local GGUF file                                                                                                               |
    | `modelsDir`   | `string` | `"models"`                                                                 | Directory to store downloaded models                                                                                                                           |
    | `gpu`         | `enum`   | `"auto"`                                                                   | GPU acceleration: `auto`, `metal`, `cuda`, `vulkan`, `false`                                                                                                   |
    | `contextSize` | `number` | `2048`                                                                     | Context size for embedding model (tokens). nomic-embed-text-v1.5 trains on 2048; extending to 8192 requires YaRN RoPE scaling not available in node-llama-cpp. |

    **OpenAI (embedding.openai)**

    | Key          | Type     | Default                    | Description                                 |
    | ------------ | -------- | -------------------------- | ------------------------------------------- |
    | `model`      | `string` | `"text-embedding-3-small"` | OpenAI embedding model                      |
    | `dimensions` | `number` | `1536`                     | Vector dimensions (must match model output) |

    **Cache (embedding.cache)**

    | Key                    | Type      | Default  | Description                                                           |
    | ---------------------- | --------- | -------- | --------------------------------------------------------------------- |
    | `maxEntries`           | `number`  | `10000`  | Maximum cached embeddings in L1 in-memory cache (0 = disabled)        |
    | `persistent`           | `boolean` | `false`  | Enable persistent L2 SQLite cache                                     |
    | `persistentMaxEntries` | `number`  | `50000`  | Maximum entries in L2 persistent cache                                |
    | `ttlMs`                | `number`  | *(none)* | TTL in milliseconds for cache entries. When unset, LRU eviction only. |
    | `pruneIntervalMs`      | `number`  | `300000` | Prune check interval in milliseconds (5 min)                          |

    **Batch (embedding.batch)**

    | Key              | Type      | Default | Description                          |
    | ---------------- | --------- | ------- | ------------------------------------ |
    | `batchSize`      | `number`  | `100`   | Texts per batch call                 |
    | `indexOnStartup` | `boolean` | `true`  | Index unembedded memories on startup |

    <Info>See [Embeddings](/agents/embeddings) for a guide on choosing between local and remote embedding providers.</Info>
  </Accordion>
</AccordionGroup>

## Gateway & API

<AccordionGroup>
  <Accordion title="gateway">
    Hono HTTPS server for JSON-RPC, WebSocket, and REST API access.

    | Key                  | Type       | Default       | Description                                               |
    | -------------------- | ---------- | ------------- | --------------------------------------------------------- |
    | `enabled`            | `boolean`  | `true`        | Enable the gateway server                                 |
    | `host`               | `string`   | `"127.0.0.1"` | Host to bind (use `"0.0.0.0"` for external access)        |
    | `port`               | `number`   | `4766`        | Port to listen on (1-65535)                               |
    | `maxBatchSize`       | `number`   | `50`          | Maximum JSON-RPC batch size                               |
    | `wsHeartbeatMs`      | `number`   | `30000`       | WebSocket heartbeat interval in ms                        |
    | `corsOrigins`        | `string[]` | `[]`          | CORS allowed origins (empty = same-origin only)           |
    | `allowInsecureHttp`  | `boolean`  | `false`       | Suppress the insecure-HTTP warning on a non-loopback bind |
    | `trustedProxies`     | `string[]` | `[]`          | Trusted proxy IPs for X-Forwarded-For                     |
    | `httpBodyLimitBytes` | `number`   | `1048576`     | Max HTTP request body size (1MB)                          |
    | `wsMaxMessageBytes`  | `number`   | `1048576`     | Max WebSocket message size (1MB)                          |

    Running without `gateway.tls` on the default loopback bind (`127.0.0.1`) is the normal install posture and logs no warning — a loopback listener has no off-host exposure. Binding a non-loopback host (for example `0.0.0.0`) without TLS logs a boot warning; set `gateway.tls` (or `allowInsecureHttp: true` behind a TLS-terminating proxy) to clear it.

    **TLS (gateway.tls)** -- optional, enables mTLS when provided

    | Key                 | Type      | Default      | Description                                          |
    | ------------------- | --------- | ------------ | ---------------------------------------------------- |
    | `certPath`          | `string`  | *(required)* | Path to server TLS certificate (PEM)                 |
    | `keyPath`           | `string`  | *(required)* | Path to server TLS private key (PEM)                 |
    | `caPath`            | `string`  | *(required)* | Path to CA certificate for client verification (PEM) |
    | `requireClientCert` | `boolean` | `true`       | Require client certificates for mTLS                 |

    **Tokens (gateway.tokens)** -- array of bearer token entries

    | Key      | Type                  | Default      | Description                                            |
    | -------- | --------------------- | ------------ | ------------------------------------------------------ |
    | `id`     | `string`              | *(required)* | Unique token identifier                                |
    | `secret` | `string \| SecretRef` | *(unset)*    | Secret value (min 32 chars; auto-generated if omitted) |
    | `scopes` | `string[]`            | `[]`         | Allowed scopes (e.g., `["rpc", "ws", "admin"]`)        |

    **Rate Limit (gateway.rateLimit)**

    | Key           | Type     | Default | Description                 |
    | ------------- | -------- | ------- | --------------------------- |
    | `windowMs`    | `number` | `60000` | Time window in ms           |
    | `maxRequests` | `number` | `100`   | Maximum requests per window |

    **WebSocket Message Rate Limit (gateway.wsMessageRateLimit)**

    | Key           | Type     | Default | Description                 |
    | ------------- | -------- | ------- | --------------------------- |
    | `maxMessages` | `number` | `60`    | Maximum messages per window |
    | `windowMs`    | `number` | `60000` | Time window in ms           |

    **Web Dashboard (gateway.web)**

    | Key       | Type      | Default | Description                                                                                                                                                                                     |
    | --------- | --------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `enabled` | `boolean` | `true`  | Mount the `@comis/web` SPA at `/app/*` and the REST/SSE API at `/api/*` (sharing gateway host/port/auth). When `false`, the daemon skips `/app/*`, `/api`, SSE, and the `/` → `/app/` redirect. |

    ```yaml theme={}
    gateway:
      enabled: true
      host: "0.0.0.0"
      port: 4766
      tokens:
        - id: admin
          secret: "${COMIS_GATEWAY_TOKEN}"
          scopes: ["*"]
      rateLimit:
        windowMs: 60000
        maxRequests: 200
    ```

    <Info>See [HTTP Gateway](/reference/http-gateway) and [WebSocket](/reference/websocket) for API usage details.</Info>
  </Accordion>

  <Accordion title="webhooks">
    Webhook subsystem for receiving external events (GitHub, Gmail, custom services).

    | Key            | Type                  | Default    | Description                                        |
    | -------------- | --------------------- | ---------- | -------------------------------------------------- |
    | `enabled`      | `boolean`             | `false`    | Enable the webhook subsystem                       |
    | `path`         | `string`              | `"/hooks"` | Base path for webhook endpoints                    |
    | `token`        | `string \| SecretRef` | *(unset)*  | Bearer token for authentication (min 32 chars)     |
    | `maxBodyBytes` | `number`              | `262144`   | Max request body size (256KB)                      |
    | `presets`      | `string[]`            | `[]`       | Preset mapping names (e.g., `["gmail", "github"]`) |
    | `mappings`     | `WebhookMapping[]`    | `[]`       | Custom webhook mappings                            |

    **Webhook Mapping (webhooks.mappings\[])**

    | Key               | Type      | Default   | Description                                                  |
    | ----------------- | --------- | --------- | ------------------------------------------------------------ |
    | `id`              | `string`  | *(unset)* | Unique mapping identifier                                    |
    | `match.path`      | `string`  | *(unset)* | URL path to match                                            |
    | `match.source`    | `string`  | *(unset)* | Source identifier to match                                   |
    | `action`          | `enum`    | `"agent"` | Action: `wake` (trigger heartbeat) or `agent` (invoke agent) |
    | `wakeMode`        | `enum`    | `"now"`   | Wake timing: `now` or `next-heartbeat`                       |
    | `name`            | `string`  | *(unset)* | Human-readable mapping name                                  |
    | `agentId`         | `string`  | *(unset)* | Target agent ID                                              |
    | `sessionKey`      | `string`  | *(unset)* | Session key template (supports `{{expr}}`)                   |
    | `messageTemplate` | `string`  | *(unset)* | Message template (supports `{{expr}}`)                       |
    | `deliver`         | `boolean` | *(unset)* | Whether to deliver to a channel                              |
    | `channel`         | `string`  | *(unset)* | Target channel for delivery                                  |
    | `to`              | `string`  | *(unset)* | Target recipient for delivery                                |
    | `model`           | `string`  | *(unset)* | Model override for agent execution                           |
    | `timeoutSeconds`  | `number`  | *(unset)* | Timeout in seconds for agent execution                       |

    <Info>See [Webhooks](/reference/webhooks) for webhook configuration patterns and payload formats.</Info>
  </Accordion>
</AccordionGroup>

## Routing & Sessions

<AccordionGroup>
  <Accordion title="routing">
    Multi-agent routing dispatch. Bindings are evaluated in order (first match wins).

    | Key              | Type               | Default     | Description                      |
    | ---------------- | ------------------ | ----------- | -------------------------------- |
    | `defaultAgentId` | `string`           | `"default"` | Agent ID when no binding matches |
    | `bindings`       | `RoutingBinding[]` | `[]`        | Ordered list of routing bindings |

    **Routing Binding (routing.bindings\[])**

    | Key           | Type     | Default      | Description                                |
    | ------------- | -------- | ------------ | ------------------------------------------ |
    | `channelType` | `string` | *(unset)*    | Channel type to match (e.g., `"telegram"`) |
    | `channelId`   | `string` | *(unset)*    | Channel identifier to match                |
    | `peerId`      | `string` | *(unset)*    | Peer (user) identifier to match            |
    | `guildId`     | `string` | *(unset)*    | Guild (server/group) identifier to match   |
    | `agentId`     | `string` | *(required)* | Agent ID to route to                       |

    ```yaml theme={}
    routing:
      defaultAgentId: general
      bindings:
        - channelType: telegram
          peerId: "123456789"
          agentId: personal
        - channelType: discord
          guildId: "987654321"
          agentId: community
    ```

    <Info>See [Routing](/agents/routing) for detailed routing patterns and specificity rules.</Info>
  </Accordion>

  <Accordion title="queue">
    Command queue for session serialization and concurrency control.

    | Key                     | Type      | Default            | Description                                                          |
    | ----------------------- | --------- | ------------------ | -------------------------------------------------------------------- |
    | `enabled`               | `boolean` | `true`             | Enable the command queue                                             |
    | `maxConcurrentSessions` | `number`  | `10`               | Max concurrent agent executions globally                             |
    | `cleanupIdleMs`         | `number`  | `600000`           | Idle lane garbage collection interval (10 min)                       |
    | `defaultMode`           | `enum`    | `"steer+followup"` | Default queue mode: `followup`, `collect`, `steer`, `steer+followup` |
    | `defaultDebounceMs`     | `number`  | `0`                | Default debounce delay in ms                                         |

    **Default Overflow (queue.defaultOverflow)**

    | Key        | Type     | Default      | Description                                          |
    | ---------- | -------- | ------------ | ---------------------------------------------------- |
    | `maxDepth` | `number` | `20`         | Max queued messages per session                      |
    | `policy`   | `enum`   | `"drop-new"` | Overflow policy: `drop-old`, `drop-new`, `summarize` |

    **Per-Channel Override (queue.perChannel.{channelType})**

    | Key          | Type             | Default              | Description                 |
    | ------------ | ---------------- | -------------------- | --------------------------- |
    | `mode`       | `enum`           | `"steer+followup"`   | Queue mode for this channel |
    | `overflow`   | `OverflowConfig` | *(inherit defaults)* | Overflow settings           |
    | `debounceMs` | `number`         | `0`                  | Debounce delay in ms        |

    **Debounce Buffer (queue.debounce)**

    Ingress-layer message coalescing before queue entry.

    | Key                     | Type      | Default | Description                          |
    | ----------------------- | --------- | ------- | ------------------------------------ |
    | `windowMs`              | `number`  | `0`     | Debounce window in ms (0 = disabled) |
    | `maxBufferedMessages`   | `number`  | `10`    | Max messages to buffer per session   |
    | `firstMessageImmediate` | `boolean` | `true`  | First message triggers immediately   |

    **Follow-up (queue.followup)**

    | Key                    | Type      | Default | Description                           |
    | ---------------------- | --------- | ------- | ------------------------------------- |
    | `maxFollowupRuns`      | `number`  | `3`     | Max follow-up runs in a single chain  |
    | `followupOnCompaction` | `boolean` | `true`  | Trigger follow-up on compaction flush |

    <Info>See [Queue](/agents/queue) for queue behavior and session serialization details.</Info>
  </Accordion>
</AccordionGroup>

## Security

<AccordionGroup>
  <Accordion title="security">
    Security configuration for log redaction, audit logging, permissions, action confirmation, agent-to-agent messaging, and encrypted secrets.

    | Key            | Type      | Default | Description                                         |
    | -------------- | --------- | ------- | --------------------------------------------------- |
    | `logRedaction` | `boolean` | `true`  | Enable structured log redaction of sensitive fields |
    | `auditLog`     | `boolean` | `true`  | Enable audit event logging                          |

    **Permission (security.permission)**

    | Key                     | Type       | Default | Description                                    |
    | ----------------------- | ---------- | ------- | ---------------------------------------------- |
    | `enableNodePermissions` | `boolean`  | `false` | Enable Node.js `--permission` flag enforcement |
    | `allowedFsPaths`        | `string[]` | `[]`    | Allowed filesystem read/write paths            |
    | `allowedNetHosts`       | `string[]` | `[]`    | Allowed network hosts for outbound connections |

    **Action Confirmation (security.actionConfirmation)**

    | Key                     | Type       | Default | Description                                  |
    | ----------------------- | ---------- | ------- | -------------------------------------------- |
    | `requireForDestructive` | `boolean`  | `true`  | Require confirmation for destructive actions |
    | `requireForSensitive`   | `boolean`  | `false` | Require confirmation for sensitive actions   |
    | `autoApprove`           | `string[]` | `[]`    | Actions that bypass confirmation             |

    **Agent-to-Agent (security.agentToAgent)**

    | Key                   | Type             | Default      | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
    | --------------------- | ---------------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `enabled`             | `boolean`        | `true`       | Enable cross-agent session messaging                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
    | `maxPingPongTurns`    | `number`         | `3`          | Max reply-back loop turns (0-5)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
    | `allowAgents`         | `string[]`       | `[]`         | Allowed agent IDs for sub-agents (empty = all)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
    | `subAgentRetentionMs` | `number`         | `3600000`    | Retention for completed sub-agent sessions (1 hour)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
    | `waitTimeoutMs`       | `number`         | `60000`      | Default timeout for wait mode (60 seconds)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
    | `subAgentMaxSteps`    | `number`         | `50`         | Default max steps for sub-agent execution                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
    | `subAgentToolGroups`  | `enum[]`         | `["coding"]` | Default tool profile groups: `minimal`, `coding`, `messaging`, `supervisor`, `full`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
    | `subAgentMcpTools`    | `enum`           | `"inherit"`  | MCP tool inheritance: `inherit` or `none`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
    | `tokenBudget`         | `number \| null` | `null`       | Per-spawn token budget for graph sub-agents. `null` (default) means inherit the graph share -- `budget.max_tokens` divided by the total node count -- but **only when the graph sets a token budget**; otherwise sub-agents are unbounded (today's behavior, unchanged). A positive integer caps every graph node's sub-agent at that many tokens. A graph node's own per-node budget overrides this default. A breach fails that node (honoring the graph's `on_failure`). See [Execution Graphs: Token budgets](/agents/execution-graphs#token-budgets).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
    | `sandboxNoDowngrade`  | `boolean`        | `true`       | Fail-closed **no-downgrade invariant** on the sub-agent spawn path: a child spawn is **refused before any run or session is created** if the child's resolved sandbox posture would be *less confined* than its spawner's on any dimension, and a [`security:sandbox_downgrade_refused`](/developer-guide/event-bus) event is emitted. Posture is compared across exec / filesystem / network / uid; missing config folds to the most-confined value (the posture is never inferred more permissive than reality). For sub-agents today the **exec sandbox** dimension is the active one (exec `always` is more confined than exec `never`); the filesystem/network/uid dimensions are present in the comparator but inert until the deferred terminal path lands. See the [Threat Model](/security/threat-model) and [Skill Sandboxing](/security/sandbox) for the invariant. A config where a parent agent's exec sandbox is `always` and a child it spawns is `never` has that spawn **refused**. The refusal is the intended default-on behavior; set `false` to disable -- like all `security.*` keys it is runtime-immutable, so toggling the off-switch means editing `config.yaml` and restarting the daemon (agents cannot self-disable it). |
    | `steerInject`         | `boolean`        | `false`      | Selects how [`subagent.steer`](/reference/json-rpc#subagent-steer) delivers a steering message to a running child. **`false` (default)** -- kill the running child and respawn a fresh run with the message as the new task (the child's transcript and progress are discarded). **`true`** -- inject the message into the *running* child's live session at its next step boundary, preserving the transcript and prior work (no kill, no respawn; the same runId continues). The injected message is a user turn, never a capability grant -- a steered request for a tool on the sub-agent tool denylist is still refused, and the child's sandbox posture is unchanged. Ships **off**; enable once you observe operators wanting to steer children mid-run. Like all `security.*` keys it is runtime-immutable (edit `config.yaml` and restart the daemon).                                                                                                                                                                                                                                                                                                                                                                                       |

    **Delivery (security.agentToAgent.delivery)**

    Bounds the self-healing retry on sub-agent completion delivery. See [Resilience: Self-healing delivery retries](/agents/resilience#self-healing-delivery-retries).

    | Key          | Type     | Default | Description                                                                                                                                                                                                                                                                                                                                                                                                      |
    | ------------ | -------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `maxRetries` | `number` | `3`     | Maximum retry attempts for a **transient** sub-agent completion-delivery failure (connection reset/refused, socket hang up, timeout, HTTP 5xx, rate limit) before the message is dead-lettered. Range `0`–`10`. Retries use exponential backoff (1s, 2s, 4s, … capped at 30s). A **permanent** failure (budget breach, killed, context exhausted, step-limit) dead-letters immediately regardless of this value. |

    **Subagent Context (security.agentToAgent.subagentContext)**

    Controls how sub-agent sessions receive context, condense results, and manage their lifecycle. All fields have sensible defaults -- you only need to configure values you want to change. See [Subagent Context Lifecycle](/agents/subagent-lifecycle) for a full explanation.

    | Key                         | Type      | Default             | Description                                                                                                                                                                                                                                                                                                                                                                                   |
    | --------------------------- | --------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `maxSpawnDepth`             | `number`  | `3`                 | Maximum spawn chain depth (1-10). A depth of 3 means parent -> child -> grandchild.                                                                                                                                                                                                                                                                                                           |
    | `maxChildrenPerAgent`       | `number`  | `5`                 | Maximum concurrent active children per parent agent (1-20). Graph pipeline nodes bypass this limit.                                                                                                                                                                                                                                                                                           |
    | `maxResultTokens`           | `number`  | `4000`              | Token threshold for condensation (100-100,000). Results under this pass through unchanged.                                                                                                                                                                                                                                                                                                    |
    | `resultRetentionMs`         | `number`  | `86400000`          | How long full result files are kept on disk before auto-sweep (default 24 hours).                                                                                                                                                                                                                                                                                                             |
    | `condensationStrategy`      | `enum`    | `"auto"`            | When to condense: `auto` (based on token count), `always` (force condensation), `never` (always passthrough).                                                                                                                                                                                                                                                                                 |
    | `includeParentHistory`      | `enum`    | `"none"`            | Parent context mode: `none` (no parent context), `summary` (condensed parent conversation summary).                                                                                                                                                                                                                                                                                           |
    | `objectiveReinforcement`    | `boolean` | `true`              | Inject the sub-agent's objective after compaction so it survives context trimming.                                                                                                                                                                                                                                                                                                            |
    | `artifactPassthrough`       | `boolean` | `true`              | Pass artifact file references from the spawn call to the sub-agent's context.                                                                                                                                                                                                                                                                                                                 |
    | `autoCompactThreshold`      | `number`  | `0.95`              | Context fill ratio (0.5-1.0) for triggering auto-compaction. This field is present in the schema but its runtime effect on the context engine compaction trigger is being refined in a future release.                                                                                                                                                                                        |
    | `maxRunTimeoutMs`           | `number`  | `600000`            | Maximum wall-clock time for a sub-agent run before watchdog force-fail (10 minutes). Hard ceiling regardless of step count.                                                                                                                                                                                                                                                                   |
    | `perStepTimeoutMs`          | `number`  | `60000`             | Per-step time budget for dynamic watchdog calculation (1 minute). Dynamic timeout = min(max\_steps x perStepTimeoutMs, maxRunTimeoutMs).                                                                                                                                                                                                                                                      |
    | `stuckKillThresholdMs`      | `number`  | `180000`            | Idle threshold for the daemon health monitor's stuck sweep (3 minutes). Measures time since the run's last observed tool/LLM boundary event -- NOT total runtime -- so a healthy long run with recent progress is never killed. `0` disables. A tripped sweep kills the run with `killedBy: health_monitor`, notifies the announce channel, and records a `subagent.killed` trajectory event. |
    | `graphStuckKillThresholdMs` | `number`  | `600000`            | Idle threshold for graph sub-agents (10 minutes) -- graph nodes routinely pause longer between boundary events. Same idle semantics as `stuckKillThresholdMs`; `0` disables for graph runs.                                                                                                                                                                                                   |
    | `errorPreservation`         | `boolean` | `true`              | Preserve error details in condensed results instead of summarizing them away.                                                                                                                                                                                                                                                                                                                 |
    | `narrativeCasting`          | `boolean` | `true`              | Format sub-agent results with tagged prefixes and metadata for the parent agent.                                                                                                                                                                                                                                                                                                              |
    | `resultTagPrefix`           | `string`  | `"Subagent Result"` | Tag prefix used in narrative casting (1-100 characters). Appears as `[{prefix}: {label}]`.                                                                                                                                                                                                                                                                                                    |
    | `parentSummaryMaxTokens`    | `number`  | `1000`              | Token limit for the parent context summary when `includeParentHistory` is `"summary"` (100-10,000).                                                                                                                                                                                                                                                                                           |

    **Storage Mode (security.storage)**

    | Key       | Type                                 | Default       | Description                                                                                              |
    | --------- | ------------------------------------ | ------------- | -------------------------------------------------------------------------------------------------------- |
    | `storage` | `"encrypted"` \| `"file"` \| `"env"` | `"encrypted"` | Credential storage mode (`security.storage`) for all three stores (secrets, OAuth profiles, MCP tokens). |

    Three modes are supported:

    **`storage: "encrypted"` (default — secure-by-default)** — AES-256-GCM-encrypted rows in
    `~/.comis/secrets.db` (SQLite). Requires `SECRETS_MASTER_KEY` to be set (generated automatically
    on first boot). Defends against disk/backup theft at the cost of making `SECRETS_MASTER_KEY`
    the crown jewel.

    **`storage: "file"` — plaintext opt-in bargain** — structured JSON at `~/.comis/secrets.json`,
    `~/.comis/auth-profiles.json`, and `~/.comis/mcp-tokens/` with mode `0600` (user-only read) in a
    `0700` directory. Defends against other local users reading secrets; does **not** defend against
    root, disk/backup theft, or process-memory inspection. No `SECRETS_MASTER_KEY` required.
    Hot-reload: `comis auth login` writes are picked up without a daemon restart.

    **`storage: "env"` (`security.storage: env`) — read-only posture** — snapshots `.env`/`process.env` into the
    `SecretManager` at boot and scrubs sensitive names from `process.env`. Runtime writes
    (`env.set`, `secrets.set`, `comis auth login`) are rejected with an actionable error. Use
    for read-only deployments where credentials are injected via environment variables.

    <Warning>
      `security.storage` is **runtime-immutable** — changing it requires editing `config.yaml`
      and restarting the daemon. The config schema is `z.strictObject`, so unknown keys are
      rejected at boot. **Back up `~/.comis/config.yaml` before editing.**
    </Warning>

    <Info>
      **Mode mismatch detection:** If you switch modes while credentials remain in the inactive
      backend, the daemon emits a boot WARN naming the stranded store and the manual migration step.
      Cross-mode migration tooling is planned for a future release.
    </Info>

    ```yaml title="~/.comis/config.yaml" theme={}
    security:
      storage: encrypted  # default — AES-256-GCM, requires SECRETS_MASTER_KEY
      # storage: file     # plaintext opt-in — 0600 JSON files, no SECRETS_MASTER_KEY
      # storage: env      # read-only — snapshots process.env, rejects runtime writes
    ```

    <Info>See [Security](/security/index) for a comprehensive overview of the security model.</Info>
  </Accordion>

  <Accordion title="approvals">
    Action approval workflow for execution paths that explicitly call the approval
    gate. Enabling this section does not wrap every action, tool, or RPC method.

    | Key                  | Type             | Default  | Description                                                                                                                          |
    | -------------------- | ---------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------ |
    | `enabled`            | `boolean`        | `false`  | Wire the approval gate into supported execution paths                                                                                |
    | `defaultMode`        | `enum`           | `"auto"` | Schema value for unmatched actions; no general runtime evaluator currently consumes it                                               |
    | `rules`              | `ApprovalRule[]` | `[]`     | Schema values; no general runtime evaluator currently consumes them                                                                  |
    | `defaultTimeoutMs`   | `number`         | `300000` | Approval request timeout in ms (5 min)                                                                                               |
    | `denialCacheTtlMs`   | `number`         | `60000`  | How long denied actions are cached before re-prompting (ms). Set to 0 to disable denial caching.                                     |
    | `batchApprovalTtlMs` | `number`         | `30000`  | How long approved actions are cached for automatic re-approval in batch operations (ms). Set to 0 to disable batch approval caching. |

    <Info>Batch approval caching (`batchApprovalTtlMs`) allows sequential requests with the same session key and action name to auto-approve within the TTL window. Parameters are not part of the cache key. The cache persists across managed daemon restarts.</Info>

    **Approval Rule (approvals.rules\[])**

    | Key             | Type     | Default      | Description                                                             |
    | --------------- | -------- | ------------ | ----------------------------------------------------------------------- |
    | `actionPattern` | `string` | *(required)* | Pattern matching action types                                           |
    | `mode`          | `enum`   | `"auto"`     | Approval mode: `auto`, `require`, `deny`                                |
    | `timeoutMs`     | `number` | `300000`     | Timeout for human approval (0 = no timeout)                             |
    | `minTrustLevel` | `enum`   | `"verified"` | Trust level for auto-approve: `untrusted`, `basic`, `verified`, `admin` |

    <Warning>
      `defaultMode`, `rules`, and `security.actionConfirmation` are not a universal
      action-policy engine. Only an explicit `ApprovalGate.requestApproval()` call
      creates a human approval pause. See [Approvals](/security/approvals) for the
      currently guarded paths and runtime configuration.
    </Warning>
  </Accordion>
</AccordionGroup>

## Daemon & Scheduler

<AccordionGroup>
  <Accordion title="daemon">
    Daemon process configuration for shutdown, metrics, logging, and config change webhooks.

    | Key                 | Type                       | Default | Description                                     |
    | ------------------- | -------------------------- | ------- | ----------------------------------------------- |
    | `shutdownTimeoutMs` | `number`                   | `45000` | Graceful shutdown timeout in ms (minimum 40000) |
    | `metricsIntervalMs` | `number`                   | `30000` | Process metrics collection interval in ms       |
    | `logLevels`         | `Record<string, LogLevel>` | `{}`    | Per-module log level overrides                  |

    **Logging (daemon.logging)**

    | Key        | Type      | Default                      | Description                                    |
    | ---------- | --------- | ---------------------------- | ---------------------------------------------- |
    | `filePath` | `string`  | `"~/.comis/logs/daemon.log"` | Path to the active log file                    |
    | `maxSize`  | `string`  | `"10m"`                      | Max file size before rotation (k/m/g suffixes) |
    | `maxFiles` | `number`  | `5`                          | Number of rotated files to keep (0-100)        |
    | `compress` | `boolean` | `false`                      | Compress rotated files                         |

    **Tracing Defaults (daemon.logging.tracing)**

    | Key         | Type     | Default             | Description                                          |
    | ----------- | -------- | ------------------- | ---------------------------------------------------- |
    | `outputDir` | `string` | `"~/.comis/traces"` | Default output directory for JSONL traces            |
    | `maxSize`   | `string` | `"5m"`              | Max trace file size before rotation (k/m/g suffixes) |
    | `maxFiles`  | `number` | `3`                 | Rotated trace files to keep per session (0-100)      |

    **Config Webhook (daemon.configWebhook)**

    | Key         | Type                  | Default   | Description                                 |
    | ----------- | --------------------- | --------- | ------------------------------------------- |
    | `url`       | `string (URL)`        | *(unset)* | Webhook URL for config change notifications |
    | `timeoutMs` | `number`              | `5000`    | Delivery timeout in ms                      |
    | `secret`    | `string \| SecretRef` | *(unset)* | Shared secret for HMAC-SHA256 signature     |

    <Info>See [Daemon](/operations/daemon) for daemon management and [Logging](/operations/logging) for log configuration details.</Info>
  </Accordion>

  <Accordion title="scheduler">
    Proactive automation configuration for cron scheduling, heartbeat monitoring, quiet hours, execution safety, and task extraction.

    **Cron (scheduler.cron)**

    | Key                              | Type      | Default   | Description                                                                                                                                                                                                                                                                                                                                                                                                 |
    | -------------------------------- | --------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `enabled`                        | `boolean` | `true`    | Enable cron job scheduling                                                                                                                                                                                                                                                                                                                                                                                  |
    | `maxRunsPerTick`                 | `number`  | `3`       | Maximum due cron occurrences admitted by one scheduler tick                                                                                                                                                                                                                                                                                                                                                 |
    | `defaultTimezone`                | `string`  | `"UTC"`   | IANA authoring timezone used when a cron expression omits an explicit zone                                                                                                                                                                                                                                                                                                                                  |
    | `maxJobs`                        | `number`  | `100`     | Positive authored-job cap; config-owned jobs and retained terminal records are separate                                                                                                                                                                                                                                                                                                                     |
    | `maxConsecutiveDependencyErrors` | `number`  | `5`       | Consecutive scheduled dependency failures before suspension; `0` disables suspension                                                                                                                                                                                                                                                                                                                        |
    | `staggerWindowMs`                | `number`  | `0`       | Stable eligibility spread applied only to recurring scheduled fires                                                                                                                                                                                                                                                                                                                                         |
    | `wakeGate`                       | `boolean` | *(unset)* | Enable scheduler-initiated [wake-gates](/agent-tools/scheduling#wake-gate-run-the-model-only-when-it-matters). `true` forces on, `false` forces off (an explicit `false` wins even if the agent's `autonomy.script` surface is on); when unset, a gate runs only if that script surface is explicitly on. A gate still executes under the agent's own resolved caps -- the toggle grants no new capability. |

    **Heartbeat (scheduler.heartbeat)**

    | Key               | Type      | Default  | Description                                       |
    | ----------------- | --------- | -------- | ------------------------------------------------- |
    | `enabled`         | `boolean` | `true`   | Enable periodic heartbeat checks                  |
    | `intervalMs`      | `number`  | `300000` | Heartbeat interval in ms (5 min)                  |
    | `showOk`          | `boolean` | `false`  | Show OK status in heartbeat output                |
    | `showAlerts`      | `boolean` | `true`   | Show alerts in heartbeat output                   |
    | `alertThreshold`  | `number`  | `2`      | Consecutive failures before alerting              |
    | `alertCooldownMs` | `number`  | `300000` | Minimum ms between alerts for same source (5 min) |
    | `staleMs`         | `number`  | `120000` | Max ms before stuck detection (2 min)             |

    **Quiet Hours (scheduler.quietHours)**

    | Key              | Type      | Default   | Description                                                    |
    | ---------------- | --------- | --------- | -------------------------------------------------------------- |
    | `enabled`        | `boolean` | `false`   | Enable quiet hours                                             |
    | `start`          | `string`  | `"22:00"` | Start time (HH:MM format)                                      |
    | `end`            | `string`  | `"07:00"` | End time (HH:MM format)                                        |
    | `timezone`       | `string`  | `"UTC"`   | Explicit IANA timezone for deterministic quiet-hour evaluation |
    | `criticalBypass` | `boolean` | `true`    | Allow critical items to bypass quiet hours                     |

    **Execution (scheduler.execution)**

    | Key                  | Type     | Default   | Description                                                                                          |
    | -------------------- | -------- | --------- | ---------------------------------------------------------------------------------------------------- |
    | `maxLogBytes`        | `number` | `2000000` | Maximum execution ledger size in bytes; must reserve capacity for the configured per-tick admissions |
    | `retainedExecutions` | `number` | `1000`    | Complete start/terminal execution groups retained when capacity permits                              |

    **Tasks (scheduler.tasks)**

    | Key                        | Type      | Default    | Description                                                                     |
    | -------------------------- | --------- | ---------- | ------------------------------------------------------------------------------- |
    | `enabled`                  | `boolean` | `false`    | Explicit opt-in for model-inferred follow-up tasks from delivered conversations |
    | `confidenceThreshold`      | `number`  | `0.8`      | Minimum extraction confidence from `0` to `1`                                   |
    | `debounceMs`               | `number`  | `15000`    | Per-agent delay used to form a bounded extraction batch                         |
    | `batchMax`                 | `number`  | `8`        | Maximum delivered turns in one extraction batch                                 |
    | `maxPerCheck`              | `number`  | `3`        | Maximum exact-origin tasks considered in one proactive check                    |
    | `maxPerDayPerConversation` | `number`  | `3`        | Rolling visible-send cap for one conversation                                   |
    | `defaultWindowMs`          | `number`  | `43200000` | Default slack after the minimum due instant (12 hours)                          |
    | `preAcceptanceRetryLimit`  | `number`  | `3`        | Additional pre-acceptance attempts allowed after the initial attempt            |

    <Info>See [Scheduler](/operations/scheduler) for scheduling configuration and [Monitoring](/operations/monitoring) for heartbeat setup.</Info>
  </Accordion>

  <Accordion title="monitoring">
    System health monitoring with sub-monitors for disk, resources, systemd, security updates, and git repos.

    **Disk (monitoring.disk)**

    | Key                | Type       | Default | Description                        |
    | ------------------ | ---------- | ------- | ---------------------------------- |
    | `enabled`          | `boolean`  | `true`  | Enable disk space monitoring       |
    | `paths`            | `string[]` | `["/"]` | Filesystem paths to monitor        |
    | `thresholdPercent` | `number`   | `90`    | Alert threshold percentage (0-100) |

    **Resources (monitoring.resources)**

    | Key                      | Type      | Default | Description                    |
    | ------------------------ | --------- | ------- | ------------------------------ |
    | `enabled`                | `boolean` | `true`  | Enable CPU/memory monitoring   |
    | `cpuThresholdPercent`    | `number`  | `85`    | CPU alert threshold (0-100)    |
    | `memoryThresholdPercent` | `number`  | `90`    | Memory alert threshold (0-100) |

    **Systemd (monitoring.systemd)**

    | Key        | Type       | Default | Description                                       |
    | ---------- | ---------- | ------- | ------------------------------------------------- |
    | `enabled`  | `boolean`  | `true`  | Enable systemd service monitoring                 |
    | `services` | `string[]` | `[]`    | Specific services to monitor (empty = all failed) |

    **Security Updates (monitoring.securityUpdates)**

    | Key            | Type      | Default | Description                           |
    | -------------- | --------- | ------- | ------------------------------------- |
    | `enabled`      | `boolean` | `true`  | Enable security update monitoring     |
    | `securityOnly` | `boolean` | `true`  | Only check security updates (not all) |

    **Git (monitoring.git)**

    | Key            | Type       | Default | Description                        |
    | -------------- | ---------- | ------- | ---------------------------------- |
    | `enabled`      | `boolean`  | `false` | Enable git repository monitoring   |
    | `repositories` | `string[]` | `[]`    | Absolute paths to git repositories |
    | `checkRemote`  | `boolean`  | `true`  | Check remote for unpushed commits  |

    <Info>See [Monitoring](/operations/monitoring) for monitoring setup and alert configuration.</Info>
  </Accordion>
</AccordionGroup>

## Integrations

<AccordionGroup>
  <Accordion title="integrations">
    External service integrations for search, MCP servers, media processing, and auto-reply rules.

    **Brave Search (integrations.braveSearch)**

    | Key                 | Type                  | Default   | Description                                |
    | ------------------- | --------------------- | --------- | ------------------------------------------ |
    | `apiKey`            | `string \| SecretRef` | *(unset)* | Brave Search API key (disabled without it) |
    | `maxResultsDefault` | `number`              | `5`       | Default number of results                  |
    | `cacheTtlMs`        | `number`              | `3600000` | Cache TTL in ms (1 hour)                   |
    | `rateLimitRps`      | `number`              | `1`       | Rate limit in requests per second          |

    **MCP (integrations.mcp)**

    | Key                 | Type               | Default  | Description                              |
    | ------------------- | ------------------ | -------- | ---------------------------------------- |
    | `callToolTimeoutMs` | `number`           | `120000` | Default timeout for MCP tool calls in ms |
    | `servers`           | `McpServerEntry[]` | `[]`     | List of MCP servers                      |

    **MCP Server Entry (integrations.mcp.servers\[])**

    | Key         | Type                     | Default      | Description                                                                                        |
    | ----------- | ------------------------ | ------------ | -------------------------------------------------------------------------------------------------- |
    | `name`      | `string`                 | *(required)* | Unique server name                                                                                 |
    | `transport` | `enum`                   | *(inferred)* | `stdio`, `http`, or legacy `sse`; omitted values infer `stdio` from `command` or `http` from `url` |
    | `command`   | `string`                 | *(unset)*    | Command for stdio transport                                                                        |
    | `args`      | `string[]`               | *(unset)*    | Arguments for stdio command                                                                        |
    | `url`       | `string (URL)`           | *(unset)*    | URL for HTTP or SSE transport                                                                      |
    | `env`       | `Record<string, string>` | *(unset)*    | Environment variables for stdio process                                                            |
    | `enabled`   | `boolean`                | `true`       | Whether this server is enabled                                                                     |

    **Media (integrations.media)** -- contains nested sub-schemas for all media processing:

    **Transcription (integrations.media.transcription)**

    | Key                 | Type      | Default       | Description                                                                                                                                                                                                                                      |
    | ------------------- | --------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
    | `provider`          | `enum`    | `"auto"`      | STT provider: `auto` (keyless-first default), `local`, `openai`, `groq`, `deepgram`. `auto` resolves the local engine → main-provider key (if present) → fallback → honest-unavailable; an OAuth-only main is never sent an empty-bearer request |
    | `model`             | `string`  | *(unset)*     | Provider-specific model ID                                                                                                                                                                                                                       |
    | `local`             | `object`  | *(see below)* | Keyless local STT sub-config (used by `auto`/`local`)                                                                                                                                                                                            |
    | `maxFileSizeMb`     | `number`  | `25`          | Max file size in MB                                                                                                                                                                                                                              |
    | `timeoutMs`         | `number`  | `60000`       | API request timeout in ms                                                                                                                                                                                                                        |
    | `language`          | `string`  | *(unset)*     | BCP-47 language hint (auto-detect if omitted)                                                                                                                                                                                                    |
    | `autoTranscribe`    | `boolean` | `true`        | Auto-transcribe voice messages in pipeline                                                                                                                                                                                                       |
    | `preflight`         | `boolean` | `true`        | Enable preflight STT for mention detection                                                                                                                                                                                                       |
    | `fallbackProviders` | `enum[]`  | `[]`          | Ordered fallback providers (`auto`, `local`, `openai`, `groq`, `deepgram`)                                                                                                                                                                       |

    **Local STT (integrations.media.transcription.local)** -- keyless

    | Key       | Type     | Default   | Description                                         |
    | --------- | -------- | --------- | --------------------------------------------------- |
    | `model`   | `string` | `"base"`  | Local whisper model name                            |
    | `baseUrl` | `string` | *(unset)* | Optional OpenAI-compatible local whisper server URL |
    | `engine`  | `string` | *(unset)* | Engine selector (reserved for a later release)      |

    **TTS (integrations.media.tts)**

    | Key             | Type     | Default                      | Description                                                             |
    | --------------- | -------- | ---------------------------- | ----------------------------------------------------------------------- |
    | `provider`      | `enum`   | `"edge"`                     | TTS provider: `edge` (keyless default), `openai`, `elevenlabs`, `local` |
    | `voice`         | `string` | `"alloy"`                    | Voice identifier                                                        |
    | `format`        | `string` | `"opus"`                     | Output audio format                                                     |
    | `model`         | `string` | *(unset)*                    | Provider-specific model ID                                              |
    | `autoMode`      | `enum`   | `"off"`                      | Auto mode: `off`, `always`, `inbound`, `tagged`                         |
    | `maxTextLength` | `number` | `4096`                       | Max text length for synthesis                                           |
    | `tagPattern`    | `string` | `"\\[\\[tts(?::.*?)?\\]\\]"` | Regex pattern for TTS-tagged responses                                  |

    **TTS Output Formats (integrations.media.tts.outputFormats)**

    | Key        | Type     | Default  | Description     |
    | ---------- | -------- | -------- | --------------- |
    | `telegram` | `string` | `"opus"` | Telegram format |
    | `discord`  | `string` | `"mp3"`  | Discord format  |
    | `whatsapp` | `string` | `"mp3"`  | WhatsApp format |
    | `slack`    | `string` | `"mp3"`  | Slack format    |
    | `default`  | `string` | `"mp3"`  | Default format  |

    **ElevenLabs Settings (integrations.media.tts.elevenlabsSettings)** -- optional

    | Key                      | Type      | Default   | Description                             |
    | ------------------------ | --------- | --------- | --------------------------------------- |
    | `stability`              | `number`  | *(unset)* | Voice stability (0-1)                   |
    | `similarityBoost`        | `number`  | *(unset)* | Similarity boost (0-1)                  |
    | `style`                  | `number`  | *(unset)* | Style exaggeration (0-1)                |
    | `useSpeakerBoost`        | `boolean` | *(unset)* | Enable speaker boost                    |
    | `speed`                  | `number`  | *(unset)* | Playback speed multiplier               |
    | `seed`                   | `number`  | *(unset)* | Random seed for reproducible output     |
    | `applyTextNormalization` | `enum`    | `"auto"`  | Text normalization: `auto`, `on`, `off` |

    **Image Analysis (integrations.media.imageAnalysis)**

    | Key             | Type     | Default | Description               |
    | --------------- | -------- | ------- | ------------------------- |
    | `maxFileSizeMb` | `number` | `20`    | Max image file size in MB |

    **Image Generation (integrations.media.imageGeneration)**

    Controls the `image_generate` tool. By default (`provider: auto`) image generation **follows the agent's main provider and reuses its credentials** — no image-specific key is needed when the main provider can generate images (e.g. an OpenRouter agent uses its `OPENROUTER_API_KEY`). When the main provider cannot generate images (e.g. Anthropic), the tool returns an honest "unavailable" with a hint to set `provider` explicitly — it never silently routes to a different paid provider.

    | Key                 | Type      | Default       | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
    | ------------------- | --------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `provider`          | `enum`    | `"auto"`      | `auto` (follow the agent's main provider, reuse its credentials), or an explicit provider: `openrouter`, `openai-codex`, `openai`, `google`, `fal`. Explicit overrides follow-main. `fal`/`openai` are retained for existing configs.                                                                                                                                                                                                                                                                                                                                |
    | `model`             | `string`  | *(unset)*     | Provider-specific model ID; overrides the per-provider default (e.g. `black-forest-labs/flux.2-pro`).                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
    | `safetyChecker`     | `boolean` | `true`        | Run the provider safety checker on generated images (only affects fal.ai's `enable_safety_checker`; OpenAI enforces server-side).                                                                                                                                                                                                                                                                                                                                                                                                                                    |
    | `maxPerHour`        | `number`  | `10`          | Max image generations per hour per agent (rate limit).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
    | `defaultSize`       | `string`  | `"1024x1024"` | Default image size when the tool call omits `size`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
    | `timeoutMs`         | `number`  | `60000`       | Generation timeout in ms.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
    | `fallbackChain`     | `enum[]`  | `[]`          | Ordered providers consulted **only after** follow-main fails (same value set as `provider`). Each skipped entry is logged with a reason.                                                                                                                                                                                                                                                                                                                                                                                                                             |
    | `maxCostPerHourUsd` | `number`  | *(unset)*     | Optional per-agent hourly **cost** ceiling (USD). **Enforced:** once an agent's accumulated image-generation spend in the current hour reaches this value, further requests are blocked with `quota_exceeded` (a `success: false` + a hint naming this key) **before** the provider is called; the cost of each successful generation is accumulated per agent over a fixed one-hour window. Orthogonal to and additive over `maxPerHour` (the count limit, which still applies). When unset there is no cost ceiling — `maxPerHour` is the only image cost control. |

    In the common `auto` case, four follow-main image paths are wired, each reusing the agent main provider's own credentials:

    * **OpenAI** (key-auth) — a key-auth `openai` main (with `OPENAI_API_KEY`) generates via the OpenAI Images API, default `gpt-image-1`. Pin explicitly with `provider: openai`.
    * **Google Gemini** (key-auth) — a `google` main generates via Gemini (`generateContent`) with `GOOGLE_API_KEY`, default `gemini-2.5-flash-image`. A `google-vertex` main follows this same media route but still needs a separate `GOOGLE_API_KEY`; its `GOOGLE_CLOUD_API_KEY` authenticates Vertex model calls, not the Gemini image API. Pin explicitly with `provider: google`.
    * **OpenRouter** — a main on the OpenRouter proxy, or explicit `provider: openrouter` + `OPENROUTER_API_KEY`.
    * **Codex (ChatGPT-login)** — when the main provider is `openai-codex` (or you set `provider: openai-codex`), `image_generate` reuses the agent's **ChatGPT/Codex OAuth credentials** — there is **no image-specific API key** to set; the OAuth bearer is resolved per call and refreshes automatically (bootstrapped from `OAUTH_OPENAI_CODEX` or `comis auth login --provider openai-codex`). If the agent is not logged in, the tool returns an honest "unavailable" with a `comis auth login --provider openai-codex` hint. The Codex request's top-level model must be a **current codex chat model** (the hosted `image_generation` tool picks the actual image model server-side): the request uses your `model` override when set, otherwise the agent's own chat model **when the main provider is `openai-codex`**, otherwise the built-in codex chat default — so pinning `provider: openai-codex` on a non-codex main (e.g. an Anthropic agent) works out of the box and never sends the foreign main model to the ChatGPT backend.

    Two optional `image_generate` tool params ride these paths: `model` (override the provider's default image model — rejected with a hint listing the valid models if it is not valid for the active provider) and `reference_image` (a workspace file path, URL, or data-URI for edit/img2img — path-traversal- and SSRF-guarded). An image-incapable main (e.g. Anthropic) with no explicit `provider` stays honest-"unavailable" rather than silently routing to a paid provider.

    ```yaml theme={}
    integrations:
      media:
        imageGeneration:
          provider: auto        # follow the agent's main provider + reuse its credentials
          maxPerHour: 10
    ```

    ```yaml theme={}
    integrations:
      media:
        imageGeneration:
          provider: openai-codex         # reuse the agent's ChatGPT/Codex OAuth — NO image key needed
          maxPerHour: 10                 # the bearer comes from OAUTH_OPENAI_CODEX / `comis auth login --provider openai-codex`
    ```

    ```yaml theme={}
    integrations:
      media:
        imageGeneration:
          provider: openrouter           # explicit — opt in to OpenRouter (needs OPENROUTER_API_KEY)
          model: black-forest-labs/flux.2-pro
          fallbackChain: [openrouter]    # consulted only if follow-main fails
          maxCostPerHourUsd: 5           # per-agent/hour USD ceiling → quota_exceeded once exceeded
    ```

    <Note>
      A Codex image request reuses the authenticated header shape of the Codex text transport, including `originator`, a Codex-shaped `User-Agent`, and the account ID decoded from the same OAuth token. This compatibility path uses your own credentials, but backend acceptance can vary or change. Configure an explicit supported image provider if the endpoint rejects the request.
    </Note>

    **Video Generation (integrations.media.videoGeneration)**

    Controls the `video_generate` tool. By default (`provider: auto`) video generation **follows the agent's main provider and reuses its credentials**, mirroring image generation: a `google` main resolves to **Google Veo** and an `xai` main resolves to **xAI Grok Imagine**, each with the key the agent already uses (`GOOGLE_API_KEY` / `XAI_API_KEY`). Where the main provider has no video API (e.g. `openai`, `anthropic`), the tool returns an honest "unavailable" with a hint to set `provider` (e.g. `fal` + `FAL_KEY`) — it never silently routes to a different paid provider.

    | Key                   | Type      | Default   | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
    | --------------------- | --------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
    | `provider`            | `enum`    | `"auto"`  | `auto` (follow the agent's main provider, reuse its credentials), or an explicit provider: `fal`, `google`, `xai`. Explicit overrides follow-main.                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
    | `model`               | `string`  | *(unset)* | Provider-specific model/endpoint ID; overrides the per-backend default (e.g. `fal-ai/veo3.1/fast`).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
    | `defaultDurationSecs` | `number`  | `8`       | Default clip length in seconds when the tool call omits `duration` (provider-validated).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
    | `defaultAspectRatio`  | `string`  | `"16:9"`  | Default aspect ratio (`16:9`, `9:16`, `1:1`).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
    | `defaultResolution`   | `string`  | `"720p"`  | Default resolution (`720p`, `1080p`, `4k`).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
    | `generateAudio`       | `boolean` | *(unset)* | Generate audio when the backend supports it (omit for the provider default). Raises the worst-case cost estimate.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
    | `maxPerHour`          | `number`  | `5`       | Max video generations per hour per agent (rate limit).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
    | `timeoutMs`           | `number`  | `300000`  | Inline `execute()` poll-loop timeout in ms (a render can take 30 s–5 min).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
    | `pollIntervalMs`      | `number`  | `10000`   | Poll cadence in ms while awaiting a terminal job state.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
    | `maxConcurrentJobs`   | `number`  | *(unset)* | Optional cap on concurrent in-flight jobs, enforced by the background poller.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
    | `maxDeliveryAttempts` | `number`  | `5`       | Max delivery/completion attempts the background poller makes before it **dead-letters** a job to `failed`. Each sweep re-drives a still-`pending` job whose channel delivery failed; this bounds that retry so a **persistent** delivery failure (channel down, oversize attachment, bad credential) converges to `failed` instead of re-polling + re-downloading the clip every `pollIntervalMs` forever. The render cost is recorded on the limiter **only** on a successful terminal delivery, so a dead-lettered job is never billed.                                                                    |
    | `fallbackChain`       | `enum[]`  | `[]`      | Ordered providers consulted **only after** follow-main fails (same value set as `provider`). Each skipped entry is logged with a reason.                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
    | `maxCostPerHourUsd`   | `number`  | *(unset)* | Optional per-agent hourly **cost** ceiling (USD), gated **estimate-first**. **Enforced:** because a video clip is already rendering once submitted, the ceiling is checked against a **worst-case pre-submit estimate** (duration × per-second rate, audio/4k upper bound) — if `(accumulated + estimate)` would exceed this value the request is blocked with `quota_exceeded` (a `success: false` + a hint naming this key) **before** the provider is called; the actual cost is reconciled after a successful render. Orthogonal to and additive over `maxPerHour`. When unset there is no cost ceiling. |

    <Note>
      All three backends are wired: under `provider: auto` a `google` main renders via **Veo** and an `xai` main via **Grok Imagine**, each reusing the main provider's credentials; the explicit `fal` path renders via the FAL queue API and needs `FAL_KEY`.
    </Note>

    ```yaml theme={}
    integrations:
      media:
        videoGeneration:
          provider: auto        # follow the agent's main provider (google→Veo, xai→Grok)
          maxPerHour: 5
    ```

    ```yaml theme={}
    integrations:
      media:
        videoGeneration:
          provider: fal                  # explicit FAL queue path (needs FAL_KEY)
          model: fal-ai/veo3.1/fast
          defaultDurationSecs: 8
          timeoutMs: 300000
          pollIntervalMs: 10000
          maxCostPerHourUsd: 5           # per-agent/hour USD ceiling, gated on the pre-submit estimate
    ```

    **Vision (integrations.media.vision)**

    | Key                        | Type                | Default                             | Description                                                                                                                                                                                                                                                |
    | -------------------------- | ------------------- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `enabled`                  | `boolean`           | `true`                              | Enable vision analysis                                                                                                                                                                                                                                     |
    | `providers`                | `enum[]`            | `["openai", "anthropic", "google"]` | Ordered vision-registry providers (the fallback chain)                                                                                                                                                                                                     |
    | `defaultProvider`          | `string`            | *(unset)*                           | Explicit vision provider. When set (non-empty), it **overrides** the follow-main behavior and the registry serves this provider directly. When unset, `image_analyze` follows the agent's main vision-capable model first (see the precedence Note below). |
    | `videoMaxBase64Bytes`      | `number`            | `70000000`                          | Max base64 video size (70MB)                                                                                                                                                                                                                               |
    | `videoMaxRawBytes`         | `number`            | `50000000`                          | Max raw video file size (50MB)                                                                                                                                                                                                                             |
    | `videoTimeoutMs`           | `number`            | `120000`                            | Video description timeout in ms                                                                                                                                                                                                                            |
    | `videoMaxDescriptionChars` | `number`            | `500`                               | Max chars for video description                                                                                                                                                                                                                            |
    | `imageMaxFileSizeMb`       | `number`            | `20`                                | Max image file size in MB                                                                                                                                                                                                                                  |
    | `scopeRules`               | `VisionScopeRule[]` | `[]`                                | Scope rules (first match wins)                                                                                                                                                                                                                             |
    | `defaultScopeAction`       | `enum`              | `"allow"`                           | Default when no rule matches: `allow` or `deny`                                                                                                                                                                                                            |

    <Note>
      **Vision provider precedence (`image_analyze`).** Resolution order, highest first:

      1. **Explicit `defaultProvider`** -- when set (non-empty), the vision registry serves that provider; this wins over everything.
      2. **Follow the agent's main provider** (the default when `defaultProvider` is unset) -- when the agent's main model accepts image input (Claude / GPT-4o / Gemini and other vision-capable models), `image_analyze` routes through that main model, **reusing the agent's existing credentials**. No new vision config key and **no separate vision API key** are required for this -- it is automatic.
      3. **The vision registry chain** (`providers`, default `openai -> anthropic -> google`, first with a configured key) -- when the main model is not vision-capable, or as the fallback if a main-vision attempt fails at runtime.
      4. **Honest "unavailable"** -- when no tier can serve, with an error kind and a hint naming the knob to set.

      `describe_video` (raw video) is **Gemini-only** and does not follow the main provider -- it routes straight to the registry's Google provider (no frame extraction this release). The existing `providers` / `defaultProvider` / `scopeRules` keys are unchanged; the follow-main behavior adds no new vision config key.
    </Note>

    **Vision Scope Rule (integrations.media.vision.scopeRules\[])**

    | Key         | Type     | Default      | Description                 |
    | ----------- | -------- | ------------ | --------------------------- |
    | `channel`   | `string` | *(unset)*    | Channel type to match       |
    | `chatType`  | `string` | *(unset)*    | Chat type to match          |
    | `keyPrefix` | `string` | *(unset)*    | Session key prefix to match |
    | `action`    | `enum`   | *(required)* | Action: `allow` or `deny`   |

    **Link Understanding (integrations.media.linkUnderstanding)**

    | Key               | Type      | Default                            | Description                         |
    | ----------------- | --------- | ---------------------------------- | ----------------------------------- |
    | `enabled`         | `boolean` | `false`                            | Enable automatic link understanding |
    | `maxLinks`        | `number`  | `3`                                | Max links to process per message    |
    | `fetchTimeoutMs`  | `number`  | `10000`                            | Timeout for each URL fetch in ms    |
    | `maxContentChars` | `number`  | `5000`                             | Max extracted content per link      |
    | `userAgentString` | `string`  | `"Comis/1.0 (Link Understanding)"` | User-Agent for outbound requests    |

    **Media Infrastructure (integrations.media.infrastructure)**

    | Key                     | Type     | Default    | Description                        |
    | ----------------------- | -------- | ---------- | ---------------------------------- |
    | `maxRemoteFetchBytes`   | `number` | `26214400` | Max remote media fetch size (25MB) |
    | `concurrencyLimit`      | `number` | `3`        | Max concurrent media operations    |
    | `tempFileTtlMs`         | `number` | `1800000`  | Temp file TTL in ms (30 min)       |
    | `tempCleanupIntervalMs` | `number` | `300000`   | Cleanup interval in ms (5 min)     |

    **Document Extraction (integrations.media.documentExtraction)**

    | Key                         | Type       | Default           | Description                                         |
    | --------------------------- | ---------- | ----------------- | --------------------------------------------------- |
    | `enabled`                   | `boolean`  | `true`            | Enable document extraction                          |
    | `allowedMimes`              | `string[]` | *(13 MIME types)* | Allowed MIME types for extraction                   |
    | `maxBytes`                  | `number`   | `10485760`        | Max file size (10MB)                                |
    | `maxChars`                  | `number`   | `200000`          | Max extracted text chars                            |
    | `maxTotalChars`             | `number`   | `500000`          | Max total chars across all attachments              |
    | `maxPages`                  | `number`   | `20`              | Max pages from paginated documents                  |
    | `timeoutMs`                 | `number`   | `30000`           | Extraction timeout in ms                            |
    | `pdfImageFallback`          | `boolean`  | `false`           | Use OCR for PDFs with little text                   |
    | `pdfImageFallbackThreshold` | `number`   | `50`              | Min chars per page to trigger fallback (0 = always) |

    **Media Persistence (integrations.media.persistence)**

    | Key            | Type      | Default    | Description                                  |
    | -------------- | --------- | ---------- | -------------------------------------------- |
    | `enabled`      | `boolean` | `true`     | Enable automatic media file persistence      |
    | `maxStorageMb` | `number`  | `1024`     | Soft limit for workspace media storage (1GB) |
    | `maxFileBytes` | `number`  | `52428800` | Max individual file size (50MB)              |

    **Auto-Reply (integrations.autoReply)**

    | Key       | Type              | Default | Description                           |
    | --------- | ----------------- | ------- | ------------------------------------- |
    | `enabled` | `boolean`         | `false` | Enable pattern-based auto-reply rules |
    | `rules`   | `AutoReplyRule[]` | `[]`    | List of auto-reply rules              |

    **Auto-Reply Rule (integrations.autoReply.rules\[])**

    | Key        | Type       | Default      | Description                              |
    | ---------- | ---------- | ------------ | ---------------------------------------- |
    | `id`       | `string`   | *(required)* | Unique rule identifier                   |
    | `pattern`  | `string`   | *(required)* | Regex pattern to match messages          |
    | `template` | `string`   | *(required)* | Response template (supports `{{match}}`) |
    | `channels` | `string[]` | *(unset)*    | Channel filter (omit for all channels)   |
    | `priority` | `number`   | `0`          | Rule ordering priority (higher = first)  |

    <Info>See [MCP](/skills/mcp) for MCP server configuration and [Voice](/media/voice) for STT/TTS setup.</Info>
  </Accordion>

  <Accordion title="browser">
    Browser automation via Chrome DevTools Protocol (CDP).

    | Key                       | Type      | Default     | Description                                                                                                                                                                                    |
    | ------------------------- | --------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `enabled`                 | `boolean` | `true`      | Enable browser automation (on by default; stays sandboxed, orch:browse still escalates)                                                                                                        |
    | `chromePath`              | `string`  | *(unset)*   | Path to Chrome/Chromium binary (auto-detected)                                                                                                                                                 |
    | `cdpPort`                 | `number`  | `9222`      | CDP debug port (1-65535)                                                                                                                                                                       |
    | `defaultProfile`          | `string`  | `"default"` | Named browser profile directory                                                                                                                                                                |
    | `headless`                | `boolean` | `true`      | Run in headless mode                                                                                                                                                                           |
    | `noSandbox`               | `boolean` | `false`     | Disable Chrome sandbox (security-sensitive)                                                                                                                                                    |
    | `allowLoopbackNavigation` | `boolean` | `false`     | Allow navigation to loopback addresses (127.0.0.0/8, ::1) — e.g. a local dev server. Off by default: loopback is an SSRF target. Private ranges and cloud-metadata IPs stay blocked regardless |
    | `screenshotMaxSide`       | `number`  | `2000`      | Max screenshot dimension in pixels                                                                                                                                                             |
    | `screenshotQuality`       | `number`  | `80`        | JPEG quality (1-100)                                                                                                                                                                           |
    | `snapshotMaxChars`        | `number`  | `120000`    | Max chars for DOM snapshot                                                                                                                                                                     |
    | `timeoutMs`               | `number`  | `30000`     | Page load timeout in ms                                                                                                                                                                        |
    | `baseCdpPort`             | `number`  | `18800`     | Base CDP port for profile allocation                                                                                                                                                           |
    | `maxProfiles`             | `number`  | `10`        | Max concurrent named profiles (1-50)                                                                                                                                                           |
    | `profilesDir`             | `string`  | *(unset)*   | Override directory for profile data                                                                                                                                                            |
    | `downloadsDir`            | `string`  | *(unset)*   | Directory for tracked downloads                                                                                                                                                                |
    | `downloadTimeoutMs`       | `number`  | `120000`    | Max download wait time in ms                                                                                                                                                                   |

    **Viewport (browser.viewport)**

    | Key      | Type     | Default | Description               |
    | -------- | -------- | ------- | ------------------------- |
    | `width`  | `number` | `1280`  | Viewport width in pixels  |
    | `height` | `number` | `720`   | Viewport height in pixels |

    <Info>See [Browser](/agent-tools/browser) for browser tool usage and configuration.</Info>
  </Accordion>

  <Accordion title="plugins">
    Schema-backed metadata for source-integrated lifecycle plugins. These values do
    not discover, import, or load plugin code. The generic registry exposes
    `registerHook` only, so each plugin must be imported and wired in application
    source, and that wiring must explicitly honor this configuration.

    | Key       | Type                          | Default | Description                                                      |
    | --------- | ----------------------------- | ------- | ---------------------------------------------------------------- |
    | `enabled` | `boolean`                     | `true`  | Global value available to source integration wiring              |
    | `plugins` | `Record<string, PluginEntry>` | `{}`    | Metadata keyed by the ID used by source-integrated plugin wiring |

    **Plugin Entry (plugins.plugins.{id})**

    | Key        | Type                      | Default | Description                                                                    |
    | ---------- | ------------------------- | ------- | ------------------------------------------------------------------------------ |
    | `enabled`  | `boolean`                 | `true`  | Whether source wiring should register this plugin                              |
    | `priority` | `number`                  | `0`     | Priority value available to hook registration (-100 to 100, higher runs first) |
    | `config`   | `Record<string, unknown>` | `{}`    | Opaque values interpreted by the source integration                            |

    <Warning>
      The generic registry does not automatically consume this section. Setting an
      entry without corresponding source wiring has no runtime effect.
    </Warning>

    <Info>See [Plugins](/developer-guide/plugins) for the supported hook surface.</Info>
  </Accordion>
</AccordionGroup>

## tooling

The tool-first capability layer. Operator-only — agents cannot self-configure capability routing or detour policy. The entire `tooling` tree is added to `IMMUTABLE_CONFIG_PREFIXES` and rejected by `config.patch` from agent-callable surfaces.

<Warning>
  **Restart required.** Changes to `tooling.capabilityIndex.enabled` and `tooling.installDetours.mode` apply only after the daemon restarts. The `capabilityIndex.enabled` toggle selects between two cached system-prompt shapes (one-line residual vs flat tool dump); in-process reload is not supported. Operator config edits go through the standard `config.patch` / `config.apply` path which validates → writes → 200 ms delayed `SIGUSR1` → process restart.
</Warning>

Top-level fields:

* `capabilityClusters` — cluster definitions and builtin tool→cluster assignments. Operator-defined clusters merge key-by-key with the three reserved IDs (`external-integrations`, `prompt-skills`, `other-tools`); operator values win per key.
* `mcp.capabilityHints` — operator hints for connected MCP servers (record keyed by server name; each entry is `{ cluster, description, replacesPackages }`).
* `skills.capabilityHints` — operator hints for prompt skills (record keyed by skill name or skill key; each entry is `{ cluster, description?, replacesPackages }`).
* `capabilityIndex.enabled` — boolean toggle for the per-turn `## Capabilities` block (default `true`).
* `installDetours.mode` — `"observe"` / `"advise"` / `"soft-stop"` (default `"advise"`). Controls how the install-detour validator acts when an exec command would `pip install` / `npm install` a package that overlaps with an already-connected MCP server or skill.

Operator typos in any cluster reference (under `capabilityClusters.builtinAssignments[*]`, `mcp.capabilityHints[*].cluster`, or `skills.capabilityHints[*].cluster`) emit a Pino WARN at daemon startup with `errorKind: "config"`, an operator-actionable hint, and the offending `{ configPath, unresolvedClusterId }` payload — the daemon does NOT crash; unresolved cluster references fall back to `external-integrations` (for MCP hints) or `prompt-skills` (for skill hints). Check `pm2 logs comis` or `journalctl -u comis` after restart to surface typos.

## orchestration

Small-model DAG-authoring gates. These flags help a **weaker** model author multi-agent execution graphs that a frontier model would write correctly on its own — by repairing a schema-invalid graph to its intended canonical template, synthesizing a graph from a one-line intent, and grammar-constraining the tool schema for local providers. Operator-only — the `orchestration` tree is not agent-configurable.

<Warning>
  **The entire authoring layer ships ENABLED — every flag defaults to `true`** (full capability out of the box): the repair producer, the `from_intent` synthesizer, and the GBNF constrain path are all available without operator action. Set a flag `false` to opt it out. The [`pipeline:authored`](/developer-guide/event-bus) event + the `pipeline_authoring` finding in [`obs.system.health`](/reference/json-rpc#obs-system-health) let an operator measure small-tier authoring quality and opt a flag out if it is not helping.
</Warning>

**orchestration.authoring** fields:

| Key              | Type      | Default | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| ---------------- | --------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `repairProducer` | `boolean` | `true`  | Enable the conservative weak-model graph repair producer. When a weak-tier model emits a schema-invalid pipeline graph, the daemon attempts a deterministic match to a canonical template (`research-fanout` / `debate` / `vote` / `map-reduce`) and repairs it; an ambiguous match returns a structured "did you mean this template?" instead of synthesizing. The calling tier is resolved **server-side** (never tool-supplied). Off → the existing fail-closed validation error throws unchanged. |
| `intentAction`   | `boolean` | `true`  | Enable the `pipeline` tool's `from_intent` action and its deterministic synthesizer — a one-line intent (`{ pattern, agents \| tasks, budget? }`) expands into a validated, **template-shaped** graph (plain agent nodes, not the typed multi-round drivers). Off → the daemon refuses any `from_intent` call before a graph runs. See [Pipelines: from\_intent](/agent-tools/pipelines#from-intent).                                                                                                 |
| `gbnfConstrain`  | `boolean` | `true`  | **Best-effort.** Grammar-constrain the raw `pipeline` tool schema for GBNF-eligible local providers (the local/default family — never a cloud provider by name). This is a removal/relaxation-only structural transform; it **never widens field value validation**. The daemon-side schema validation remains the single source of truth. Off → byte-identical schema normalization.                                                                                                                 |

Governance is preserved regardless of these flags: a repaired or synthesized graph runs the **same** validation, denylist, and child⊆parent sandbox checks as a hand-authored graph — the producer/synthesizer return a graph and never execute one directly. See [Execution Graphs: Small-model authoring](/agents/execution-graphs#small-model-authoring) for the behavior and [Event Bus](/developer-guide/event-bus) for the `graph:repaired` and `graph:synthesized_from_intent` audit events.

```yaml theme={}
orchestration:
  authoring:
    repairProducer: false   # flip on once telemetry justifies it
    intentAction: false
    gbnfConstrain: false    # best-effort
```

## Streaming & Messaging

<AccordionGroup>
  <Accordion title="streaming">
    Block-based response delivery across all channels.

    | Key                      | Type      | Default       | Description                                                      |
    | ------------------------ | --------- | ------------- | ---------------------------------------------------------------- |
    | `enabled`                | `boolean` | `true`        | Global enable/disable for block streaming                        |
    | `defaultChunkMode`       | `enum`    | `"paragraph"` | Default chunk mode: `paragraph`, `newline`, `sentence`, `length` |
    | `defaultTypingMode`      | `enum`    | `"thinking"`  | Default typing mode: `never`, `instant`, `thinking`, `message`   |
    | `defaultTypingRefreshMs` | `number`  | `6000`        | Typing indicator refresh interval in ms                          |
    | `defaultTableMode`       | `enum`    | `"code"`      | Table conversion mode: `code`, `bullets`, `off`                  |
    | `defaultUseMarkdownIR`   | `boolean` | `true`        | Enable Markdown IR pipeline for format-aware chunking            |

    **Per-Channel Override (streaming.perChannel.{channelType})**

    | Key                             | Type      | Default       | Description                                                                                                                                                  |
    | ------------------------------- | --------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
    | `enabled`                       | `boolean` | `true`        | Enable streaming for this channel                                                                                                                            |
    | `chunkMode`                     | `enum`    | `"paragraph"` | Chunk mode                                                                                                                                                   |
    | `chunkMaxChars`                 | `number`  | *(unset)*     | Max chars per block (falls back to platform limit)                                                                                                           |
    | `chunkMinChars`                 | `number`  | `100`         | Min chars before allowing split                                                                                                                              |
    | `typingMode`                    | `enum`    | `"thinking"`  | Typing indicator mode                                                                                                                                        |
    | `typingRefreshMs`               | `number`  | `6000`        | Typing refresh interval in ms. Per-platform defaults are applied automatically (Telegram 4s, Discord 8s, etc.) -- this field overrides the automatic default |
    | `typingCircuitBreakerThreshold` | `number`  | `3`           | Consecutive typing failures before circuit breaker permanently stops indicator                                                                               |
    | `typingTtlMs`                   | `number`  | `60000`       | Maximum typing indicator duration in ms before auto-stop (refreshes on content signals)                                                                      |
    | `useMarkdownIR`                 | `boolean` | `true`        | Use Markdown IR pipeline                                                                                                                                     |
    | `tableMode`                     | `enum`    | `"code"`      | Table conversion mode                                                                                                                                        |

    Each per-channel entry also includes nested `deliveryTiming` and `coalescer` overrides with the same schema as the top-level versions.

    <Info>See [Delivery](/channels/delivery) for block streaming behavior and platform-specific delivery.</Info>
  </Accordion>

  <Accordion title="autoReplyEngine">
    Controls whether the agent pipeline activates for inbound messages. Separate from the pattern-based auto-reply rules in `integrations.autoReply`.

    | Key                       | Type       | Default           | Description                                          |
    | ------------------------- | ---------- | ----------------- | ---------------------------------------------------- |
    | `enabled`                 | `boolean`  | `true`            | Enable the auto-reply engine                         |
    | `groupActivation`         | `enum`     | `"mention-gated"` | Group chat mode: `always`, `mention-gated`, `custom` |
    | `customPatterns`          | `string[]` | `[]`              | Custom regex patterns for `custom` mode              |
    | `historyInjection`        | `boolean`  | `true`            | Inject non-trigger group messages as context         |
    | `maxHistoryInjections`    | `number`   | `50`              | Max history-injected messages per session            |
    | `maxGroupHistoryMessages` | `number`   | `20`              | Max group history messages stored per session        |
  </Accordion>

  <Accordion title="sendPolicy">
    Outbound message gating rules. Rules evaluated in order; first match wins.

    | Key             | Type               | Default   | Description                       |
    | --------------- | ------------------ | --------- | --------------------------------- |
    | `enabled`       | `boolean`          | `true`    | Enable send policy enforcement    |
    | `defaultAction` | `enum`             | `"allow"` | Default action: `allow` or `deny` |
    | `rules`         | `SendPolicyRule[]` | `[]`      | Ordered list of rules             |

    **Send Policy Rule (sendPolicy.rules\[])**

    | Key           | Type     | Default   | Description                                              |
    | ------------- | -------- | --------- | -------------------------------------------------------- |
    | `channelId`   | `string` | *(unset)* | Channel ID to match                                      |
    | `chatType`    | `string` | *(unset)* | Chat type: `dm`, `group`, `thread`, `channel`, `forum`   |
    | `channelType` | `string` | *(unset)* | Channel type: `telegram`, `discord`, `slack`, `whatsapp` |
    | `action`      | `enum`   | `"allow"` | Action: `allow` or `deny`                                |
    | `description` | `string` | *(unset)* | Human-readable description                               |
  </Accordion>

  <Accordion title="envelope">
    Message envelope enrichment for inbound messages before they reach the LLM.

    | Key            | Type      | Default    | Description                                       |
    | -------------- | --------- | ---------- | ------------------------------------------------- |
    | `timezoneMode` | `string`  | `"utc"`    | Timezone: `utc`, `local`, or IANA timezone string |
    | `timeFormat`   | `enum`    | `"12h"`    | Time display: `12h` or `24h`                      |
    | `showElapsed`  | `boolean` | `true`     | Show elapsed time since previous message          |
    | `showProvider` | `boolean` | `true`     | Show platform prefix (e.g., `[telegram]`)         |
    | `elapsedMaxMs` | `number`  | `86400000` | Max elapsed time to display (24 hours)            |
  </Accordion>

  <Accordion title="messages">
    Messaging UX configuration for outbound message formatting.

    | Key                   | Type      | Default       | Description                                |
    | --------------------- | --------- | ------------- | ------------------------------------------ |
    | `maxOutboundLength`   | `number`  | `0`           | Max outbound message length (0 = no limit) |
    | `splitLongMessages`   | `boolean` | `true`        | Split long messages into parts             |
    | `splitMaxChars`       | `number`  | `4000`        | Character limit per split part             |
    | `splitSeparator`      | `string`  | `"\n\n"`      | Separator between split parts              |
    | `showTypingIndicator` | `boolean` | `true`        | Show typing indicator during processing    |
    | `systemMessagePrefix` | `string`  | `"[System] "` | Prefix for system messages                 |
    | `readReceipts`        | `boolean` | `false`       | Enable read receipts                       |
  </Accordion>

  <Accordion title="models">
    Model catalog and alias configuration for model discovery and friendly names.

    | Key               | Type           | Default | Description                                                   |
    | ----------------- | -------------- | ------- | ------------------------------------------------------------- |
    | `scanOnStartup`   | `boolean`      | `false` | Enable automatic model scanning                               |
    | `scanTimeoutMs`   | `number`       | `30000` | Scan timeout in ms                                            |
    | `aliases`         | `ModelAlias[]` | `[]`    | Friendly model aliases                                        |
    | `defaultModel`    | `string`       | `""`    | Default model ID (falls back to `claude-sonnet-4-5-20250929`) |
    | `defaultProvider` | `string`       | `""`    | Default provider (falls back to `anthropic`)                  |

    **Model Alias (models.aliases\[])**

    | Key        | Type     | Default      | Description                           |
    | ---------- | -------- | ------------ | ------------------------------------- |
    | `alias`    | `string` | *(required)* | Short alias name                      |
    | `provider` | `string` | *(required)* | Provider identifier                   |
    | `modelId`  | `string` | *(required)* | Full model identifier at the provider |

    ```yaml theme={}
    models:
      defaultModel: claude-sonnet-4-5-20250929
      defaultProvider: anthropic
      aliases:
        - alias: gpt4
          provider: openai
          modelId: gpt-4o
    ```
  </Accordion>

  <Accordion title="providers">
    LLM provider configuration. API keys are referenced by SecretManager key name, never stored in plaintext.

    | Key       | Type                            | Default | Description                   |
    | --------- | ------------------------------- | ------- | ----------------------------- |
    | `entries` | `Record<string, ProviderEntry>` | `{}`    | Named provider configurations |

    **Provider Entry (providers.entries.{name})**

    | Key            | Type                     | Default           | Description                                                                                                                                                                                                              |
    | -------------- | ------------------------ | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
    | `type`         | `string`                 | *(required)*      | Provider type (e.g., `"anthropic"`, `"openai"`, `"ollama"`)                                                                                                                                                              |
    | `name`         | `string`                 | `""`              | Display name                                                                                                                                                                                                             |
    | `baseUrl`      | `string`                 | `""`              | API base URL override                                                                                                                                                                                                    |
    | `apiKeyName`   | `string`                 | `""`              | SecretManager key name for API key                                                                                                                                                                                       |
    | `enabled`      | `boolean`                | `true`            | Whether this provider is enabled                                                                                                                                                                                         |
    | `timeoutMs`    | `number`                 | `120000`          | Config-echo only — NOT enforced on completion calls. The completion deadline is `agents.<id>.promptTimeout.promptTimeoutMs` (stall budget). Setting a non-default value emits a one-time boot WARN naming the real knob. |
    | `maxRetries`   | `number`                 | `2`               | Max retries for transient errors                                                                                                                                                                                         |
    | `headers`      | `Record<string, string>` | `{}`              | Custom headers for API requests                                                                                                                                                                                          |
    | `capabilities` | `ProviderCapabilities`   | *(auto-detected)* | Provider-level behavioral overrides. Usually auto-detected from provider type; manual config overrides auto-detection. See sub-schema below.                                                                             |
    | `models`       | `UserModel[]`            | `[]`              | User-defined model entries for this provider. Allows registering custom/fine-tuned models with capability metadata. See sub-schema below.                                                                                |

    **ProviderCapabilities (providers.entries.{name}.capabilities)**

    | Key                              | Type                                             | Default                      | Description                                                                                                                                                                                                                                                                                                                                                                                                      |
    | -------------------------------- | ------------------------------------------------ | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `providerFamily`                 | `enum`                                           | `"default"`                  | Provider family for response handling: `default`, `openai`, `anthropic`, `google`                                                                                                                                                                                                                                                                                                                                |
    | `dropThinkingBlockModelHints`    | `string[]`                                       | `[]`                         | Model ID substrings that trigger thinking block suppression                                                                                                                                                                                                                                                                                                                                                      |
    | `transcriptToolCallIdMode`       | `enum`                                           | `"default"`                  | Tool call ID format: `default` (pass-through) or `strict9` (truncate to 9 chars for providers with ID length limits)                                                                                                                                                                                                                                                                                             |
    | `transcriptToolCallIdModelHints` | `string[]`                                       | `[]`                         | Model ID substrings that trigger strict9 tool call ID mode                                                                                                                                                                                                                                                                                                                                                       |
    | `supportsVision`                 | `boolean`                                        | `false`                      | When `true`, image attachments are forwarded to the model. When `false` (or unset), images are warn-dropped with a log entry. Set `true` for qwen3.6:27b/35b GGUF variants that support image input.                                                                                                                                                                                                             |
    | `supportsPromptCache`            | `boolean`                                        | *(auto from providerFamily)* | Whether models on this provider support prompt caching. Auto-detected from `providerFamily`: `true` for `anthropic`/`google`, `false` for `default`/`openai`. Set explicitly to override. When `false`, prompt assembly emits a single block with no `cache_control` split overhead.                                                                                                                             |
    | `supportsStructuredOutput`       | `boolean`                                        | `false`                      | When `true`, tool-call repair uses constrained decoding (e.g., Ollama's `/api/generate` `format` param) for near-miss tool JSON. When `false`, lenient parse-and-repair is used.                                                                                                                                                                                                                                 |
    | `capabilityClass`                | `"frontier"` \| `"mid"` \| `"small"` \| `"nano"` | *(resolver heuristic)*       | Explicit capability-class override for all models on this provider. Overrides the default resolver heuristic (which keys off model context-window and provider-family). Forces `scaffoldLevel` (GoalAnchor, critic) and `securityLevel` (lockdown intensity). Use `"small"` for an Ollama provider running qwen3.6. Setting `"frontier"` for a weak model silences scaffolding — not recommended for production. |
    | `probeServedWindow`              | `boolean`                                        | `true` for `type: "ollama"`  | Probe the Ollama served `num_ctx` at daemon boot and reconcile with the configured `contextWindow`. When unset (or `true`), Comis queries `GET /api/ps` and `POST /api/show` on start and uses the smaller of the served window vs. configured. Set to `false` to skip the probe if Ollama is offline at daemon start.                                                                                           |

    > **Capability-gated defaults:** The reliability scaffold (GoalAnchor, relevance floor,
    > cost-gated critic) is **default-ON for `small`/`nano` agents** — no per-agent opt-in required.
    > Two capacity defaults also apply to `small`/`nano`: `bootstrap.maxChars` drops to `3_500`
    > chars per file, and the active-tool ceiling is set to 24 tools (overflow tools stay
    > reachable via `discover_tools`). Additionally: a total bootstrap budget (5,000 chars sum-cap
    > for small/nano), capability-gated graph concurrency (small/nano→2, frontier/mid→4), and a
    > bootstrap-budget warn threshold based on the real context denominator. `frontier`/`mid`
    > behavior is unaffected by all of these. The security guarantee holds: a weaker model class cannot
    > lower the platform's security posture; the scaffolding defaults reinforce it.

    **UserModel (providers.entries.{name}.models\[])**

    | Key             | Type                      | Default      | Description                                                  |
    | --------------- | ------------------------- | ------------ | ------------------------------------------------------------ |
    | `id`            | `string`                  | *(required)* | Model identifier (e.g., `"my-finetuned-model"`)              |
    | `name`          | `string`                  | *(unset)*    | Display name for the model                                   |
    | `reasoning`     | `boolean`                 | `false`      | Whether this model supports extended thinking                |
    | `contextWindow` | `number`                  | *(unset)*    | Context window size in tokens                                |
    | `maxTokens`     | `number`                  | *(unset)*    | Maximum output tokens                                        |
    | `input`         | `string[]`                | `["text"]`   | Supported input modalities: `"text"`, `"image"`              |
    | `cost`          | `ModelCost`               | *(unset)*    | Token cost rates for budget tracking. See sub-schema below.  |
    | `comisCompat`   | `ModelCompatConfig`       | *(unset)*    | Comis-specific compatibility flags. See sub-schema below.    |
    | `sdkCompat`     | `Record<string, unknown>` | *(unset)*    | Pass-through SDK compatibility overrides (provider-specific) |

    **ModelCompatConfig (providers.entries.{name}.models\[].comisCompat)**

    | Key                         | Type      | Default   | Description                                                                                                                                                                                                                                                                                                                                                                                  |
    | --------------------------- | --------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `supportsTools`             | `boolean` | *(unset)* | Whether this model supports tool/function calling                                                                                                                                                                                                                                                                                                                                            |
    | `toolSchemaProfile`         | `enum`    | *(unset)* | Tool schema normalization profile: `"default"`, `"xai"` (strips constraint keywords xAI rejects), or `"gbnf"` (GBNF-safe structural transforms for llama.cpp-family local providers: collapses nullable `anyOf`/`oneOf` and `["T","null"]` type arrays, injects `properties: {}` on free-form objects and a `type` on typeless nodes — removal/relaxation only, `pattern`/`format` are kept) |
    | `toolCallArgumentsEncoding` | `enum`    | *(unset)* | How tool call arguments are encoded: `"json"` or `"html-entities"` (auto-decoded)                                                                                                                                                                                                                                                                                                            |
    | `nativeWebSearchTool`       | `boolean` | *(unset)* | Whether this model uses native web search (filters out Comis web-fetch tool)                                                                                                                                                                                                                                                                                                                 |

    <Note>
      **GBNF auto-detection:** providers with `type: "ollama"` default their models to the `gbnf`
      profile automatically. An explicit `toolSchemaProfile` value always wins for `gbnf` (set
      `"default"` to opt a model out while debugging) — unlike `xai`, whose auto-detected flags are
      non-negotiable API requirements and override user config.

      **Explicit opt-in:** LM Studio, llama-server (llama.cpp), and vLLM endpoints have no provider
      `type` that auto-enables the profile (only `type: "ollama"` does) — they opt in per model via
      `comisCompat.toolSchemaProfile: "gbnf"` (zero new config keys; this is the existing `comisCompat`
      surface).

      **Reactive repair:** if a provider still rejects a schema at grammar-compile time, Comis
      classifies the 400 (`tool_schema_unsupported`), strips `pattern`/`format` from the offending
      tools, and retries exactly once per session before failing honestly; `comis explain` names the
      offending tool. A once-per-boot INFO line summarizes which tools were transformed (names and
      keyword counts only — never schema bodies).

      For the served-context-window side of local-provider setup, see
      [Local model context window](#local-model-context-window).
    </Note>

    **ModelCost (providers.entries.{name}.models\[].cost)**

    | Key          | Type     | Default   | Description                                          |
    | ------------ | -------- | --------- | ---------------------------------------------------- |
    | `input`      | `number` | *(unset)* | Cost per input token (for budget tracking)           |
    | `output`     | `number` | *(unset)* | Cost per output token                                |
    | `cacheRead`  | `number` | *(unset)* | Cost per cache-read token (Anthropic prompt caching) |
    | `cacheWrite` | `number` | *(unset)* | Cost per cache-write token                           |

    ```yaml theme={}
    providers:
      entries:
        ollama:
          # type: "ollama" auto-enables comisCompat.toolSchemaProfile: "gbnf" for its models
          type: ollama
          name: "Local Ollama"
          baseUrl: "http://localhost:11434"
          enabled: true
        lmstudio:
          type: lm-studio
          name: "LM Studio"
          baseUrl: "http://localhost:1234/v1"
          enabled: true
          models:
            - id: qwen3.6-35b
              comisCompat:
                toolSchemaProfile: gbnf   # explicit opt-in — LM Studio / llama.cpp / vLLM have no auto-detected type
        xai:
          type: xai
          apiKeyName: XAI_API_KEY
          capabilities:
            transcriptToolCallIdMode: strict9
          models:
            - id: grok-3
              reasoning: true
              comisCompat:
                toolSchemaProfile: xai
                toolCallArgumentsEncoding: html-entities
    ```
  </Accordion>
</AccordionGroup>

### Recommended Secure qwen3.6 Configuration

The following `config.yaml` snippet shows the recommended settings for a secure, scaffolded qwen3.6 local deployment. All security-relevant keys are shown with their recommended values.

```yaml theme={}
providers:
  entries:
    qwen36-local:
      baseUrl: "http://localhost:11434/v1"
      # No apiKeyName — keyless Ollama
      capabilities:
        capabilityClass: small          # Forces scaffoldLevel=max + securityLevel=locked
        supportsVision: true            # For 27b/35b GGUF; false for MLX variants
        supportsStructuredOutput: true  # Enables constrained-decode tool-call repair

models:
  defaultModel: "qwen36-local:qwen3.6:35b"

agents:
  default:
    contextEngine:
      budget:
        effectiveContextCapSmall: 32000  # Cap effective history at 32K (prevents 256K overfill)
      compaction:
        preferEvictionByCapability: true  # Evict rather than summarize with the small model
      compactPrompt:
        enabled: true        # Compact-secure prompt (retains full safety core)
        targetTokens: 3000
    goalAnchor:
      # enabled: true is the capability default for small/nano — omit to rely on capability default
      enabled: true          # Explicit: tail-inject objective checklist each turn (scaffoldLevel=max)
      maxChars: 500
    verification:
      # enabled: true is the capability default for small/nano when operationModels.verification is set
      enabled: true          # Explicit force-on: pre-delivery critic (honest unmet-list on failure)
      minResponseChars: 200
    honesty:
      maxCriticRetries: 2
    rag:
      baseFloor: 0.3         # Memory relevance floor; capability default is 0.15 for small/nano (0 = sentinel)
```

### Capability-gated capacity defaults

These defaults fire only for `capabilityClass` in `{small, nano}` (e.g., any Ollama qwen3.6 deployment). Without them, a live measurement of a qwen3.6 deployment showed \~32K input tokens per turn — 98 active tool schemas and a 14.4K-char bootstrap file drove a 495% bootstrap warning. The capacity defaults below address this without changing `frontier`/`mid` behavior: a per-file bootstrap cap, an active-tool ceiling, a total bootstrap budget, capability-gated graph concurrency, and a bootstrap-budget warn threshold based on the real prompt denominator.

#### bootstrap.maxChars capability default

For `capabilityClass` in `{small, nano}`, `bootstrap.maxChars` defaults to `3_500` chars per file (down from the schema baseline of `20_000`). For `frontier`/`mid`, the value is unchanged (`20_000` per file).

Key properties:

* **Per-file limit.** Each workspace file is truncated individually. At `3_500` chars, AGENTS.md (the largest file) is preserved head 70% + tail 20%; smaller identity files (SOUL/IDENTITY/USER/ROLE/TOOLS/HEARTBEAT/BOOT) fit entirely within the limit.
* **Precedence:** explicit `agents.<id>.bootstrap.maxChars` > capability default (`3_500` for small/nano) > `20_000` (frontier/mid baseline).
* **Sentinel: `20_000` is treated as "unset".** Setting `bootstrap.maxChars: 20000` explicitly has the same effect as omitting the key — the capability default of `3_500` applies for small/nano. This is because `20_000` is the schema default, so the runtime cannot distinguish "operator chose 20\_000" from "no override". To force the `20_000` limit on a small/nano agent, set `agents.<id>.capabilityClassOverride: frontier` instead.

<Warning>
  If security-critical content lives in the **middle** of AGENTS.md (between the first 70% and last 20%), it may be truncated for small/nano models. Place critical rules in the **first \~2,450 chars** or **last \~700 chars** of AGENTS.md for reliable injection. See AGENTS.md placement guidance for the recommended section order.
</Warning>

To override the capability default for a single agent:

```yaml theme={}
agents:
  my-agent:
    bootstrap:
      maxChars: 8000   # override the small/nano default of 3_500
```

#### Active-tool ceiling

For `capabilityClass: small`, at most **24 tool schemas** are active in each prompt request. The cold long-tail (tools not in the core set and not recently used) is deferred.

<Note>
  **No capability is removed.** All deferred tools remain fully callable via the `discover_tools` mechanism. The model can search for and invoke any deferred tool in the same turn. This ceiling is a **prompt-size control**, not a security control — it does not restrict which tools the agent is authorized to use.
</Note>

The ceiling applies only to `capabilityClass: small`. Key properties:

* **CORE\_TOOLS are never deferred.** The following tools are always kept active regardless of the ceiling: `read`, `edit`, `write`, `grep`, `find`, `ls`, `apply_patch`, `exec`, `process`, `message`, `memory_search`, `memory_store`, `memory_get`, `web_search`, `web_fetch`.
* **Recently-used tools are never deferred.** Any tool the agent invoked in recent turns is preserved in the active set.
* **`nano` class is not affected.** Nano already uses aggressive CORE\_TOOLS-only deferral; the 24-tool ceiling does not change its behavior.
* **`frontier`/`mid` classes are not affected.** No ceiling applies — behavior is unchanged.
* **Savings:** 15 CORE\_TOOLS + 9 discretionary slots at 24 active tools vs. the previous 40 saves approximately 750–1,250 tokens per turn (\~300 chars per tool schema average).

#### Total bootstrap budget

For `capabilityClass` in `{small, nano}`, the **total bootstrap budget** caps the sum of all bootstrap file content at **5,000 chars**, applied as a second pass after the per-file `3_500`-char cap.

Key properties:

* **Sum-cap, not per-file.** If all workspace files together would exceed 5,000 chars after per-file truncation, each file is proportionally scaled down to fit the total budget.
* **Per-file floor.** Each file retains at least **300 chars**, regardless of how many files compete for the budget. No file is silenced entirely.
* **Proportional truncation.** Each file's allocation is `(file_chars / total_chars) * totalMaxChars`, floored at 300. Content is taken from the beginning of the file (direct slice — not head+tail; the per-file pass already applied head+tail truncation).
* **`frontier`/`mid` unaffected.** No total cap is applied; behavior is unchanged.
* **Config override.** Explicit `agents.<id>.bootstrap.maxChars` always takes precedence over both the per-file and total-budget capability defaults.

Typical result: with the default workspace files (AGENTS.md, SOUL, IDENTITY, USER, ROLE, TOOLS, HEARTBEAT, BOOT), the total bootstrap fits within 5,000 chars after per-file truncation — the proportional pass is a safety net, not the primary reducer.

To override the total budget for a single agent:

```yaml theme={}
agents:
  my-agent:
    bootstrap:
      maxChars: 8000   # overrides the small/nano per-file default (3_500)
      # Note: there is no explicit totalMaxChars config key — the total budget is
      # capability-derived (5_000 for small/nano). Override the per-file limit instead,
      # or use capabilityClassOverride: frontier to remove both caps entirely.
```

#### Capability-gated graph concurrency

For `capabilityClass` in `{small, nano}`, the graph coordinator's `maxConcurrency` defaults to **2** (down from 4). For `frontier`/`mid`, the default remains **4**.

**Why lower concurrency for small/nano?** Local inference on Ollama (or any local GPU-bound runtime) serializes model loads in practice. With 4 concurrent sub-agents, all four issue simultaneous inference requests; the GPU queues them, producing peak saturation that can cause 50–80% of sub-agents to time out on longer prompts. Lowering the default to 2 staggers the load while keeping two in-flight at all times.

To override the default for your deployment:

```yaml theme={}
security:
  agentToAgent:
    graphMaxConcurrency: 4   # restore the frontier default, or set any value >= 1
```

The operator override always takes precedence over the capability-class default.

**Prompt timeout for local Ollama.** `promptTimeoutMs` is a stall budget: the deadline resets on stream activity (text/thinking deltas, throttled \~1/s), tool progress, and tool completions, so it only needs to cover the longest SILENT gap — in practice the prefill before the first token, which on a loaded local GPU (e.g., qwen3.6 with multiple concurrent agents) can exceed the 180,000 ms default. Once the model streams, activity keeps the turn alive; the makespan ceiling (`promptTimeoutMs × stallCeilingMultiplier`, default ×10) still bounds the total turn even while streaming. For local deployments, raise the stall budget to cover slow prefill:

```yaml theme={}
agents:
  my-agent:
    promptTimeout:
      promptTimeoutMs: 300000   # 5 minutes — covers slow local prefill for qwen3.6 under load
```

A served context window smaller than configured makes prefill behavior harder to predict — see [Local model context window](#local-model-context-window).

**Fallback-model recommendation.** For deep multi-agent pipelines on local hardware, configure a `models.failoverModel` pointing to a lighter local model (e.g., a faster quantized variant) that can handle sub-agent calls when the primary model is loaded. This provides graceful degradation rather than timeout cascades.

#### Bootstrap-budget warn threshold

The `Bootstrap content exceeds budget threshold` WARN fires when bootstrap files exceed **40%** of the estimated total prompt (system prompt chars + tool schema definition chars).

The denominator is `systemPromptChars + toolDefOverheadChars` (the same formula used by `executor-tool-assembly.ts` for the context-budget breakdown) — deliberately not the system prompt alone, which would make the warning fire on virtually every small-model turn with a normal workspace. With a compact-secure system prompt (\~2,800 chars) and 24 active tool schemas (\~12,000 chars overhead), the denominator is \~14,800 chars. With the total bootstrap budget of 5,000 chars, the ratio is \~34% — below the 40% threshold, so no warning fires under normal conditions.

**When does the warn still fire?** If an operator adds large custom workspace files that push the total bootstrap above 5,920 chars (40% of \~14,800), the warn fires correctly — signaling that bootstrap content is crowding out the system prompt and tool schemas in a meaningful way.

#### Summary

| Class              | bootstrap.maxChars (per-file) | bootstrap total budget | Active-tool ceiling         | Graph maxConcurrency |
| ------------------ | ----------------------------- | ---------------------- | --------------------------- | -------------------- |
| `frontier` / `mid` | `20_000` (unchanged)          | No total cap           | No ceiling                  | 4                    |
| `small`            | `3_500` (capability default)  | `5_000` chars sum-cap  | 24 tools active             | 2                    |
| `nano`             | `3_500` (capability default)  | `5_000` chars sum-cap  | CORE\_TOOLS-only (existing) | 2                    |

All capability defaults follow the same precedence: **explicit per-agent config > capability default**. The capacity defaults are additive: all behaviors activate together for `small`/`nano` agents with no per-agent config required.

> **Relationship to the reliability defaults:** The GoalAnchor, `rag.baseFloor`, and verification critic defaults follow the same precedence model — explicit per-agent config > capability default > off. The capacity defaults (bootstrap budget, tool ceiling, graph concurrency) are additive: all default-on behaviors activate together for `small`/`nano` agents with no per-agent config required.

### General vs. Coding-Tuned Model Guidance

For agent reliability, prefer **general-purpose** qwen3.6 variants over coding-tuned models:

* **General models** (e.g., `qwen3.6:35b`, `qwen3.6:27b`) are the right choice for agentic tasks. They handle multi-turn conversations, multi-constraint instructions, and tool use reliably.
* **Coding-tuned models** can exhibit **goal fixation** — continuing to pursue a sub-task at the expense of the original objective. The Comis scaffold (GoalAnchor, verification critic, compact-secure prompt) is designed to detect and correct this behavior, but general models avoid the problem in the first place. This guidance originated from a live incident in which a coding-tuned model fixated on a game-building sub-task; general-purpose qwen3.6 reproduces this scenario far less.
* The Comis scaffold is designed for general models. It is not a substitute for choosing the right model class.

For local model runtime selection (MLX vs. GGUF), see the [Environment Variables](/reference/environment-variables#local-model-runtime-selection-mlx-vs-gguf) reference.

### Local model context window

For Ollama providers, the **served** context window (`num_ctx`) may differ from the configured
`contextWindow`. By default, Comis probes `GET /api/ps` and `POST /api/show` at daemon boot
and reconciles:

```
effectiveWindow = min(configured contextWindow, served num_ctx, capability class cap)
```

The reconciled value is used for context budgeting and the post-turn context-window guard —
so the agent plans against the model's actual KV-cache limit, not a stale declaration.
For model recommendations with measured receipts and the full local-deployment knob map, see
the [Local models playbook](/operations/local-models).
Probed values are sanitized before use: fractional `context_length` values are floored to
integers, and implausibly small ones (below 512 tokens — e.g. a typo'd Modelfile
`PARAMETER num_ctx`) are rejected as bogus, falling back to the configured window via the
probe's normal fail-open path.

**Changing the served window:** The probe is read-only; it discovers what Ollama has loaded.
To serve a larger context (up to the model's native maximum), set it on the Ollama side:

* Environment variable: `OLLAMA_CONTEXT_LENGTH=65536` (before starting Ollama)
* Modelfile parameter: `PARAMETER num_ctx 65536`
* Ollama.app: Settings → Models → Context Length

**VRAM / KV-cache caveat:** A larger `num_ctx` increases GPU memory usage proportionally
(the KV-cache scales linearly with context length). A 35B model at 256K context can OOM
or cause heavy memory thrashing on consumer hardware. Raise `num_ctx` only as far as
your VRAM allows; start conservatively (e.g., 32768 or 65536) and measure.

To suppress the boot probe for a provider (e.g., Ollama is offline at daemon start):

```yaml theme={}
providers:
  entries:
    my-ollama:
      type: ollama
      capabilities:
        probeServedWindow: false
```

**Boot WARN — served below configured:** When the probe discovers a served window smaller than
the configured `contextWindow`, Comis logs exactly **one WARN per provider per boot** —
`"Ollama served context window below configured"` — naming both numbers and the probed model
(the probe checks ONE model per provider: `defaultModel`, else the first `models[]` entry —
per-model probing is a known limitation). The hint names the fixes:
`OLLAMA_CONTEXT_LENGTH=131072 ollama serve` (substituting your configured window), or Modelfile
`PARAMETER num_ctx 131072` (see the VRAM caveat above), and the opt-out
(`providers.entries.<id>.capabilities.probeServedWindow: false`). Healthy boots stay silent:
served at or above configured, equal windows, non-Ollama providers, and providers the probe
skipped all log nothing.

**Exhaustion provenance — the served bind names its knobs:** When a turn exhausts a
served-bound window, the `context_exhausted` error text carries the suffix
`(model contextWindow 131072 but Ollama serves only 8192 — fix: OLLAMA_CONTEXT_LENGTH=131072 ollama serve, or Modelfile 'PARAMETER num_ctx 131072')`
— both Ollama knobs plus the TRUE configured window. In logs and `comis explain`,
`rawContextWindowTokens` reports the **configured** window with `windowCapSource: "served"`
(never the served value masquerading as the model's declared window).
When both the served window and a capability-class cap clamp, the message names the full chain:
`(model contextWindow 131072, Ollama serves 50000, capped to 32000 by contextEngine.budget.effectiveContextCapSmall — raise it (0 = uncapped) or reduce active tool schemas)`.
The cap wording is **branched by the lever that actually binds**: the
`contextEngine.budget.effectiveContextCapSmall/Nano` form above appears only when the budget-side
cap genuinely clamped (raising that key works); when the window was instead capped upstream by an
operator `providers.entries.<id>.capabilities.capabilityClass` **pin** (the executor's per-class
default — `small` 32000 / `nano` 16000 — which never reads the budget keys), the suffix reads
`capped to 32000 by providers.entries.<id>.capabilities.capabilityClass — pin a higher class (or remove the pin) or reduce active tool schemas`
and `windowCapSource` reports `"capabilityClass"` — on that branch raising
`contextEngine.budget.effectiveContextCapSmall` (or setting it to `0`) changes nothing, so the
error and `comis explain` name the pin instead of that dead knob.
The reconcile line — `"Context window reconciled (served or capability cap bound)"`, with
`source` / `effectiveWindow` / `configured` / `served` / `capabilityCap` fields — logs at INFO
**once per session** (the first reconciled turn; a session reset grants a fresh INFO) and at
DEBUG per turn. A session whose configured window simply wins logs no reconcile line at any
level (nothing was reconciled). The served window is **provider-scoped**: it clamps only
executions that resolve to the provider it was probed from — a per-execution model override to
another provider (a graph node's `model: anthropic:...` on an Ollama-primary agent, a subagent
spawn) keeps that model's full window and never gets `"Ollama serves only N"` attribution.

**Boot viable floor (minViable):** At boot, per agent, Comis computes
`minViable = bootstrapTotalTokens + toolSchemaTokens + outputHeadroomFloor + freshTailReserve + safetyMargin`
— each term single-sourced from its turn-time preflight home (the scaffold bootstrap budget,
the tool-schema overhead estimate, the output headroom at the post-downshift minimum thinking
level, the per-class preamble reserve, and the token-budget safety margin) — and WARNs when the
effective window cannot fit even that floor:
`"Boot viable-floor check: effective window below minViable — agent will degrade on real turns (WARN-only, boot continues)"`.
The hint spells the full equation with every term's value, e.g.
`minViable = bootstrapTotalTokens(1429) + toolSchemaTokens(9714) + outputHeadroomFloor(1792) + freshTailReserve(2000) + safetyMargin(2048) = 16983 exceeds effectiveWindow 8192 [source: served]`,
followed by the knob for the binding window source — served: the two Ollama knobs above;
capability: pin a higher class (or remove the pin) via
`providers.entries.<id>.capabilities.capabilityClass` (the `contextEngine.budget.*` caps cannot
raise this bind); configured: `providers.entries.<id>.models[].contextWindow`. When tool schemas
dominate the
floor, the hint adds the active-tool-ceiling lever: pin `capabilityClass` (the `small` class
defers to a 24-tool active ceiling via `discover_tools`) or disable unused MCP servers /
builtin tool groups. WARN-only: Comis **never** refuses to boot below the floor (the adapt-down
posture) — and because the boot floor and the turn-time preflight share one source module **and
one tool corpus** (the boot `toolSchemaTokens` term measures the same converted tool
definitions — lean descriptions plus guidelines — the turn actually ships, not the raw factory
descriptions), the same numbers re-appear in any later turn-time `context_exhausted` for that
agent.

The boot viable-floor WARN is engine-AGNOSTIC — it fires for `"dag"` and `"pipeline"` agents
alike (the minViable arithmetic holds regardless of engine); the per-turn preflight surfaces
are dag-only — see the engine-scope note in the
[Context Engine section](#context-engine-agents-contextengine). Deployment-wide, under-served
providers surface as the `config_posture:served_below_configured` finding in
[`comis system-health`](/reference/cli#comis-system-health) and the `obs.system.health` RPC (see the
[JSON-RPC reference](/reference/json-rpc)).

## UX Features

<AccordionGroup>
  <Accordion title="lifecycleReactions">
    Processing phase emoji reactions. When enabled, the agent reacts to messages with emoji reflecting current phase (thinking, tool use, generating, done, error).

    | Key         | Type      | Default     | Description                                |
    | ----------- | --------- | ----------- | ------------------------------------------ |
    | `enabled`   | `boolean` | `false`     | Enable lifecycle reactions globally        |
    | `emojiTier` | `enum`    | `"unicode"` | Emoji set: `unicode`, `platform`, `custom` |

    **Timing (lifecycleReactions.timing)**

    | Key           | Type     | Default | Description                                   |
    | ------------- | -------- | ------- | --------------------------------------------- |
    | `debounceMs`  | `number` | `700`   | Debounce before committing a phase transition |
    | `holdDoneMs`  | `number` | `3000`  | How long to hold done emoji                   |
    | `holdErrorMs` | `number` | `5000`  | How long to hold error emoji                  |
    | `stallSoftMs` | `number` | `15000` | Soft stall warning threshold in ms            |
    | `stallHardMs` | `number` | `30000` | Hard stall warning threshold in ms            |

    **Per-Channel (lifecycleReactions.perChannel.{channelType})**

    | Key         | Type      | Default   | Description            |
    | ----------- | --------- | --------- | ---------------------- |
    | `enabled`   | `boolean` | *(unset)* | Override enabled state |
    | `emojiTier` | `enum`    | *(unset)* | Override emoji tier    |
  </Accordion>

  <Accordion title="responsePrefix">
    Response prefix/suffix template injected into agent replies.

    | Key        | Type     | Default     | Description                                                                                    |
    | ---------- | -------- | ----------- | ---------------------------------------------------------------------------------------------- |
    | `template` | `string` | `""`        | Template string (empty = disabled). Supports variables like `{agent.emoji}`, `{model\|short}`. |
    | `position` | `enum`   | `"prepend"` | Insert position: `prepend` or `append`                                                         |
  </Accordion>

  <Accordion title="deliveryTiming">
    Inter-block delivery pacing to simulate natural typing rhythm.

    | Key                 | Type     | Default     | Description                                  |
    | ------------------- | -------- | ----------- | -------------------------------------------- |
    | `mode`              | `enum`   | `"natural"` | Mode: `off`, `natural`, `custom`, `adaptive` |
    | `minMs`             | `number` | `800`       | Minimum delay in ms between blocks           |
    | `maxMs`             | `number` | `2500`      | Maximum delay in ms between blocks           |
    | `jitterMs`          | `number` | `200`       | Random jitter in ms                          |
    | `firstBlockDelayMs` | `number` | `0`         | Extra delay before first block               |
  </Accordion>

  <Accordion title="coalescer">
    Block coalescer that accumulates small streaming blocks before delivery.

    | Key               | Type      | Default        | Description                                     |
    | ----------------- | --------- | -------------- | ----------------------------------------------- |
    | `minChars`        | `number`  | `0`            | Blocks below this are always coalesced          |
    | `maxChars`        | `number`  | `500`          | Flush threshold                                 |
    | `idleMs`          | `number`  | `1500`         | Idle timeout in ms before flushing              |
    | `codeBlockPolicy` | `enum`    | `"standalone"` | Code block handling: `standalone` or `coalesce` |
    | `adaptiveIdle`    | `boolean` | `false`        | Adapt timeout to accumulated block length       |
  </Accordion>

  <Accordion title="senderTrustDisplay">
    Controls how sender identity is surfaced to the LLM in the message envelope.

    | Key             | Type                     | Default  | Description                                                              |
    | --------------- | ------------------------ | -------- | ------------------------------------------------------------------------ |
    | `enabled`       | `boolean`                | `false`  | Include sender identity in envelope                                      |
    | `displayMode`   | `enum`                   | `"hash"` | Mode: `raw` (platform ID), `hash` (HMAC prefix), `alias` (operator name) |
    | `hashPrefix`    | `number`                 | `8`      | Hex characters from HMAC digest (4-16)                                   |
    | `hashSecretRef` | `string`                 | `""`     | SecretManager key for HMAC secret                                        |
    | `aliases`       | `Record<string, string>` | `{}`     | Sender ID to alias mapping                                               |
  </Accordion>

  <Accordion title="documentation">
    Documentation links injected into the system prompt so the agent can reference them.

    | Key                    | Type                  | Default | Description                           |
    | ---------------------- | --------------------- | ------- | ------------------------------------- |
    | `enabled`              | `boolean`             | `false` | Enable documentation link injection   |
    | `localDocsPath`        | `string`              | `""`    | Filesystem path to local docs         |
    | `publicDocsUrl`        | `string`              | `""`    | Public documentation URL              |
    | `sourceUrl`            | `string`              | `""`    | Source code repository URL            |
    | `communityUrl`         | `string`              | `""`    | Community or support URL              |
    | `skillsMarketplaceUrl` | `string`              | `""`    | Skills marketplace URL                |
    | `mcpRegistryUrl`       | `string`              | `""`    | MCP server registry URL               |
    | `customLinks`          | `DocumentationLink[]` | `[]`    | Additional custom links (label + url) |
  </Accordion>

  <Accordion title="telegramFileRefGuard">
    Detects hallucinated file paths in responses destined for Telegram (where file:// links are meaningless).

    | Key                    | Type       | Default | Description                          |
    | ---------------------- | ---------- | ------- | ------------------------------------ |
    | `enabled`              | `boolean`  | `true`  | Enable file reference guard          |
    | `additionalExtensions` | `string[]` | `[]`    | Extra file extensions to detect      |
    | `excludedExtensions`   | `string[]` | `[]`    | Extensions to exclude from detection |
  </Accordion>

  <Accordion title="deliveryQueue">
    Crash-safe outbound delivery queue. Messages are persisted to SQLite before delivery attempts, surviving daemon restarts.

    | Key                  | Type      | Default   | Description                                                         |
    | -------------------- | --------- | --------- | ------------------------------------------------------------------- |
    | `enabled`            | `boolean` | `true`    | Enable the delivery queue. When false, messages bypass persistence. |
    | `maxQueueDepth`      | `number`  | `10000`   | Maximum entries allowed in the queue. Enqueue rejects when full.    |
    | `defaultMaxAttempts` | `number`  | `5`       | Maximum delivery attempts before marking as failed.                 |
    | `defaultExpireMs`    | `number`  | `3600000` | Time-to-live in ms before an entry expires (1 hour).                |
    | `drainOnStartup`     | `boolean` | `true`    | Drain pending entries on daemon startup (crash recovery).           |
    | `drainBudgetMs`      | `number`  | `60000`   | Maximum time in ms for startup drain before continuing.             |
    | `pruneIntervalMs`    | `number`  | `300000`  | Interval in ms between prune sweeps for expired entries.            |

    ```yaml theme={}
    deliveryQueue:
      enabled: true
      maxQueueDepth: 10000
      defaultMaxAttempts: 5
      defaultExpireMs: 3600000
      drainOnStartup: true
      drainBudgetMs: 60000
      pruneIntervalMs: 300000
    ```
  </Accordion>

  <Accordion title="deliveryMirror">
    Session delivery mirroring for deduplication. Records delivered messages so they can be injected into agent context, preventing repeated deliveries.

    | Key                      | Type      | Default    | Description                                                      |
    | ------------------------ | --------- | ---------- | ---------------------------------------------------------------- |
    | `enabled`                | `boolean` | `true`     | Enable the delivery mirror. When false, no entries are recorded. |
    | `retentionMs`            | `number`  | `86400000` | Maximum age in ms before mirror entries are pruned (24 hours).   |
    | `pruneIntervalMs`        | `number`  | `300000`   | Interval in ms between prune sweeps (5 minutes).                 |
    | `maxEntriesPerInjection` | `number`  | `10`       | Maximum mirror entries injected per prompt turn.                 |
    | `maxCharsPerInjection`   | `number`  | `4000`     | Maximum total characters of mirror text injected per turn.       |

    ```yaml theme={}
    deliveryMirror:
      enabled: true
      retentionMs: 86400000
      pruneIntervalMs: 300000
      maxEntriesPerInjection: 10
      maxCharsPerInjection: 4000
    ```
  </Accordion>

  <Accordion title="observability">
    Observability persistence layer. Stores channel health snapshots, execution metrics, and system telemetry in SQLite for historical analysis and dashboards.

    | Key                              | Type                                | Default              | Description                                                                                                                                                                                                                                                                                 |
    | -------------------------------- | ----------------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `persistence.enabled`            | `boolean`                           | `true`               | Enable observability persistence.                                                                                                                                                                                                                                                           |
    | `persistence.retentionDays`      | `number`                            | `30`                 | Days to retain data before pruning (1-365).                                                                                                                                                                                                                                                 |
    | `persistence.snapshotIntervalMs` | `number`                            | `300000`             | Interval in ms between channel health snapshots (min 60000).                                                                                                                                                                                                                                |
    | `persistence.cacheBreaks`        | `boolean`                           | `true`               | Persist detected prompt-cache breaks to `obs_diagnostics` (`category:'cache_break'`) + a `cache.break` trajectory record (rate-by-reason + est-\$).                                                                                                                                         |
    | `audit.persist`                  | `boolean`                           | `true`               | Persist security-decision audit events (secret access, injection detection, command blocks, …) to the durable `obs_audit_events` table + `security-audit.jsonl`. Queryable via `comis security audit-log` / `obs.audit.query`.                                                              |
    | `audit.sink`                     | `"sqlite"` \| `"jsonl"` \| `"both"` | `"both"`             | Which sink(s) receive audit records. Rotation of the JSONL rides the shared `logRotation` policy (no per-sink rotation knob).                                                                                                                                                               |
    | `spend.perAgentUsd`              | `number` \| `null`                  | `null`               | Per-agent cumulative USD ceiling. `null` = off (opt-in).                                                                                                                                                                                                                                    |
    | `spend.perTenantUsd`             | `number` \| `null`                  | `null`               | Per-tenant cumulative USD ceiling — the cross-tenant isolation dimension; a tenant's breach aborts only that tenant. `null` = off.                                                                                                                                                          |
    | `spend.daemonGlobalUsd`          | `number` \| `null`                  | `null`               | Daemon-wide cumulative USD ceiling across all agents and tenants. `null` = off.                                                                                                                                                                                                             |
    | `spend.action`                   | `"warn"` \| `"abort"`               | `"warn"`             | Behaviour on a ceiling breach. `warn` (the shipped default) observes only; `abort` halts subsequent turns.                                                                                                                                                                                  |
    | `spend.warnAtFraction`           | `number`                            | `0.8`                | Emit the early `observability:spend_warning` once spend crosses this fraction of a ceiling, before it trips (0–1).                                                                                                                                                                          |
    | `spend.perTurnMax`               | `number`                            | `0.5`                | Conservative per-turn USD reservation held at admission (there is no pre-flight cost estimate), reconciled to the actual billed amount after the turn. Bounds a single in-flight turn's overshoot past a ceiling.                                                                           |
    | `spend.pricingFallback`          | `"snapshot"`                        | `"snapshot"`         | Pricing source for enforcement. Only `snapshot` (the dated model-catalog rate) exists today; a transient pricing-fetch failure falls back to it rather than aborting. No live price fetch is in scope (forward-extensibility placeholder).                                                  |
    | `spend.onUnknownPricing`         | `"warn"` \| `"abort"`               | `"warn"`             | Behaviour when a remote model burns tokens with no catalog price. `warn` fails loud (the default); `abort` (with `action: abort`) trips `spend_unpriceable`.                                                                                                                                |
    | `otel.enabled`                   | `boolean`                           | `false`              | Enable the OTLP push surface (traces/metrics/logs) via the opt-in `@comis/observability-otel` extension. Ships OFF — the daemon loads the extension only when this or `prometheus.enabled` is true (a config-gated dynamic import; core stays OpenTelemetry-free otherwise).                |
    | `otel.endpoint`                  | `string`                            | `""`                 | OTLP collector endpoint URL. `""` uses the standard OTel env/SDK default (`OTEL_EXPORTER_OTLP_ENDPOINT`).                                                                                                                                                                                   |
    | `otel.protocol`                  | `"http/protobuf"` \| `"grpc"`       | `"http/protobuf"`    | OTLP transport. Only `http/protobuf` ships today; `grpc` validates but falls back to `http/protobuf` with a WARN (a documented later addition).                                                                                                                                             |
    | `otel.traces`                    | `boolean`                           | `true`               | Emit OTLP trace spans (per-turn/tool/graph), correlated by the Comis `traceId` carried as the `comis.trace_id` span attribute. Active only when `otel.enabled`.                                                                                                                             |
    | `otel.metrics`                   | `boolean`                           | `true`               | Emit OTLP metrics off the single metric catalog. Active only when `otel.enabled`.                                                                                                                                                                                                           |
    | `otel.logs`                      | `boolean`                           | `true`               | Emit bus events as OTLP log records (re-redacted). Active only when `otel.enabled`.                                                                                                                                                                                                         |
    | `otel.genaiSemconv`              | `boolean`                           | `false`              | Opt into the latest (pre-stable `Development`) GenAI semantic convention (the `OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental` gate). Default keeps the pre-1.36 shape. The exporter applies redaction independently.                                                             |
    | `otel.captureContent`            | `boolean`                           | `false`              | Capture the 3 GenAI content span attributes (input/output messages, system instructions). Spec opt-in → default OFF (omitted). Even when on, `sanitizeForPersistence` re-redacts at the exporter boundary; enabling it still increases the sensitive data sent to your collector.           |
    | `prometheus.enabled`             | `boolean`                           | `false`              | Enable the standalone Prometheus `/metrics` pull surface (independent of `otel.enabled` — serves valid exposition with no OTLP collector). Realized by the OTel `PrometheusExporter`, which opens its own loopback HTTP listener (not the gateway).                                         |
    | `prometheus.host`                | `string`                            | `"127.0.0.1"`        | Bind host for the `/metrics` listener. Loopback by default — never `0.0.0.0` implicitly.                                                                                                                                                                                                    |
    | `prometheus.port`                | `number`                            | `9464`               | Bind port for the `/metrics` listener (1–65535).                                                                                                                                                                                                                                            |
    | `prometheus.path`                | `string`                            | `"/metrics"`         | The scrape path the exporter serves.                                                                                                                                                                                                                                                        |
    | `prometheus.auth`                | `"trusted-operator"`                | `"trusted-operator"` | Access posture. The OTel exporter has no built-in auth; the posture is realized as the loopback bind + the operator's reverse proxy/firewall (NOT gateway-token-gated).                                                                                                                     |
    | `prometheus.exemplars`           | `boolean`                           | `true`               | Whether exemplars are desired. NOTE: the pinned exporter does not render OpenMetrics exemplars on the pull surface — the `trace_id` rides as a span attribute instead. Forward-looking knob.                                                                                                |
    | `prometheus.cardinalityCap`      | `number`                            | `10000`              | Max active series before a WARN-with-hint fires (the label-explosion DoS guard); the `comis_prometheus_series` self-metric exposes the count.                                                                                                                                               |
    | `costGranularity.perTool`        | `boolean`                           | `true`               | Tag each `observability:token_usage` row with the distinct tools that fired the turn (persisted as `tool_tag` on `obs_token_usage`). The per-tool \$ attribution is **best-effort** — an even split across the turn's tools that conserves the turn total, never exact per-tool accounting. |
    | `costGranularity.subagentRollup` | `boolean`                           | `true`               | Surface the per-subagent corrected-\$ rollup (a node + its subtree) on the cost views.                                                                                                                                                                                                      |
    | `export.csv`                     | `boolean`                           | `true`               | Offer CSV (alongside JSON) on the cost-export surface (the `comis cost export` CLI + the SPA billing view).                                                                                                                                                                                 |
    | `export.quarterHourBuckets`      | `boolean`                           | `true`               | Expose 15-minute (quarter-hour) cost buckets in addition to the hourly aggregate.                                                                                                                                                                                                           |

    ```yaml theme={}
    observability:
      persistence:
        enabled: true
        retentionDays: 30
        snapshotIntervalMs: 300000
        cacheBreaks: true
      audit:
        persist: true
        sink: both
      spend:                  # cost kill-switch — ships OFF (null ceilings, action 'warn')
        perAgentUsd: null     # set a number to cap per-agent cumulative USD
        perTenantUsd: null    # per-tenant cap (cross-tenant isolation)
        daemonGlobalUsd: null # daemon-wide cap across all agents/tenants
        action: warn          # 'warn' observes; 'abort' halts subsequent turns
        warnAtFraction: 0.8   # early warning at 80% of a ceiling
        perTurnMax: 0.5       # per-turn admission reservation
        pricingFallback: snapshot
        onUnknownPricing: warn # 'abort' to refuse unpriced remote spend
      otel:                   # OTLP push — opt-in extension, ships OFF + content-free
        enabled: false        # set true to load @comis/observability-otel and export OTLP
        endpoint: ""          # OTLP collector URL ("" uses the OTel env/SDK default)
        protocol: http/protobuf # 'grpc' falls back to http/protobuf with a WARN today
        traces: true
        metrics: true
        logs: true
        genaiSemconv: false   # latest GenAI semconv opt-in (pre-stable, will churn)
        captureContent: false # capture the 3 GenAI content attrs (re-redacted even if on)
      prometheus:             # /metrics pull — opt-in, standalone, loopback-bound, ships OFF
        enabled: false        # set true to serve /metrics (independent of otel.enabled)
        host: 127.0.0.1       # loopback by default — never 0.0.0.0 implicitly
        port: 9464
        path: /metrics
        auth: trusted-operator # loopback + your reverse proxy/firewall (no built-in auth)
        exemplars: true
        cardinalityCap: 10000  # WARN above this many active series (label-explosion guard)
      costGranularity:        # per-tool + per-subagent cost attribution — ship ON
        perTool: true         # tag token_usage with the turn's tools (best-effort, even split)
        subagentRollup: true  # surface the per-subagent corrected-$ rollup
      export:                 # cost-export surface — ships ON
        csv: true             # offer CSV (alongside JSON) on `comis cost export` + the SPA
        quarterHourBuckets: true # expose 15-minute buckets in addition to hourly
    ```

    <Info>
      The spend kill-switch is configurable in `config.yaml` only — there is **no** environment-variable form. See [Observability → Spend Governance](/operations/observability#16-spend-governance-cost-kill-switch) for the runtime behaviour (cooperative abort, three-state pricing, the events, and the `pricing_gap` system finding).
    </Info>
  </Accordion>
</AccordionGroup>

## Diagnostics

<AccordionGroup>
  <Accordion title="diagnostics.recallTrace">
    Per-recall ranking trace written as bounded JSONL for "why did recall pick X?" debugging. Opt-in (default off) -- unlike its `cacheTrace` sibling (which defaults `true`), the recall trace is only captured during a focused debug session.

    | Key            | Type      | Default      | Description                                                                                              |
    | -------------- | --------- | ------------ | -------------------------------------------------------------------------------------------------------- |
    | `enabled`      | `boolean` | `false`      | Enable the recall-trace writer (opt-in). Also honors the `COMIS_DISABLE_RECALL_TRACE` env hard-off.      |
    | `filePath`     | `string`  | *(optional)* | Full path override. When unset, resolves to `~/.comis/logs/recall-trace.jsonl` (tilde-prefix supported). |
    | `maxFileBytes` | `number`  | `52428800`   | Per-file byte cap (50 MB; positive).                                                                     |

    <Warning>
      The recall trace has **no** raw-content opt-in -- there is intentionally no `includeMessages` / `includeSystem` / `includePrompt` slot (unlike `cacheTrace`). Every payload is full-sanitized (bound, then sanitize, then redact) before it touches disk. There is no way to disable that sanitization.
    </Warning>

    ```yaml theme={}
    diagnostics:
      recallTrace:
        enabled: true                   # opt-in (default false) -- full-sanitized, bounded JSONL
    ```
  </Accordion>
</AccordionGroup>

## Credential Broker Bindings (executor.broker)

<Info>
  The `executor.broker` block is wired into the daemon (`AppConfigSchema` → `setupBroker`): adding it to `config.yaml` starts the broker at boot — a TCP listener plus a `0600` unix socket at `~/.comis/broker.sock`.
</Info>

```yaml title="~/.comis/config.yaml" theme={}
# executor.broker.bindings — provider-agnostic; presets are optional sugar
# The broker starts at daemon boot whenever an executor.broker block is present.
executor:
  broker:
    bindings:
      # Option A: built-in preset — Anthropic (header injection)
      - preset: anthropic
        secretRef: ANTHROPIC_EXECUTOR_KEY

      # Option B: built-in preset — Finnhub (query param injection)
      - preset: finnhub
        secretRef: FINNHUB_API_KEY

      # Option C: custom binding — any host, no preset required
      # A binding with no 'inject' defaults to Authorization: Bearer
      - hostRules:
          - pattern: { kind: exact, host: my-internal-api.example.com }
            inject: []    # defaults to Authorization: Bearer
        secretRef: INTERNAL_API_TOKEN

      # Option D: custom binding with explicit header injection
      - hostRules:
          - pattern: { kind: suffix, suffix: .amazonaws.com }
            inject:
              - kind: setHeader
                name: x-amz-security-token
                format: raw
        secretRef: AWS_SESSION_TOKEN
```

**Credential Broker Bindings (executor.broker.bindings\[\*])**

| Key              | Type                     | Required                | Description                                                                  |
| ---------------- | ------------------------ | ----------------------- | ---------------------------------------------------------------------------- |
| `preset`         | `string`                 | one of preset/hostRules | Built-in preset ID (`anthropic`, `finnhub`) — expands to preset's host rules |
| `hostRules`      | `HostRule[]`             | one of preset/hostRules | Custom host rules (provider-agnostic)                                        |
| `secretRef`      | `string`                 | yes                     | SecretManager key resolved per-request; never cached to disk                 |
| `credentialRefs` | `Record<string, string>` | no                      | Extra refs for multi-field finalizers                                        |

**HostRule fields (hostRules\[\*]):**

| Key          | Type                                                                    | Required | Description                                                              |
| ------------ | ----------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------ |
| `pattern`    | `{ kind: "exact", host: string } \| { kind: "suffix", suffix: string }` | yes      | Host match pattern                                                       |
| `inject`     | `InjectionRule[]`                                                       | no       | Injection rules; empty array defaults to `Authorization: Bearer`         |
| `pathPrefix` | `string`                                                                | no       | Restrict to requests with this path prefix                               |
| `pathPolicy` | `string`                                                                | no       | Glob pattern for allowed paths                                           |
| `finalizer`  | `object`                                                                | no       | Post-injection body transform (e.g., `awsSigV4` — no-op in this release) |

For full configuration details and examples, see [Credential Broker →](/security/credential-broker).

## Related

<CardGroup cols={2}>
  <Card title="Environment Variables" icon="key" href="/reference/environment-variables">
    Environment variable reference
  </Card>

  <Card title="Hot Reload" icon="rotate" href="/reference/hot-reload">
    Configuration hot reload behavior
  </Card>

  <Card title="Secret Manager" icon="lock" href="/reference/secret-manager">
    Encrypted secret storage and \${`VAR`} substitution
  </Card>

  <Card title="Configuration Guide" icon="sliders" href="/installation/configuration">
    Step-by-step configuration walkthrough
  </Card>
</CardGroup>
