Configuration¶
LLM Council can be configured through environment variables or YAML configuration files.
Configuration Priority¶
- Environment variables (highest priority)
- YAML configuration file
- Default values
YAML Configuration¶
Create llm_council.yaml in your project root or ~/.config/llm-council/:
council:
tiers:
default: high
# `pools` is optional — see "Default model pools" below. Set it only for
# the tiers you want to change; the rest keep the shipped defaults.
pools:
quick:
models:
- google/gemini-3.5-flash-lite
- anthropic/claude-haiku-4.5
timeout_seconds: 30
gateways:
default: openrouter
# Per-provider overrides and per-gateway model-id translation
providers:
requesty:
enabled: true
base_url: https://router.requesty.ai/v1/chat/completions
model_name_map:
requesty:
"some/model:free": "some/model" # Requesty rejects OpenRouter's ":free" suffix
Default model pools¶
You do not have to choose models. Each tier — quick, balanced, high,
reasoning and frontier — ships with a default council, and a fresh install
uses it with no configuration at all.
Those defaults live in one file inside the installed package,
llm_council/models/default_pools.yaml. To see the pool a tier will actually
use:
Overriding is per tier. Anything you put under tiers.pools.<tier> wins
for that tier; every tier you leave out falls back to the packaged default. So
the snippet above changes quick only — balanced, high, reasoning and
frontier are untouched.
Two rules the defaults follow, worth keeping if you write your own:
- Every model in a pool must fit that tier's
timeout_seconds. Selection can pick any member, not just the first, so one slow model makes the whole tier unreliable rather than occasionally slow. - New and preview models go in
frontier. That is the audition tier (ADR-027): its members are scored and recorded but carry no weight in consensus until they have a track record.
LLM_COUNCIL_MODELS does not override a tier. A run picks its members in
this order: models passed explicitly to the call, else the selected tier's
pool, else LLM_COUNCIL_MODELS. Which of those applies depends on the entry
point:
| Entry point | Default council |
|---|---|
MCP consult_council, verify |
the selected tier's pool |
HTTP POST /v1/council/run |
LLM_COUNCIL_MODELS — this endpoint is tier-agnostic |
Library run_full_council() |
LLM_COUNCIL_MODELS |
So editing a tier pool changes what MCP consults run; setting
LLM_COUNCIL_MODELS changes what the HTTP endpoint runs. If you leave both
alone they agree: LLM_COUNCIL_MODELS defaults to the high pool.
Environment Variables¶
Essential¶
| Variable | Description |
|---|---|
OPENROUTER_API_KEY |
OpenRouter API key |
LLM_COUNCIL_MODELS |
Comma-separated model list |
LLM_COUNCIL_CHAIRMAN |
Chairman model |
LLM_COUNCIL_CHAIRMAN_DISABLED |
Skip chairman synthesis, return top-ranked response directly. Never enable for council-verify/council-gate — see Verification guide. |
Feature Flags¶
| Variable | Default | Description |
|---|---|---|
LLM_COUNCIL_RUBRIC_SCORING |
false |
Multi-dimensional scoring |
LLM_COUNCIL_BIAS_AUDIT |
false |
Bias detection |
LLM_COUNCIL_SAFETY_GATE |
false |
Content safety checks |
Modes¶
| Variable | Options | Description |
|---|---|---|
LLM_COUNCIL_MODE |
consensus, debate |
Synthesis mode |
LLM_COUNCIL_VERDICT_TYPE |
synthesis, binary |
Verdict format |
External Spend Telemetry (ADR-056)¶
Optional. With the endpoint unset, council emits nothing and needs nothing. Any OTLP/HTTP collector works. The span attributes follow the external contract that skills-telemetry reads, but council does not depend on skills-telemetry in any way.
| Variable | Default | Description |
|---|---|---|
OTEL_EXPORTER_OTLP_ENDPOINT |
unset | Where to send one OTLP span per council run, reporting what it cost. Unset means no exporter, no network call and no added latency — an install without it behaves exactly as before. |
CLAUDE_CODE_SESSION_ID |
set by Claude Code | Read, never written. Stamped on the span as session.id so spend can be attributed to the agent session that caused it. Absent outside a Claude session, which is honest rather than invented. |
Needs the otel extra: pip install "llm-council-core[otel]". With an endpoint
set but the extra missing, council_health_check reports
external_telemetry.reason = "sdk_missing" rather than staying silent — so you
can tell "nothing was spent" from "nothing was recorded".
Under Claude Code, set the endpoint in the MCP server's env block, in
whichever config registers llm-council (user scope is usually ~/.claude.json):
A shell export does not work: Claude Code removes this variable from the
environment it starts MCP servers with, and nothing reports that it did. Use the
base URL; the SDK appends /v1/traces. Restart Claude Code afterwards, because
an MCP server reads its environment only at launch.
Observed cost goes out as std.external.cost_usd with a cost_source of
provider or local. A registry estimate goes out separately as
std.external.cost_estimated_usd and never in cost_usd, so an estimate is
never summed as a bill. An unobserved amount is omitted, never sent as zero.
Gateway Options¶
LLM Council supports multiple gateways:
| Gateway | Best For | Setup |
|---|---|---|
| OpenRouter | Easy setup | OPENROUTER_API_KEY |
| Direct | Control | Provider API keys |
| Requesty | Analytics | REQUESTY_API_KEY |
| Ollama | Local/Air-gapped | No key needed |
Model IDs sometimes differ across gateways (e.g. Requesty rejects OpenRouter's
:free suffix); use gateways.model_name_map in the YAML config above to
translate a canonical model ID per gateway.
Note:
gateways.default(above) is the config that actually routes live traffic today. The class-basedGatewayRouter/circuit-breaker abstraction described in the ADR-023 spec is not currently reachable via configuration — see #524.
See README for complete configuration reference.