read_session
Fetch a coding session transcript by UUID, with optional agent hint, pagination, redaction, token usage, subagent details, and outcome classification.
Instructions
Read a single session by uuid; agent is an optional hint.
Args:
uuid: Session identifier.
agent: One of claude, codex, opencode, antigravity,
pi. Optional: when omitted, the uuid is looked up
across every parser (session ids are unique across agents in
practice). If — rarely — the same id exists under several
agents, a candidates list is returned (not an error) so
the caller can re-ask with an explicit agent.
offset: Zero-based index of the first message to return
(applied to the projected {role, content} list).
limit: Maximum number of messages to return. Defaults to
:data:_MESSAGES_CAP (100). A non-positive value means
"no upper bound".
redact: When True (default) secrets in the emitted title,
message content/intent/qa are masked as
[REDACTED_<TYPE>] and the response carries a
redactions type→count dict when any replacement happened
(see ai_r.redact); False returns the raw content.
with_tokens: When True (F3.3) attach token usage read at
request time (nothing runs in the background):
* ``summary["tokens"]`` — the session's flat
:func:`ai_r.tokens.session_tokens` block (exact where the agent
records usage, a labeled estimate otherwise, honest
``source=None`` without any signal);
* ``summary["component_tokens"]`` — the
:func:`ai_r.tokens.component_tokens` breakdown: the transcript's
estimated token volume split across ai-r's event taxonomy
(``user_turn`` / ``assistant_turn`` / ``thinking`` / ``plan``
and a ``tool_call`` per-``tool_kind`` sub-dict), always
``source="estimate"`` (never merged with the exact tier),
``None`` on an empty transcript;
* per-message ``tokens`` on projected entries that carry exact
usage — Claude (deduplicated per streamed API call), OpenCode
and Pi. Codex / Antigravity / user turns carry **no**
``tokens`` key at all (absent, not ``null``). The dedup/attach
is decided on absolute message positions BEFORE pagination, so
page boundaries never shift which record is "first" for a call.
Default ``False``: output is byte-identical to the historical
shape (no ``tokens`` / ``component_tokens`` key on the summary or
any message). The token blocks carry only integers and
ai-r-authored labels (never raw session text), so they stay
outside the F2.1 redaction pass by construction.
include_subagents: When ``True`` attach
``summary["subagent_rollup"]`` — the parent session's
``component_tokens`` block plus one per spawned subagent child
(resolved via :func:`ai_r.session_stats.children_of` on
``parent_uuid``) and a ``total`` folding parent + children through
the ``aggregate`` ``component_tokens`` metric. Each child entry
carries ``uuid`` / ``agent`` / ``component_tokens`` (the estimate)
plus **what it actually cost**: ``tokens`` read from the child's own
transcript on the honest three-tier ``source`` ladder — billed
``"exact"`` where the child's transcript records usage, a labeled
``"estimate"`` where it does not (a truncated / reference-only run),
``source=None`` without any signal (never a fabricated zero) —
``models`` (the model(s) it ran on — a persona pinned to a cheaper
tier shows up here), ``subagent_type`` (its persona, from the
child's own spawn metadata, falling back to the spawning call's
sidecar) and ``status`` where the spawn recorded one
(``"async_launched"`` for a background spawn). A childless parent
(or an agent like Antigravity that never records ``parent_uuid``)
yields an empty ``children`` list and a ``total`` equal to the
parent's own block — honest, not an error. Independent of
``with_tokens``. Default ``False``.
include_thinking: When ``True`` attach the model's reasoning
(``message.thinking``) as a separate ``thinking`` string field on
each projected message that carried it — kept OUT of ``content``
so the historical text projection is unchanged. Default
``False``: output is byte-identical to the historical shape (no
``thinking`` key on any message). Reasoning is opt-in to save the
caller's budget; ``Event.has_thinking`` / the presence of the key
signals which messages have it. Fail-soft: a message without
reasoning never carries the key.Returns: A dict with session metadata plus:
* ``messages`` — the projected ``{role, content}`` list, sliced
to ``[offset:offset+limit]``.
* ``total`` — the full uncapped projected message count (the
length the slice was taken from).
* ``offset`` / ``limit`` — the pagination echo values actually
used.
* ``messages_truncated`` — True when the MCP hard cap stopped
extraction before every projected message could be returned.
* ``outcome`` — session outcome classification (F2.3):
``{status: success|failure|mixed|unknown, signals, user_verdict,
markers, tool_results, tool_errors, error_rate,
error_rate_reliable}``. ``status`` combines the tool-call
error rate (real flag only for Claude/OpenCode —
``None`` elsewhere, never guessed) with a calibrated bilingual
success/failure dictionary over the tail user turns;
``"unknown"`` when neither signal exists (SSOT
:mod:`ai_r.outcome`).
On an id collision (agent omitted, several agents own the id):
``{"ambiguous": True, "uuid": ..., "candidates": [...],
"count": N, "message": ...}`` where each candidate is a session
summary carrying its ``agent``.
On a missing session, returns an ``error`` dict instead of
raising (``agents_scanned`` lists the parsers probed when the
agent was omitted).
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| uuid | Yes | ||
| agent | No | ||
| limit | No | ||
| offset | No | ||
| redact | No | ||
| with_tokens | No | ||
| include_thinking | No | ||
| include_subagents | No |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||