Skip to content

feat(usage): Settings → Usage page + DB-rate/write-stamp contract (#291)#300

Open
axewilledge wants to merge 15 commits into
aep-rewritefrom
console/usage-page-291
Open

feat(usage): Settings → Usage page + DB-rate/write-stamp contract (#291)#300
axewilledge wants to merge 15 commits into
aep-rewritefrom
console/usage-page-291

Conversation

@axewilledge

@axewilledge axewilledge commented Jul 22, 2026

Copy link
Copy Markdown

Closes #291. Frontend + contract for Usage & cost v2. Supersedes the per-phase cost surfaces shipped in #267 and reverses ADR-0011's read-time pricing.

What changed

Contract (packages/contracts/api/v1/openapi.yaml)

  • New GET /usage/projects (list-project-usage) → ProjectUsageList of ProjectUsageCard (per-project lifetime totals; deleted flag; Usage with a stamped, nullable costUsd).
  • Removed GET /projects/{p}/usage + the ProjectUsage per-phase schema.
  • Usage.costUsd re-documented as a write-time stamp (never repriced), null when unstamped.

Console

  • New Settings → Usage page — one card per project (incl. deleted projects, greyed), folded USD cost chip; click to expand the input/output/cache token breakdown. Empty / loading / error states. Wired as a third Settings tab.
  • Removed the v1 cost surfaces: the Builds page build-chip + per-task cost captions, and the Spec view drafting-cycle chip.
  • features/usage api swapped from useProjectUsage(project) to useProjectUsageList(); typed MSW mocks + fixtures cover live, deleted, and pre-stamping (null-cost) projects, plus empty/error scenarios.

aep-api

Docs

Why (the reversal)

Read-time USD derivation reprices all historical usage whenever a rate changes — a completed cycle silently shows dollars never actually spent. v2 stamps cost_usd at capture from a DB model_rates table, so history is immutable and rates become data, not env config. Full decision trail: #291 (grilling comment).

Backend handshake

Real capture + stamping + the non-empty /usage/projects response is #299 (open, not in this PR). Until it lands, the endpoint correctly returns {"projects":[]} and the page shows its empty state. Verified against the live dev aep-api:

GET /aep-api-service/api/v1/usage/projects → {"projects":[]}

Verification

  • tsc, eslint, knip --production, 163 console tests, Go edge/arch/projects gates, license-check — all green.
  • Mock-mode UI verified end-to-end (screenshots below): card list, expanded breakdown, deleted-project card, null-cost (tokens-only) card, empty + error states, and the Builds page with chips removed.
01-usage-page-project-cards 02-usage-expanded-token-breakdown 03-usage-empty-state

axewilledge and others added 4 commits July 22, 2026 13:21
…#291)

- /usage/projects (list-project-usage): one card per project with any
  recorded usage, deleted projects included; lifetime token totals with
  write-time-stamped costUsd (null when unstamped)
- remove /projects/{p}/usage + ProjectUsage (per-phase shape, #245)
- Usage schema: costUsd is now a capture-time stamp, never repriced
- aep-api: projectusage slice placeholder swapped to empty-list
  list-project-usage; ledger + README rows follow

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Builds page loses the build UsageChip + per-task cost captions; Spec view
loses the draft-cycle chip. features/usage api swaps to the org-wide
/usage/projects read; mocks/fixtures follow (org cards incl. deleted +
unstamped projects, per-phase rollup gone).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Project cards with a folded USD cost chip (click to expand the token
breakdown), greyed cards for deleted projects, empty/error states.
Wired into SettingsLayout nav as a third tab. Backed by
useProjectUsageList / GET /usage/projects; typed MSW mocks cover live,
deleted, and pre-stamping (null-cost) projects.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…#291)

ADR-0011 reversed: USD is stamped at capture time from DB-backed model
rates (immutable history) instead of derived at read from env-config
rates; original read-time rationale kept as re-proposable History. PRD
In-flight #245 line replaced by the #291 Settings → Usage line.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b92d233d-33cd-4ff1-a147-6ed00521ff5d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch console/usage-page-291

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

launch.json holds a local dev launch config with hardcoded absolute
paths and never belonged in version control.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
axewilledge and others added 7 commits July 23, 2026 09:51
…eakdown (#291)

- contract: ProjectUsageCard gains phases (PhaseUsage: spec/build/validation)
- UsageChip no longer hides zero usage — an idle project shows $0; a null
  unpriced cost still degrades to tokens
- UsageBreakdown adds a small 'Cost by phase' section (spec/design, build,
  validation) under the token totals
- mock fixture: idle project + ~20/70/10 phase split; tests cover both

Amends #291 grilling decision #3: per-phase returns as secondary detail only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…#299)

Backend for Usage & cost v2 (#291), built on the contract in #300. Resurrects
the token-capture pipeline (from the closed #269) and stamps USD at write time.

- model_rates table (platform/modelcost.ModelRate) + idempotent seed of
  claude-sonnet-5 at today's intro rates; boot-loaded immutable Stamper
- cost_usd stamped on agent_turns + executions at capture; never re-derived
- GET /usage/projects (projects.UsageService): every live project carded
  (idle → $0), since-deleted projects with spend greyed, ordered stamped-cost
  desc; SUM(cost_usd) NULL = no-backfill semantic; zero-token rows filtered
- per-phase split: delivery SumUsageByProjectPhase (validation kind vs build);
  card carries spec/build/validation
- agents + agent-stream emit per-turn TurnUsage on the terminal manifest

Tests: stamp math, fold/order/deleted/idle labelling, phase split, migrate
step-order. Gates: build, vet, edge/arch, deadcode, license, 149 agents.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e-page-291

# Conflicts:
#	apps/console/src/features/builds/components/BuildsPage.tsx
#	apps/console/src/features/settings/components/SettingsLayout.tsx
#	apps/console/src/features/spec/components/SpecView.tsx
#	apps/console/src/features/tasks/components/TasksList.tsx
#	services/aep-api/internal/gen/server_gen.go
…ep-api/usage-capture-291

# Conflicts:
#	services/aep-api/internal/app/app.go
#	services/aep-api/internal/gen/server_gen.go
#	services/aep-api/internal/migrate/run_all.go
#	services/aep-api/internal/projects/httpapi/aggregate.go
#	services/aep-api/internal/projects/module.go
feat(usage): capture + write-time USD stamping + org usage endpoint (BE for #291/#299)
…e-page-291

# Conflicts:
#	apps/console/src/features/settings/components/SettingsLayout.tsx
#	apps/console/src/features/spec/components/SpecView.test.tsx
#	apps/console/src/features/spec/components/SpecView.tsx
#	services/aep-api/internal/gen/server_gen.go
#	services/aep-api/internal/spec/repository_turn.go
#	services/aep-api/internal/spec/turn_runner.go
No content change — nudges GitHub to re-sync the PR head + recompute
mergeability after the aep-rewrite merge (branch was verified clean:
git compare shows 11 ahead, 0 behind aep-rewrite).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements Usage & cost v2 across the contract, agents runtime, console UI, and aep-api to support a new Settings → Usage experience and a new org-wide usage endpoint. It shifts cost semantics to write-time USD stamping backed by DB model rates, and removes the previous scattered v1 cost surfaces.

Changes:

  • Introduces GET /usage/projects + new ProjectUsageList/ProjectUsageCard/PhaseUsage contract shapes; removes GET /projects/{projectName}/usage.
  • Captures/threads per-turn usage + model id in the agents terminal manifest and persists/stamps usage + cost in aep-api (turns + executions) with model_rates seeding + an in-memory stamper.
  • Adds the console Settings → Usage page (cards, deleted projects, null-cost/token-only degradation, per-phase breakdown) and removes v1 cost chips/captions from Builds/Spec/Tasks views.

Reviewed changes

Copilot reviewed 68 out of 68 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
services/agents/test/run-conversation-turn.test.ts Adds tests asserting manifest usage totals and model id threading.
services/agents/src/shared/model.ts Adds resolveModelId and reuses it in createModel for consistent model attribution.
services/agents/src/server.ts Threads optional modelId from app deps into turns for usage attribution.
services/agents/src/main.ts Provides modelId to createApp using resolveModelId().
services/agents/src/conversation/run-conversation-turn.ts Projects whole-turn usage onto manifest and emits it with the terminal manifest.
services/agents/src/conversation/manifest.ts Adds toTurnUsage and extends manifest part to optionally include usage.
services/aep-api/internal/spec/turn_runner.go Folds manifest usage onto turn terminals (including parity-rejected terminals).
services/aep-api/internal/spec/repository_turn.go Adds usage persistence/stamping on turns and adds per-project usage rollup.
services/aep-api/internal/spec/repository_turn_dbtest_test.go Updates repo construction for new stamper dependency.
services/aep-api/internal/spec/genai_component_test.go Updates in-memory repo stub with new usage rollup method.
services/aep-api/internal/spec/agent_turn.go Adds persisted token/model/cost columns to agent_turns.
services/aep-api/internal/projects/usage_service.go Implements org-wide per-project usage card assembly + sorting + deleted handling.
services/aep-api/internal/projects/usage_service_test.go Adds tests for folding, ordering, deleted labeling, idle projects, and empty org behavior.
services/aep-api/internal/projects/README.md Updates method-origin ledger/docs for new list-project-usage slice behavior.
services/aep-api/internal/projects/projectusage/handler.go Replaces placeholder handler with UsageService delegation for /usage/projects.
services/aep-api/internal/projects/projectusage/doc.go Updates slice documentation for org-wide usage endpoint.
services/aep-api/internal/projects/project_service_test.go Extends fake delivery exec repo surface to include usage methods.
services/aep-api/internal/projects/project_dbtest_test.go Updates execution repository constructor signature.
services/aep-api/internal/projects/module.go Adds UsageSvc to projects module deps.
services/aep-api/internal/projects/httpapi/aggregate.go Wires projectusage handler with UsageSvc.
services/aep-api/internal/platform/modelcost/modelcost.go Introduces DB-backed model rate entity + immutable in-memory stamper.
services/aep-api/internal/platform/modelcost/modelcost_test.go Tests stamping math, rounding, and nil/zero behavior.
services/aep-api/internal/platform/agentfold/sse.go Extends stream part with manifest usage payload type.
services/aep-api/internal/platform/agentfold/manifest.go Carries usage through manifest extraction.
services/aep-api/internal/organization/org_disconnect_dbtest_test.go Updates execution repository constructor signature.
services/aep-api/internal/migrate/step_order_test.go Adds model_rates_seed to pinned migration step order.
services/aep-api/internal/migrate/run_all.go Adds model_rates table to base models and seeds it via a new migration step.
services/aep-api/internal/migrate/model_rates.go Adds seed + load helpers for model rates (gorm-based).
services/aep-api/internal/gen/server_gen.go Regenerates server interfaces/routes to remove old usage op and add /usage/projects.
services/aep-api/internal/gen/models_gen.go Regenerates models for ProjectUsageList/ProjectUsageCard/PhaseUsage; updates Usage.costUsd docs.
services/aep-api/internal/edge/method_origin_test.go Updates op owner map to replace GetProjectUsage with ListProjectUsage.
services/aep-api/internal/delivery/repository_execution.go Adds execution usage persistence + stamping + per-project phase rollup; updates constructor for stamper.
services/aep-api/internal/delivery/repository_execution_dbtest_test.go Updates repo construction for new stamper dependency.
services/aep-api/internal/delivery/execution/funnel.go Threads SpecTag into execution rows for lineage/rollups.
services/aep-api/internal/delivery/execution/funnel_dbtest_test.go Updates execution repository constructor signature.
services/aep-api/internal/delivery/execution/events.go Threads SpecTag into execution rows created by events path.
services/aep-api/internal/delivery/execution.go Adds SpecTag plus persisted token/model/cost columns and Usage() accessor.
services/aep-api/internal/delivery/codingagent/watcher.go Records captured token usage from final logs onto execution rows.
services/aep-api/internal/delivery/codingagent/usage_capture.go Adds log parsing helper to extract the terminal result.usage.
services/aep-api/internal/delivery/codingagent/usage_capture_test.go Tests usage extraction from logs and last-result-wins behavior.
services/aep-api/internal/delivery/codingagent/fakes_test.go Extends fake exec repo surface to include usage methods.
services/aep-api/internal/contracts/usage.go Adds shared token usage + stamped aggregate types for cross-domain rollups.
services/aep-api/internal/contracts/progress.go Adds usage field to progress events for runner result reporting.
services/aep-api/internal/app/infra.go Loads model rates after migration and builds RateStamper for injection.
services/aep-api/internal/app/app.go Injects RateStamper into repositories and wires UsageService into projects handlers.
packages/contracts/api/v1/openapi.yaml Updates contract: new /usage/projects, new schemas, updated Usage.costUsd semantics.
packages/agent-stream/src/stream-types.ts Extends stream part typing to include manifest usage.
packages/agent-stream/src/index.ts Re-exports TurnUsage.
packages/agent-stream/src/contracts/sse-events.ts Defines TurnUsage and adds optional usage on terminal manifest.
apps/console/src/routes/settings.usage.tsx Adds Settings → Usage route.
apps/console/src/mocks/handlers/usage.ts Adds MSW handler for /usage/projects with scenario selection.
apps/console/src/mocks/handlers/project.ts Removes v1 per-project usage mock handler.
apps/console/src/mocks/fixtures/usage.ts Reworks fixtures for org-wide usage cards, deleted/null-cost/idle scenarios.
apps/console/src/mocks/browser.ts Registers usage handlers in the MSW worker.
apps/console/src/features/usage/lib/format.ts Exposes PhaseUsage typing for UI rendering.
apps/console/src/features/usage/components/UsageSection.tsx Implements Settings → Usage page UI (loading/error/empty/cards/deleted).
apps/console/src/features/usage/components/UsageSection.test.tsx Adds comprehensive component tests for all primary UI states.
apps/console/src/features/usage/components/UsageChip.tsx Updates chip behavior for the usage page and passes per-phase split to breakdown.
apps/console/src/features/usage/components/UsageBreakdown.tsx Adds per-phase cost split section and updated null-cost copy.
apps/console/src/features/usage/api/queries.ts Switches data hook from per-project usage to org-wide list query.
apps/console/src/features/usage/api/keys.ts Updates query key to org-wide usage list.
apps/console/src/features/tasks/components/TasksList.tsx Removes per-task cost caption + tooltip breakdown.
apps/console/src/features/spec/components/SpecView.tsx Removes draft-cycle usage chip integration.
apps/console/src/features/spec/components/SpecView.test.tsx Removes now-unused usage hook mock.
apps/console/src/features/settings/components/SettingsLayout.tsx Adds “Usage” tab and updates subtitle copy.
apps/console/src/features/builds/components/BuildsPage.tsx Removes build usage chip from builds UI.
apps/console/PRD.md Updates in-flight PRD line to reflect Usage & cost v2 scope.
apps/console/design/decisions/ADR-0011-usage-stored-as-tokens-usd-derived.md Amends ADR-0011 to reflect write-time stamping + DB-backed rates; preserves prior history.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread services/aep-api/internal/spec/repository_turn.go Outdated
Comment thread services/aep-api/internal/delivery/repository_execution.go Outdated
Comment thread packages/contracts/api/v1/openapi.yaml Outdated
Comment thread services/aep-api/internal/contracts/usage.go
Comment thread services/aep-api/internal/platform/agentfold/sse.go Outdated
Comment thread services/aep-api/internal/delivery/codingagent/usage_capture.go
- aggregation model degrade: count DISTINCT model_id INCLUDING '' in the
  per-project (turn) and per-(project,phase) (execution) rolls-ups, so a mix
  of known + unknown-model rows reports model '' (matches TokenUsage.Add),
  instead of surfacing the one known model
- usageFromLog: raise the scanner buffer to 16 MiB and check scanner.Err()
  so a token-too-long line can't silently drop the terminal result usage
- agentfold TurnUsage.model: drop omitempty — '' (unknown/mixed) is
  meaningful and the TS @aep/agent-stream contract marks model required
- contract: list-project-usage + ProjectUsageList descriptions now match the
  real semantics (every live project carded incl. idle $0; tiered ordering)
- contracts.TokenUsage doc: USD is stamped at capture, not derived at read

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@axewilledge

Copy link
Copy Markdown
Author

Addressed all six review comments in 34872fe0:

# Comment Fix
1 repository_turn.go — distinct-model count ignored model_id == '' Now COUNT(DISTINCT model_id) / MAX(model_id) including '', so a mix of known + unknown-model rows degrades the project's model to '' (matches contracts.TokenUsage.Add).
2 repository_execution.go — same in the per-(project,phase) roll-up Same fix applied.
3 contract — description said "one card per project that has any recorded usage" but idle live projects also get $0 cards list-project-usage + ProjectUsageList descriptions rewritten to match: every live project carded (idle as $0) plus since-deleted projects with usage; tiered ordering documented.
4 contracts.TokenUsage doc still said USD "derived at read time" Updated to write-time stamping onto cost_usd (amended ADR-0011).
5 agentfold.TurnUsage.model had json:"model,omitempty" but TS contract marks it required Dropped omitempty'' (unknown/mixed) is meaningful and now always on the wire.
6 usageFromLog — 1 MiB scanner buffer, no scanner.Err() check Raised to 16 MiB and now logs on scanner.Err(), so a token-too-long line can't silently drop the terminal result usage.

Verified: go build, go vet, make gen-api, console tsc, and the edge/arch/projects/codingagent test suites all green.

axewilledge and others added 2 commits July 23, 2026 15:35
The coding-agent runner's terminal `result` event carried no token usage,
so aep-api's usageFromLog found nothing to stamp and the Usage page's Build
phase stayed empty even after a coding + build run completed.

Extract usage from the SDK result message (`usage` snake_case tokens +
`modelUsage` for the model id) and attach it as a camelCase `usage` object
on the success result, matching contracts.TokenUsage byte-for-byte so the Go
parse path (contracts.ProgressEvent.Usage) reads it and stamps cost_usd onto
the execution row. Model is "" when the run spanned multiple models.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e-page-291

# Conflicts:
#	services/aep-api/internal/gen/server_gen.go
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants