Skip to content

feat(db): DB-backed UsageStore + agent_calls table extension [STORY-060]#19

Merged
khoks merged 1 commit intomainfrom
story/060-agent-calls-db-sink
Apr 26, 2026
Merged

feat(db): DB-backed UsageStore + agent_calls table extension [STORY-060]#19
khoks merged 1 commit intomainfrom
story/060-agent-calls-db-sink

Conversation

@khoks
Copy link
Copy Markdown
Owner

@khoks khoks commented Apr 26, 2026

Summary

  • Drizzle migration 0002_agent_calls_telemetry.sql extends the agent_calls table with the STORY-012 telemetry columns: session_id, task (new agent_task enum: complete/stream/embed/tool_call), cached_tokens, cost_usd numeric(18,8), pricing_version, tool_used. Existing columns from STORY-013 are preserved.
  • DrizzleLLMTelemetrySink (in packages/db/src/llm-telemetry-sink.ts) writes one row per LLMTelemetryEvent. Failures are logged + swallowed via the configured logger so a Postgres outage can't take down the LLM call path. cost_usd passes through as a toFixed(8) string to avoid float64 round-trip.
  • DrizzleUsageStore (in packages/db/src/llm-usage-store.ts) implements today() as sum(input + output) WHERE user_id = $1 AND called_at >= start_of_utc_day. record() is a deliberate no-op — the sink is the single source of truth, otherwise every call would double-count.
  • 6 unit tests for the sink + 5 integration tests for the store (gated by DATABASE_URL so pnpm test still passes in CI without a DB). Schema test extended to assert the new columns + agent_task enum.

Deferred to STORY-005

3 ACs require auth middleware (user_id from a logged-in session) and are explicitly allowed to defer by the Story spec:

  • GET /llm/usage/today route
  • 429 mapping for TokenBudgetExceededError
  • Manual playground smoke test

These land with the API wiring in STORY-005.

Story

Closes STORY-060 (table + sink + store; the 3 API-wiring ACs above ride along with STORY-005).

Test plan

  • pnpm --filter @learnpro/db test → 29 passed, 5 skipped (gated integration)
  • pnpm format:check clean
  • pnpm lint clean
  • pnpm typecheck clean
  • Full repo pnpm test → 12 tasks, all green
  • Local integration: with Docker compose + DATABASE_URL set → 5 integration tests pass against a real Postgres (run by user when convenient)

🤖 Generated with Claude Code

Drizzle migration 0002 extends `agent_calls` with the STORY-012
columns (`session_id`, `task` via new `agent_task` enum,
`cached_tokens`, `cost_usd numeric(18,8)`, `pricing_version`,
`tool_used`).

`DrizzleLLMTelemetrySink` writes one row per `LLMTelemetryEvent`
into `agent_calls`. Inserts are fire-and-forget so a Postgres
outage can't take down an LLM call — failures are logged via the
configured logger (default: console.error) and swallowed. cost_usd
is passed as a string via `toFixed(8)` to avoid float64 round-trip
through the pg driver.

`DrizzleUsageStore.today()` aggregates `sum(input_tokens +
output_tokens) WHERE user_id = $1 AND called_at >= start_of_utc_day`.
`record()` is intentionally a no-op since the sink is the single
source of truth (otherwise every call would double-count).

Tests: 6 unit tests for the sink (full-mapping / optional-omit /
org_id stamping / cost formatting / error swallowing / unparseable
decided_at fallback) + 5 integration tests for the store (zero
state / aggregation / yesterday boundary / per-user isolation /
record no-op) gated by DATABASE_URL so `pnpm test` still passes
in CI without a DB. Schema test extended to assert all new columns
present + `agent_task` enum mirrors `LLMTelemetryEventSchema.task`.

The 3 API-wiring ACs (`GET /llm/usage/today`, 429 mapping, manual
smoke) are deferred to STORY-005 since they need the auth
middleware that doesn't exist yet — explicitly allowed by the
Story spec.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@khoks khoks merged commit 6c092ad into main Apr 26, 2026
1 check passed
@khoks khoks deleted the story/060-agent-calls-db-sink branch April 26, 2026 22:40
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.

1 participant