Model-aware token budgets from a generate-and-diff export of the Go ModelCapability catalog, with labeled character/rune estimates (ADR-0067).
Milestone 3.5.C.1 — Token Budget Calculator (Model-Aware, Estimate-Based)
Status: Completed
Goal: Track C — Context Assembly Engine
Phase: 3.5 — Extraction & Context Assembly
Estimated effort: 2 days
Track: Track C — Context Assembly Engine
Depends on: Phase 2.5 ModelCapability registry (ADR-0041) + tokenizer family keys (ADR-0043)
ADR: ADR-0067
Follow-up: #690 exact HF/Qwen tokenization
Why This Milestone Exists
Every downstream stage in context assembly depends on knowing the usable token budget for the specific model being called. Phase 2.5 already curates that metadata in Go (BuiltInCapabilityCatalog). This milestone wires Python budget math to that catalog without inventing a live tokenizer/capability RPC (reserved for 3.5.C.6).
Non-Goals
- Retrieval orchestration itself (3.5.C.2)
- gRPC
ContextAssemblyService/ degradation contract (3.5.C.6) - Exact tiktoken or Hugging Face token counting (#690)
- Cross-model comparison analytics
File layout (as shipped)
services/context/
app/data/model_capabilities.v1.json # generate-and-diff from Go catalog (package data)
app/budget.py # BudgetCalculator / TokenBudget
app/capability_catalog.py # load/validate JSON
app/estimate.py # labeled chars_div_4 / runes_div_3_5
packages/provider/scripts/export_capabilities/ # Go exporter + -checkRegenerate:
go run ./packages/provider/scripts/export_capabilities \
-o services/context/app/data/model_capabilities.v1.jsonDesign
@dataclass(frozen=True)
class TokenBudget:
context_window: int
response_reserve: int
safety_buffer: int
usable_budget: int
directive_tokens: int
messages_tokens: int
is_constrained: bool
estimate_kind: str # labeled: chars_div_4 | runes_div_3_5
class BudgetCalculator:
def __init__(self, catalog: CapabilityCatalog | None = None): ...
def calculate(self, model: str, messages: list[Message], directive: str) -> TokenBudget:
cap = self._catalog.for_model(model) # generated JSON; unknown model raises
# estimate_tokens uses family policy from the same JSON (not a live TokenizerClient)
...There is no TokenizerClient or network CapabilityRegistry in C.1. The catalog is the committed generate-and-diff artifact of packages/provider.BuiltInCapabilityCatalog.
Design note
Safety buffers are tokenizer-family-specific fractions from the export (o200k_base/cl100k_base 2%, claude 5%, unknown 10%), not a flat 10% guess. Token counts are labeled approximations (estimate_kind on every TokenBudget) until #690 lands exact backends.
Failure mode: unknown model IDs raise UnknownModelError (fail closed). Estimates always run locally — there is no “tokenizer unreachable” network path in C.1.
Success signals
-
TokenBudgetcomputed from generate-and-diff catalog rows (not a hand-copied Python table) - Unknown models fail closed; estimates always labeled via
estimate_kind - Unit tests:
gpt-4ovsclaude-sonnet-4-5produce differentsafety_buffervalues (catalog has no Llama row yet) -
is_constrainedwhen usable budget is belowMIN_VIABLE_MEMORY_BUDGET(256) - CI
capability-catalog-freshnessfails on catalog/JSON drift
Last updated on