MCPcopy Create free account
hub / github.com/agentforce314/clawcodex / TranscriptReader

Class TranscriptReader

src/agent/transcript.py:243–316  ·  view source on GitHub ↗

Replay companion for ``resume_agent_background`` (Chunk F / WI-7.4). Reads the JSONL transcript line-by-line, parsing each into a Python object (typically a dict mirroring the original ``asdict(Message)`` payload). Callers with a typed Message hierarchy can hydrate the dicts back in

Source from the content-addressed store, hash-verified

241
242
243class TranscriptReader:
244 """Replay companion for ``resume_agent_background`` (Chunk F / WI-7.4).
245
246 Reads the JSONL transcript line-by-line, parsing each into a Python
247 object (typically a dict mirroring the original ``asdict(Message)``
248 payload). Callers with a typed Message hierarchy can hydrate the
249 dicts back into ``AssistantMessage``/``UserMessage`` etc. via their
250 own factories — this reader stays loose-typed to avoid a cycle
251 between the agent module and the transcript module.
252
253 Tolerant of:
254 * **Missing file** — yields nothing rather than raising.
255 * **Trailing partial line** — log-once-and-skip rather than poison
256 the entire history. Mirrors the chapter §"Mailbox" approach.
257 * **Embedded blank lines** — skipped.
258
259 Defining the reader here (per critic concern C6) so Chunk F's
260 ``resume_agent_background`` SOLID DIP claim has a real interface
261 to depend on rather than the writer's IO layer.
262 """
263
264 def __init__(self, path: str | Path) -> None:
265 self._path = str(path)
266 self._logged_partial = False
267
268 @property
269 def path(self) -> str:
270 return self._path
271
272 def __iter__(self) -> Iterator[Any]:
273 return self._iterate()
274
275 def _iterate(self) -> Iterator[Any]:
276 """Yield one parsed object per line; skip blank/unparseable lines."""
277 try:
278 handle = open(self._path, "rb")
279 except FileNotFoundError:
280 return
281 try:
282 for raw_line in handle:
283 # raw_line still has its trailing newline; strip and skip
284 # blanks. Decode is utf-8 with replacement so a corrupt
285 # byte doesn't crash the iterator.
286 #
287 # N2 caveat (Chunk-D fold-in): ``errors="replace"`` maps
288 # corrupt bytes to U+FFFD. In theory a corrupted line
289 # could still parse as JSON if the U+FFFD substitutions
290 # land inside string literals — yielding "garbage but
291 # technically valid JSON". For chapter-10 transcripts
292 # the writer is the only producer and uses utf-8
293 # round-trip, so the regime is safe; downstream replay
294 # consumers should validate message shape post-parse.
295 line = raw_line.decode("utf-8", errors="replace").rstrip("\r\n")
296 if not line:
297 continue
298 try:
299 yield json.loads(line)
300 except json.JSONDecodeError:

Calls

no outgoing calls