symfonic.services.conversation.transcript¶
transcript ¶
Transcript persistence and query.
Surface separation: this is verbatim replay, not episodic memory. The source is a port, so the same query semantics hold over a checkpointer, an adopter's chat store, or a test double.
The semantics that must not drift from the legacy surface:
indexis the ordinal within the speaker-filtered view (so "the first thing I asked" is index 0 of a user-filtered read), and it is selectable —TranscriptQuery.index, negatives counting from the end, out-of-range answering empty — because the legacy read offered it and porting a caller to "fetch the thread and slice client-side" is a behaviour change on every large thread;timestampis checkpoint granularity and isNonewhen the source cannot resolve one — benign for ordinal reads, fatal for a time-range read, which is why the time-range path raises instead of returning everything; andlimitis a cap applied last, after the ordinal/time filtering, and it keeps the first rows of that view — the legacyrows[:limit]. A tail window would answer a different question for an identical query during the window in which both paths must be interchangeable.
TranscriptQuery
dataclass
¶
TranscriptQuery(thread_id: str, speaker: Speaker = 'all', limit: int | None = None, index: int | None = None, since: datetime | None = None, until: datetime | None = None)
One transcript read. Validated at construction, not at the store.
TranscriptService ¶
Reads verbatim transcripts through a source port.
Source code in src/symfonic/services/conversation/transcript.py
read
async
¶
Filter, then select an ordinal, then cap — the legacy order.
limit is applied last and keeps the first rows of the resulting
view (rows[:limit]), exactly as the legacy read did. Rows keep the
ordinal of the speaker-filtered view they came from, so a capped or
ordinal-selected read still correlates with an uncapped one.