The operator-facing knobs, with defaults read from the source rather than from memory. Every variable on this page is asserted by a test to still exist, so this document cannot quietly rot into fiction.
The codebase references a few hundred LOKI_* tokens. Most are internal
plumbing between the CLI and the runner (LOKI_REG_TARGET,
LOKI_CI_JSON_FINDINGS, and similar), or prefix fragments that are never a
whole variable. Publishing that number as an “operator surface” would be
misleading, so this page covers only what an operator would deliberately set.
loki config schema lists 64 more keys that map to environment variables and
can be written to .loki/config.yaml. Those are not repeated here. The
variables below are the ones not in that schema – historically the ones
with nowhere to look them up.
| Variable | Default | Effect |
|---|---|---|
LOKI_PRD_FILE |
none | Path to a spec file, as an alternative to the positional argument. |
LOKI_MAX_ITERATIONS |
1000 |
Hard cap on iterations. Reaching it is a deterministic terminal failure, not a success. Also in config schema. |
LOKI_BUDGET_LIMIT |
100.00 |
Spend cap in USD on the previous loop (the Loki 10 engine uses --max-cost, budgets.per_run or a default of $100 only when ANTHROPIC_API_KEY or OPENAI_API_KEY is set; none on a subscription). Also enforced by kill on codex, cline, aider and opencode in the Loki 10 engine. Breaching it pauses the run (writes .loki/PAUSE and saves state); it never kills work or discards a deliverable. Remove .loki/PAUSE to resume, or raise the cap. Set to "" for no cap. A warning is logged at 80%. |
LOKI_MAX_DURATION |
unset (no cap) | Wall-clock cap in seconds. Stops cleanly at the next iteration boundary with a max_duration_reached terminal status. loki start --max-duration also accepts 90m / 2h. |
LOKI_AUTO_CONFIRM |
unset | true/false to control prompts. Takes precedence over CI. |
LOKI_CONFIG_DUMP |
0 |
1 prints the resolved configuration and exits without starting a run or spending anything. |
For a cost estimate before committing to a run, loki plan <spec> --json is
the better tool: it reports complexity, iterations, tokens and cost without
executing. See cost controls for how the three caps
interact and why hitting one is a failure rather than a success.
| Variable | Default | Effect |
|---|---|---|
LOKI_PROVIDER |
claude |
claude, cline, codex, aider, or opencode. |
LOKI_SESSION_MODEL |
medium |
The capability tier for the run: small, medium, or high. See below. |
LOKI_MAX_TIER |
unlimited | Caps model tier, so a run cannot escalate past what you are willing to pay for. |
LOKI_MODEL_OVERRIDE |
unset | Overrides the resolved model outright. A route spec (ollama/<id>) works too. |
LOKI_MODEL_ROUTE |
unset | <kind>/<id> with kind openrouter, ollama, lmstudio, llamacpp, vllm or custom. Routes the Claude harness to that endpoint; loki models lists them, loki models --local discovers local runtimes. |
LOKI_MODEL_BASE_URL |
unset | Base URL for a custom/<id> route (required for custom). |
LOKI_MODEL_API_KEY |
unset | Token for a custom/<id> route (optional). OpenRouter reads OPENROUTER_API_KEY. |
LOKI_OPENCODE_MODEL |
unset | provider/model id passed to opencode (for example openrouter/deepseek/deepseek-v3.2). Also LOKI_OPENCODE_FLAGS and LOKI_OPENCODE_CLI. |
LOKI_EDIT_MODEL |
unset | Model for loki edit. |
LOKI_ENGINE |
unset | legacy forces the previous loop; the --engine flag wins over it. |
LOKI_SUMMARY_JSON |
unset | 1 prints the end-of-run summary as one JSON line on the previous loop. |
LOKI_TIER |
oss |
Not a model setting. The open-core licensing seam. Leave it unset. |
LOKI_E10_STABLE_PREFIX |
on | Engine10 sessions send the claude_code preset with excludeDynamicSections: true, so the system prompt is byte-identical across stages and runs and hits the prompt cache. 0 restores the previous preset. LOKI_E10_PREFIX=lean still wins when set. |
LOKI_REPO_BASELINE |
unset | 0 skips the intake repo profile and baseline test run (see docs/HARNESS-INTELLIGENCE-PLAN.md). |
LOKI_ALLOW_CLAUDE_SIDECALLS |
unset | 1 (exactly) lets PRD enrichment, done recognition, the council voters, the USAGE.md refresh and the quickstart intent check prompt the claude CLI under a non-claude provider; loki doctor --airgap then reports that as required egress to Anthropic. |
You do not need to know any vendor’s model names. Ask for a capability class and each provider supplies its own latest model in that class:
| Tier | Means | Claude | Codex | Cline / Aider / OpenCode |
|---|---|---|---|---|
small |
cheap and fast | claude-haiku-4-5 |
account default | deepseek-chat |
medium |
the workhorse (default) | claude-sonnet-5 |
account default | deepseek-v3.2 |
high |
the most capable | claude-opus-4-8 |
account default | deepseek-v3.2 |
LOKI_SESSION_MODEL=small loki start ./prd.md # or: loki start --session-model small ./prd.md
medium is the default and resolves to the same model today’s builds already
use, so setting it explicitly changes nothing. The older spellings
(fast/development/planning, and the Claude aliases haiku/sonnet/
opus) still work and are unchanged.
To see what a tier actually resolves to on your machine, run loki provider
models. It prints the dispatched model per tier per provider along with
which environment variable set it, so you can verify what you will really get
rather than trusting the table above. Codex deliberately shows a provider
default: it sends no --model flag and lets Codex pick a model appropriate to
your account, because a hardcoded name breaks ChatGPT-account users.
Override a single tier for one provider with LOKI_<PROVIDER>_MODEL_<TIER>
(for example LOKI_CLAUDE_MODEL_FAST=claude-haiku-4-5), or every tier at once
with LOKI_<PROVIDER>_MODEL.
LOKI_MAX_TIER is the cost control worth knowing: it bounds escalation, while
LOKI_BUDGET_LIMIT bounds total spend. They answer different questions and are
usefully set together. Note that LOKI_MAX_TIER is a ceiling and
LOKI_SESSION_MODEL is a choice – the ceiling still clamps the choice.
These control how the engine decides what to do next. Every one is on by
default and takes 0 to turn it off, so an operator who needs the older
behaviour has a switch rather than a fork.
| Variable | Default | Effect |
|---|---|---|
LOKI_EVAL_TREND |
on | Injects the run’s own cost, duration and cache-hit trend into the next prompt, so the agent can steer on its own efficiency. 0 removes the block. |
LOKI_INJECT_FINDINGS |
on | Injects the previous iteration’s reviewer findings, by severity and file:line. 0 makes the next iteration a blind retry. |
LOKI_ITERATION_GRACE |
on | When the agent reports done and no gate is failing, grants ONE extra iteration past the cap to land the work. Granted at most once per run. 0 restores a pure counter. |
LOKI_SCOPED_CHANGE |
auto | Forces the scoped-change profile on (1) or off (0). Auto-detected from an issue-sourced spec on a repository with real history. Never disables a trust gate; it skips phases irrelevant to a scoped fix. |
LOKI_RISK_TIER |
computed | Raises the verification tier (low, medium, high) above the one computed from the diff. Lowering is ignored unless LOKI_RISK_OVERRIDE_REASON is also set. |
LOKI_RISK_OVERRIDE_REASON |
unset | Recorded reason that lets LOKI_RISK_TIER lower the computed tier. Stored in the risk record and shown in the run summary. |
LOKI_INJECT_FINDINGS=0 is the one to think twice about. Without it the model
is told to fix problems without being told which, so it re-derives them from
scratch every iteration – the slowest and least accurate way to converge.
The v10 verify stage classifies risk on the diff (the artifact), never on the
prompt, and records the result in .loki/state/risk-tier.json. The run
summary prints its one-line summary (also summary.risk under --json; in
default output only while the 8-line cap still holds). A record older than the
run start is ignored, and a classification failure never fails verify.
loki review --risk (autonomy/lib/review_risk.py) is a separate 0-100
advisory score and is unchanged.
.sql, CI config, lockfiles, infra, .env), a deleted test file, public API surface (openapi/swagger, .proto, .d.ts, api/, routes/, public/, index files), 15+ files, or 500+ changed lines.| Tier | Selected checks | Recorded as not run |
|---|---|---|
| low | targeted tests, lint of changed files | full test command, reviewer, council, security scan (skipped by design) |
| medium | low plus full test command, one reviewer | one reviewer (and the full test command when no runner was detected) |
| high | medium plus full suite, council, security scan | council, security scan (and the full test command and full suite when no runner was detected) |
Targeted tests and lint always run. At medium and high, verify also runs the
detected full test command (the deep.ts per-runner table); a full-test
failure counts like any failing check, and a not_run result is added to
not_proven. Reviewer, council and security scan are not run by fast verify at
any tier, so they stay recorded as not run; nothing marks a skipped check as
passing. Record shape: {version, tier, computed, reasons[], stats{files, lines,
sensitive[], tests_modified[], tests_deleted[], api[]}, checks_selected[],
checks_deferred[], override?, override_ignored?, summary}.
Known gaps: the bash run.sh council triggers and the outcome record do not
yet use this tier; public API detection is path-based, not a semantic diff of
exports; a full-test failure that also fails on the base is not subtracted.
| Variable | Default | Effect |
|---|---|---|
LOKI_PROVIDER_IDLE_TIMEOUT |
120 (seconds) |
Kills a provider call that has produced NO output for this long. Idle, not total: a coding agent streams constantly, so silence is the signal, and a long legitimate iteration is never interrupted. |
LOKI_PROVIDER_CALL_TIMEOUT |
7200 (seconds) |
Hard ceiling on a single provider call, as a backstop against a process that streams forever without converging. |
Setting either to 0 disables that guard entirely, which is the pre-v8.12
behaviour: a hung provider then has nothing to stop it. Neither timeout
retries. Re-running a coding agent that may already have edited files is not
protective, so a timed-out call fails to the normal failure path instead.
| Variable | Default | Effect |
|---|---|---|
LOKI_LOG_LEVEL |
info |
debug, info, warn, or error. |
LOKI_QUIET |
0 |
1 is shorthand for warn. |
Errors are never suppressed. error is the floor, so even the quietest
setting still prints failures – a verbosity control that could hide why a
build failed would be a footgun. An unrecognized value falls back to info
rather than silencing the run, so a typo in a pipeline config cannot blind you.
Available as flags too: loki start --quiet, loki start --log-level LEVEL.
Decorative output (the HUD, the completion card, the start headline) already
suppresses itself when stdout is not a TTY, so CI logs were never the wall of
banners you might expect. These variables control the remaining [INFO] and
[STEP] lines.
| Variable | Default | Effect |
|---|---|---|
LOKI_DURABLE_STATE |
0 |
1 enables durable state and the richer process-exit contract that lets Kubernetes distinguish a deterministic failure from a crash. See exit codes. |
LOKI_SDK_LOOP |
unset | Routes the run through the Bun/TypeScript runner instead of bash. Both implement the same exit contract. |
LOKI_ALLOW_AGENT_GITHUB_TOKEN |
unset | By default GH_TOKEN, GITHUB_TOKEN, GH_ENTERPRISE_TOKEN, and GITHUB_ENTERPRISE_TOKEN are withheld from the agent’s environment (the agent runs as the user, so OS keychain and credential files are not yet isolated; P9-ISOLATION is in design). The two routes differ: on the bash route (autonomy/run.sh) the withheld tokens are re-granted only for Loki’s own trusted calls, namely the gh() CLI wrapper, the _loki_net helper used by the optional local-PR path in on_run_complete(), the session-end create_session_pr call, and autonomy/lib/proof-check.sh’s network probe. On the Bun route (loki-ts/src/runner/github_token.ts’s withholdGithubTokens()) the tokens are dropped from the process outright, with no re-grant and no push/PR step of its own. Exactly 1 lets the agent inherit the tokens as before, on either route; that is a Rule of Two exposure, and a one-line stderr warning is printed. |
LOKI_AI_MARKING |
unset (off) | 1 (exactly) adds machine-readable trailers to the Loki commit (AI-Generated: true, AI-Provider, AI-Model, Loki-Receipt, after Loki-Run) and one AI-Generated: true (...) marker line under the verdict in the PR body. Values are single-line and bounded. Off, the commit and PR body are byte-identical to before. |
LOKI_DURABLE_STATE=1 is the one to set in a Job or task definition. Without
it every failure collapses to exit 1 and the platform cannot tell “re-running
this is pointless” from “this crashed and should resume”.
Command-line flags beat environment variables, which beat .loki/config.yaml,
which beats ~/.config/loki-mode/config.yaml.
To see what actually resolved, without starting a run:
LOKI_CONFIG_DUMP=1 loki start ./prd.md
loki config set writes to the config file for the keys it supports:
loki config set budget 25
loki config set provider claude
loki config set maxTier sonnet
loki config schema lists every key the config file understands, and
loki config show prints the current effective values.