Configuring a frontier model¶
A substrate is four facts: where it is, what it may hold, what it costs, and
which environment variable holds its key. All four live in policy.yaml; the
key itself never does.
substrates:
- id: local-gpu
kind: ollama
endpoint: http://localhost:11434
model: qwen2.5:14b
jurisdiction: on-prem
max_class: restricted
- id: claude
kind: anthropic
model: claude-opus-5
api_key_env: ANTHROPIC_API_KEY # the NAME, never the value
jurisdiction: us
max_class: public # the field the whole product turns on
quality: 98
cost_per_mtok: 15.0
rules:
- match: {class: restricted}
allow: [local-gpu]
on_unavailable: hold
- match: {class: internal}
allow: [local-gpu]
- match: {class: public}
allow: [claude, local-gpu]
prefer: quality
max_class is the whole story. A substrate may serve a step only when the
step's class is at or below its ceiling, so max_class: public means this
provider never sees anything the policy classified higher — no matter which
rule allows it, no matter what the model is worth. Raising that ceiling is one
line, and it is the line an auditor reads first.
The two kinds you need¶
anthropic — the native Messages API, with tool use.
openai-compatible — anything speaking /v1/chat/completions, which is
most of the industry. endpoint is the base URL including /v1.
| Provider | endpoint |
model (example) |
api_key_env |
|---|---|---|---|
| OpenAI | https://api.openai.com/v1 |
gpt-4o |
OPENAI_API_KEY |
| Google Gemini | https://generativelanguage.googleapis.com/v1beta/openai |
gemini-2.0-flash |
GEMINI_API_KEY |
| Groq | https://api.groq.com/openai/v1 |
llama-3.3-70b-versatile |
GROQ_API_KEY |
| OpenRouter | https://openrouter.ai/api/v1 |
anthropic/claude-sonnet-5 |
OPENROUTER_API_KEY |
| Mistral | https://api.mistral.ai/v1 |
mistral-large-latest |
MISTRAL_API_KEY |
| Together | https://api.together.xyz/v1 |
meta-llama/Llama-3.3-70B-Instruct-Turbo |
TOGETHER_API_KEY |
| vLLM (your rack) | http://gpu-01.internal:8000/v1 |
whatever you serve | — |
| LM Studio | http://localhost:1234/v1 |
whatever you loaded | — |
kind: ollama is separate because Ollama's /api/chat is its own dialect.
Two frontier providers need two keys, which is why api_key_env is per
substrate. Without it a policy could only ever hold one credential — the older
global names (ANTHROPIC_API_KEY, OPENAI_API_KEY, ANNONA_SUBSTRATE_KEY)
still work as fallbacks for single-provider installs.
An on-prem vLLM behind your own firewall is not a frontier model, and the
policy should say so: jurisdiction: on-prem, max_class: restricted. The
same kind serves both — geography is a claim you make, not one the HTTP
client can verify.
Check it before you trust it¶
annona substrates # registered, jurisdiction, ceiling, and whether it answers
annona why --class internal # which substrate would take this class, and why not the others
annona substrates probes over HTTP. A substrate that is configured but not
answering shows as down and is skipped by placement — with the reason in the
ledger, not in a log nobody reads.
Testing against the real thing¶
The hermetic suite never touches the network. The live checks do, and they run only when a credential is present:
ANTHROPIC_API_KEY=sk-… env/bin/python -m pytest tests/test_live_frontier.py -v
OPENAI_API_KEY=sk-… env/bin/python -m pytest tests/test_live_frontier.py -v
# any other provider
ANNONA_LIVE_ENDPOINT=https://openrouter.ai/api/v1 \
ANNONA_LIVE_MODEL=anthropic/claude-sonnet-5 \
ANNONA_LIVE_KEY=sk-or-… env/bin/python -m pytest tests/test_live_frontier.py -v
Four checks per provider: the substrate becomes a backend that answers, a tool definition survives the round trip, the crossing is placed and recorded, and — the one worth having — restricted material does not reach a real provider that is one HTTP call away and fully credentialed. Every other guarantee in this repository is verified against a fake substrate; that one is verified against the network.
Two things that were broken until they were tested¶
Written down because both are the kind of defect that only a live test finds, and both shipped:
temperaturemade every Anthropic request fail. The adapter sent a sampling parameter on every call; current Anthropic models (Opus 5, Opus 4.8/4.7, Sonnet 5) reject sampling parameters with a 400. A correctly configured frontier substrate returned an error on the first request. The adapter no longer sends it.- The default model was a retired one. A policy that named
kind: anthropicand omittedmodelgot a model ID that no longer resolves. The default is nowclaude-opus-5, and the live test fails the day that stops being true.