feat(web): Agent Sessions list under Explore, behind the agent_tracing flag - #541
Merged
JeremyFunk merged 6 commits intoAug 19, 2026
Merged
Conversation
…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>
🍁 Maple PR previewNote Preview resources were removed when this pull request closed. Final commit |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #540 (
feat/ai-agent-session-read). Consumes the newaiSessionListQueryand shows the session list in the dashboard. No detail page yet — that's the next step.What's here
API (internal tier)
AiSessionsInternalApiGroup→POST /internal/ai-sessions/list, session-authorized, registered onMapleInternalApi. Internal on purpose: the shape exists for the flagged dashboard page and is free to follow the UI.Integrations.aiSessionListQuerywithaiSessionListRowSchema, so the UInt64 aggregates decode without per-field coercion at the response edge.Web
agentTracingorg rollout flag, decoded from Clerk org public metadata keyagent_tracing(literaltrueonly; fails closed while loading, open when self-hosted — same contract asaiAutoTriage).navGroups()/paletteNavItems()take back the optionalOrganizationFeatureFlagsparameter (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.vendorIds/serviceNamesserver-side viavendor/serviceURL params. Option lists and counts come fromPOST /internal/ai-sessions/facets, backed by the base PR'saiSessionFacetsQuery— 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.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 typecheckgreen (40/40),bun run lintgreen.agent_tracingkey, string-"true" rejected, fails closed) and nav gating (row + palette entry only with the flag, hidden by default)./agent-sessionsrenders not-found; withagent_tracing: trueon the org's Clerk public metadata the row appears under Explore, the page renders, andPOST /internal/ai-sessions/listfires 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
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.