Skip to main content
What this is for: every environment variable Comis reads — provider API keys, channel tokens, encryption secrets, infrastructure paths. Who it’s for: anyone wiring API keys, deploying to Docker, or running CI pipelines. Comis reads 40+ environment variables across encryption, LLM providers, voice/media, web search, channel credentials, and infrastructure. Most variables are also referenceable from config.yaml via ${VAR_NAME} substitution — you typically pick one location per credential. The canonical templates are /.env.example (host install) and /.env.docker.example (Docker Compose).
The interactive setup wizard (comis init, comis configure) populates the most common variables for you and writes them to ~/.comis/.env. Manual editing is only needed for advanced or non-interactive setups.

Comis Variables

COMIS_CONFIG_PATHS

Specifies which YAML configuration files to load, as a colon-separated list (like PATH). Files are loaded in order and deep-merged, with later files overriding earlier ones. Non-existent files are silently skipped. The daemon reads this variable at startup to locate config files. The CLI uses it in config validate and pm2 setup commands. For full details on the config-path syntax and merge behavior, see Configuration.

COMIS_DATA_DIR

Overrides the base data directory where Comis stores its databases, logs, and runtime files. When set, the daemon uses this path instead of the default ~/.comis directory. This is useful for containerized deployments or when running multiple instances.
Durable terminal-driver drives (drive.durable: true) persist their resume journals under this directory (terminal-drive/<agentId>/journals/ — see the data directory reference). They use no dedicated environment variable: the journals are content-free files under the data dir, so COMIS_DATA_DIR (or the default ~/.comis) is the only knob that relocates them.

COMIS_GATEWAY_URL

Overrides the WebSocket URL the CLI uses to connect to the daemon gateway. The CLI normally reads the gateway host and port from the config file on disk, but this variable takes precedence when set.

COMIS_GATEWAY_TOKEN

Overrides the bearer token the CLI sends when connecting to the daemon gateway. The CLI normally extracts the first token secret from the gateway config file, but this variable takes precedence.

COMIS_GATEWAY_HOST

Overrides gateway.host. Applied via buildGatewayEnvLayer() at lower priority than config.yaml. Use 0.0.0.0 to bind all interfaces (e.g., Docker deployments). Source: packages/core/src/config/env-layer.ts:27

COMIS_GATEWAY_PORT

Overrides gateway.port. Must be a valid integer 1–65535; non-numeric values are dropped silently. Source: packages/core/src/config/env-layer.ts:28

COMIS_INSECURE

When set to 1, allows the CLI to send bearer tokens over unencrypted WebSocket connections to non-localhost hosts. Without this variable, the CLI refuses to send credentials over cleartext connections to remote hosts as a security measure.
Setting this variable bypasses transport security checks. Use only for development or when TLS termination happens at a reverse proxy. Never use in production without a TLS layer.

COMIS_LOG_PATH

Overrides the file that the comis daemon logs command reads from when systemd is not available. That file is the daemon’s raw stdout/stderr capture (daemon.console.log), written by the CLI when it spawns the daemon in direct mode — not the structured JSON log. For the structured application logs, read ~/.comis/logs/daemon.1.log directly.

COMIS_OFFLINE

When set to 1, disables outbound network calls to fetch optional tooling at startup (e.g. binary downloads, model lists, MCP server discovery). Use this for air-gapped deployments. Configured channels and provider APIs continue to work — COMIS_OFFLINE only gates provisioning traffic, not runtime LLM/channel calls.

COMIS_TRAJECTORY_DIR

Overrides observability.trajectory.dirOverride. Relocates runtime trajectory JSONL files to a custom directory. Empty or whitespace values are dropped silently. Source: packages/core/src/config/env-layer.ts:30

COMIS_DISABLE_RECALL_TRACE

Set to 1 to hard-disable the recall trace recorder regardless of config.yaml. Secondary escape hatch alongside diagnostics.recallTrace.enabled: false. Source: packages/observability/src/recall-trace/runtime.ts:304

COMIS_CONFIG_AUDIT_LOG

Overrides the full file path of the config-audit JSONL log. Source: packages/observability/src/config-audit/log-path.ts:22

COMIS_BROKER_TOKEN

Internal use only. A single-use proxy token injected into the exec tool’s spawn environment by the daemon’s broker-activation wiring. Consumed by the credential broker via Proxy-Authorization: Bearer. Set internally by the daemon per-command — operators do not set this variable directly. Documented here so operators diagnosing broker 407 errors can understand where the token originates. Source: packages/daemon/src/wiring/setup-broker-activation.ts:79

