Session Filtering#
Session filtering in Phoenix operates across two distinct surfaces:
- Project Sessions tab — observability-focused, uses a Python-like DSL expression language compiled to SQL, introduced in PR #14101.
- Agents admin UI — manages persistent chat sessions, uses ownership-based SQL scoping, introduced across PR #14680 and PR #14711.
These two surfaces share no filtering mechanism — the DSL filter applies only to project-level session observability, while agent session access is controlled purely by user ownership and role checks.
Session Filter DSL (Project Sessions Tab)#
Introduced in PR #14101, the session filter DSL mirrors the existing span/trace filter DSL at session grain. Requirements were driven by issues #13983, #14041, and #14171.
DSL Vocabulary#
The filter accepts Python-like expressions. Supported bindings fall into several categories :
| Category | Fields |
|---|---|
| Intrinsic | session_id, start_time, end_time, duration_ms |
| Aggregates | num_traces, num_traces_with_error, token_count_prompt/completion/total, prompt_cost, completion_cost, total_cost, llm_span_count, tool_span_count |
| Root-span reads | user.id, metadata["key"], attributes[...] |
| Text containment | any_input, any_output, first_input, last_output |
| Annotations | annotations['name'].score, annotations['name'].label (supports is None) |
| Comprehension iterables | spans, traces, session_annotations, span_annotations, span_cost_details |
Comprehensions support any/all/len/sum/max/min reductions, enabling cross-span predicates like any(span.status_code == "ERROR" for span in spans) or max(span.latency_ms for span in spans) > 5_000 .
Backend#
src/phoenix/trace/dsl/session_filter.py—SessionFilterclass; compiles DSL to SQL using shared infrastructure extracted fromfilter.py(_FilterBindings,_compile_condition). Comprehensions are extracted and compiled as correlated subqueries (~2ms flat regardless of session count) .src/phoenix/db/session_aggregates.py— Centralizes all per-session aggregate SQL builders with both grouped and correlated rendering shapes .- GraphQL:
sessionFilterVocabularyandvalidateSessionFilterConditionresolvers added to theProjecttype;sessions(),sessionCount()now acceptsessionFilterCondition, replacing the olderfilterIoSubstring/sessionIdparameters .
Frontend#
SessionFilterConditionField.tsx— Wraps the genericDSLFilterConditionFieldwith session-specific vocabulary, 21 snippet templates, and comprehension-aware completion (element fields likespan.latency_msonly appear inside a comprehension scope) .SessionFiltersContext.tsx— React context holding current filter condition state .- Validation calls
validateSessionFilterConditionagainst the backend; valid conditions are persisted per-project in browser storage for recent-search completions .
See DSL Filter Autocompletion for details on the shared DSLFilterConditionField component and autocomplete architecture.
Time-Range Filtering#
PR #14023 is a breaking change to how sessions are matched against a time window.
Before: sessions were included if start_time fell within the window.
After: sessions are included if [start_time, end_time] intersects [window.start, window.end) (interval-overlap semantics).
This means long-running sessions whose activity overlaps the window are no longer excluded because they started before it. Sessions entirely before or after the window remain excluded .
Updated locations :
src/phoenix/server/api/dataloaders/record_counts.py— session overlap predicates:start_time <= ProjectSession.end_time AND ProjectSession.start_time < end_timesrc/phoenix/server/api/dataloaders/annotation_summaries.py— same overlap predicates for session-kind rows- DB migration
eaf1907ae453— new composite index on session time columns for efficient overlap queries app/schema.graphql— updated docs clarifying overlap semantics and noting "long-running sessions appear in every window they overlap"
Admin vs. Non-Admin Scoping (Agent Sessions)#
Agent (chat) sessions in the agents admin UI use ownership-based access control — not the DSL filter — enforced at both the GraphQL and REST layers.
GraphQL#
CanAccessAgentSession is a Strawberry permission class in src/phoenix/server/api/agent_helpers.py :
- Non-admin: access denied if
owner_id != viewer_id - Admin: unrestricted access
get_agent_session_owner_filter()returns a SQLWHERE user_id == viewer_idpredicate for non-admins, orNonefor admins (for use in list queries)
All fields on the AgentSession GraphQL type (title, created_at, updated_at, user, first_input, latest_output, messages) are gated by permission_classes=[CanAccessAgentSession] .
REST API#
GET /agents/{agent_id}/sessions and GET /agents/{agent_id}/sessions/{session_id}, added in PR #14711:
- Only persisted (non-temporary, non-expired) sessions are returned
- If authentication is enabled: queries are scoped to
user_id == <request user> - If authentication is disabled: all persisted sessions for the agent are returned
- The single-session endpoint returns 404 if the session is expired or owned by another user
_get_request_user_id(request)extracts the authenticated user ID or returnsNone
Integration tests in tests/integration/auth/test_auth.py verify that a member API key can only list and retrieve its own sessions, and receives 404 for sessions owned by another user .
Known bug (#14897): The admin session dropdown in the agents UI currently shows every user's session, indicating the owner filter is not yet applied consistently in that UI surface.
Key Files#
| File | Purpose |
|---|---|
src/phoenix/trace/dsl/session_filter.py | Session DSL compiler (SessionFilter class), SQL bindings, comprehension extraction |
src/phoenix/db/session_aggregates.py | Per-session aggregate SQL builders (grouped and correlated shapes) |
src/phoenix/trace/dsl/filter.py | Shared filter compiler infrastructure (_FilterBindings, _compile_condition) |
app/src/pages/project/SessionFilterConditionField.tsx | Session filter bar UI with vocabulary, snippets, comprehension-aware completion |
app/src/pages/project/SessionFiltersContext.tsx | React context for session filter state |
src/phoenix/server/api/agent_helpers.py | CanAccessAgentSession permission class; get_agent_session_owner_filter() |
src/phoenix/server/api/types/AgentSession.py | AgentSession GraphQL type with per-field CanAccessAgentSession enforcement |
src/phoenix/server/api/routers/agents.py | REST routes GET /agents/{agent_id}/sessions[/{session_id}] with user scoping |
src/phoenix/server/api/dataloaders/record_counts.py | Session interval-overlap time-range predicates |