Model aliases are a trap. gpt-4 doesn't point at a fixed model. claude-3-opus might not either. Providers update what's behind the alias and you find out when your agent starts behaving differently in production — if you're lucky enough to notice at all.
anchor pins your agent to specific model versions, snapshots their behavior, and lets you diff outputs when you need to swap. Silent deprecations become visible diffs instead of mysterious regressions.
Create anchor.yaml in your project root:
models:
reasoner: { provider: anthropic, version: "claude-opus-4-8" }
cheap: { provider: anthropic, version: "claude-haiku-4-5-20251001" }
coder: { provider: openai, version: "gpt-4.1-2025-04-14" }
rules:
forbid_aliases: trueUse pinned models in code:
from anchor import complete
response = complete(
messages=[{"role": "user", "content": "Hello"}],
model="reasoner" # resolves to claude-opus-4-8, always
)Record a golden suite against your current model:
anchor snapshot --suite examples/golden_suite.jsonl --model reasonerWhen you want to switch models, diff them first:
anchor diff --suite examples/golden_suite.jsonl --from reasoner --to cheapThe diff report shows per-prompt text similarity scores and flags prompts where the outputs diverged significantly. Aggregate stats at the bottom: mean similarity, number of prompts that shifted past a threshold.
from anchor.diff import DiffEngine
engine = DiffEngine()
report = engine.suite_diff("snapshots/model_a.jsonl", "snapshots/model_b.jsonl")
print(report.to_text())Run this in CI to catch bare model strings before they ship:
anchor lint ./srcFlags things like:
model = "gpt-4" # bad — alias, could change
model = "claude-3-opus" # bad — alias
model = "gpt-4-0613" # ok — pinned versionWraps LiteLLM under the hood, so you get the same interface regardless of which provider you're hitting.
MIT