> ## Documentation Index
> Fetch the complete documentation index at: https://comis-feature-skill-archive-import.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)*   | Reply language for deterministic degraded replies (context-exhausted / output-starved notices). Accepts a BCP-47 tag (`"he"`) or an English display name (`"Hebrew"`). When omitted, Comis auto-detects the reply language from the USER.md preferred language, then the inbound message script (Hebrew, Arabic, and Russian/Cyrillic only). Does not affect the live agent reply, which always follows the user's language.                                                                                                                                                                                          |
    | `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 + reconciles any crashed-mid-send message exactly-once. 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.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
    | `message`                       | `object`   | `{ channels: ["origin"], maxPerHour: 20 }` | Outward-message posture. `channels` lists allowed channels (`"origin"` = the agent's own); `maxPerHour` is the rolling-hour send quota. A new channel is an explicit, always-escalating grant.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
    | `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   | When it fires                                                                                                                                          | Recommended model                                                                                                                   |
    | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- |
    | `cron`           | Scheduled cron task execution                                                                                                                          | —                                                                                                                                   |
    | `heartbeat`      | Periodic heartbeat check                                                                                                                               | —                                                                                                                                   |
    | `subagent`       | Sub-agent task delegation                                                                                                                              | —                                                                                                                                   |
    | `compaction`     | Context compaction summarization                                                                                                                       | Capable model or `contextEngine.compaction.strongerSummarizerModel`                                                                 |
    | `taskExtraction` | Task extraction from conversation                                                                                                                      | —                                                                                                                                   |
    | `condensation`   | Memory condensation                                                                                                                                    | Capable model                                                                                                                       |
    | `verification`   | 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`       | Pre-execution planner.                                                                                                                                 | Capable model for checklist generation. Omit to use the primary model.                                                              |

    ```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) 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.

    #### 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 lossless LCD engine). The **pipeline** value (the simpler sequential layered engine) is the first-class **opt-in** — set `version: "pipeline"` to use it. DAG does lossless verbatim assembly (full faithful history + a verbatim fresh tail of the most recent steps + transcript repair), zoomably compresses the oldest history under a token budget, and exposes the in-session `ctx_*` expansion tools. 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** (lossless verbatim assembly + threshold-triggered leaf summarization + multi-tier condensation + budget eviction); only the on-demand `ctx_*` recall keys (`maxExpandTokens`, `maxRecallsPerDay`, `recallTimeoutMs`) are **reserved** for the planned recall tools and not yet consumed:

    | 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 **Verified Learning** outcome signal: a durable record of each finished trajectory's net task-outcome (`success` / `failure` / `corrected` / `unknown`), so learning can gate on a real task-outcome instead of a text-overlap proxy. It is the signal the [reflection engine](#learning-agents-learning) and the wrongness-based forgetting both gate on. **Default-OFF** and **gated by the master cost switch** (`memory.enabled` -- when that is `false`, `learningOutcome` is force-disabled regardless of the per-agent flag). The deterministic tool/pipeline signal is the primary source; the cost-gated LLM judge (`judge.enabled`, default **on** / opt-out) is the **fallback** that resolves a CONVERSATIONAL turn — one with no deterministic tool/pipeline signal, which would otherwise fuse to `unknown` and derive no learning. The judge runs ONE cheap-model pass ONLY when the deterministic resolve is `unknown` (so resolved turns skip it) and is force-disabled by the master cost switch. The capture is daemon-side and content-free -- ids / closed enums / counts only, never a message body. When enabled, observations are written to the `outcome_events` ledger (see [data-directory.mdx](/operations/data-directory)) and surfaced on the trajectory for `comis explain` (the `learning` obs signal) and via `comis memory learning`.

    | Key                    | Type           | Default                                          | Description                                                                                                                                                                                                                                                                                                                                                                                                               |
    | ---------------------- | -------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `enabled`              | `boolean`      | `false`                                          | Enable outcome capture for this agent (opt-in; additionally 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`      | `false`                                          | Enable the optional cost-gated correction detector: a follow-up "no, do X instead" turn is classified (via the `outcomeJudge` tier) and emits a `corrected` soft-failure of the prior trajectory. Default off — an LLM over untrusted follow-up text is a net-new surface; also 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 **one outcome-gated reflection engine**: a single per-agent learning layer that maintains named **Mental Model** docs (`kind: skill | profile | topic`) at `trust=learned` via byte-stable delta-ops, plus the wrongness-based soft eviction. There are no separate per-loop blocks — skill-synthesis, profile, and topic learning all run through this one layer. **One flag** — `enabled` — gates the WHOLE layer, **default-ON (opt-out)** and **gated by the master cost switch** (`memory.enabled` -- when that is `false`, the whole learning layer is force-disabled regardless of the per-agent flag). It gates on the [`learningOutcome`](#learning-outcome-agents-learningoutcome) signal (a real task outcome), not a text-overlap proxy. Recall **ranking** itself is always the fixed `rag.scoring` fusion (deterministic RRF + the cross-encoder reranker) — the learning layer learns docs and forgets wrong memories, but tunes **no** recall weight.

    When the reflection cron admits a doc that **promotes to `active`** and is **read-only**, it is **surfaced** to the agent: it is listed in `<available_skills>` (appended after platform skills so the prompt-cache prefix stays stable — see [prompt cache](/agents/cache#learned-skill-listing-stability)) and materialized to a per-agent `.learned-skills/<name>/SKILL.md` listed by its absolute path (the same shape platform skills use, so the `read` tool opens it directly and skill-use attribution matches). Promotion is gated by `reflect.promoteAtProofCount` from attributed successful reuse; corroborated, sustained failing reuse demotes (`active` → `stale` → `archived`) and drops it from the surface. A learned doc is **advisory** (no executable column) and can **never** exceed `learned` trust (a DB `CHECK` makes `system` structurally impossible — see [data-directory.mdx](/operations/data-directory)). **Procedure** docs distilled from verifiably-successful `orchestrate` runs (a `kind: skill` doc that additionally records the run's deterministic tool footprint) surface through the **same** listing, but that subset is capped per agent by `reflect.maxProcedureDocsSurfaced` (default `10`) so a burst cannot bloat every prompt — user-intent skill docs and topic docs keep a separate, uncapped path. The agent **re-authors** the script from the advisory guidance under its own permissioned tools; the surface adds no execution path.

    | 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 verified-success count at which an admitted `candidate` doc promotes to `active`, reinforced on each **attributed successful reuse** (a single success below the bar never mints an `active` doc)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
    | `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"` | How a topic must **corroborate** before it can seed a learned doc. `single_owner` (**default**, the primary Comis use case): **repetition-as-corroboration** — an **explicitly-trusted** owner (a sender named in `elevatedReply.senderTrustMap`, non-external tier) repeating the same success `≥minObservations` times corroborates it, so a single trusted DM (where distinct sessions is structurally unreachable) can learn. Safe by construction: a promiscuous-`defaultTrustLevel` or unknown sender can ride past the success filter yet never self-corroborate by repetition, and a box with ≥2 explicitly-trusted senders auto-falls-back to the stricter gate. `distinct_sessions`: the anti-domination gate — ≥2 distinct `(session, sender)` observations (one flooding actor stays cardinality 1); set it explicitly to require independent sessions even from a single trusted owner |
    | `reflect.corroboration.minObservations` | `number` (int ≥ 2)                      | `2`              | `single_owner` mode ONLY: the minimum distinct successful repetitions by the owner before a topic corroborates. `≥2` — a single success never corroborates. 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`              | Corroborated `failure_count` at/above which a NON-EXEMPT memory is soft-evicted regardless of its decayed strength (each `failure_count` increment is corroboration-gated: ≥2 independent failures, 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                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |

    > **Reflection is fail-closed and corroboration-bounded.** In the **default** `single_owner` mode reflection corroborates a topic on an **explicitly-trusted owner's repetition** (`≥minObservations` successes of the same task) and writes a **low** initial proof count regardless — so a single trusted DM (where distinct sessions is unreachable) can learn, while a stranger still cannot: repetition is counted **only** among senders the operator explicitly named, and a box with ≥2 explicitly-trusted senders auto-falls-back to the distinct-sessions gate. In `distinct_sessions` mode a topic instead needs **≥2 distinct `(session, sender)`** observations (N near-identical successes from one sender count once). External-trust sources are excluded by the engine's two-axis anti-poison SELECT in **every** mode. On a `small`/`nano` agent below the synthesis capability gate the reflection cron **abstains** (Defer ≠ Retry — a benign skip, never a failure or a retry). The whole loop is **observable** (counts/ids only) via [`comis explain`](/reference/cli#comis-explain) (the `learning` signal + the `learned_skill_failing` verdict, plus the `singleOwnerCorroborated` funnel count) and the [`comis memory skills`](/reference/cli#comis-memory) admission funnel.

    > **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 fleet`. 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)**

    | 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 |

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

    | Key                  | Type       | Default    | Description                                                                                                                              |
    | -------------------- | ---------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
    | `enabled`            | `enum`     | `"always"` | OS-level sandbox for exec commands: `"always"` (sandbox active, graceful fallback if binary unavailable) or `"never"` (sandbox disabled) |
    | `readOnlyAllowPaths` | `string[]` | `[]`       | Additional filesystem paths exposed read-only inside the sandbox (e.g., `/opt/data`)                                                     |

    **Staged Import (agents.\*.skills.import)**

    Bounded-unpack caps -- and the registry allowlist -- for the staged [skill-import pipeline](/skills/importing). The caps bound what an untrusted archive can expand to before any file is written; the defaults are deliberately conservative, raise them only if you knowingly import large skills. Every key is required-with-a-default and the block is closed -- a typo'd key rejects at config load. Shared-scope imports read the default agent's block.

    | Key                         | Type       | Default             | Description                                                                                                                                                                          |
    | --------------------------- | ---------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
    | `maxArchiveBytes`           | `number`   | `8388608` (8 MiB)   | Max compressed archive size accepted for fetch/decode                                                                                                                                |
    | `maxTotalUncompressedBytes` | `number`   | `67108864` (64 MiB) | Max total uncompressed bytes, stream-counted mid-inflate                                                                                                                             |
    | `maxFileCount`              | `number`   | `200`               | Max number of entries unpacked from one archive                                                                                                                                      |
    | `maxFileBytes`              | `number`   | `4194304` (4 MiB)   | Max size of any single unpacked file                                                                                                                                                 |
    | `maxPathDepth`              | `number`   | `10`                | Max path depth of any unpacked entry                                                                                                                                                 |
    | `registries`                | `string[]` | `[]`                | Allowlist of registry origins a `source: "wellknown"` import may resolve from, plus the literal `clawhub` token for `source: "clawhub"` (below). Default-empty = no registry imports |
    | `requireOfficialPublisher`  | `boolean`  | `true`              | Require a `--confirm` acknowledgement to install a **non-official-publisher** skill from ClawHub (`source: "clawhub"`); see below. Never applies to github/archive/wellknown         |

    **`registries` -- the registry allowlist.** Unlike the size caps above, `registries` is a **security allowlist**, not a bound: it names the registry origins a `source: "wellknown"` import may resolve from, and gates `source: "clawhub"` behind the literal `clawhub` token. Entries are normalized origins `https://<host>[:port]` (matched exactly against the requested registry's origin) plus the literal `clawhub` token. It is **default-empty**, so no registry or ClawHub imports are permitted until you add one -- archive and GitHub imports are unaffected either way. Changing it is a deliberate **config edit**, kept out-of-band from the import call: a non-allowlisted registry (or a `clawhub` import without the token) refuses **flatly** and `confirm` never overrides that refusal. See [Registry import](/skills/importing#registry-import-well-known-index) and [ClawHub import](/skills/importing#clawhub-import).

    **`requireOfficialPublisher` -- the ClawHub official-publisher gate.** When `true` (the default), a [`source: "clawhub"`](/skills/importing#clawhub-import) import of a skill whose publisher is **not** official records `officialPublisher: false` and requires `--confirm` to install -- so pulling an unofficial `@owner/slug` is a deliberate, acknowledged install rather than a silent one (it fires on a fresh install as much as on an update). It is **ClawHub-only**: it never affects `github`, `archive`, or `wellknown` imports, which carry no publisher signal. Like `registries`, it is a per-agent setting a **config edit** changes (shared-scope imports read the default agent's block); it is a policy knob, so a `--confirm` on a single import call acknowledges that one call -- it does not change the setting. Set it to `false` to install non-official ClawHub skills without the extra acknowledgement.

    <Note>
      There is intentionally **no** auto-connect knob here. An imported skill's bundled MCP servers persist disabled by construction -- you opt in per server. See [Importing Skills](/skills/importing#the-imported-trust-tier).
    </Note>

    #### 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                                                                                                                                                                                                                                                   |
    | `maxConcurrentRuns` | `number`  | `3`       | Maximum concurrent cron job runs                                                                                                                                                                                                                                             |
    | `defaultTimezone`   | `string`  | `""`      | Default timezone (empty = UTC)                                                                                                                                                                                                                                               |
    | `maxJobs`           | `number`  | `100`     | Maximum cron jobs (0 = unlimited)                                                                                                                                                                                                                                            |
    | `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`         | *(inherit)* | Override heartbeat enabled state                               |
    | `intervalMs`                | `number`          | *(inherit)* | Override heartbeat interval in ms                              |
    | `showOk`                    | `boolean`         | *(inherit)* | Override show OK status                                        |
    | `showAlerts`                | `boolean`         | *(inherit)* | Override show alerts                                           |
    | `target`                    | `HeartbeatTarget` | *(unset)*   | Delivery target channel (channelType, channelId, chatId, isDm) |
    | `prompt`                    | `string`          | *(unset)*   | Custom heartbeat prompt                                        |
    | `model`                     | `string`          | *(unset)*   | Model override for heartbeat LLM calls                         |
    | `session`                   | `string`          | *(unset)*   | Session key for heartbeat isolation                            |
    | `allowDm`                   | `boolean`         | *(inherit)* | Allow heartbeat in DM 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             |
    | `skipHeartbeatOnlyDelivery` | `boolean`         | *(unset)*   | Suppress delivery of HEARTBEAT\_OK-only responses              |
    | `alertThreshold`            | `number`          | *(inherit)* | Consecutive failure threshold for alerting                     |
    | `alertCooldownMs`           | `number`          | *(inherit)* | Alert cooldown period in ms                                    |
    | `staleMs`                   | `number`          | *(inherit)* | Stuck detection timeout in ms                                  |

    ```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 |

    **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 fleet` 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 fleet` 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. Rules are evaluated in order (first match wins).

    | Key                  | Type             | Default  | Description                                                                                                                          |
    | -------------------- | ---------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------ |
    | `enabled`            | `boolean`        | `true`   | Enable the approval workflow                                                                                                         |
    | `defaultMode`        | `enum`           | `"auto"` | Default mode for unmatched actions: `auto`, `require`, `deny`                                                                        |
    | `rules`              | `ApprovalRule[]` | `[]`     | Ordered approval rules                                                                                                               |
    | `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 identical tool calls to auto-approve within the TTL window, reducing approval fatigue during batch operations. The cache persists across 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` |

    <Info>See [Approvals](/security/approvals) for approval workflow configuration patterns.</Info>
  </Accordion>
</AccordionGroup>

## Daemon & Scheduler

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

    | Key                         | Type                       | Default | Description                                             |
    | --------------------------- | -------------------------- | ------- | ------------------------------------------------------- |
    | `watchdogIntervalMs`        | `number`                   | `30000` | Watchdog interval in ms (0 to disable)                  |
    | `shutdownTimeoutMs`         | `number`                   | `30000` | Graceful shutdown timeout in ms                         |
    | `metricsIntervalMs`         | `number`                   | `30000` | Process metrics collection interval in ms               |
    | `eventLoopDelayThresholdMs` | `number`                   | `500`   | Event loop delay threshold -- skip watchdog if exceeded |
    | `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                                                                                                                                                                                                                                                                                                                                                                                  |
    | `storeDir`          | `string`  | `"./data/scheduler"` | Directory for cron job state persistence                                                                                                                                                                                                                                                                                                                                                                    |
    | `maxConcurrentRuns` | `number`  | `3`                  | Max concurrent cron job runs                                                                                                                                                                                                                                                                                                                                                                                |
    | `defaultTimezone`   | `string`  | `""`                 | Default timezone (empty = UTC)                                                                                                                                                                                                                                                                                                                                                                              |
    | `maxJobs`           | `number`  | `100`                | Max cron jobs (0 = unlimited)                                                                                                                                                                                                                                                                                                                                                                               |
    | `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`  | `""`      | Timezone (empty = system local)            |
    | `criticalBypass` | `boolean` | `true`    | Allow critical items to bypass quiet hours |

    **Execution (scheduler.execution)**

    | Key           | Type     | Default                    | Description                        |
    | ------------- | -------- | -------------------------- | ---------------------------------- |
    | `lockDir`     | `string` | `"./data/scheduler/locks"` | Directory for execution lock files |
    | `staleMs`     | `number` | `600000`                   | Lock stale timeout in ms (10 min)  |
    | `updateMs`    | `number` | `30000`                    | Lock update interval in ms         |
    | `logDir`      | `string` | `"./data/scheduler/logs"`  | Directory for execution log files  |
    | `maxLogBytes` | `number` | `2000000`                  | Max log file size in bytes         |
    | `keepLines`   | `number` | `2000`                     | Max lines in ring-buffer log       |

    **Tasks (scheduler.tasks)**

    | Key                   | Type      | Default                    | Description                                                                                  |
    | --------------------- | --------- | -------------------------- | -------------------------------------------------------------------------------------------- |
    | `enabled`             | `boolean` | `true`                     | Enable task extraction from conversations (on by default — the agent proactively follows up) |
    | `confidenceThreshold` | `number`  | `0.8`                      | Minimum confidence threshold (0-1)                                                           |
    | `storeDir`            | `string`  | `"./data/scheduler/tasks"` | Directory for task state persistence                                                         |

    <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`                   | *(required)* | Transport: `stdio` or `sse`             |
    | `command`   | `string`                 | *(unset)*    | Command for stdio transport             |
    | `args`      | `string[]`               | *(unset)*    | Arguments for stdio command             |
    | `url`       | `string (URL)`           | *(unset)*    | URL for 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` (or `google-vertex`) main (with `GOOGLE_API_KEY`) generates via Gemini (`generateContent`), default `gemini-2.5-flash-image`. Pin explicitly with `provider: google`. (The key is `GOOGLE_API_KEY` — the same key the completion path and vision use, **not** `GEMINI_API_KEY`.)
    * **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 main provider's image request identifies itself to the ChatGPT backend as the official Codex client (`originator: codex_cli_rs`, a Codex-shaped `User-Agent`, and a `ChatGPT-Account-ID` decoded from your token) — a **first-party-client compatibility shim** using **your own** OAuth credentials against the **same endpoint** the official Codex CLI uses (so Cloudflare does not 403 non-residential IPs). It is authorized reuse of your own credentials, **not detection evasion**.
    </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)                                             |
    | `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">
    Plugin system configuration.

    | Key       | Type                          | Default | Description                                 |
    | --------- | ----------------------------- | ------- | ------------------------------------------- |
    | `enabled` | `boolean`                     | `true`  | Global plugin system toggle                 |
    | `plugins` | `Record<string, PluginEntry>` | `{}`    | Per-plugin configuration keyed by plugin ID |

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

    | Key        | Type                      | Default | Description                                              |
    | ---------- | ------------------------- | ------- | -------------------------------------------------------- |
    | `enabled`  | `boolean`                 | `true`  | Whether this plugin is active                            |
    | `priority` | `number`                  | `0`     | Hook execution priority (-100 to 100, higher runs first) |
    | `config`   | `Record<string, unknown>` | `{}`    | Plugin-specific configuration (opaque)                   |

    <Info>See [Plugins](/developer-guide/plugins) for the plugin system and hook reference.</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.fleet.health`](/reference/json-rpc#obs-fleet-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) 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). Fleet-wide, under-served
providers surface as the `config_posture:served_below_configured` finding in
[`comis fleet`](/reference/cli#comis-fleet) and the `obs.fleet.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`              | `""`    | A skills-marketplace URL injected into the system prompt as a reference hint only. It is **not** an import allowlist or control -- it does not gate, restrict, or authorize what [`skills.import`](/skills/importing) will fetch. Import safety comes from the pipeline (SSRF-guarded fetch, bounded unpack, fail-closed scan, imported tier), not this link. |
    | `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. Content never leaks regardless — re-redaction is independent.                                             |
    | `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.                                                                                  |
    | `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` fleet 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>