COMIS_CAP_LEASE

Internal use only. The capability-lease bearer the daemon mints per root-run and injects into the jailed script surface’s spawn environment so it can authenticate to the loopback capability endpoint (COMIS_ORCH_SOCKET). Multi-use, bounded, revocable, and audience-bound — a captured lease cannot be replayed at a foreign method. Registered with the OutputGuard at mint so it is never logged. Set internally by the daemon — operators do not set this variable directly. Trusted-source preservation: this var rides the daemon’s own trusted injection path (the placeholders slot, merged last). The terminal-driver / exec env scrub preserves COMIS_CAP_LEASE on that inherited path while fail-closed-blocking the entire COMIS_* prefix from an untrusted workspace-loaded .env (see the env-scrub note below), so attacker-supplied workspace content can never forge or smuggle a lease into the jail.

COMIS_ORCH_SOCKET

Internal use only. The filesystem path of the loopback capability endpoint’s Unix socket (0600, owner-only), bind-mounted into the jail and injected alongside COMIS_CAP_LEASE. The jailed surface dials this socket, presents the lease bearer, and the endpoint resolves the lease’s capabilities and dispatches through the same handler gates as every other origin. Set internally by the daemon per jail — operators do not set this variable directly. Like COMIS_CAP_LEASE, it rides the trusted inherited path and is preserved by the env scrub while the COMIS_* prefix is blocked from a workspace .env.

COMIS_AGENT_BIN

Internal use only. The in-jail path of the thin, capability-scoped comis-agent CLI binary. The daemon binds the binary read-only (--ro-bind) into the jail and sets COMIS_AGENT_BIN to that same path so the in-jail CLI resolves. The binary is sha256-pinned against a committed build manifest: at jail construction the daemon re-hashes the bound bytes and refuses to bind a mismatched/tampered binary (a writable or swapped binary would be a host-RCE vector — the bind is always read-only). Honest-degrade: when the binary is missing or its hash does not match the pin, the daemon emits a loud WARN, leaves COMIS_AGENT_BIN unset, and the comis-agent CLI surface is unavailable — the orchestrate script surface still works. Set internally by the daemon per jail — operators do not set this variable directly. Like COMIS_CAP_LEASE / COMIS_ORCH_SOCKET, it rides the trusted inherited path (merged after the scrub) and is preserved while the COMIS_* prefix is blocked from a workspace .env.

Child-env scrub (jailed surfaces)

When the daemon spawns a sandboxed child — a driven CLI via the terminal-driver, or the jailed orchestrate(script) surface — it scrubs the spawn environment before the jail starts, because bubblewrap inherits the spawner env by default. The scrub is a blocklist (strip the known-dangerous keys, keep the rich env a TUI needs):
  • Interpreter-control vars are stripped unconditionallyNODE_OPTIONS, BASH_ENV, ENV, PYTHONSTARTUP, RUBYOPT, PERL5OPT, the JVM *_OPTIONS set, and the prefix families LD_*, DYLD_*, PIP_*, UV_*. These instruct a runtime to load attacker-controlled code at startup (dynamic-linker preload, package-index redirection) → remote code execution, so they never reach the child regardless of where they came from.
  • The COMIS_* prefix is fail-closed-blocked from an untrusted workspace .env. A COMIS_-prefixed key loaded from workspace content is dropped, so attacker-supplied workspace content cannot forge a runtime-control variable (e.g. a fake COMIS_CAP_LEASE) into the jail.
  • The daemon’s own injection is preserved. COMIS_CAP_LEASE / COMIS_ORCH_SOCKET / COMIS_AGENT_BIN set by the daemon ride a separate trusted path and survive the scrub — blocking them would break the capability socket or the in-jail comis-agent CLI. Only the untrusted workspace source is blocked; the trusted daemon-injected source is allowed.
Source: packages/skills/src/tools/builtin/terminal-driver/terminal-env-scrub.ts

Microsoft Teams live-test seams (test-only)

These two variables exist ONLY to point the Microsoft Teams channel at a local loopback emulator for live testing (see the self-drive emulator). They are unset in production — with them unset the daemon behaves identically to a normal Teams deployment (the live Bot Framework JWKS validator + the global fetch). Never set them on a production daemon. Neither relaxes a security control: the inbound seam swaps in a full local-JWKS signature/issuer/audience verify, and the outbound seam only redirects the network egress of the already-allowlisted Connector host — the isSafeServiceUrl host gate is untouched.

