Skip to main content
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 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

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

Minimal Example

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

Core Settings

Scalar fields at the root of the configuration.

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

Multi-account example — two agents, two ChatGPT accounts:
~/.comis/config.yaml

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 page for the full model and the legible-degrade behavior.
~/.comis/config.yaml -- standard with a tighter budget
~/.comis/config.yaml -- a long-running coordinator lead

Budget (agents.*.budgets)

Circuit Breaker (agents.*.circuitBreaker)

Model Routes (agents.*.modelRoutes)

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

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.

Model Failover (agents.*.modelFailover)

SDK Retry (agents.*.sdkRetry)

Prompt Timeout (agents.*.promptTimeout)

See Resilience for how prompt timeouts fit into the full resilience stack.

RAG (agents.*.rag)

Security: A weaker ModelProfile cannot lower this below the operator-set value. FROZEN_TRUST_PATHS and MemoryWriteValidator remain enforced for all capabilityClasses 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.Scoring boosts (agents.*.rag.scoring) — all number, range 0-1Multiplicative boosts applied to the reranked-or-fused score before the trust filter.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.Enabling the opt-in recall featuresReranking 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:

Bootstrap (agents.*.bootstrap)

Workspace (agents.*.workspace)

Concurrency (agents.*.concurrency)

Broadcast Groups (agents.*.broadcastGroups)

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

Elevated Reply (agents.*.elevatedReply)

Tracing (agents.*.tracing)

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

Context Guard (agents.*.contextGuard)

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 for a user-friendly explanation.Core fieldsShared fields (both modes)Pipeline mode fields (version: "pipeline")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:DAG robustness / spend / deferred-compaction fields (version: "dag") — all active in the current release:
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.
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.
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) 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.

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.

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.

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.
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.
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.
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 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) and surfaced on the trajectory for comis explain (the learning obs signal) and via comis memory learning.
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.

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 flagenabled — 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 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) 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 (activestalearchived) 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). 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.
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 (the learning signal + the learned_skill_failing verdict, plus the singleOwnerCorroborated funnel count) and the comis memory skills 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.
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)

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

Skills (agents.*.skills)

Built-in Tools (agents.*.skills.builtinTools)Tool Policy (agents.*.skills.toolPolicy)Prompt Skills (agents.*.skills.promptSkills)Runtime Eligibility (agents.*.skills.runtimeEligibility)Content Scanning (agents.*.skills.contentScanning)Exec Sandbox (agents.*.skills.execSandbox)

Secrets (agents.*.secrets)

Session (agents.*.session)

Optional session configuration containing reset policy, DM scope, pruning, and compaction.Reset Policy (agents.*.session.resetPolicy)DM Scope (agents.*.session.dmScope)Pruning (agents.*.session.pruning)Compaction (agents.*.session.compaction)

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.Heartbeat (agents.*.scheduler.heartbeat)All fields are optional and inherit from the global scheduler.heartbeat when omitted.
See Agent Identity and Agent Lifecycle for how these settings affect agent behavior.

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)

Media Processing (channels.*.mediaProcessing)

Ack Reactions (channels.*.ackReaction — global schema)

Platform-Specific Fields

Slack (channels.slack)WhatsApp (channels.whatsapp)Signal (channels.signal)iMessage (channels.imessage)LINE (channels.line)IRC (channels.irc)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.
Matrix (channels.matrix)Matrix connects to a homeserver over the Client-Server API using either an access token or a password login, with a persisted device identity. End-to-end encryption is on by default. Trust is keyed on the full sender MXID: allowFrom gates both auto-join on invite and which senders the agent responds to, while allowMode decides how an empty allowlist behaves. allowPrivateHomeserver relaxes the SSRF private-range block for a self-hosted homeserver — cloud-metadata endpoints stay blocked regardless.
See the Channels section for platform-specific setup guides.
SQLite-backed memory system configuration.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.Compaction (memory.compaction)Retention (memory.retention)
See Memory and Search for how these settings affect agent memory behavior.
Embedding provider configuration for vector search. Supports local GGUF models via node-llama-cpp or remote OpenAI.Local (embedding.local)OpenAI (embedding.openai)Cache (embedding.cache)Batch (embedding.batch)
See Embeddings for a guide on choosing between local and remote embedding providers.

