Query sessions and turns
List agents and sessions, and read turns and their captured content with the Lens read API.
Use the Lens read application programming interface (API) to list sessions and read turns from code, for example to export failed turns or build your own reports. The Python examples use the Lens read client:
python -m pip install 'redis-agent-lens>=1.0.0'
The cURL examples use Bash and jq. Every operation is a GET request.
Authenticate
Export the Iris host, your instance ID, and a read key:
export LENS_URL='<iris-host>'
export LENS_INSTANCE_ID='<lens-instance-id>'
export LENS_API_KEY='<lens-read-key>'
The read client reads its key from LENS_API_KEY and sends it as a bearer token. Every operation is scoped to your instance:
import os
from redis_agent_lens import Lens
client = Lens(
server_url=os.environ["LENS_URL"],
instance_id=os.environ["LENS_INSTANCE_ID"],
)
import os
from redis_agent_lens import Lens
client = Lens(
server_url=os.environ["LENS_URL"],
instance_id=os.environ["LENS_INSTANCE_ID"],
)
agents = client.list_agents()
for agent in agents.items:
print(agent.agent_id)
page = client.list_sessions(agent_id="support-bot", range="24h", outcome=["FAILED"])
print(f"Showing {page.effective_range} of sessions")
sessions = list(page.items)
while page.next_cursor:
page = client.list_sessions(
agent_id="support-bot", range="24h", outcome=["FAILED"], cursor=page.next_cursor
)
sessions.extend(page.items)
for session in sessions:
print(session.session_id, session.turn_count, session.last_seen)
if not sessions:
raise SystemExit("No matching sessions in the last 24 hours")
session = client.get_session(session_id=sessions[0].session_id)
for summary in session.turns:
outcome = summary.outcome.value if summary.outcome else "no outcome"
print(summary.trace_id, outcome, summary.duration_ms, summary.tokens.total)
turn = client.get_turn(turn_id=session.turns[0].trace_id)
for entry in turn.manifest.entries:
print(f"{entry.source}: {entry.state.value}, fetched {entry.fetched_count}, used {entry.used_count}")
for candidate in entry.candidates:
print(f" {candidate.id} score={candidate.score} used={candidate.used}")
for entry in turn.manifest.entries:
for candidate in entry.candidates:
if candidate.content and candidate.content.hash:
content = client.get_content(turn_id=turn.trace_id, hash=candidate.content.hash)
print(candidate.id, content.availability.value, content.body)
for step in turn.context_retriever_tool_steps:
result = client.get_context_retriever_result(turn_id=turn.trace_id, span_id=step.span_id)
print(step.tool_name, result.outcome.value, result.classification.value)
LENS_API="${LENS_URL%/}/iris/data/v1beta1/lens/instances/$LENS_INSTANCE_ID"
AUTH="Authorization: Bearer $LENS_API_KEY"
set -euo pipefail
LENS_API="${LENS_URL%/}/iris/data/v1beta1/lens/instances/$LENS_INSTANCE_ID"
AUTH="Authorization: Bearer $LENS_API_KEY"
curl --fail-with-body --silent --show-error "$LENS_API/agents" -H "$AUTH" \
| jq -r '.items[].agentId'
curl --fail-with-body --silent --show-error -G "$LENS_API/sessions" -H "$AUTH" \
--data-urlencode "agentId=support-bot" \
--data-urlencode "range=24h" \
--data-urlencode "outcome=FAILED" > sessions.json
jq '{effectiveRange, nextCursor, sessions: [.items[] | {sessionId, turnCount, lastSeen}]}' sessions.json
SESSION_ID=$(jq -er '.items[0].sessionId' sessions.json)
curl --fail-with-body --silent --show-error "$LENS_API/sessions/$SESSION_ID" -H "$AUTH" > session.json
jq '[.turns[] | {traceId, outcome, durationMs, tokens: .tokens.total}]' session.json
TURN_ID=$(jq -er '.turns[0].traceId' session.json)
curl --fail-with-body --silent --show-error "$LENS_API/turns/$TURN_ID" -H "$AUTH" > turn.json
jq '[.manifest.entries[] | {source, state, fetchedCount, usedCount}]' turn.json
HASH=$(jq -r '.manifest.entries[0].candidates[0].content.hash // empty' turn.json)
if [ -n "$HASH" ]; then
curl --fail-with-body --silent --show-error "$LENS_API/turns/$TURN_ID/content/$HASH" -H "$AUTH" \
| jq '{availability, body}'
fi
for SPAN_ID in $(jq -r '.contextRetrieverToolSteps[].spanId' turn.json); do
curl --fail-with-body --silent --show-error \
"$LENS_API/turns/$TURN_ID/tools/$SPAN_ID/result" -H "$AUTH" \
| jq '{spanId, outcome, classification}'
done
List agents
List every agent with at least one retained session:
agents = client.list_agents()
for agent in agents.items:
print(agent.agent_id)
curl --fail-with-body --silent --show-error "$LENS_API/agents" -H "$AUTH" \
| jq -r '.items[].agentId'
List sessions
List one agent's sessions over a time range, newest activity first. This example lists the sessions with a failed turn in the last 24 hours, then follows the cursor to the next page:
page = client.list_sessions(agent_id="support-bot", range="24h", outcome=["FAILED"])
print(f"Showing {page.effective_range} of sessions")
sessions = list(page.items)
while page.next_cursor:
page = client.list_sessions(
agent_id="support-bot", range="24h", outcome=["FAILED"], cursor=page.next_cursor
)
sessions.extend(page.items)
for session in sessions:
print(session.session_id, session.turn_count, session.last_seen)
curl --fail-with-body --silent --show-error -G "$LENS_API/sessions" -H "$AUTH" \
--data-urlencode "agentId=support-bot" \
--data-urlencode "range=24h" \
--data-urlencode "outcome=FAILED" > sessions.json
jq '{effectiveRange, nextCursor, sessions: [.items[] | {sessionId, turnCount, lastSeen}]}' sessions.json
SESSION_ID=$(jq -er '.items[0].sessionId' sessions.json)
| Parameter | Description |
|---|---|
agentId |
Required. The agent whose sessions to list. |
range |
Required. A lookback duration such as 24h, 7d, or 30d. |
sessionIdPrefix |
Sessions whose ID starts with this value, ignoring case. At least 2 characters. |
userId |
Sessions with this user ID, ignoring case. |
outcome |
Sessions with a turn that has any of these outcomes. Use UNSET for turns without an outcome. |
limit |
Sessions per page, up to 200. Default 50. |
cursor |
The nextCursor value from the previous page. |
The Python client uses the same parameters in snake_case, such as agent_id and session_id_prefix.
Lens fits the range to your instance's retention. The response returns the range it used as effectiveRange, and the ranges your instance supports as availableRanges.
To read the next page, pass nextCursor with the same filters. When nextCursor is absent, you have the last page. A cursor expires after 5 minutes without use; start again from the first page.
Get a session
Read a session's threads, turn summaries, and outcome counts:
if not sessions:
raise SystemExit("No matching sessions in the last 24 hours")
session = client.get_session(session_id=sessions[0].session_id)
for summary in session.turns:
outcome = summary.outcome.value if summary.outcome else "no outcome"
print(summary.trace_id, outcome, summary.duration_ms, summary.tokens.total)
curl --fail-with-body --silent --show-error "$LENS_API/sessions/$SESSION_ID" -H "$AUTH" > session.json
jq '[.turns[] | {traceId, outcome, durationMs, tokens: .tokens.total}]' session.json
TURN_ID=$(jq -er '.turns[0].traceId' session.json)
Each turn summary includes the turn ID, outcome, duration, number of steps, and token counts.
Get a turn
Read one turn by its ID. The response includes the turn's steps, token counts, outcome, and its context manifest: one entry per retrieval step, with what it fetched and what the prompt used.
turn = client.get_turn(turn_id=session.turns[0].trace_id)
for entry in turn.manifest.entries:
print(f"{entry.source}: {entry.state.value}, fetched {entry.fetched_count}, used {entry.used_count}")
for candidate in entry.candidates:
print(f" {candidate.id} score={candidate.score} used={candidate.used}")
curl --fail-with-body --silent --show-error "$LENS_API/turns/$TURN_ID" -H "$AUTH" > turn.json
jq '[.manifest.entries[] | {source, state, fetchedCount, usedCount}]' turn.json
Each manifest entry has one of these states:
| State | Meaning |
|---|---|
ENTERED_PROMPT |
At least one result entered the prompt. |
FETCHED_NOT_USED |
The source returned results, and none entered the prompt. |
INVOKED_EMPTY |
The source returned no results. |
NOT_INSTRUMENTED |
The turn recorded no prompt assembly, or the results had no text to match. |
Each candidate in an entry includes its ID, score where the source provides one, and whether it entered the prompt.
Get captured content
Captured text, such as a retrieved document or a model prompt, is stored by content hash. Read it with the turn ID and the hash from the turn:
for entry in turn.manifest.entries:
for candidate in entry.candidates:
if candidate.content and candidate.content.hash:
content = client.get_content(turn_id=turn.trace_id, hash=candidate.content.hash)
print(candidate.id, content.availability.value, content.body)
HASH=$(jq -r '.manifest.entries[0].candidates[0].content.hash // empty' turn.json)
if [ -n "$HASH" ]; then
curl --fail-with-body --silent --show-error "$LENS_API/turns/$TURN_ID/content/$HASH" -H "$AUTH" \
| jq '{availability, body}'
fi
The response's availability is AVAILABLE with the text in body, or says why the text isn't available, such as EXPIRED or NOT_CAPTURED. See retention.
Get a Context Gateway tool result
Read the result of a Context Gateway tool call in a turn, by the step's ID. The turn lists these steps in contextRetrieverToolSteps:
for step in turn.context_retriever_tool_steps:
result = client.get_context_retriever_result(turn_id=turn.trace_id, span_id=step.span_id)
print(step.tool_name, result.outcome.value, result.classification.value)
for SPAN_ID in $(jq -r '.contextRetrieverToolSteps[].spanId' turn.json); do
curl --fail-with-body --silent --show-error \
"$LENS_API/turns/$TURN_ID/tools/$SPAN_ID/result" -H "$AUTH" \
| jq '{spanId, outcome, classification}'
done
The result classifies what the tool returned, such as RECORDS or AGGREGATE, and includes the returned data.
Next steps
- See the API reference for every field.
- See Limits and errors for request limits and error responses.