COMIS_MSTEAMS_TEST_JWKS

When set, the Teams ingress verifies inbound activity tokens against the local JWKS at this path (the emulator’s public signing key) instead of the remote Bot Framework keys. A full RS256 signature + issuer + audience verify — not a bypass. Source: packages/daemon/src/wiring/msteams-test-seams.ts

COMIS_MSTEAMS_TEST_CONNECTOR

When set, the Teams adapter’s outbound Connector + token-mint calls are redirected to this loopback base (path + query preserved) — the programmatic equivalent of a hosts-override to the loopback emulator. The isSafeServiceUrl host allowlist still runs on the real smba.trafficmanager.net serviceUrl first; only the transport is redirected after the gate passes. Source: packages/daemon/src/wiring/msteams-test-seams.ts

Secret Variables

SECRETS_MASTER_KEY

The AES-256-GCM master encryption key used to encrypt and decrypt secrets stored in secrets.db. The key is auto-generated on first boot — you do not need to run comis secrets init for a fresh install. The daemon and CLI both resolve this key in the same order: first from the environment, then from ~/.comis/.env. Providing this variable explicitly overrides the auto-generated value.
Back up ~/.comis/.env. This file is the only copy of the auto-generated master key. Losing it makes secrets.db permanently unreadable — AES-256-GCM has no key escrow or recovery path.
To run without the encrypted store, set security.storage: env (or file) in config.yaml. The daemon emits a startup WARN when a non-encrypted storage mode is active.

OAuth Variables

OAUTH_OPENAI_CODEX

Pre-seeds the OAuth credential store with a profile for openai-codex on the first daemon start when the store is empty. After bootstrap, the stored profile is the source of truth — subsequent daemon starts ignore this env var even if its value changes.
Bootstrap precedence:
  1. Stored profile wins. If the OAuth credential store already has a profile for openai-codex, the daemon uses it and ignores OAUTH_OPENAI_CODEX entirely.
  2. Env var is a one-time seed. When the store is empty for the provider, the daemon parses the env var and writes the profile. Subsequent token refreshes update the stored profile, not the env var.
  3. WARN once on drift. If the env-var refresh token differs from the stored profile’s refresh token, the daemon emits one WARN per (provider, process) with errorKind: "config_drift" and hint: "env-override-ignored". Operators clear the drift by either:
    • Running comis auth logout --profile <id> then restarting the daemon to re-bootstrap from env, or
    • Deleting the env var if the stored profile is the intended source of truth.
This env var contains an OAuth refresh token. Treat it like a password. Use your CI / orchestrator’s secret-store integration; never paste it into config files or commit it to version control.
For the full bootstrap-precedence walkthrough including drift-handling examples, see OAuth concepts → Env-var bootstrap precedence.

Standard Variables

NODE_ENV

Standard Node.js environment variable. Affects behavior of some dependencies and may influence logging verbosity. Comis does not require this variable to be set.

OpenTelemetry Variables

These are the standard OpenTelemetry SDK environment variables, honored by the opt-in @comis/observability-otel extension (the SDK reads them; Comis does not store them). They apply only when observability.otel.enabled or observability.prometheus.enabled is set in config.yaml. The config.yaml values take precedence for the keys Comis controls. None carry secrets or content. See Prometheus & Grafana for the full surface.

OTEL_EXPORTER_OTLP_ENDPOINT

The OTLP collector endpoint for traces/metrics/logs push. Used only when the observability.otel.endpoint config key is empty; the config value takes precedence when set.

OTEL_SEMCONV_STABILITY_OPT_IN

Opts into the latest (pre-stable Development) GenAI semantic convention. Set it to gen_ai_latest_experimental alongside observability.otel.genaiSemconv: true. The convention will churn until it stabilizes. Content never leaks regardless — the exporter re-redacts independently and the message-content attributes are omitted unless otel.captureContent is separately enabled.

OTEL_SERVICE_NAME

Overrides the service.name resource attribute on every emitted signal.

Development and Regression Variables

These variables gate an optional developer regression suite. They are never required for normal operation — when they are unset the suite self-skips, so a default pnpm test / pnpm validate run makes no provider call and incurs no cost. They are read only at the test boundary; provider keys live in a git-ignored scripts/lcd-regression.env (the committed scripts/lcd-regression.env.example documents them) and are never logged or committed.

COMIS_LCD_REGRESSION

