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).
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.
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 jailedorchestrate(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 unconditionally —
NODE_OPTIONS,BASH_ENV,ENV,PYTHONSTARTUP,RUBYOPT,PERL5OPT, the JVM*_OPTIONSset, and the prefix familiesLD_*,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. ACOMIS_-prefixed key loaded from workspace content is dropped, so attacker-supplied workspace content cannot forge a runtime-control variable (e.g. a fakeCOMIS_CAP_LEASE) into the jail. - The daemon’s own injection is preserved.
COMIS_CAP_LEASE/COMIS_ORCH_SOCKET/COMIS_AGENT_BINset by the daemon ride a separate trusted path and survive the scrub — blocking them would break the capability socket or the in-jailcomis-agentCLI. Only the untrusted workspace source is blocked; the trusted daemon-injected source is allowed.
packages/skills/src/tools/builtin/terminal-driver/terminal-env-scrub.ts
Microsoft Teams live-test seams (test-only)
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.
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.
- Stored profile wins. If the OAuth credential store already has a profile for
openai-codex, the daemon uses it and ignoresOAUTH_OPENAI_CODEXentirely. - 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.
- 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"andhint: "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.
- Running
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 defaultpnpm 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 thepnpm 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 viacomis secrets set instead of plaintext in .env.
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 toauto (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 aweb_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 inconfig.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
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:
~/.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.
$${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:
Related
Config YAML
Full configuration file reference
Secret Manager
Encrypted secret storage
CLI Reference
Command-line interface reference
Installation
Initial setup and configuration
