BE handshake for #291 (Usage & cost v2). Supersedes #249 / PR #269 (closed unmerged; capture pipeline to be resurrected from aep-api/usage-capture-249).
Requested backend work (aep-api)
1. Resurrect the capture pipeline from aep-api/usage-capture-249 — turn-level capture (agents usage: frames → agent_turns token columns), execution-level capture (runner AEP_USAGE log line → JobWatcher parse → executions token columns), unchanged.
2. model_rates table (new) — model_id PK, input_per_mtok / output_per_mtok / cache_read_per_mtok / cache_write_per_mtok (USD), updated_at. Seeded by migration with the platform's current model at current Anthropic rates. Ops-managed (SQL); no CRUD API this round. Replaces MODEL_PRICING_MODEL_ID / MODEL_RATE_*_PER_MTOK env config and config.ModelPricingConfig.
3. Write-time USD stamping — at capture time, resolve the record's model_id against model_rates and persist cost_usd on the same row (agent_turns.cost_usd, executions.cost_usd). Unknown model or missing rate row → cost_usd NULL (never guessed). No read-time pricing: platform/modelcost is retired. Historical rows are immutable — a later rate change affects only subsequent stamps. No backfill of pre-v2 rows (decided on #291).
Proposed contract diff
- New
GET /usage/projects (org-scoped): { projects: [{ projectId, displayName, deleted: bool, costUsd: number|null, inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens }] } — one row per project having any usage, including deleted projects (identified from usage rows' stored project slug).
- Remove
GET /projects/{p}/usage (the v1 per-phase shape; its only consumer — the v1 chips — is being removed).
Rationale
Read-time derivation reprices history whenever a rate changes; costs must be immutable once incurred. Rates-as-data removes single-model config coupling. Full context + decisions: #291.
BE handshake for #291 (Usage & cost v2). Supersedes #249 / PR #269 (closed unmerged; capture pipeline to be resurrected from
aep-api/usage-capture-249).Requested backend work (aep-api)
1. Resurrect the capture pipeline from
aep-api/usage-capture-249— turn-level capture (agentsusage:frames →agent_turnstoken columns), execution-level capture (runnerAEP_USAGElog line → JobWatcher parse →executionstoken columns), unchanged.2.
model_ratestable (new) —model_idPK,input_per_mtok / output_per_mtok / cache_read_per_mtok / cache_write_per_mtok(USD),updated_at. Seeded by migration with the platform's current model at current Anthropic rates. Ops-managed (SQL); no CRUD API this round. ReplacesMODEL_PRICING_MODEL_ID/MODEL_RATE_*_PER_MTOKenv config andconfig.ModelPricingConfig.3. Write-time USD stamping — at capture time, resolve the record's
model_idagainstmodel_ratesand persistcost_usdon the same row (agent_turns.cost_usd,executions.cost_usd). Unknown model or missing rate row →cost_usdNULL (never guessed). No read-time pricing:platform/modelcostis retired. Historical rows are immutable — a later rate change affects only subsequent stamps. No backfill of pre-v2 rows (decided on #291).Proposed contract diff
GET /usage/projects(org-scoped):{ projects: [{ projectId, displayName, deleted: bool, costUsd: number|null, inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens }] }— one row per project having any usage, including deleted projects (identified from usage rows' stored project slug).GET /projects/{p}/usage(the v1 per-phase shape; its only consumer — the v1 chips — is being removed).Rationale
Read-time derivation reprices history whenever a rate changes; costs must be immutable once incurred. Rates-as-data removes single-model config coupling. Full context + decisions: #291.