Agent Session Title Management#
Session titles in Phoenix's agents feature identify conversations in the session list UI. Each title is either auto-generated from the first user message (via LLM summarization) or manually set by the user. The system has three main concerns:
- Auto-generation — an LLM summarizes the first user message into a short title.
- Eager persistence — the title is written to the database immediately after summarization, not after the full turn completes, to avoid a race condition that showed untitled sessions in the UI (PR #14417, closes issue #14415).
- Manual editing — users can rename sessions via a GraphQL mutation, subject to validation and RBAC (PR #14683).
Auto-Generation Flow#
Title generation happens inside _summarize_untitled_session() in src/phoenix/server/api/routers/agents.py. It calls summarize_messages() in src/phoenix/server/agents/summarization.py, which:
- Uses
pydantic_aiwith a tool-calling approach (_Summaryprivate model, strict JSON schema) andallow_text_output=Falseto force structured output . - Returns
str | None— the summary string if generated,Noneon failure. This return type was simplified from a model object in PR #14417 . - Applies formatting rules specified in
src/phoenix/server/agents/prompts/summarization/SUMMARIZATION_PROMPT_INSTRUCTIONS.xml.j2: typically 2–6 words, sentence case, no quotes or trailing punctuation.
Fallback: if summarization returns None (model failure or timeout), the session retains the derived title set at creation time — the first user message, truncated.
Persistence Architecture and Race Condition Fix (PR #14417)#
The race condition (issue #14415): Sessions were created with an empty title. Before PR #14417, the LLM-generated title was not written to the database until the end-of-turn _upsert_agent_session call completed — after the full assistant response had finished streaming. During that window, users viewing the session list saw untitled or empty sessions .
Two-stage persistence introduced in PR #14417 :
| Stage | Timing | Function |
|---|---|---|
| Early | Immediately after LLM summarizes (lines 1742–1750 of agents.py) | _persist_agent_session_title() |
| Late | After assistant response fully streams | _upsert_agent_session() (on-conflict, title untouched if already set) |
Eager message persistence: When a new session is created, initial user messages are now written to models.AgentSessionMessage in the same transaction as the session row (agents.py lines 1171–1176), so the session is never empty from the moment it appears in the list .
Failure isolation: _persist_agent_session_title() (agents.py lines 1219–1238) catches and logs exceptions instead of re-raising them. A title persistence failure does not abort the chat stream .
All key changes live in two files: src/phoenix/server/api/routers/agents.py and src/phoenix/server/agents/summarization.py.
Manual Title Editing (PR #14683)#
PR #14683 adds end-to-end title editing, building on the persistence foundation in PR #14417.
Validation utilities in src/phoenix/server/agents/session_titles.py :
MAX_AGENT_SESSION_TITLE_LENGTH = 100— hard cap enforced everywhere.validate_agent_session_title(title, *, allow_empty)— raisesValueErrorif the title is empty (whenallow_empty=False) or exceeds 100 characters.truncate_agent_session_title(title)— strips whitespace and silently truncates to 100 characters; used when titles come from LLM output or session branching.
Validation is applied consistently across all title-setting paths in src/phoenix/server/api/mutations/agent_session_mutations.py:
- Session creation:
validate_agent_session_title(..., allow_empty=True)(empty allowed at creation). - Session branching:
truncate_agent_session_title()(silently clamps fork-inherited titles). - Manual editing: new
updateAgentSessionTitleGraphQL mutation validates non-empty, checks ownership, then persists .
Frontend: app/src/components/agent/EditAgentSessionTitleDialog.tsx renders the edit UI, accessible from both the chat header (in AgentChatPanelView.tsx) and the session list in the settings admin page (SettingsAgentSessionActionMenu.tsx). Title editing is hidden from Viewer-role users (RBAC gate added in the final commit of the PR).
Future Direction: In-Stream Title Delivery (Issue #14158)#
Issue #14158 proposes eliminating the residual cross-request architecture entirely by folding title generation into the chat SSE stream .
Current remaining gap: After the first turn, the client fires a fire-and-forget POST /agents/{agent_id}/sessions/{session_id}/summary via the useGenerateSessionSummary hook. Problems with this hook :
- A once-per-pageload ref means a failed request is never retried.
- Two-tabs-double-fire: opening the same session in two browser tabs both fire the summarize call.
- Re-uploads the full message history the server just persisted.
Proposed design :
- Kick off the title LLM call concurrently with the agent run at turn start (input is just the incoming user message).
- Await the title call at stream end; emit it as a transient
data-*SSE chunk so the client can update its UI without a separate fetch. - Fall back to the derived first-message title if the concurrent call times out.
- Fold title persistence into
_persist_agent_session_turn, deleting the/summaryendpoint entirely.
Open design questions before this can ship :
- Forked sessions: forked sessions are pre-seeded with a title via
buildForkSummary(). A naive "session row didn't exist at turn start" predicate would overwrite fork titles. Resolution options: persist the fork title at fork creation time, a request flag, or detecting multi-message incoming history. - Model selection: the summarization model becomes the turn's model (or a server-configured cheap model) rather than a client-supplied one.
This issue is open as of mid-2026.
Key Files Reference#
| File | Purpose |
|---|---|
src/phoenix/server/api/routers/agents.py | Chat route: _summarize_untitled_session, _persist_agent_session_title, _upsert_agent_session, _create_or_load_agent_session |
src/phoenix/server/agents/summarization.py | LLM-based title generation via summarize_messages() |
src/phoenix/server/agents/session_titles.py | MAX_AGENT_SESSION_TITLE_LENGTH, validate_agent_session_title, truncate_agent_session_title |
src/phoenix/server/api/mutations/agent_session_mutations.py | updateAgentSessionTitle GraphQL mutation |
app/src/components/agent/EditAgentSessionTitleDialog.tsx | Frontend edit dialog component |
app/src/components/agent/AgentChatPanelView.tsx | Chat header with title display and edit affordance |