Skip to content

feat(web): Agent Sessions list under Explore, behind the agent_tracing flag - #541

Merged
JeremyFunk merged 6 commits into
feat/ai-agent-session-readfrom
feat/agent-sessions-explore-ui
Aug 19, 2026
Merged

feat(web): Agent Sessions list under Explore, behind the agent_tracing flag#541
JeremyFunk merged 6 commits into
feat/ai-agent-session-readfrom
feat/agent-sessions-explore-ui

Conversation

@JeremyFunk

@JeremyFunk JeremyFunk commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #540 (feat/ai-agent-session-read). Consumes the new aiSessionListQuery and shows the session list in the dashboard. No detail page yet — that's the next step.

What's here

API (internal tier)

  • AiSessionsInternalApiGroupPOST /internal/ai-sessions/list, session-authorized, registered on MapleInternalApi. Internal on purpose: the shape exists for the flagged dashboard page and is free to follow the UI.
  • Handler compiles Integrations.aiSessionListQuery with aiSessionListRowSchema, so the UInt64 aggregates decode without per-field coercion at the response edge.

Web

  • New agentTracing org rollout flag, decoded from Clerk org public metadata key agent_tracing (literal true only; fails closed while loading, open when self-hosted — same contract as aiAutoTriage).
  • navGroups()/paletteNavItems() take back the optional OrganizationFeatureFlags parameter (the recipe the old webAnalytics rollout left in the doc comment), so the sidebar row and the ⌘K entry appear and disappear together.
  • Explore > Agent Sessions (/agent-sessions): time-range picker, session rows with vendor, services, duration, trace/span counts and an error accent, modeled on the Replays list but without pagination — the query is limit-only (50, newest first), so the list shows a cap notice instead of an infinite scroll.
  • Framework + service filters in a Replays-style sidebar, driving the query's vendorIds/serviceNames server-side via vendor/service URL params. Option lists and counts come from POST /internal/ai-sessions/facets, backed by the base PR's aiSessionFacetsQuery — exact distinct-session counts over the whole window, deliberately unfiltered so selecting one option leaves the rest visible. Facet counts match the filter's ANY-span semantics, so a count always equals what selecting it returns.
  • Route gates in the component after isLoaded, so entitled orgs don't get a not-found flash; unflagged orgs get the not-found page and never fire the query.

Testing

  • bun typecheck green (40/40), bun run lint green.
  • New/updated unit tests: flag decoding (agent_tracing key, string-"true" rejected, fails closed) and nav gating (row + palette entry only with the flag, hidden by default).
  • Verified in the local dashboard: with the flag off the nav row and ⌘K entry are absent and /agent-sessions renders not-found; with agent_tracing: true on the org's Clerk public metadata the row appears under Explore, the page renders, and POST /internal/ai-sessions/list fires with session auth. The rows-with-data path was then exercised too: 21 trace-capture framework captures replayed through the local ingest gateway → collector → Tinybird, and the page lists the resulting sessions (9 vendors, incl. an eve session with 796 spans) with vendor, services, duration, trace/span counts and error chips.

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

JeremyFunk and others added 6 commits August 19, 2026 20:57
…g flag

Consumes aiSessionListQuery end to end: a dashboard-only internal endpoint
(/internal/ai-sessions/list, session-authorized, rowSchema-decoded) and an
Explore > Agent Sessions page listing sessions with vendor, services,
trace/span counts, errors and duration. No detail page yet.

Gating is the webAnalytics-rollout pattern: an agentTracing flag decoded from
Clerk org publicMetadata ("agent_tracing"), threaded as an optional parameter
through navGroups/paletteNavItems so the sidebar and command palette go dark
together, and checked in the route component after isLoaded so entitled orgs
don't see a not-found flash. The flag hides the surface only; the data stays
org-scoped through CurrentTenant like every other warehouse read.

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

The gradient-initial circle was copied from the replays list, where it encodes
a person's identity; an initial for a framework just reads as a fake vendor
logo. Rows now lead with the session id, framework named in text below.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review fixes, none changing behavior for entitled orgs on preset ranges:

- Vendor labels: cover the gateway's stamped vendor ids with brand casing
  (Claude Agent SDK, Google ADK, LiteLLM, ...) and fall back to Title Case so
  a newly stamped vendor degrades to a readable name, not a raw id.
- Reload on an absolute time range: the atom key never rolls there, so plain
  useAtomValue made the button a no-op; useRetainedRefreshableResultValue
  wires the refresh subscription (and retains the last success across rolls).
- Sidebar: the Clerk-backed flag read moves into a small memoized child so a
  Clerk resource tick redraws the nav groups, not the whole sidebar shell the
  outer memo exists to protect.
- Flag hook: memoize the returned state so `flags` keeps its identity across
  Clerk ticks that leave metadata untouched (consumers use it in deps).
- Error contract: declare warehouseReadHttpErrors — exactly what a compiled
  read can fail with - instead of inheriting the replay union's legacy
  QueryEngine wrappers and token-mint errors this endpoint cannot produce.
- Handler: drop the redundant per-row spread; the row schema already decodes
  the response shape.
- Tests: assert the flag-off state with the fully-populated disabled object
  production actually passes, and run the icon-preview invariant with flags
  on so it covers the flagged child.
- PageRefreshProvider: no preset default while an absolute range is active,
  mirroring the picker's own expression.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Exposes the vendorIds/serviceNames filters aiSessionListQuery already
supports: optional arrays on the internal contract, passed through the
handler, with single-select URL params (vendor, service) driving them from a
Replays-style filter sidebar.

There is no facets warehouse query yet, so option lists and counts derive
client-side from the unfiltered window's rows — served by the same atom
entry the unfiltered list already occupies (undefined filter fields drop
from the cache key), so the sidebar costs no extra query until a filter is
active. Options therefore only see what the list limit returned; that is
the seam a real facets aggregation replaces when windows outgrow it. A
selected value absent from the window stays checkable at count 0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The sidebar derived its option lists and counts from the unfiltered list
atom, so it only ever saw the page of rows the list's own limit returned —
a window holding more sessions than the limit hid options entirely and
under-counted the rest.

`aiSessionFacetsQuery` now backs them end to end: a `facets` endpoint on the
internal AI-sessions group, `getAiSessionsFacets` on the web client, and an
`aiSessionsFacetsResultAtom` the route reads instead of a second list read.

The request carries the window and nothing else. Keeping the facets
unfiltered is what lets a reader switch between vendors without the
unselected ones disappearing, and the counts are distinct sessions over the
whole window rather than over one page.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@JeremyFunk
JeremyFunk merged commit 64d98b3 into main Aug 19, 2026
1 check passed
@JeremyFunk
JeremyFunk deleted the feat/agent-sessions-explore-ui branch August 19, 2026 20:55
@github-actions

Copy link
Copy Markdown

🍁 Maple PR preview

Note

Preview resources were removed when this pull request closed.

Final commit 8bfc719 · View workflow run

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