ADR 0021: Anthropic as the model provider
Context
Section titled “Context”Rhumbatron uses Claude Haiku, Sonnet, and Opus as its model family. Anthropic serves each tier. Model calls are the only cost of the stack. Model spend has a hard monthly limit, $100 by default. Anthropic is the only model provider.
The Anthropic API key is organization-level. Each request must send the header
anthropic-workspace-id. An invalid workspace ID gets the Anthropic 400 error
“anthropic-workspace-id header must be a valid workspace ID”.
Decision
Section titled “Decision”-
Provider: the Anthropic Messages API, called directly at
<base URL>/v1/messages(packages/model-router/src/gateway-client.ts).ANTHROPIC_BASE_URLsets the base URL; the default ishttps://api.anthropic.com. The key is theANTHROPIC_API_KEYsetting of the core service. The workspace ID isANTHROPIC_WORKSPACE_ID(not a secret).bun run stack:env(scripts/local-env.ts) writes both to.envfrom the environment or from the macOS Keychain itemsrhumbatron-anthropic-api-keyandrhumbatron-anthropic-workspace-id. A failed call never goes to another provider. -
Tier mapping (
packages/model-router/src/models.ts):Tier Model Effort haiku claude-haiku-5-5thinking off sonnet claude-sonnet-5-5adaptive, medium opus claude-sonnet-5-5unlessALLOW_OPUSis"true", thenclaude-opus-5-5adaptive, high ALLOW_OPUSis a core setting from.env(defaultfalse). An opus decision that runs on Sonnet becomes the Sonnet decision. The Decision Receipt recordsrequestedTier: opus,downgradeReason: opus_disabled, and the reasonopus_disabled. Escalation keeps its rules but stops at Sonnet while Opus is off: two failures at Sonnet re-plan. Conflict review does not run its Opus pass, and the Sonnet verdict applies. -
Messages API use:
- Function tools map to Anthropic tools (
input_schema).tool_useblocks map back to tool calls. A follow-up turn sends the full assistant turn again, with its thinking blocks. - Agent runs set
max_tokensper tier (workers/agents/src/models.ts): 8,000 haiku, 16,000 sonnet and opus. - Single calls such as the Root Planner use the route limit (
packages/model-router/src/rules.ts): 4,000 haiku, 8,000 sonnet and opus. The intake calls set 900 to 1,200. - The API never returns thinking text (
display: "omitted"), so Rhumbatron stores no reasoning. - Prompt caching:
complete()putscache_controlon the stable system prompt, which also covers the tool definitions. Pi puts it on the system prompt, the tools, and the latest message. Rhumbatron uses only the 5-minute TTL. - A refusal is an invalid output. Its cost stays exact.
- Function tools map to Anthropic tools (
-
Cost: exact per call, from the response
usagefields: uncached input, output (thinking included), cache reads, and 5-minute and 1-hour cache writes, at the prices below (USD per million tokens,ANTHROPIC_PRICESinpackages/model-router/src/models.ts). Haiku bills each token of a prompt above 100K tokens at its long-prompt rate. An unknown model gets the highest known rates. Run cost, the Change budget, Decision Receipts, the Costs page, and the monthly meter all use these numbers.Model Input Output Cache read Cache write 5 min Cache write 1 h Haiku 5.5 (prompt up to 100K) 0.10 0.50 0.01 0.125 0.20 Haiku 5.5 (prompt above 100K) 0.50 2.50 0.05 0.625 1.00 Sonnet 5.5 2 10 0.20 2.50 4 Opus 5.5 4 20 0.40 5 8 -
Spend safety:
- The per-run call cap, the 1,000 agent calls per day (
AGENT_DAILY_CALL_CAP), and the Change budget apply. - The monthly Anthropic budget is the core setting
ANTHROPIC_MONTHLY_BUDGET_USD: $100 by default (compose.yaml). Without the setting, the code uses $10. Code enforces the budget on the meterusage_countersin the product database (day=YYYY-MM, counteranthropic-usd-micro, micro-USD). Each call first reserves an estimate (full output cap, prompt uncached) with one conditional statement. Then the call settles to the exact cost. A Pi run settles its share after each call and at the end. A timeout keeps its reservation. - A refused reservation, a 429 or 402 that is not a rate limit, or an Anthropic billing error
is
spend_limited. The Task isblocked, and the Change isbudget_blockedwith the limitmonthly_model_spend. Nothing retries, and no other provider runs. - Clef and Clef-flash questions go to Claude Haiku through a forced tool call
(
packages/platform/src/ai/models.ts). The dailyclef-flashcounter caps them.
- The per-run call cap, the 1,000 agent calls per day (
Consequences
Section titled “Consequences”- Each model call costs money. The monthly meter is the hard stop. A spend limit in the Anthropic Console is a second line of defense.
- The Costs page shows the Claude spend of the month against the monthly budget.
- The readers of stored Decision Receipts and blocked rows accept the provider names
openrouterandgoogle-vertex-aiand the limit namevertex_spend_limitin stored rows.