Configuration
All environment variables for configuring the AI Agent — LLM provider, model picker, access control, and conversation persistence.
Configure the agent entirely through environment variables. Start with the core LLM settings, then add provider keys, access control, and persistence as you need them.
Core LLM settings
These three variables cover most deployments.
| Variable | Default | Purpose |
|---|---|---|
LLM_API_KEY | — | Provider API key. Required to enable the agent. |
LLM_API_BASE | https://openrouter.ai/api/v1 | OpenAI-compatible base URL. |
LLM_MODEL | openrouter:openrouter/free | Model identifier (provider:modelId). |
LLM_API_KEY=sk-or-...
LLM_API_BASE=https://openrouter.ai/api/v1
LLM_MODEL=openrouter:openrouter/autoProvider-specific keys
Set the key for each provider you want to use. Only providers with a key configured appear as selectable in the model picker.
| Variable | Provider | Notes |
|---|---|---|
OPENROUTER_API_KEY | OpenRouter | Alias for LLM_API_KEY when using OpenRouter. |
OPENROUTER_API_BASE | OpenRouter | Override base URL. |
OPENROUTER_REFERER | OpenRouter | HTTP referer sent with requests (for leaderboard attribution). |
OPENROUTER_APP_NAME | OpenRouter | App name sent with requests. |
OPENROUTER_MODELS_API | OpenRouter | Override models list URL. |
NVIDIA_API_KEY | NVIDIA NIM | NIM API key. |
NVIDIA_API_BASE | NVIDIA NIM | NIM base URL (defaults to NVIDIA's hosted endpoint). |
ANYROUTER_API_KEY | AnyRouter | AnyRouter API key. |
ANYROUTER_API_BASE | AnyRouter | AnyRouter base URL. |
ANYROUTER_DYNAMIC_MODELS | AnyRouter | Set to false / 0 / off to disable live catalog enrichment (static registry only). On by default when ANYROUTER_API_KEY is set. |
ANYROUTER_TOP_MODELS_N | AnyRouter | Max usage-ranked models to add to the picker (default 8, max 32). |
ANYROUTER_METRICS_CANDIDATE_CAP | AnyRouter | Max catalog models to score via /models/{id}/metrics (default 24, max 64). |
ANYROUTER_PRESETS | AnyRouter | Set to false / 0 / off to hide your workspace presets from the picker. On by default when ANYROUTER_API_KEY is set. |
ANYROUTER_PRESETS_MAX | AnyRouter | Max presets to surface (default 8, max 32). |
ANYROUTER_OAUTH_CLIENT_ID | AnyRouter | Pin a pre-registered OAuth client for "Sign in with AnyRouter". Optional — the server registers one on first use. |
OPENROUTER_DYNAMIC_MODELS | OpenRouter | Set to false / 0 / off to disable live catalog enrichment (curated registry only). On by default when OPENROUTER_API_KEY is set. |
OPENROUTER_TOP_MODELS_N | OpenRouter | Max dynamically ranked models to add to the picker (default 12, max 32). |
OpenRouter dynamic models (relevance ranking)
When OPENROUTER_API_KEY is set, GET /api/v1/agents/models also loads
OpenRouter's public catalog (GET {OPENROUTER_MODELS_API}) and appends a
bounded set of tool-capable models on top of the curated registry.
Unlike AnyRouter, OpenRouter exposes no usage-ranking API — the order
query parameter is accepted but ignored, and the ranking endpoints behind
openrouter.ai are not public. The ranking is therefore a deterministic
relevance score (curated-registry membership, well-known model authors, free
variants, context size, recency), not measured usage. Models without tool
calling are excluded, since the agent cannot use them.
AnyRouter presets
When ANYROUTER_API_KEY is set, the authenticated catalog response also
carries your workspace's presets, which the picker lists as
anyrouter:@preset/<slug>. Selecting one sends the preset's own model, system
prompt, and sampling settings.
Sign in with AnyRouter (bring your own credits)
Users can sign in to AnyRouter directly from the model picker (or the Provider & Models settings tab) instead of relying on the deployment's key. The server runs an OAuth 2.1 authorization-code + PKCE flow in a popup; the resulting token is held in that browser only and replayed per request through the existing BYOK path, so it is never persisted or logged server-side. AnyRouter tokens last about 30 days and have no refresh token — signing in again mints a fresh one.
Sign-in is offered only when ANYROUTER_API_KEY is not set. With a key
configured the provider already works for everyone, so the flow is hidden;
without one, it unlocks AnyRouter models that would otherwise be unavailable.
AnyRouter dynamic models (usage ranking)
When ANYROUTER_API_KEY is set, GET /api/v1/agents/models also loads AnyRouter’s public catalog (GET {ANYROUTER_API_BASE}/models) and ranks a bounded set of tool-capable models by real usage (GET .../models/{id}/metrics → request_count). List-level sort=usage is not supported by AnyRouter — ranking is always metrics-based.
The picker then includes:
anyrouter:auto— default when AnyRouter is configured. The agent route resolves it at request time to the current top tool-capable model by usage (cached ~5 minutes), falling back to the curated static default (anyrouter:google/gemma-4-26b-a4b-it) if the catalog/metrics are unavailable.- Router aliases when present in the catalog — e.g.
anyrouter/agent,anyrouter/free,anyrouter/coding. - Top-N concrete models by
request_count(function-calling preferred).
Failures are fail-soft: OpenRouter-only / OSS deploys without AnyRouter keep the static MODEL_REGISTRY path with no live dependency on anyrouter.dev.
Extra models
LLM_EXTRA_MODELS adds entries to the model picker without touching code. Format:
provider:modelId[|contextLength][|description]provider— one ofopenrouter,nvidia,anyroutermodelId— model ID passed to the provider (may contain colons)contextLength— optional integer token count; defaults to128000description— optional display label; defaults tomodelId
Comma-separate multiple models:
LLM_EXTRA_MODELS="nvidia:meta/llama-3.3-70b|131072|Llama 3.3 70B,openrouter:x-ai/grok-2"Extra models are appended after the built-in registry. If an extra entry shares a provider:modelId key with a built-in, the built-in wins.
Access & safety
| Variable | Default | Purpose |
|---|---|---|
CHM_FEATURE_AGENT_ACCESS | public | Set to authenticated to require login before using the agent. |
AGENT_API_TOKEN | — | Shared Bearer token accepted by POST /api/v1/agent. |
AGENT_ENABLE_CONTROL_TOOLS | false | When true, enables optimize_table, kill_query, and kill_mutation. Keep off unless the ClickHouse user is trusted. |
CHM_AGENT_FIRECRAWL_MCP | true | Connect Firecrawl's hosted MCP (no API key) on every agent request. Set false to disable. |
CHM_AGENT_FIRECRAWL_ALLOW_DOMAINS | (empty) | Comma-separated hostnames Firecrawl may scrape/crawl/map. Empty / unset = unrestricted. Apex tokens also match subdomains; *.example.com is subdomain-only (not the apex). Search-only calls (no URL) stay allowed. |
RATE_LIMIT_AGENT_PER_MIN | 10 | Max POST /api/v1/agent requests per minute, applied per signed-in identity and per IP. |
RATE_LIMIT_AGENT_GUEST_PER_MIN | 5 | Cloud-only tighter per-guest identity limit (key agent:guest:guest:<hash>). OSS anonymous callers stay IP-only. |
CHM_GUEST_AI_REQUESTS_PER_DAY | 3 | Cloud-only daily AI messages for anonymous visitors. Tracked in D1 ai_usage_daily per guest:<ip-hash>. Signed-in users keep their plan cap (Free 5 / Pro 100 / …). OSS is not gated. |
AGENT_DEBUG | false | When truthy (1/true/yes/on), logs verbose agent request/usage details. Leave off in production — it prints message keys, resolved user ids, and token usage. |
Require auth on public deployments:
CHM_FEATURE_AGENT_ACCESS=authenticatedProtect the API endpoint with a bearer token:
AGENT_API_TOKEN=your-secret-tokencurl -H "Authorization: Bearer $AGENT_API_TOKEN" \
-d '{"message":"...","hostId":0}' \
https://your-host/api/v1/agentCross-source Postgres tools
Set CHM_FEATURE_POSTGRES_SOURCE=true to expose the agent's read-only Postgres
tools (run_postgres_select_query, get_postgres_metrics,
list_postgres_slow_query_patterns, get_postgres_table_stats). They resolve a per-call pgHostId against
the POSTGRES_* env lists below — a flat positional index, exactly like a
ClickHouse hostId indexes the CLICKHOUSE_* lists. All lists are
comma-separated and index-aligned with POSTGRES_HOST; a single POSTGRES_USER
/ POSTGRES_PASSWORD broadcasts to every host.
| Variable | Default | Purpose |
|---|---|---|
CHM_FEATURE_POSTGRES_SOURCE | false | Master gate. When true, the three Postgres tools are added to the agent. Pure env gate (no Clerk) so self-hosted has equal support. |
POSTGRES_HOST | — | Comma-separated Postgres hosts (host or host:port). Defines the pgHostId id space. |
POSTGRES_PORT | 5432 | Ports (used when a host has no inline :port). |
POSTGRES_USER | postgres | Users; a single value applies to all hosts. |
POSTGRES_PASSWORD | — | Passwords; a single value applies to all hosts. |
POSTGRES_DATABASE | postgres | Database names. |
POSTGRES_SSLMODE | — | libpq sslmode (disable | require | verify-full). |
POSTGRES_NAME | — | Display names for the sources. |
Env hosts only, for now
The agent's Postgres tools read env-configured hosts (POSTGRES_*), like the
ClickHouse tools read CLICKHOUSE_*. Per-user (D1) Postgres connections managed
in the UI are not yet visible to the agent — the same limitation the ClickHouse
agent tools already have. Env Postgres hosts are operator-supplied and trusted
(no SSRF re-guard), matching the CLICKHOUSE_HOST precedent.
Point the Postgres user at a read-only role. The tools pin the session read-only and gate the SQL, but a least-privilege user is defense in depth.
CHM_FEATURE_POSTGRES_SOURCE=true
POSTGRES_HOST=db.internal:5432
POSTGRES_USER=readonly
POSTGRES_PASSWORD=secret
POSTGRES_DATABASE=appdbLeast-privilege ClickHouse user
The agent's query tool (and the MCP server) run
read-only SELECTs that the model composes. Because the model can read any
table the connection user can, point it at a dedicated, restricted user —
not default or an admin account. Two concerns in particular:
system.query_logcan contain credentials embedded in query text (e.g. aCREATE ... IDENTIFIED BYor ans3(...)call another client ran).system.users,system.grants, andsystem.quotasexpose your access model.
Create a monitoring user restricted to the system tables the dashboard needs and deny the sensitive ones:
CREATE USER chmonitor_agent IDENTIFIED BY 'a-strong-password'
SETTINGS readonly = 1;
-- Read-only access to the metrics/monitoring tables the dashboard reads.
GRANT SELECT ON system.* TO chmonitor_agent;
-- Revoke the tables that leak secrets or your access model.
REVOKE SELECT ON system.query_log FROM chmonitor_agent;
REVOKE SELECT ON system.users FROM chmonitor_agent;
REVOKE SELECT ON system.grants FROM chmonitor_agent;
REVOKE SELECT ON system.quotas FROM chmonitor_agent;Keep control tools off by default
Leave AGENT_ENABLE_CONTROL_TOOLS=false unless the ClickHouse user is trusted —
it enables mutating tools (optimize_table, kill_query, kill_mutation). A
readonly = 1 user is a second line of defense even if it is ever turned on.
Built-in Firecrawl MCP
The agent connects Firecrawl's hosted MCP (https://mcp.firecrawl.dev/v2/mcp)
on every request. No API key is required for scrape, search, and parse (rate-
limited). Set CHM_AGENT_FIRECRAWL_MCP=false to disable. Register the same
URL with a bearer token to override the built-in server.
To restrict which sites Firecrawl may fetch, set
CHM_AGENT_FIRECRAWL_ALLOW_DOMAINS to a comma-separated list of hostnames
(clickhouse.com,chmonitor.dev,github.com). Unset or empty leaves scrape/crawl/map
unrestricted (the OSS default). When set, any url / urls argument whose host
is not on the list is blocked before the MCP call; tools with no URL (for
example search with only a query) stay allowed. An apex token matches that
host and its subdomains (clickhouse.com covers docs.clickhouse.com). A
leading *. is subdomain-only (*.example.com matches foo.example.com but
not example.com). The same allowlist applies to a user-registered Firecrawl
server (mcp.firecrawl.dev or a sanitized name of firecrawl), so renaming
the server cannot bypass it.
Conversation persistence
| Variable | Type | Default | Purpose |
|---|---|---|---|
VITE_FEATURE_CONVERSATION_DB | Build-time | false | Enable server-side conversation storage. Derived from canonical CHM_FEATURE_CONVERSATION_DB — set that one before pnpm run build. Requires Clerk. |
CONVERSATION_STORE_BACKEND | Runtime | (auto) | Backend: agentstate, d1, postgres, or memory. Auto-selects when unset. |
For per-backend env vars and setup, see Conversation history and Store backends.
Where to set these
- Cloudflare Workers —
wrangler secret put LLM_API_KEYfor secrets;[vars]inwrangler.tomlfor non-secret values in your own worker. (In this repo's hosted deploy, non-secret config lives inapps/dashboard/.env.productionand there is no[vars]block.) See Deploy to Cloudflare. - Docker —
environment:block indocker-compose.ymlor--env-file .env. See Deploy with Docker. - Kubernetes —
Secretfor keys,ConfigMapfor non-secret values. See Deploy to Kubernetes. - Vercel / self-hosted — environment variables in the project dashboard or
.env.local.
Keep LLM keys server-side
Never put LLM API keys in VITE_* variables. Anything prefixed VITE_ is baked into browser JavaScript at build time and is visible to anyone who opens DevTools. Use plain (non-VITE) variables for all keys — they stay on the server.