symfonic.agent.transcript¶
transcript ¶
Transcript-retrieval value types and read helpers (v7.27.0).
The verbatim conversation transcript lives in the LangGraph checkpointer's
state['messages'] channel, persisted per thread_id. This module
defines the public :class:TranscriptMessage value type returned by
SymfonicAgent.get_transcript(...) (decoupling the public surface from
LangChain's internal BaseMessage shape) plus the pure helpers that
turn a checkpoint's message list into transcript rows.
Surface separation (v8.0 §14.a): this is surface 1 (verbatim replay), NOT episodic HMS (surface 3, lossy + similarity-only). See docs/concepts/conversation-persistence.md.
Non-goal (v7.27.0): semantic / embedding search over verbatim turns. The episodic embedding index is truncated (lossy) and re-using it would re-blur the transcript/HMS line. Adopters who need semantic search over verbatim turns use their own chat-store search. See docs/concepts/v8-roadmap.md.
TranscriptMessage
dataclass
¶
TranscriptMessage(
index: int,
role: TranscriptRole,
content: str,
message_id: str | None = None,
timestamp: datetime | None = None,
)
A single verbatim transcript row.
Decouples the public get_transcript surface from LangChain's
BaseMessage. index is the row's 0-based position in the
speaker-filtered view that produced it (so an index returned for
a speaker="user" query is the ordinal among user messages, which is
exactly the T14 "first thing I asked" semantics).
timestamp is checkpoint-granularity (the wall-clock ts of the
checkpoint that first introduced the message), NOT a per-message stamp
-- LangChain BaseMessage carries no native timestamp (see
docs/concepts/conversation-persistence.md). It is None when the
introducing checkpoint cannot be resolved (e.g. a saver without
alist support, where only ordinal/aget_tuple reads are available).
build_transcript_rows ¶
build_transcript_rows(
messages: list[Any],
*,
speaker: Speaker = "all",
ts_by_message_id: dict[str, datetime] | None = None,
) -> list[TranscriptMessage]
Turn a checkpoint message list into speaker-filtered transcript rows.
index is assigned 0-based over the FILTERED view (so it counts only
rows that pass speaker). ts_by_message_id maps a message id to
the timestamp of the checkpoint that introduced it (checkpoint
granularity); absent ids get timestamp=None.
Source code in src/symfonic/agent/transcript.py
filter_by_time_range ¶
filter_by_time_range(
rows: list[TranscriptMessage],
time_range: tuple[datetime, datetime],
) -> list[TranscriptMessage]
Keep rows whose checkpoint-granularity timestamp falls in [start, end].
Rows with timestamp is None (unresolved introducing checkpoint) are
dropped from a time-range query -- a row we cannot place in time cannot
honestly be claimed to fall inside a window. The bound is inclusive on
both ends.
Source code in src/symfonic/agent/transcript.py
slice_by_index ¶
Return a single-element list for the Nth row, or [] out-of-range.
Python-slice index semantics: negatives count from the end
(index=-1 is the last row). Out-of-range NEVER raises -- it
returns [] (the plan's locked contract). The returned row keeps
its original index from the filtered view so callers can correlate.