Master gate for the Lossless Context DAG real-LLM regression driver. When unset, the entire suite is skipped (no provider call, no network). When set, the driver runs a tool-free baseline probe + a single-read probe and records the stream:context cache-trace.

COMIS_LCD_ANSWER_PROVIDER / COMIS_LCD_ANSWER_MODEL / COMIS_LCD_ANSWER_API_KEY

The answer-model lane for the regression driver. All three must be set for the provider-backed probes to run; absent any one, the probes self-skip even when COMIS_LCD_REGRESSION is set. The key is forwarded to the provider’s typed apiKey option and is never stored, logged, or echoed.

COMIS_LCD_LIMIT

Cost bound for the regression driver. Caps the number of real provider calls when the suite is enabled; set to 0 for a record-only ($0) run that still executes the structural stream:context / assembledShape assertions without a live call.

Live-fire testing (operator)

These variables gate the pnpm test:live real-provider test tier. They are never required for normal operation — when COMIS_LIVE is unset the entire live tier self-skips, so a default pnpm test / pnpm validate run makes no provider call and incurs no cost.

COMIS_LIVE

Master gate for the real-provider live test tier. When unset, pnpm test:live exits 0 immediately with a “Live tier skipped” message — no provider call, no network, no cost. When set to 1, the runner dispatches to the scenario specified by the mode argument.

COMIS_LIVE_BUDGET_USD

Hard-stop ceiling for a single pnpm test:live run. The cost governor accumulates per-call cost estimates and aborts the run with a non-zero exit when the running total exceeds this value, preventing runaway spend. Set a lower value for exploratory runs; raise it only for full suite sweeps.

COMIS_LIVE_JUDGE_PROVIDER / COMIS_LIVE_JUDGE_MODEL / COMIS_LIVE_JUDGE_API_KEY

The judge model lane for behavioral / answer-quality scoring (reuses the bench-memory judge discipline). When all three are present, judgeAnswer invokes the real judge at temperature 0 and returns a scored pass/fail; when absent, it returns a skip (judged Stage-C scenarios then skip — never fail). For any published readiness claim, use cross-judge ≥2 (a second judge model + agreement) to avoid self-preference bias. The judge prompt, output, and key are never logged or written to a report (secret residency).

COMIS_LIVE_PROBES

Filters which sweep probes run when executing pnpm test:live sweep. When unset, all probes in PROBE_REGISTRY are executed. When set, only probes whose id or category matches one of the comma-separated values are included. Parsed by parseProbeFilter() in test/live/sweep/sweep.ts.

LLM Provider Keys

At least one provider key is required. Each provider is detected at startup; missing keys disable the matching provider gracefully. All keys can also be stored encrypted via comis secrets set instead of plaintext in .env.
Some providers reuse a parent key for sub-services: OpenAI Whisper STT and native image_generate (OpenAI Images API, default gpt-image-1) use OPENAI_API_KEY; a google main’s native image_generate (Gemini, default gemini-2.5-flash-image) uses GOOGLE_API_KEY; Groq Whisper uses GROQ_API_KEY; Grok search uses XAI_API_KEY. See Models for the wired catalog. Image generation reuses the main provider’s key. With the default integrations.media.imageGeneration.provider: auto, the image_generate tool follows the agent’s main provider and reuses its credentials — so in the common case no image-specific key is needed. A key-auth openai main generates via the OpenAI Images API with OPENAI_API_KEY; a google main generates via Gemini with GOOGLE_API_KEY (the same key the completion path and vision use — not GEMINI_API_KEY). To opt in to OpenRouter for image generation, set provider: openrouter and supply OPENROUTER_API_KEY; FAL_KEY is still used for the explicit provider: fal path. With a Codex (ChatGPT-login) main provider and provider: auto (or explicit provider: openai-codex), image_generate reuses the agent’s OAuth bearer — bootstrapped from OAUTH_OPENAI_CODEX or comis auth login --provider openai-codex — so no image-specific key is needed; the token refreshes automatically per call. The bearer is an OAuth credential, never an OPENAI_API_KEY. FAL_KEY also enables video generation. The video_generate tool uses the same FAL_KEY for the explicit provider: fal path (FAL queue API, fal-ai/veo3.1/fast). With the default integrations.media.videoGeneration.provider: auto, video generation follows the agent’s main provider and reuses its key, mirroring image generation: a google main generates via Veo (default veo-3.0-fast-generate-001) reusing GOOGLE_API_KEY, and an xai main generates via Grok Imagine (grok-imagine-video) reusing XAI_API_KEY (or a SuperGrok login) — so in the common case no video-specific key is needed. (The native-Veo path also has a FAL-hosted fallback: provider: fal + model: fal-ai/veo3.1, which needs FAL_KEY, a second credential.) There is no video-specific env var beyond reusing these provider keys. Vision (image_analyze) reuses the main provider’s key too. When the agent’s main model is vision-capable (Claude / GPT-4o / Gemini and other image-accepting models), image_analyze routes through that main model and reuses its credentials — ANTHROPIC_API_KEY / OPENAI_API_KEY / GOOGLE_API_KEY for a key-auth main, or the OAuth bearer for a Codex main — so no separate vision key is needed in that case. There is no new vision env var: if you fall back to the independent vision registry (when the main model can’t see images, or you set an explicit integrations.media.vision.defaultProvider), it uses the same OPENAI_API_KEY / ANTHROPIC_API_KEY / GOOGLE_API_KEY. describe_video (raw video) is Gemini-only and uses GOOGLE_API_KEY.