Gateway & API

Hono HTTPS server for JSON-RPC, WebSocket, and REST API access.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 providedTokens (gateway.tokens) — array of bearer token entriesRate Limit (gateway.rateLimit)WebSocket Message Rate Limit (gateway.wsMessageRateLimit)Web Dashboard (gateway.web)
See HTTP Gateway and WebSocket for API usage details.
Webhook subsystem for receiving external events (GitHub, Gmail, custom services).Webhook Mapping (webhooks.mappings[])
See Webhooks for webhook configuration patterns and payload formats.

Routing & Sessions

Multi-agent routing dispatch. Bindings are evaluated in order (first match wins).Routing Binding (routing.bindings[])
See Routing for detailed routing patterns and specificity rules.
Command queue for session serialization and concurrency control.Default Overflow (queue.defaultOverflow)Per-Channel Override (queue.perChannel.)Debounce Buffer (queue.debounce)Ingress-layer message coalescing before queue entry.Follow-up (queue.followup)
See Queue for queue behavior and session serialization details.

Security

Security configuration for log redaction, audit logging, permissions, action confirmation, agent-to-agent messaging, and encrypted secrets.Permission (security.permission)Action Confirmation (security.actionConfirmation)Agent-to-Agent (security.agentToAgent)Delivery (security.agentToAgent.delivery)Bounds the self-healing retry on sub-agent completion delivery. See Resilience: Self-healing delivery retries.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 for a full explanation.Storage Mode (security.storage)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.
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.
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.
~/.comis/config.yaml
See Security for a comprehensive overview of the security model.
Action approval workflow. Rules are evaluated in order (first match wins).
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.
Approval Rule (approvals.rules[])
See Approvals for approval workflow configuration patterns.

Daemon & Scheduler

Daemon process configuration for watchdog, shutdown, metrics, logging, and config change webhooks.Logging (daemon.logging)Tracing Defaults (daemon.logging.tracing)Config Webhook (daemon.configWebhook)
See Daemon for daemon management and Logging for log configuration details.
Proactive automation configuration for cron scheduling, heartbeat monitoring, quiet hours, execution safety, and task extraction.Cron (scheduler.cron)Heartbeat (scheduler.heartbeat)Quiet Hours (scheduler.quietHours)Execution (scheduler.execution)Tasks (scheduler.tasks)
See Scheduler for scheduling configuration and Monitoring for heartbeat setup.
System health monitoring with sub-monitors for disk, resources, systemd, security updates, and git repos.Disk (monitoring.disk)Resources (monitoring.resources)Systemd (monitoring.systemd)Security Updates (monitoring.securityUpdates)Git (monitoring.git)
See Monitoring for monitoring setup and alert configuration.

Integrations

External service integrations for search, MCP servers, media processing, and auto-reply rules.Brave Search (integrations.braveSearch)MCP (integrations.mcp)MCP Server Entry (integrations.mcp.servers[])Media (integrations.media) — contains nested sub-schemas for all media processing:Transcription (integrations.media.transcription)Local STT (integrations.media.transcription.local) — keylessTTS (integrations.media.tts)TTS Output Formats (integrations.media.tts.outputFormats)ElevenLabs Settings (integrations.media.tts.elevenlabsSettings) — optionalImage Analysis (integrations.media.imageAnalysis)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.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.
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.
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.
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.
Vision (integrations.media.vision)
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.
Vision Scope Rule (integrations.media.vision.scopeRules[])Link Understanding (integrations.media.linkUnderstanding)Media Infrastructure (integrations.media.infrastructure)Document Extraction (integrations.media.documentExtraction)Media Persistence (integrations.media.persistence)Auto-Reply (integrations.autoReply)Auto-Reply Rule (integrations.autoReply.rules[])
See MCP for MCP server configuration and Voice for STT/TTS setup.
Browser automation via Chrome DevTools Protocol (CDP).Viewport (browser.viewport)
See Browser for browser tool usage and configuration.
Plugin system configuration.Plugin Entry (plugins.plugins.)
See Plugins for the plugin system and hook reference.

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.
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.
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.
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 event + the pipeline_authoring finding in obs.fleet.health let an operator measure small-tier authoring quality and opt a flag out if it is not helping.
orchestration.authoring fields: 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 for the behavior and Event Bus for the graph:repaired and graph:synthesized_from_intent audit events.

