Skip to main content

Sessions

Sessions group related agent runs under a single identifier. A common use case is tracking multi-turn conversations where each user message triggers a separate agent run, but you want to analyze the entire conversation as a unit.

What Gets Tracked

When runs share a session_id, Waxell Observe automatically aggregates:

MetricDescription
run_countNumber of runs in the session
first_runTimestamp of the earliest run
last_activityTimestamp of the most recent run
total_durationCombined execution time across all runs (seconds)
total_costSum of LLM costs across all runs (USD)
total_tokensSum of tokens used across all runs
agentsList of distinct agent names that participated

Setting a Session ID

Context Manager

Pass session_id when creating a WaxellContext:

from waxell_observe import WaxellContext

async with WaxellContext(
agent_name="chat-agent",
session_id="session-abc123",
) as ctx:
response = await generate_response(user_message)
ctx.record_llm_call(
model="gpt-4o",
tokens_in=response.usage.prompt_tokens,
tokens_out=response.usage.completion_tokens,
)
ctx.set_result({"output": response.text})

Decorator

Pass session_id to the @observe decorator:

import waxell_observe as waxell

waxell.init()

@waxell.observe(agent_name="chat-agent", session_id="session-abc123")
async def handle_message(user_message: str) -> str:
response = await llm.chat(user_message)
return response.text

Dynamic Session IDs

For real applications, you typically derive the session ID from your application's conversation or request context:

from waxell_observe import WaxellContext
from waxell_observe.context import generate_session_id

# Generate a new session ID (format: sess_ + 16 hex chars)
session_id = generate_session_id()
# e.g. "sess_a1b2c3d4e5f60718"

# Or use your own identifier
session_id = f"conv-{conversation.id}"

async with WaxellContext(
agent_name="chat-agent",
session_id=session_id,
) as ctx:
# All runs with this session_id are grouped together
...
info

The session_id is propagated to both the HTTP data path (stored on the AgentExecutionRun record) and the OTel tracing path (as a waxell.session_id span attribute). This means sessions are queryable from both the Waxell UI and Grafana TraceQL.

REST API

List Sessions

GET /api/v1/observability/sessions/

Authentication: Session (UI)

Query Parameters:

ParameterTypeDefaultDescription
searchstringFilter by session_id (substring match)
agentstringFilter by agent name
startISO8601Only sessions with runs after this time
endISO8601Only sessions with runs before this time
sortstring-last_activitySort field. Options: last_activity, -last_activity, first_run, -first_run, run_count, -run_count
limitint25Page size (max 100)
offsetint0Pagination offset

Example:

curl -s "https://acme.waxell.dev/api/v1/observability/sessions/?limit=10&sort=-run_count" \
-H "Cookie: sessionid=..."

Response:

{
"results": [
{
"session_id": "sess_a1b2c3d4e5f6g7h8",
"run_count": 5,
"first_run": "2026-02-07T10:00:00Z",
"last_activity": "2026-02-07T10:05:32Z",
"total_duration": 12.45,
"total_cost": 0.0234,
"total_tokens": 4520,
"agents": ["chat-agent", "retrieval-agent"]
}
],
"count": 42,
"next": "?offset=10&limit=10",
"previous": null
}

Get Session Detail

GET /api/v1/observability/sessions/{session_id}/

Authentication: Session (UI)

Returns the session's aggregate metrics and a chronological list of all runs.

Example:

curl -s "https://acme.waxell.dev/api/v1/observability/sessions/sess_a1b2c3d4e5f6g7h8/" \
-H "Cookie: sessionid=..."

Response:

{
"session_id": "sess_a1b2c3d4e5f6g7h8",
"aggregates": {
"run_count": 5,
"total_duration": 12.45,
"total_cost": 0.0234,
"total_tokens": 4520,
"agents": ["chat-agent", "retrieval-agent"]
},
"runs": [
{
"id": 101,
"agent_name": "chat-agent",
"workflow_name": "default",
"started_at": "2026-02-07T10:00:00Z",
"completed_at": "2026-02-07T10:00:02Z",
"duration": 2.1,
"status": "success",
"cost": 0.0045,
"tokens": 890,
"trace_id": "abcdef1234567890abcdef1234567890"
}
]
}

UI Walkthrough

Sessions List

The sessions list view shows all tracked sessions with sortable columns:

  • Session ID -- click to open session detail
  • Runs -- number of agent runs in the session
  • First Run / Last Activity -- time range of the session
  • Duration -- total execution time
  • Cost -- aggregated LLM spend
  • Tokens -- total token usage
  • Agents -- which agents participated

Use the search bar to filter by session ID, or the agent dropdown to see sessions for a specific agent.

Session Detail

The session detail page shows:

  1. Summary cards at the top with run count, total duration, total cost, and total tokens
  2. Vertical timeline of runs in chronological order, showing each run's agent, duration, cost, and status
  3. Click any run to navigate to its full trace detail

Multi-Agent Sessions

Sessions are especially useful for multi-agent workflows where several agents collaborate on a single request. Pass the same session_id to each agent:

session_id = generate_session_id()

async with WaxellContext(agent_name="router", session_id=session_id) as ctx:
intent = await classify_intent(query)
ctx.set_result({"intent": intent})

async with WaxellContext(agent_name="retrieval", session_id=session_id) as ctx:
docs = await search_documents(query)
ctx.set_result({"doc_count": len(docs)})

async with WaxellContext(agent_name="synthesizer", session_id=session_id) as ctx:
answer = await synthesize(query, docs)
ctx.set_result({"output": answer})

All three runs appear under the same session, giving you a complete picture of the multi-agent pipeline.

Next Steps

  • User Tracking -- Attribute sessions and costs to individual users
  • Scoring -- Attach quality scores to runs within a session
  • LLM Call Tracking -- Understand token and cost breakdown per run