Configuration (env)
Copy .env.example → .env. The only required variable is an LLM key.
| Variable | Default | Purpose |
|---|---|---|
LLM_PROFILE | anthropic | anthropic | openrouter | mixed — which provider profile (per-tier default models) |
LLM_ROUTING | — | Per-role preset: fast (Groq worker) | volume (OpenRouter worker) — see Role routing |
ANTHROPIC_API_KEY | — | API key for the Anthropic profile |
OPENROUTER_API_KEY | — | API key for the OpenRouter profile (cheaper) |
GROQ_API_KEY | — | API key for the Groq worker (used by LLM_ROUTING=fast) |
QA_TESTCASE_LANG | English | Language for generated test cases, e.g. Ukrainian / uk |
BROWSER_BACKEND | lib | lib (in-process Playwright) | cli — how the browser is driven |
BROWSER_CHANNEL | — | chrome to use real Chrome (helps with OAuth) |
MAX_REPAIR | 2 | Max self-repair attempts per failing spec (keep-best) |
LANGFUSE_BASE_URL | — | Langfuse — cloud or self-hosted (optional) |
LANGFUSE_PUBLIC_KEY | — | Langfuse public key |
LANGFUSE_SECRET_KEY | — | Langfuse secret key |
Minimal .env
Section titled “Minimal .env”ANTHROPIC_API_KEY=sk-ant-...Role routing
Section titled “Role routing”By default each profile uses its own per-tier models. LLM_ROUTING optionally layers a cheap
worker over any profile while keeping the strong reasoner — one flag picks where the
mechanical steps (identify elements, generate code, repair) run:
LLM_ROUTING | Worker | Notes |
|---|---|---|
| unset (default) | the profile’s own per-tier models | no routing |
fast | Groq llama-3.3-70b-versatile | lowest latency / cost (needs GROQ_API_KEY) |
volume | OpenRouter deepseek/deepseek-chat | model breadth (needs OPENROUTER_API_KEY) |
In every preset the reasoner — designing test cases and the
Pilot verdict — stays on Anthropic claude-opus-4-8 for judgment
quality. Override any role with CAIRN_ROLE_WORKER / CAIRN_ROLE_REASONER=provider:model, or pass
--routing <preset> on explore / design / automate to set it per run. Per-run, per-role cost
(tokens + $) is printed in the run summary — see
ADR 0011.
Decision layer
Section titled “Decision layer”The decision layer (0.8.0+) lets a System One model answer Cairn’s pick-from-a-list steps. It
is off unless DECIDER names a provider, and a key on its own changes nothing.
| Variable | Default | Purpose |
|---|---|---|
DECIDER | off | jev | laya | compat (--decider overrides it for one run) |
DECIDER_USES | repair-triage (in shadow mode: every use point) | Use points to enable: repair-triage, coverage, locator-heal (which needs repair-triage). An unknown name is an error |
DECIDER_SHADOW | 0 | 1 = record the answers in runs/<id>/decider-shadow.json and act on none (--decider-shadow). Needs a provider |
DECIDER_MIN_CONFIDENCE | 0.75 | Below it an answer is ignored and the current path runs |
DECIDER_TIMEOUT_MS | 10000 | Per call, one retry on 429/5xx included |
DECIDER_MAX_CALLS | 200 | Per-run ceiling on decisions (separate from the LLM call budget) |
DECIDER_MODEL | jev-latest | Laya picks its checkpoint by language unless pinned: multilingual for non-English apps |
TYPESAFE_API_KEY | — | jev only (required) |
TYPESAFE_BASE_URL | https://api.typesafe.ai | jev only. Only an https://*.typesafe.ai address is accepted |
DECIDER_BASE_URL | — | laya / compat only (required), e.g. http://127.0.0.1:8000 |
LAYA_API_KEY | — | laya only: the token laya-serve was started with |
DECIDER_API_KEY | — | compat only |
cairn doctor shows the provider, where the data goes and the latency of one real call. A misconfiguration fails
at start, with the reason.
Going further
Section titled “Going further”- Steer the design pass with domain knowledge files & prompt overrides
- Saved sessions live in
.auth/<name>.storageState.json— capture them withcairn session capture --url <url> --name <name>