Streaming & Messaging

Block-based response delivery across all channels.Per-Channel Override (streaming.perChannel.)Each per-channel entry also includes nested deliveryTiming and coalescer overrides with the same schema as the top-level versions.
See Delivery for block streaming behavior and platform-specific delivery.
Controls whether the agent pipeline activates for inbound messages. Separate from the pattern-based auto-reply rules in integrations.autoReply.
Outbound message gating rules. Rules evaluated in order; first match wins.Send Policy Rule (sendPolicy.rules[])
Message envelope enrichment for inbound messages before they reach the LLM.
Messaging UX configuration for outbound message formatting.
Model catalog and alias configuration for model discovery and friendly names.Model Alias (models.aliases[])
LLM provider configuration. API keys are referenced by SecretManager key name, never stored in plaintext.Provider Entry (providers.entries.)ProviderCapabilities (providers.entries..capabilities)
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..models[])ModelCompatConfig (providers.entries..models[].comisCompat)
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.
ModelCost (providers.entries..models[].cost)
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.

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.
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.
To override the capability default for a single agent:

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

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:
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:
A served context window smaller than configured makes prefill behavior harder to predict — see 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

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.

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:
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. 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):
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. Fleet-wide, under-served providers surface as the config_posture:served_below_configured finding in comis fleet and the obs.fleet.health RPC (see the JSON-RPC reference).

UX Features

Processing phase emoji reactions. When enabled, the agent reacts to messages with emoji reflecting current phase (thinking, tool use, generating, done, error).Timing (lifecycleReactions.timing)Per-Channel (lifecycleReactions.perChannel.)
Response prefix/suffix template injected into agent replies.
Inter-block delivery pacing to simulate natural typing rhythm.
Block coalescer that accumulates small streaming blocks before delivery.
Controls how sender identity is surfaced to the LLM in the message envelope.
Documentation links injected into the system prompt so the agent can reference them.
Detects hallucinated file paths in responses destined for Telegram (where file:// links are meaningless).
Crash-safe outbound delivery queue. Messages are persisted to SQLite before delivery attempts, surviving daemon restarts.
Session delivery mirroring for deduplication. Records delivered messages so they can be injected into agent context, preventing repeated deliveries.
Observability persistence layer. Stores channel health snapshots, execution metrics, and system telemetry in SQLite for historical analysis and dashboards.
The spend kill-switch is configurable in config.yaml only — there is no environment-variable form. See Observability → Spend Governance for the runtime behaviour (cooperative abort, three-state pricing, the events, and the pricing_gap fleet finding).

Diagnostics

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

Credential Broker Bindings (executor.broker)

The executor.broker block is wired into the daemon (AppConfigSchemasetupBroker): adding it to config.yaml starts the broker at boot — a TCP listener plus a 0600 unix socket at ~/.comis/broker.sock.
~/.comis/config.yaml
Credential Broker Bindings (executor.broker.bindings[*]) HostRule fields (hostRules[*]): For full configuration details and examples, see Credential Broker →.

Environment Variables

Environment variable reference

Hot Reload

Configuration hot reload behavior

Secret Manager

Encrypted secret storage and $ substitution

Configuration Guide

Step-by-step configuration walkthrough