Class: Agent::Sessions::Readers::Base
- Inherits:
-
Object
- Object
- Agent::Sessions::Readers::Base
- Defined in:
- lib/agent/sessions/readers/base.rb
Overview
Layer 3: turning one session file into messages. Subclasses supply the mapping; everything about how the file is read lives here, because the three rules that make this layer survivable (design doc §5) are properties of the reading, not of any one agent's format:
1. raw is never dropped.
2. Unknown records become :unknown parts and warnings, never exceptions.
3. Reading streams. No code path may assume a file fits in memory.
Rule 3 is why this does not use File.foreach without a chunk size. A truncated log can hold no newline at all, and "read one line" would then mean "read 2.6 GB into a String" — the file the article that started this gem found on a real machine.
Constant Summary collapse
- MAX_RECORD_BYTES =
Chunk size, and so the largest record that can be read whole. Measured against 415 real Codex rollout files (128,987 records, 2026-08-12): 14 records exceed 1 MB and the largest is 2.41 MB, so Layer 2's MAX_LINE_BYTES of 1 MB would silently drop real messages. 8 MB is ~3.3x the observed maximum. A record beyond it is reported, never dropped in silence, because a missing message is this gem's worst failure mode.
8_000_000
Instance Attribute Summary collapse
-
#session ⇒ Object
readonly
Returns the value of attribute session.
Instance Method Summary collapse
-
#branching? ⇒ Boolean
Whether this agent records which turn each turn followed.
-
#compactions ⇒ Object
Boundaries where the agent replaced earlier turns with a summary.
-
#each_message ⇒ Object
Streams.
- #fidelity ⇒ Object
-
#initialize(session, include_events: false) ⇒ Base
constructor
A new instance of Base.
-
#messages ⇒ Object
Eager, for sessions small enough to hold.
-
#partial? ⇒ Boolean
True where the local file is not the whole story — Amp, whose server holds the canonical copy.
-
#tree ⇒ Object
The conversation as roots and their continuations, for an agent that records parent links.
-
#usage ⇒ Object
This session's token totals as a Usage, or nil where the format does not record them (Amp) or this reader has not learned where they live.
-
#warnings ⇒ Object
Populated as records are read, so this answers for whatever has been consumed so far.
Constructor Details
#initialize(session, include_events: false) ⇒ Base
Returns a new instance of Base.
30 31 32 33 34 |
# File 'lib/agent/sessions/readers/base.rb', line 30 def initialize(session, include_events: false) @session = session @include_events = include_events @warnings = [] end |
Instance Attribute Details
#session ⇒ Object (readonly)
Returns the value of attribute session.
28 29 30 |
# File 'lib/agent/sessions/readers/base.rb', line 28 def session @session end |
Instance Method Details
#branching? ⇒ Boolean
Whether this agent records which turn each turn followed. False here: most stores are an append-only list and a tree would have to be invented.
65 |
# File 'lib/agent/sessions/readers/base.rb', line 65 def branching? = false |
#compactions ⇒ Object
Boundaries where the agent replaced earlier turns with a summary. Its own pass: a caller asking only for compactions should not have to materialize every message to get them.
99 100 101 102 103 |
# File 'lib/agent/sessions/readers/base.rb', line 99 def compactions found = [] each_record { |record, _line| (boundary = compaction_for(record)) && found << boundary } found end |
#each_message ⇒ Object
Streams. Yields each message as it is parsed; a caller that breaks after one has read one record, not the file.
49 50 51 52 53 54 55 56 |
# File 'lib/agent/sessions/readers/base.rb', line 49 def return enum_for(:each_message) unless block_given? each_record do |record, line_number| = (record, line_number) yield if end end |
#fidelity ⇒ Object
36 |
# File 'lib/agent/sessions/readers/base.rb', line 36 def fidelity = session.fidelity |
#messages ⇒ Object
Eager, for sessions small enough to hold. The design doc offers both and
names this the convenience: messages is what a script wants, and
each_message is what a 2.6 GB file requires.
61 |
# File 'lib/agent/sessions/readers/base.rb', line 61 def = .to_a |
#partial? ⇒ Boolean
True where the local file is not the whole story — Amp, whose server holds the canonical copy. Overridden there, false everywhere else.
40 |
# File 'lib/agent/sessions/readers/base.rb', line 40 def partial? = false |
#tree ⇒ Object
The conversation as roots and their continuations, for an agent that records parent links. Unlike every other method here this cannot stream — a tree is not knowable until the last record is read — so it holds one session's messages at once and says so rather than pretending otherwise.
Raises rather than returning an empty list or nil for a store with no parent links, for the reason Agent::Sessions.read raises: "this format does not record that" must never read as "this session has none".
75 76 77 78 79 80 81 82 |
# File 'lib/agent/sessions/readers/base.rb', line 75 def tree unless branching? raise UnsupportedFormat, "#{session.agent} does not record parent links; its messages are a flat list" end build_tree end |
#usage ⇒ Object
This session's token totals as a Usage, or nil where the format does not record them (Amp) or this reader has not learned where they live. nil, not an empty Usage: "this store does not say" must never read as "this session cost nothing" — the same rule tree() enforces by raising.
Each reader that overrides this also decides its own summation rule, because that rule is format knowledge: Claude repeats one API response's usage across several records (94 of 124 message ids in one real transcript), Codex writes a running total where only the last record counts. A base-class sum would get both wrong.
94 |
# File 'lib/agent/sessions/readers/base.rb', line 94 def usage = nil |
#warnings ⇒ Object
Populated as records are read, so this answers for whatever has been consumed so far. uniq because a second pass over the same file would otherwise repeat every warning it already reported.
45 |
# File 'lib/agent/sessions/readers/base.rb', line 45 def warnings = @warnings.uniq |