Epic 3 — Model Gateway / Task T7

Cost Tracking

Per-request cost logging and queryable tier analytics for the Model Gateway. Every completion call is metered, priced against a configurable pricing table, and aggregated into reports that prove the Judge-Router's cost savings claim.

Package internal/gateway
Files changed 5 files, +1,060 lines
Dependencies stdlib only
PR #51
Test coverage
10/10
All Gherkin scenarios pass
Race detector
0
go test -race clean
External deps
0
context + sync + time
Cost savings
72.8%
vs all-frontier baseline

Request-to-Report Data Flow

01

Ctrl/Cmd + wheel to zoom. Scroll to pan. Drag to pan when zoomed. Double-click to fit.

Loading...
Write path
Read path
Pricing input
Config dependency

Components Delivered

02
CostTracker interface
Records per-request cost data and queries aggregated reports by time period. Two methods: Record and Report.
type CostTracker interface { Record(ctx context.Context, record CostRecord) error Report(ctx context.Context, period TimePeriod) (CostReport, error) }
InMemoryCostTracker struct
Thread-safe in-memory implementation. Uses sync.RWMutex — write lock on Record, read lock on Report and RecordCount. No external deps.
type InMemoryCostTracker struct { mu sync.RWMutex records []CostRecord } // + Record(), Report(), RecordCount()
EstimateCost func
Pure function. Computes USD cost from model ID, token counts, and a pricing table. Returns 0 for unknown models — no panic, no error.
// (input × $/M + output × $/M) / 1,000,000 func EstimateCost( model string, inputTokens, outputTokens int, pricing map[string]TokenCost, ) float64
NewCostRecordFromResponse func
Bridges gateway completion output to cost tracking. Constructs a CostRecord from a CompletionResponse, computing estimated cost via the pricing table.
func NewCostRecordFromResponse( resp CompletionResponse, tier ModelTier, pricing map[string]TokenCost, ) CostRecord
DefaultPricing + Model Constants const/var
Named constants for Claude model IDs eliminate string literals across the codebase. DefaultPricing serves as the fallback pricing table until the config loader (T5) loads from models.yaml.
const ( ModelHaiku = "claude-haiku-4-5-20251001" ModelSonnet = "claude-sonnet-4-6" ModelOpus = "claude-opus-4-6" ) var DefaultPricing = map[string]TokenCost{ ModelHaiku: {Input: 0.80, Output: 4.00}, ModelSonnet: {Input: 3.00, Output: 15.00}, ModelOpus: {Input: 15.00, Output: 75.00}, }

Pricing Reference

03
Model Tier Input $/M Output $/M 1K in + 500 out
claude-haiku-4-5-20251001 cheap $0.80 $4.00 $0.0028
claude-sonnet-4-6 mid $3.00 $15.00 $0.0105
claude-opus-4-6 frontier $15.00 $75.00 $0.0525
Formula. cost = (inputTokens × pricing.Input + outputTokens × pricing.Output) / 1,000,000. Each record stores its own pre-computed EstimatedCost — Report sums stored values rather than recomputing from raw tokens, avoiding float precision drift in large aggregates.

Cost Savings Verification

04
72.8%
Cost reduction
Tiered routing
$10.87
800 cheap + 150 mid + 50 frontier
All-frontier baseline
$39.98
1000 requests at Opus pricing
Cheap tier total
$1.34
800 × $0.00168 each
Frontier tier total
$7.50
50 × $0.150 each

Concurrency Model

05
goroutine 1
W
R
goroutine 2
W
R
goroutine 3
R
mutex
WLock
WLock
RLock
Write (Record) — exclusive
Read (Report/RecordCount) — shared
Waiting for lock
100 concurrent goroutines. The test launches 100 goroutines recording simultaneously. After all complete, RecordCount() must equal exactly 100. Validated with go test -race.

Files Delivered

06
internal/gateway/
  gateway.go — +TokenCost, types (T1 base + T7 additions)
  errors.go — +ProviderError, FallbackError, 7 sentinels
  cost.go — InMemoryCostTracker, EstimateCost, DefaultPricing, NewCostRecordFromResponse
  cost_test.go — 10 test functions, 402 lines, table-driven

cost-tracking-spec.md — feature specification with Gherkin scenarios
New file
Modified
Existing

Test Coverage

07
Test Function Gherkin Rule Status
TestInMemoryCostTracker_RecordAndReport Record and Report basic requests PASS
TestInMemoryCostTracker_TimeFiltering Time filtering PASS
TestInMemoryCostTracker_PeriodBoundariesInclusive Period boundaries inclusive PASS
TestInMemoryCostTracker_EmptyReport Empty period PASS
TestEstimateCost_KnownPricing EstimateCost pure function (4 subtests) PASS
TestInMemoryCostTracker_ConcurrentSafety Concurrent safety (100 goroutines) PASS
TestInMemoryCostTracker_PerTierBreakdown Per-tier breakdown PASS
TestInMemoryCostTracker_SpecScenario_AggregationByTier Spec scenario: 5 records, 3 cheap + 2 mid PASS
TestNewCostRecordFromResponse Constructor bridge: response to record PASS
TestNewCostRecordFromResponse_UnknownModel Unknown model returns zero cost PASS