Skip to content

feat(agent-sessions): Overview page - #841

Open
JeremyFunk wants to merge 16 commits into
mainfrom
feat/agent-sessions-overview
Open

feat(agent-sessions): Overview page#841
JeremyFunk wants to merge 16 commits into
mainfrom
feat/agent-sessions-overview

Conversation

@JeremyFunk

@JeremyFunk JeremyFunk commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • New Agent Sessions › Overview page at /agent-sessions/overview: the high-level trend view for every agent session — volume, cost, tokens, reliability and latency — for a quick health check and as the starting point of a debugging session.
  • Seven KPI tiles with deltas against the previous period; nine aligned small-multiple trends on one bucket clock (sessions started, cost per session, token distribution, tool calls per session, error rate on three layers, session duration p50/p95, LLM calls per session, model mix, cache hit ratio) with a previous-period ghost and a synced crosshair.
  • A "What changed" rail that ranks the biggest movers across every breakdown dimension; per-dimension breakdown tables whose rows filter the whole page; top sessions (most expensive / longest / errored) that open the session detail.
  • Three index-only endpoints under /internal/ai-sessions/overview/* (summary, breakdown, model-mix) over ai_trace_index, reconciled with the Sessions list: the same session key and usage netting, sessions bucketed by their first span so the buckets sum to the totals, and a half-open previous window.
  • A sub-navigation strip (Overview · Sessions) on both routes, and a lab page at /lab/agent-overview with a healthy and a regression fixture.

Notes

  • The LLM error rate is computed over the raw model-span population (erroredLlmCalls / llmCallSpans); llmCalls stays the netted volume the list shows.
  • Cost is instrumentation-reported, so the page states the share of priced LLM calls instead of presenting an unpriced window as free.
  • The tab strip is the only overlap with the Tools page branch; merging is a union of tab entries.

Verification

  • Scoped vitest suites for the query builders, the domain contract, the API routes and the web page; the ClickHouse e2e reconciliation test (ai-overview.clickhouse.e2e.test.ts) and the SQL catalog sweep against a local ClickHouse; the lab page checked at 1440 and 1280.

Review round

  • Two review passes (an eight-angle correctness/cleanup review and an Effect v4 review) were applied on the branch: previous-period alignment by whole buckets, no deltas against an empty previous window, request windows validated at the boundary (400, not 500), the model-mix tail folded in SQL, dead per-call latency quantiles removed, breakdown scans trimmed, per-section error states with retry, boundary schema mirrored on the web adapter, span names aligned with the sibling reads.
  • Deliberately deferred: compare stays a client-side display flag (the previous window is always read); the "Open in Sessions" link lands on the list's own rolling window; netting IsError into the usage reporter tuple was rejected because it would rewrite every list query.

Summary by CodeRabbit

  • New Features

    • Added an AI Sessions Overview dashboard with KPI metrics, trends, comparisons, model mix, breakdowns, movers, filters, and top-session views.
    • Added filtering by model, framework, agent, service, environment, and tool, including failing-session filtering.
    • Added comparison against the previous equivalent time window with deltas and visual indicators.
    • Added responsive charts, loading states, empty states, and section-level error recovery.
    • Added a lab preview with healthy and regression scenarios for exploring the overview experience.
  • Bug Fixes

    • Improved validation for invalid date ranges, bucket sizes, and filter values.

Two internal reads behind the Agent Sessions overview page, both off
`ai_trace_index` alone: `/overview/summary` returns the window's measures,
the equal-length window before it, and both as a bucketed series;
`/overview/breakdown` returns the busiest keys of one dimension, each
measured over both windows.

Every number reconciles with the sessions list over the same window,
because it is derived the same way: the session key is resolved per trace
with `sessionKey`, the filters are the list's per-trace existence tests,
and usage runs through `usageReportersExpr` / `sessionUsageSum`, so a
wrapper's roll-up, a gateway's mirror of a call and a provider retry each
count once. A session is filed under the bucket its first span started in,
so the series sums to the totals; the totals are their own un-bucketed read
because quantiles do not merge.

The breakdown keys a row by the value the span itself carries and nets
inside that key, so a session that used two models is a session under each
while its tokens are charged to the call that reported them. `model` reads
model calls, `tool` reads tool calls, and a span that names no value keys
under `''`.

`sessionPricedLlmCalls` joins the shared span columns: coverage for a cost
that only exists where the instrumentation reported one.
A model call's failures and its volume are different populations, and the
LLM error rate was dividing one by the other. `erroredLlmCalls` is a raw
`sumIf` over the model-call SPANS — a failure cannot be netted, the index
carries no error flag into the reporters — while `llmCalls` is the netted
volume, so a gateway's mirror of a failed call is two failures of one
call and the rate passes 100%. The measures carry `llmCallSpans` now,
the same spans counted, and the rate is `erroredLlmCalls / llmCallSpans`.
The e2e seeds the mirrored call and the call it mirrors as failed and
pins all three numbers: three failures over five spans, which net to
four calls, and two over two under the model they belong to.

The comparison window is `[start - length, start)`. Its upper bound is
exclusive now, so the second where the two windows meet is measured in
the caller's window alone rather than in both.

`AiOverviewBreakdownRow` documents which measures a row can mean under
which dimension, since `model` and `tool` restrict the population to
their own spans and the rest of the columns are structurally zero. The
breakdown's `limit` keeps the one cap the request contract enforces
instead of clamping again behind it.
`/overview/model-mix` returns the window's model-call SPANS split by
model, bucket by bucket — the band chart beside the tiles. It takes the
summary's request shape (the window, a bucket width, the same six
filters and `hasErrors`, now one shared bag) and answers the current
window alone: the chart has no comparison band.

The population is the raw one, not the netted one. `llmCallSpans` is
what the tiles already report and what the LLM error rate divides by, so
a gateway's mirror of a call is a band under the model it names, twice;
netting would charge a mirrored call to one model and leave the bands
disagreeing with the rate above them. A call whose instrumentation named
no model has no share of a model mix and is left out, which is the only
difference between this read's total and the tile's.

A span is filed under the bucket its own timestamp falls in — the rows
are spans, so there is no session to keep whole — while sessions are
selected the way every other overview read selects them, so the mix
describes the sessions the tiles measure. `LIMIT
AI_OVERVIEW_MODEL_MIX_MAX_ROWS` is a blow-up guard and not a top-N: the
client folds the minor models into an "other" band and needs every model
of every bucket to do it.
…adder

The priced share divided the netted priced calls by the raw span count, so
a fully priced window read as the netting factor rather than 100%.

The window label came from the page's default preset, which the resolver
ignores once both endpoints are in the URL — an absolute range was labelled
"prev 7d" whatever its length.

The grid asked for ~100 buckets, which nine ~104px plots cannot separate,
at widths like 105 minutes an axis had to call "2h". It now snaps to a
ladder a reader recognises: a day at 1h, a week at 6h, a month at 1d.

Also: the bucketed input mirrors the domain's `BucketSeconds`, the active
tab merges its search instead of replacing it, and the two per-call
duration quantiles leave the view model, which never rendered them.
The nine small multiples reserved a fixed 32px gutter, so any y-axis label
wider than four characters was drawn off the canvas's left edge and lost its
leading character: $0.50 read as 0.50, 150.0K as 50.0K. The gutter is now
measured from the widest top-tick label in the grid and shared by all nine, so
the plots still line up column to column.

Durations on the board went through formatLatency (42.00s, 2.5min) while the
Sessions list reads them in clock units. The page's own wrapper now delegates
to formatSessionDuration above a minute and keeps a tenth of a second below
one, and the STARTED column matches the list's relative-time rendering.
@coderabbitai

coderabbitai Bot commented Sep 11, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 6fa6b22e-c2cd-4668-9449-ab2c3993fef1

📥 Commits

Reviewing files that changed from the base of the PR and between f64111a and e73fa66.

📒 Files selected for processing (45)
  • apps/api/src/routes/internal/ai-sessions.http.test.ts
  • apps/api/src/routes/internal/ai-sessions.http.ts
  • apps/api/src/services/warehouse/ai-overview.clickhouse.e2e.test.ts
  • apps/web/src/api/warehouse/ai-agent-overview.test.ts
  • apps/web/src/api/warehouse/ai-agent-overview.ts
  • apps/web/src/components/agent-sessions/overview/agent-overview-view.test.tsx
  • apps/web/src/components/agent-sessions/overview/agent-overview-view.tsx
  • apps/web/src/components/agent-sessions/overview/overview-breakdowns.tsx
  • apps/web/src/components/agent-sessions/overview/overview-delta.tsx
  • apps/web/src/components/agent-sessions/overview/overview-filter-toolbar.tsx
  • apps/web/src/components/agent-sessions/overview/overview-metric-strip.tsx
  • apps/web/src/components/agent-sessions/overview/overview-movers-rail.tsx
  • apps/web/src/components/agent-sessions/overview/overview-scope-row.tsx
  • apps/web/src/components/agent-sessions/overview/overview-small-multiple.tsx
  • apps/web/src/components/agent-sessions/overview/overview-top-sessions.tsx
  • apps/web/src/components/agent-sessions/overview/overview-trends.tsx
  • apps/web/src/components/agent-sessions/tools/agent-sessions-tabs.tsx
  • apps/web/src/hooks/use-detected-models.ts
  • apps/web/src/lab/agent-overview-fixture.ts
  • apps/web/src/lab/agent-overview-lab.tsx
  • apps/web/src/lab/registry.ts
  • apps/web/src/lib/agent-sessions/overview-analytics.test.ts
  • apps/web/src/lib/agent-sessions/overview-analytics.ts
  • apps/web/src/lib/agent-sessions/overview-buckets.test.ts
  • apps/web/src/lib/agent-sessions/overview-buckets.ts
  • apps/web/src/lib/agent-sessions/overview-chart-specs.test.ts
  • apps/web/src/lib/agent-sessions/overview-chart-specs.ts
  • apps/web/src/lib/agent-sessions/overview-search.test.ts
  • apps/web/src/lib/agent-sessions/overview-search.ts
  • apps/web/src/lib/agent-sessions/use-agent-overview.test.ts
  • apps/web/src/lib/agent-sessions/use-agent-overview.ts
  • apps/web/src/lib/services/atoms/warehouse-query-atoms.ts
  • apps/web/src/routeTree.gen.ts
  • apps/web/src/routes/agent-sessions/index.tsx
  • apps/web/src/routes/agent-sessions/overview.tsx
  • apps/web/src/routes/lab/agent-overview.tsx
  • packages/domain/src/http/ai-sessions.ts
  • packages/domain/src/http/query-engine.ts
  • packages/query-engine-integrations/src/__sql_baseline__/integrations.sql
  • packages/query-engine-integrations/src/ai/ai-overview.test.ts
  • packages/query-engine-integrations/src/ai/ai-overview.ts
  • packages/query-engine-integrations/src/ai/ai-sessions.ts
  • packages/query-engine-integrations/src/ai/ai-span-columns.ts
  • packages/query-engine-integrations/src/ai/index.ts
  • packages/query-engine-integrations/src/benchmark/index.ts

Included review availability: Your plan provides up to 4 included reviews per hour; 3 remain after this review.


📝 Walkthrough

Walkthrough

This pull request adds an AI sessions overview feature. It introduces warehouse queries and internal API endpoints, web-side decoding and analytics, a new /agent-sessions/overview route and UI, and lab and test coverage across backend and frontend paths.

Changes

AI sessions overview

Layer / File(s) Summary
Overview contracts and warehouse queries
packages/domain/src/http/ai-sessions.ts, packages/domain/src/http/query-engine.ts, packages/query-engine-integrations/src/ai/*, packages/query-engine-integrations/src/__sql_baseline__/integrations.sql, packages/query-engine-integrations/src/benchmark/index.ts
Adds overview request and response schemas, bucket validation reuse, shared session filter helpers, priced LLM call counting, and ClickHouse totals, series, breakdown, and model-mix queries with SQL baselines and query tests.
Internal overview endpoints and backend verification
apps/api/src/routes/internal/ai-sessions.http.ts, apps/api/src/routes/internal/ai-sessions.http.test.ts, apps/api/src/services/warehouse/ai-overview.clickhouse.e2e.test.ts
Adds overviewSummary, overviewBreakdown, and overviewModelMix handlers. The handlers compute the previous window, map warehouse rows into responses, and validate behavior with HTTP and ClickHouse end-to-end tests.
Web overview reads and search plumbing
apps/web/src/api/warehouse/ai-agent-overview.ts, apps/web/src/api/warehouse/ai-agent-overview.test.ts, apps/web/src/lib/agent-sessions/overview-search.ts, apps/web/src/lib/agent-sessions/overview-buckets.ts, apps/web/src/lib/agent-sessions/use-agent-overview.ts, apps/web/src/lib/services/atoms/warehouse-query-atoms.ts, apps/web/src/components/agent-sessions/tools/agent-sessions-tabs.tsx, apps/web/src/hooks/use-detected-models.ts
Adds web-side input schemas, request mappers, warehouse reads, search and link helpers, bucket sizing, query atoms, the overview data hook, shared Overview/Sessions tabs, and related tests.
Overview analytics and chart model builders
apps/web/src/lib/agent-sessions/overview-analytics.ts, apps/web/src/lib/agent-sessions/overview-chart-specs.ts, apps/web/src/lib/agent-sessions/*.test.ts
Adds the overview view-model layer for measures, deltas, tiles, series, model mix, breakdown rows, movers, coverage, and chart summaries, plus chart-spec builders and unit tests for these transformations.
Overview route, UI, and lab surfaces
apps/web/src/routes/agent-sessions/overview.tsx, apps/web/src/routes/agent-sessions/index.tsx, apps/web/src/components/agent-sessions/overview/*, apps/web/src/lab/agent-overview-*, apps/web/src/routes/lab/agent-overview.tsx, apps/web/src/lab/registry.ts, apps/web/src/routeTree.gen.ts
Adds the overview page route, loading and error orchestration, the presentational overview board and subcomponents, fixture-backed lab support, route registration, and component tests for filtering, comparison, failures, and top-session navigation.

Priority: ➖ Normal

Estimated code review effort: 5 (Critical) | ~120 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant OverviewRoute
  participant useAgentOverview
  participant InternalAPI
  participant ClickHouse

  Browser->>OverviewRoute: Open /agent-sessions/overview
  OverviewRoute->>useAgentOverview: Resolve window and search
  useAgentOverview->>InternalAPI: Read summary, breakdowns, model mix, top sessions
  InternalAPI->>ClickHouse: Run overview queries
  ClickHouse-->>InternalAPI: Return aggregates and series
  InternalAPI-->>useAgentOverview: Return section results
  useAgentOverview-->>OverviewRoute: Return overview data inputs
  OverviewRoute-->>Browser: Render AgentOverviewView
Loading

Merge Risk: ⚪ Minimal · up to e73fa

This change adds a new, feature-flagged Agent Sessions Overview page plus three read-only internal endpoints over the existing session index. No persisted data, permissions, or existing pages change behavior, and the new numbers are covered by tests that reconcile them with the Sessions list. No merge-blocking risk remains.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 78.72% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 94 functions across 44 files. (1 skipped:… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: adding the Agent Sessions Overview page.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 78.72% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 94 functions across 44 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/agent-sessions-overview

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.

- `withinWindow` takes a `CH.Expr<string>`: the index's `Timestamp` is the
  string flavour every Maple warehouse timestamp is, which is what
  `param.dateTimeString` compares against. SQL is unchanged.
- the facet select normalises the `null` Base UI reports for a cleared
  selection onto the `undefined` the toolbar spells "no filter" with.
- a link into the Sessions list carries `AgentSessionsLinkSearch`, whose
  arrays are the mutable flavour the route's search schema declares.
- the tab strip's `TabLink` takes a `TimeRangeSearch` — an interface has no
  index signature to satisfy a `Record<string, unknown>`.
- drop the unused token band label.
`TinybirdDateTime` is a pattern, so `2026-13-45 99:99:99` passed the contract
and reached the route, where the comparison window it derives parsed to NaN and
`toISOString` threw — a 500 for a malformed request. The three overview
requests now carry the same NaN check the details request already applies, and
the route reads the shared `parseWarehouseDateTime`/`formatWarehouseDateTime`
rather than local copies of them.
… breakdown scans

The overview's `traceKeys` carried a byte-for-byte copy of the list's six
counted filters; both now read one exported `sessionFilterConditions`, and the
list's SQL is unchanged.

Two reads the breakdown was making twice are gone. The failed-session set is
built once per window, at the trace level, instead of at every level that
filters — and it is summed off the trace rollup rather than a second pass over
the index. The key count reads the groups the ranking already forms instead of
re-deriving the same aggregation. A service breakdown under `hasErrors` goes
from 20 index reads to 15; a `UNION ALL` takes no `WITH`, so the ranking
subquery is still spelled out once per measuring branch.
…he model-mix tail

Nothing rendered the per-call latency quantiles, and collecting them was an
array of every model call of every session carried up two levels — gone from
the builder, the row shape, the contract and the web adapter.

The model mix ordered by bucket and cut at a row cap, so the cap dropped the
NEWEST buckets — the end of the chart — in exactly the org that routes across
enough models to reach it. The window's models are ranked once in SQL now and
everything past the busiest five is counted under `other`, which bounds a
bucket at six rows; the cap stays as a guard the page cannot reach. The client
folds the same key, so it keeps working — it just stops plotting `other` twice
when the band arrives already folded.
…-sessions parity

The ghost series is shifted by a whole number of buckets rather than by the
window's raw length: both windows are cut into epoch-aligned bucket starts and
the ghost is matched bucket for bucket, so the default calendar-aligned 7d
window — a midnight to a now floored to the quarter hour — put every previous
point minutes off the axis and drew nothing. The trends axis is built from the
current buckets alone, and a plot names a previous-period mark only where one
actually landed on it.

A window whose comparison ran no sessions has no baseline, so every tile, chart
and mover delta is empty there — including the point-valued ones, where a raw
subtraction reads 0% to 26% as a 26-point rise. The movers rail is empty with
the comparison off and says so instead of reading as "nothing moved"; its
coverage block stays.

Top sessions prints what the Sessions list prints: every service the session
touched, the model through `ModelLabel` over the page's own detection, and one
duration formatter for the whole board, so `45.4s` and `45s` can no longer be
the same session. Only the open tab's list read runs — the tab moved to the
route, which is what issues it — and the view model is built once per resolved
read rather than once per render.

Cleanups alongside: the breakdown delta is an `overviewDelta` rendered through
`deltaToneClass` rather than a hand-rolled threshold; the breakdown lanes read
their widths from the column list the header reads; the bucket ladder moved out
of the infra chart helpers into `overview-buckets.ts`; and the unread token band
shares, bucket seconds and unreachable spec branch are gone.
…names

The web adapter's local schemas said they mirrored the overview request's
bounds, but omitted the per-value filter cap and the window rule, so a
>200-character value or an impossible datetime threw a defect out of the
request constructor instead of failing one read. Both bounds now come from
the domain, which also refuses an inverted or retention-wide window rather
than running it.

Every read the board makes now says what happened to it: a failed summary
keeps the header, tab strip and toolbar and offers a retry that re-runs the
page, a failed breakdown, model mix or top-sessions read draws an error in
its own section instead of an empty one, and a failed facets read says so
in a line and leaves a set filter clearable.
@JeremyFunk
JeremyFunk marked this pull request as ready for review September 11, 2026 07:48
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