Voice and Media

All audio keys are OPTIONAL. Voice defaults to keyless: speech-to-text to auto (keyless-first — a local whisper engine when available, else honest- unavailable) and text-to-speech to edge (Microsoft Edge TTS, no key). The keyed audio providers below — and OPENAI_API_KEY / GROQ_API_KEY for STT — are used only when you set integrations.media.transcription.provider / .tts.provider to that provider explicitly, or the agent’s main provider already supplies a usable audio key. A Codex/ChatGPT-OAuth login cannot drive OpenAI audio (the bearer is scoped to the Codex Responses backend, not /v1/audio/*); a Codex-only main is steered to keyless and never issues an empty-bearer 401 — set a separate platform OPENAI_API_KEY + provider: openai to use OpenAI audio explicitly.

Web Search Providers

Optional. The agent picks an enabled provider when a web_search tool runs. Leave them all unset and the agent falls back to a no-op or the LLM’s own search capability.

Channel Credentials

Required only for channels you enable in config.yaml. Each value can also be stored as a SecretRef and referenced via ${VAR_NAME} substitution.

Telegram

Discord

Slack

LINE

Microsoft Teams

The bot app id (channels.msteams.appId) and directory/tenant id (channels.msteams.tenantId) are plain configuration values, not secrets, and are set in config.yaml rather than as environment variables.

IRC

Email

Matrix

The bot password (channels.matrix.password) has no direct environment-variable fallback — set it inline in config or as a SecretRef (for example ${MATRIX_PASSWORD}). The homeserver URL (channels.matrix.homeserverUrl) and the bot MXID (channels.matrix.userId) are plain configuration values, not secrets, and are set in config.yaml rather than as environment variables. WhatsApp, Signal, and iMessage use file-based credentials in their respective auth directories rather than environment variables. See Channels for setup.

Quick Setup with comis configure

Most users never edit .env by hand. The interactive flow:
Both commands write to ~/.comis/.env and (optionally) into secrets.db if a SECRETS_MASTER_KEY is configured. See comis configure for non-interactive flags.

Config Variable Substitution

Config YAML files support ${VAR_NAME} substitution for referencing environment variables and stored secrets. Any variable accessible via the SecretManager (including environment variables and values stored with comis secrets set) can be referenced this way.
Use $${VAR_NAME} (double dollar sign) to produce a literal ${VAR_NAME} in the output without substitution. The $include directive enables composing config from multiple files.
The COMIS_* prefix is reserved for operational variables. During comis secrets import, variables starting with COMIS_ are skipped (treated as infrastructure, not secrets).

Local Model Runtime Selection (MLX vs GGUF)

When running qwen3.6 or other local models via Ollama, the runtime format affects inference latency: Why: MLX is optimized for the Apple Neural Engine and achieves significantly lower inference latency on Apple Silicon. GGUF via llama.cpp is the standard format for Linux deployments and cloud VPS instances. Comis does not distinguish between MLX and GGUF tags — both are treated as opaque Ollama model IDs. Set the model ID in your config.yaml to the tag that matches your hardware:
To measure the latency difference between runtimes on your hardware, run the bench harness with both tags:
The report shows average latency per model section. MLX should be faster on Apple Silicon; GGUF should be faster or equivalent on Linux.

Config YAML

Full configuration file reference

Secret Manager

Encrypted secret storage

CLI Reference

Command-line interface reference

Installation

Initial setup and configuration