agent_sessions 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +71 -3
- data/doc/Agent/Sessions/Adapters/Amp.md +91 -0
- data/doc/Agent/Sessions/Adapters/Base.md +198 -0
- data/doc/Agent/Sessions/Adapters/Claude.md +63 -0
- data/doc/Agent/Sessions/Adapters/Codex.md +79 -0
- data/doc/Agent/Sessions/Adapters/Copilot.md +38 -0
- data/doc/Agent/Sessions/Adapters/Cursor.md +54 -0
- data/doc/Agent/Sessions/Adapters/CursorIde.md +49 -0
- data/doc/Agent/Sessions/Adapters/Enumeration.md +135 -0
- data/doc/Agent/Sessions/Adapters/Gemini.md +67 -0
- data/doc/Agent/Sessions/Adapters/Grok.md +64 -0
- data/doc/Agent/Sessions/Adapters/Opencode.md +93 -0
- data/doc/Agent/Sessions/Adapters/Pi.md +137 -0
- data/doc/Agent/Sessions/Adapters/Qwen.md +32 -0
- data/doc/Agent/Sessions/Adapters.md +5 -0
- data/doc/Agent/Sessions/Audit/Finding.md +27 -0
- data/doc/Agent/Sessions/Audit.md +21 -0
- data/doc/Agent/Sessions/CLI.md +36 -0
- data/doc/Agent/Sessions/Check.md +27 -0
- data/doc/Agent/Sessions/Compaction.md +23 -0
- data/doc/Agent/Sessions/EnvOverride.md +19 -0
- data/doc/Agent/Sessions/Error.md +6 -0
- data/doc/Agent/Sessions/HomeExpansion.md +11 -0
- data/doc/Agent/Sessions/Location.md +60 -0
- data/doc/Agent/Sessions/Message.md +59 -0
- data/doc/Agent/Sessions/MissingDependency.md +6 -0
- data/doc/Agent/Sessions/Node.md +24 -0
- data/doc/Agent/Sessions/Part.md +41 -0
- data/doc/Agent/Sessions/Readers/Amp.md +41 -0
- data/doc/Agent/Sessions/Readers/Base.md +91 -0
- data/doc/Agent/Sessions/Readers/Claude.md +77 -0
- data/doc/Agent/Sessions/Readers/Codex.md +81 -0
- data/doc/Agent/Sessions/Readers/Copilot.md +31 -0
- data/doc/Agent/Sessions/Readers/Gemini.md +38 -0
- data/doc/Agent/Sessions/Readers/Grok.md +45 -0
- data/doc/Agent/Sessions/Readers/Opencode.md +37 -0
- data/doc/Agent/Sessions/Readers/Pi.md +38 -0
- data/doc/Agent/Sessions/Readers/Qwen.md +30 -0
- data/doc/Agent/Sessions/Readers.md +5 -0
- data/doc/Agent/Sessions/Session.md +73 -0
- data/doc/Agent/Sessions/Sqlite.md +43 -0
- data/doc/Agent/Sessions/Store.md +57 -0
- data/doc/Agent/Sessions/UnknownAgent.md +6 -0
- data/doc/Agent/Sessions/UnreadableStore.md +6 -0
- data/doc/Agent/Sessions/UnsupportedFormat.md +6 -0
- data/doc/Agent/Sessions/Usage.md +56 -0
- data/doc/Agent/Sessions.md +177 -0
- data/doc/Agent.md +5 -0
- data/doc/CHANGELOG.md +69 -0
- data/doc/README.md +162 -0
- data/doc/index.csv +316 -0
- data/exe/agent-sessions +0 -2
- data/lib/agent/sessions/adapters/cursor.rb +3 -3
- data/lib/agent/sessions/cli.rb +2 -2
- data/lib/agent/sessions/message.rb +1 -2
- data/lib/agent/sessions/part.rb +1 -2
- data/lib/agent/sessions/version.rb +1 -1
- data/llm.txt +177 -0
- metadata +54 -2
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Class Agent::Sessions::Readers::Codex <a id="class-Agent-Sessions-Readers-Codex"></a>
|
|
2
|
+
|
|
3
|
+
| | |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| **Inherits** | [Agent::Sessions::Readers::Base](Base.md) |
|
|
6
|
+
| **Defined in** | lib/agent/sessions/readers/codex.rb |
|
|
7
|
+
|
|
8
|
+
Codex rollout files. Every mapping here was written against a real corpus
|
|
9
|
+
rather than from the format notes: 415 files, 128,987 records, inventoried
|
|
10
|
+
2026-08-12. The distribution is the reason for several decisions below
|
|
11
|
+
—response_item 57%, event_msg 38%, turn_context 4%, session_meta 416,
|
|
12
|
+
world_state 182, inter_agent_communication_metadata 129, compacted 18.
|
|
13
|
+
|
|
14
|
+
Codex was chosen as the first reader for exactly this reason. pi was the
|
|
15
|
+
planned reference implementation, but its store held no session files at all
|
|
16
|
+
on the machine available, so every claim about its content would have been
|
|
17
|
+
inference. A reference implementation has to be falsifiable.
|
|
18
|
+
|
|
19
|
+
## Constants
|
|
20
|
+
### `CALL_INPUTS` <a id="constant-CALL_INPUTS"></a> <a id="CALL_INPUTS-constant"></a>
|
|
21
|
+
Where a tool call keeps what it was called with. custom_tool_call uses input,
|
|
22
|
+
function_call uses arguments, web_search_call uses action, and
|
|
23
|
+
tool_search_call uses arguments as a Hash rather than a String.
|
|
24
|
+
|
|
25
|
+
### `CALL_OUTPUTS` <a id="constant-CALL_OUTPUTS"></a> <a id="CALL_OUTPUTS-constant"></a>
|
|
26
|
+
And where an output keeps its result.
|
|
27
|
+
|
|
28
|
+
### `CONTENT_PARTS` <a id="constant-CONTENT_PARTS"></a> <a id="CONTENT_PARTS-constant"></a>
|
|
29
|
+
encrypted_content maps to :unknown deliberately, not for want of a better
|
|
30
|
+
bucket: 80 real content items are encrypted by the model and this gem will
|
|
31
|
+
never read them. Recognized-and-unreadable is a different thing from
|
|
32
|
+
unrecognized, and only the second deserves a warning — a warning that fires on
|
|
33
|
+
a permanent, understood condition is noise on every read.
|
|
34
|
+
|
|
35
|
+
### `NON_MESSAGE_ITEMS` <a id="constant-NON_MESSAGE_ITEMS"></a> <a id="NON_MESSAGE_ITEMS-constant"></a>
|
|
36
|
+
Internal state that happens to travel as a response_item. Skipped in silence
|
|
37
|
+
for the same reason turn_context is: it is not conversation, and a warning a
|
|
38
|
+
caller must learn to ignore is worse than no warning.
|
|
39
|
+
|
|
40
|
+
### `NON_MESSAGE_TYPES` <a id="constant-NON_MESSAGE_TYPES"></a> <a id="NON_MESSAGE_TYPES-constant"></a>
|
|
41
|
+
Known, and deliberately not messages: the session header, per-turn
|
|
42
|
+
configuration, and two state records Codex added in July 2026. Silence here is
|
|
43
|
+
a judgement, not an oversight — these are not conversation, and warning about
|
|
44
|
+
them would train a caller to ignore warnings.
|
|
45
|
+
|
|
46
|
+
### `ROLES` <a id="constant-ROLES"></a> <a id="ROLES-constant"></a>
|
|
47
|
+
"developer" is what Codex writes where the normalized vocabulary says :system.
|
|
48
|
+
It is 101 of 292 role-bearing records in the sample, so this is the common
|
|
49
|
+
path, not an edge case.
|
|
50
|
+
|
|
51
|
+
### `TOOL_CALLS` <a id="constant-TOOL_CALLS"></a> <a id="TOOL_CALLS-constant"></a>
|
|
52
|
+
Every entry past the first three in each list came from running this reader
|
|
53
|
+
over all 415 files and reading its own warnings: a 25-file sample showed none
|
|
54
|
+
of them. Counts in that corpus: web_search_call 288, ghost_snapshot 197,
|
|
55
|
+
agent_message 129, tool_search_call and tool_search_output 26 each,
|
|
56
|
+
image_generation_call 1.
|
|
57
|
+
|
|
58
|
+
### `TOOL_OUTPUTS` <a id="constant-TOOL_OUTPUTS"></a> <a id="TOOL_OUTPUTS-constant"></a>
|
|
59
|
+
Not documented.
|
|
60
|
+
|
|
61
|
+
## Public Instance Methods
|
|
62
|
+
### `usage()` <a id="method-i-usage"></a> <a id="usage-instance_method"></a>
|
|
63
|
+
Session totals. Codex writes no usage on its messages; it writes token_count
|
|
64
|
+
event records whose info.total_token_usage is a RUNNING TOTAL — verified
|
|
65
|
+
against a real rollout on this machine (2026-08-24): consecutive records
|
|
66
|
+
report total 33,751 then 69,135 while their last_token_usage differ, so the
|
|
67
|
+
last record is the session and summing would multiply-count every earlier
|
|
68
|
+
turn.
|
|
69
|
+
|
|
70
|
+
Two normalizations, both from that same file:
|
|
71
|
+
|
|
72
|
+
input_tokens INCLUDES cached_input_tokens (33,431 including 19,200
|
|
73
|
+
in the sample) — the opposite of Claude's disjoint spelling — so the
|
|
74
|
+
cached share is subtracted to make Usage#input mean one thing across
|
|
75
|
+
agents. Clamped at zero: a count that went negative would mean the
|
|
76
|
+
two fields disagree, and a wrong zero beats a negative token count.
|
|
77
|
+
|
|
78
|
+
cache_write_input_tokens maps to cache_creation. total_tokens is
|
|
79
|
+
deliberately not mapped anywhere: it restates the other fields, and
|
|
80
|
+
any bucket it landed in would be double-counted by a caller summing
|
|
81
|
+
buckets.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Class Agent::Sessions::Readers::Copilot <a id="class-Agent-Sessions-Readers-Copilot"></a>
|
|
2
|
+
|
|
3
|
+
| | |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| **Inherits** | [Agent::Sessions::Readers::Base](Base.md) |
|
|
6
|
+
| **Defined in** | lib/agent/sessions/readers/copilot.rb |
|
|
7
|
+
|
|
8
|
+
GitHub Copilot CLI turns, from the SQLite store the adapter enumerates.
|
|
9
|
+
|
|
10
|
+
The SCHEMA is verified (schema_version 3 on this machine, 2026-08-24): turns
|
|
11
|
+
holds id, session_id, turn_index, user_message, assistant_response, timestamp.
|
|
12
|
+
The CONTENT is not — the one real session here has zero turn rows, so no turn
|
|
13
|
+
has ever been read. The column names are unambiguous enough to map without
|
|
14
|
+
guessing at structure, which is why this reader exists at all rather than
|
|
15
|
+
waiting; what it cannot promise is that a real turn holds plain text in those
|
|
16
|
+
columns rather than, say, JSON.
|
|
17
|
+
|
|
18
|
+
fidelity is :messages, not :full — one row is a whole exchange, so the tool
|
|
19
|
+
calls and reasoning that happened inside it are not recoverable from this
|
|
20
|
+
table. What a caller gets is what was said, not how.
|
|
21
|
+
|
|
22
|
+
## Public Instance Methods
|
|
23
|
+
### `each_message()` <a id="method-i-each_message"></a> <a id="each_message-instance_method"></a>
|
|
24
|
+
One row is a user turn AND the assistant's reply, so each row yields two
|
|
25
|
+
messages. They share a raw record: rule 1 keeps the row intact, and splitting
|
|
26
|
+
it into two half-rows would misreport what was stored.
|
|
27
|
+
|
|
28
|
+
### `usage()` <a id="method-i-usage"></a> <a id="usage-instance_method"></a>
|
|
29
|
+
No token or cost column exists anywhere in this schema — not on sessions, not
|
|
30
|
+
on turns. nil is the format speaking, and must not be mistaken for a session
|
|
31
|
+
that cost nothing.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Class Agent::Sessions::Readers::Gemini <a id="class-Agent-Sessions-Readers-Gemini"></a>
|
|
2
|
+
|
|
3
|
+
| | |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| **Inherits** | [Agent::Sessions::Readers::Base](Base.md) |
|
|
6
|
+
| **Defined in** | lib/agent/sessions/readers/gemini.rb |
|
|
7
|
+
|
|
8
|
+
Gemini CLI chat files. Written against a real store on this machine
|
|
9
|
+
(2026-08-24): 12 sessions, 121 records — user 20, gemini 97, info 4.
|
|
10
|
+
|
|
11
|
+
A chat is one JSON document, not JSONL, so it is read whole under a cap the
|
|
12
|
+
way Amp's thread is. The cap is the reason this does not simply JSON.parse the
|
|
13
|
+
file: the largest real chat here is 103 KB, but nothing in the format bounds
|
|
14
|
+
it, and an unbounded read is the failure the base reader's chunked streaming
|
|
15
|
+
exists to prevent.
|
|
16
|
+
|
|
17
|
+
## Constants
|
|
18
|
+
### `MAX_DOCUMENT_BYTES` <a id="constant-MAX_DOCUMENT_BYTES"></a> <a id="MAX_DOCUMENT_BYTES-constant"></a>
|
|
19
|
+
Amp's bound, for the same reason: a whole document must fit in memory to be
|
|
20
|
+
parsed at all, so the only protection available is refusing to read one that
|
|
21
|
+
is absurdly large.
|
|
22
|
+
|
|
23
|
+
### `ROLES` <a id="constant-ROLES"></a> <a id="ROLES-constant"></a>
|
|
24
|
+
"gemini" is the assistant. "info" is the CLI talking to the user ("Update
|
|
25
|
+
successful! The new version will be used on your next run."), which is neither
|
|
26
|
+
turn — context the operator saw, the same judgement Claude's system records
|
|
27
|
+
get, so it arrives with include_events.
|
|
28
|
+
|
|
29
|
+
## Public Instance Methods
|
|
30
|
+
### `header()` <a id="method-i-header"></a> <a id="header-instance_method"></a>
|
|
31
|
+
The document's own header, exposed because Layer 2's session id is the
|
|
32
|
+
filename (the trailing hex in it is shared between sessions) while the agent's
|
|
33
|
+
own sessionId lives in here.
|
|
34
|
+
|
|
35
|
+
### `usage()` <a id="method-i-usage"></a> <a id="usage-instance_method"></a>
|
|
36
|
+
Session totals, summed per message — the counts are per API call, not a
|
|
37
|
+
running total (verified: the real series falls as well as rises, 64138 then
|
|
38
|
+
8069 then 8265, which no cumulative counter does).
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Class Agent::Sessions::Readers::Grok <a id="class-Agent-Sessions-Readers-Grok"></a>
|
|
2
|
+
|
|
3
|
+
| | |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| **Inherits** | [Agent::Sessions::Readers::Base](Base.md) |
|
|
6
|
+
| **Defined in** | lib/agent/sessions/readers/grok.rb |
|
|
7
|
+
|
|
8
|
+
Grok Build sessions. PROVISIONAL, like the adapter: written against
|
|
9
|
+
tokentelemetry's parser of this format, not against real Grok output.
|
|
10
|
+
|
|
11
|
+
Two things make this reader unlike every other one here. The session it is
|
|
12
|
+
handed points at summary.json, while the conversation is in chat_history.jsonl
|
|
13
|
+
beside it — so the streaming base reads a SIBLING file. And billed usage is
|
|
14
|
+
not in the session directory at all: it lives in ~/.grok/logs/unified.jsonl,
|
|
15
|
+
one row per request across every session, keyed by session id. A rotated log
|
|
16
|
+
means no usage, which is why `usage` answers nil rather than zero when the log
|
|
17
|
+
is gone.
|
|
18
|
+
|
|
19
|
+
## Constants
|
|
20
|
+
### `INFERENCE` <a id="constant-INFERENCE"></a> <a id="INFERENCE-constant"></a>
|
|
21
|
+
The row that records one completed request, per the reference parser.
|
|
22
|
+
|
|
23
|
+
### `ROLES` <a id="constant-ROLES"></a> <a id="ROLES-constant"></a>
|
|
24
|
+
Not documented.
|
|
25
|
+
|
|
26
|
+
### `TRANSCRIPT` <a id="constant-TRANSCRIPT"></a> <a id="TRANSCRIPT-constant"></a>
|
|
27
|
+
Not documented.
|
|
28
|
+
|
|
29
|
+
### `UNIFIED_LOG` <a id="constant-UNIFIED_LOG"></a> <a id="UNIFIED_LOG-constant"></a>
|
|
30
|
+
Not documented.
|
|
31
|
+
|
|
32
|
+
## Public Instance Methods
|
|
33
|
+
### `summary()` <a id="method-i-summary"></a> <a id="summary-instance_method"></a>
|
|
34
|
+
The session's summary.json, exposed because it holds what Layer 2 does not
|
|
35
|
+
surface: generated_title, session_summary, current_model_id, the git branch
|
|
36
|
+
and commit the work happened on.
|
|
37
|
+
|
|
38
|
+
### `usage()` <a id="method-i-usage"></a> <a id="usage-instance_method"></a>
|
|
39
|
+
Summed across this session's rows in the shared inference log.
|
|
40
|
+
|
|
41
|
+
prompt_tokens INCLUDES cached_prompt_tokens (the reference parser subtracts
|
|
42
|
+
one from the other, as this gem does for Codex and Gemini), so `input` is the
|
|
43
|
+
difference and `cache_read` the cached share. The cached count is clamped to
|
|
44
|
+
the prompt first: a log row claiming more cached than prompt would otherwise
|
|
45
|
+
produce a negative input.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Class Agent::Sessions::Readers::Opencode <a id="class-Agent-Sessions-Readers-Opencode"></a>
|
|
2
|
+
|
|
3
|
+
| | |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| **Inherits** | [Agent::Sessions::Readers::Base](Base.md) |
|
|
6
|
+
| **Defined in** | lib/agent/sessions/readers/opencode.rb |
|
|
7
|
+
|
|
8
|
+
opencode sessions, read from the shared SQLite database the adapter already
|
|
9
|
+
enumerates. Written against a real store on this machine (2026-08-24): 365
|
|
10
|
+
sessions, whose message and part rows settled every mapping below — this is
|
|
11
|
+
the first reader whose "corpus" is a database rather than files.
|
|
12
|
+
|
|
13
|
+
A "record" here is synthetic: one message row's parsed `data` plus every part
|
|
14
|
+
row belonging to it, as {"message" => ..., "parts" => [...]}. That composite
|
|
15
|
+
IS the raw a Message carries — rule 1 needs the parts included, because the
|
|
16
|
+
content lives in them, not in the message row.
|
|
17
|
+
|
|
18
|
+
## Constants
|
|
19
|
+
### `CONTENT_PARTS` <a id="constant-CONTENT_PARTS"></a> <a id="CONTENT_PARTS-constant"></a>
|
|
20
|
+
Conversation content. text and reasoning map 1:1; a `tool` part holds BOTH the
|
|
21
|
+
call and its result in one row (state.input / state.output), so it becomes two
|
|
22
|
+
Parts — the assistant's act and the tool answering —rather than flattening one
|
|
23
|
+
of them away.
|
|
24
|
+
|
|
25
|
+
### `STATE_PARTS` <a id="constant-STATE_PARTS"></a> <a id="STATE_PARTS-constant"></a>
|
|
26
|
+
State, not conversation, skipped in silence — the same judgement Claude's
|
|
27
|
+
session-state records get. Observed counts in the real store: step-start
|
|
28
|
+
4,391, step-finish 4,380 (consumed for usage below), patch 569 (files a step
|
|
29
|
+
touched), file 25 (attachments), agent 1, compaction 1 (surfaced through
|
|
30
|
+
`compactions`, not as a message).
|
|
31
|
+
|
|
32
|
+
## Public Instance Methods
|
|
33
|
+
### `usage()` <a id="method-i-usage"></a> <a id="usage-instance_method"></a>
|
|
34
|
+
Session totals, summed per message. No dedup is needed: one row is one API
|
|
35
|
+
response, and the sum was verified against the store's own per-session rollup
|
|
36
|
+
columns — 9,727,437 input / 94,266 output / 22,184,157 cache-read, exactly
|
|
37
|
+
equal both ways on the real store.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Class Agent::Sessions::Readers::Pi <a id="class-Agent-Sessions-Readers-Pi"></a>
|
|
2
|
+
|
|
3
|
+
| | |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| **Inherits** | [Agent::Sessions::Readers::Base](Base.md) |
|
|
6
|
+
| **Defined in** | lib/agent/sessions/readers/pi.rb |
|
|
7
|
+
|
|
8
|
+
pi session files. PROVISIONAL in a way no other reader is: this machine holds
|
|
9
|
+
nine real pi project directories and zero session files inside them
|
|
10
|
+
(2026-08-24), so every mapping below is written against tokentelemetry's
|
|
11
|
+
working parser of the same format (resources/tokentelemetry, backend/main.py,
|
|
12
|
+
_scan_pi_sessions) rather than a corpus of pi's own output. That is
|
|
13
|
+
observation of running code, not of data — one step better than the design
|
|
14
|
+
doc's prose, one step short of every other reader's evidence. Where the two
|
|
15
|
+
could disagree, rule 2 already decides the outcome: a shape this reader has
|
|
16
|
+
not seen becomes an :unknown part and a warning, never an exception, and raw
|
|
17
|
+
carries what really happened.
|
|
18
|
+
|
|
19
|
+
The format per that parser: a header record {"type":"session", id, cwd,
|
|
20
|
+
timestamp}, then typed records — "model_change" (provider, modelId) and
|
|
21
|
+
"message" ({role, model, content[], usage}). usage spells its keys camelCase
|
|
22
|
+
(cacheRead, cacheWrite) and carries agent-computed cost.
|
|
23
|
+
|
|
24
|
+
## Constants
|
|
25
|
+
### `NON_MESSAGE_TYPES` <a id="constant-NON_MESSAGE_TYPES"></a> <a id="NON_MESSAGE_TYPES-constant"></a>
|
|
26
|
+
The header and settings records are session state, not conversation —the same
|
|
27
|
+
judgement Codex's session_meta gets. model_change is state too: the model a
|
|
28
|
+
LATER message used is on that message.
|
|
29
|
+
|
|
30
|
+
### `ROLES` <a id="constant-ROLES"></a> <a id="ROLES-constant"></a>
|
|
31
|
+
Not documented.
|
|
32
|
+
|
|
33
|
+
## Public Instance Methods
|
|
34
|
+
### `usage()` <a id="method-i-usage"></a> <a id="usage-instance_method"></a>
|
|
35
|
+
Session totals, summed per message record. No dedup: nothing observed or
|
|
36
|
+
reported suggests pi repeats one response across records the way Claude does —
|
|
37
|
+
but nothing proves it either, so if pi totals ever read roughly double a
|
|
38
|
+
provider's bill, this is where to look.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Class Agent::Sessions::Readers::Qwen <a id="class-Agent-Sessions-Readers-Qwen"></a>
|
|
2
|
+
|
|
3
|
+
| | |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| **Inherits** | [Agent::Sessions::Readers::Base](Base.md) |
|
|
6
|
+
| **Defined in** | lib/agent/sessions/readers/qwen.rb |
|
|
7
|
+
|
|
8
|
+
Qwen Code chat files. PROVISIONAL, like the adapter: written against
|
|
9
|
+
tokentelemetry's parser of this format, not against real Qwen output.
|
|
10
|
+
|
|
11
|
+
The record shape is Anthropic's — type user/assistant, message.content as an
|
|
12
|
+
array of typed parts, message.usage with the same five spellings Claude uses.
|
|
13
|
+
This is deliberately NOT a subclass of Readers::Claude despite that overlap:
|
|
14
|
+
Claude's reader also carries Claude Code's sidecar machinery (spilled tool
|
|
15
|
+
output, subagent transcripts, uuid/parentUuid branching), none of which is
|
|
16
|
+
known to exist here, and inheriting would mean disabling each one and then
|
|
17
|
+
re-checking every future Claude change against an agent nobody can test. Two
|
|
18
|
+
readers with two evidence bases will drift honestly; one reader pretending to
|
|
19
|
+
serve both will drift silently.
|
|
20
|
+
|
|
21
|
+
## Constants
|
|
22
|
+
### `CONTENT_PARTS` <a id="constant-CONTENT_PARTS"></a> <a id="CONTENT_PARTS-constant"></a>
|
|
23
|
+
Not documented.
|
|
24
|
+
|
|
25
|
+
## Public Instance Methods
|
|
26
|
+
### `usage()` <a id="method-i-usage"></a> <a id="usage-instance_method"></a>
|
|
27
|
+
Session totals, deduplicated by message.id the way Claude's are: the same API
|
|
28
|
+
response can stream into one record per content block, and both agents speak
|
|
29
|
+
the same wire format. Unverified for Qwen — if its writer does not repeat ids,
|
|
30
|
+
this dedup is simply a no-op.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Module Agent::Sessions::Readers <a id="module-Agent-Sessions-Readers"></a>
|
|
2
|
+
|
|
3
|
+
| | |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| **Defined in** | lib/agent/sessions/readers/pi.rb, lib/agent/sessions/readers/amp.rb, lib/agent/sessions/readers/base.rb, lib/agent/sessions/readers/grok.rb, lib/agent/sessions/readers/qwen.rb, lib/agent/sessions/readers/codex.rb, lib/agent/sessions/readers/claude.rb, lib/agent/sessions/readers/gemini.rb, lib/agent/sessions/readers/copilot.rb, lib/agent/sessions/readers/opencode.rb |
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Class Agent::Sessions::Session <a id="class-Agent-Sessions-Session"></a>
|
|
2
|
+
|
|
3
|
+
| | |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| **Inherits** | Object |
|
|
6
|
+
| **Defined in** | lib/agent/sessions/session.rb |
|
|
7
|
+
|
|
8
|
+
One recorded conversation. Everything here comes from a stat or the store's
|
|
9
|
+
own metadata — except project_path, which may need to read inside the session
|
|
10
|
+
file (design doc section 7: the on-disk directory encodings are lossy, so the
|
|
11
|
+
recorded cwd inside the file is the only reliable source). It is computed on
|
|
12
|
+
first access and memoized, which is why this is a plain class rather than a
|
|
13
|
+
frozen Data: an instance is immutable except for that one memo.
|
|
14
|
+
|
|
15
|
+
Equality is identity, not value — unlike every sibling value object here,
|
|
16
|
+
which gets value equality for free from Data. A value comparison would have to
|
|
17
|
+
force project_path on both operands, turning a `uniq` over thousands of
|
|
18
|
+
sessions into the full content sweep the design works to avoid. A caller
|
|
19
|
+
keying a mixed-agent collection should use `uid`, which exists for exactly
|
|
20
|
+
that. Note `to_h` includes `uid`, so its output does not round-trip back
|
|
21
|
+
through `new`.
|
|
22
|
+
|
|
23
|
+
## Constants
|
|
24
|
+
### `UNRESOLVED` <a id="constant-UNRESOLVED"></a> <a id="UNRESOLVED-constant"></a>
|
|
25
|
+
Not documented.
|
|
26
|
+
|
|
27
|
+
## Attributes
|
|
28
|
+
### `agent` [R] <a id="attribute-i-agent"></a> <a id="agent-instance_method"></a>
|
|
29
|
+
Returns the value of attribute agent.
|
|
30
|
+
|
|
31
|
+
### `bytes` [R] <a id="attribute-i-bytes"></a> <a id="bytes-instance_method"></a>
|
|
32
|
+
Returns the value of attribute bytes.
|
|
33
|
+
|
|
34
|
+
### `fidelity` [R] <a id="attribute-i-fidelity"></a> <a id="fidelity-instance_method"></a>
|
|
35
|
+
Returns the value of attribute fidelity.
|
|
36
|
+
|
|
37
|
+
### `format` [R] <a id="attribute-i-format"></a> <a id="format-instance_method"></a>
|
|
38
|
+
Returns the value of attribute format.
|
|
39
|
+
|
|
40
|
+
### `id` [R] <a id="attribute-i-id"></a> <a id="id-instance_method"></a>
|
|
41
|
+
Returns the value of attribute id.
|
|
42
|
+
|
|
43
|
+
### `path` [R] <a id="attribute-i-path"></a> <a id="path-instance_method"></a>
|
|
44
|
+
Returns the value of attribute path.
|
|
45
|
+
|
|
46
|
+
### `started_at` [R] <a id="attribute-i-started_at"></a> <a id="started_at-instance_method"></a>
|
|
47
|
+
Returns the value of attribute started_at.
|
|
48
|
+
|
|
49
|
+
### `updated_at` [R] <a id="attribute-i-updated_at"></a> <a id="updated_at-instance_method"></a>
|
|
50
|
+
Returns the value of attribute updated_at.
|
|
51
|
+
|
|
52
|
+
## Public Instance Methods
|
|
53
|
+
### `initialize(agent:, id:, path:, started_at:, updated_at:, bytes:, format:, fidelity:, project_path: UNRESOLVED, &project_path_resolver)` <a id="method-i-initialize"></a> <a id="initialize-instance_method"></a>
|
|
54
|
+
- **@return** [Session] a new instance of Session
|
|
55
|
+
|
|
56
|
+
### `inspect()` <a id="method-i-inspect"></a> <a id="inspect-instance_method"></a>
|
|
57
|
+
Never calls project_path: inspecting a session in a debugger must not trigger
|
|
58
|
+
the read that enumeration deliberately deferred.
|
|
59
|
+
|
|
60
|
+
### `project_path()` <a id="method-i-project_path"></a> <a id="project_path-instance_method"></a>
|
|
61
|
+
A resolver that raises is deliberately not memoized: a failed read is not an
|
|
62
|
+
answer, so the next call retries rather than freezing the failure in place.
|
|
63
|
+
|
|
64
|
+
Not thread-safe by design: concurrent first access can run the resolver more
|
|
65
|
+
than once, but every run yields the same value and the assignment is atomic on
|
|
66
|
+
MRI, so there is no torn read to guard against. Do not add a mutex.
|
|
67
|
+
|
|
68
|
+
### `to_h()` <a id="method-i-to_h"></a> <a id="to_h-instance_method"></a>
|
|
69
|
+
The honest full dump — includes project_path, so it forces that read. Callers
|
|
70
|
+
listing thousands of sessions should build their own slimmer rows.
|
|
71
|
+
|
|
72
|
+
### `uid()` <a id="method-i-uid"></a> <a id="uid-instance_method"></a>
|
|
73
|
+
Collision-free across a mixed-agent collection, where bare ids may repeat.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Module Agent::Sessions::Sqlite <a id="module-Agent-Sessions-Sqlite"></a>
|
|
2
|
+
|
|
3
|
+
| | |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| **Defined in** | lib/agent/sessions/sqlite.rb |
|
|
6
|
+
|
|
7
|
+
The one way this gem opens a SQLite store: read-only, URI-escaped, with a
|
|
8
|
+
bounded retry against a live writer's lock. Extracted from the opencode
|
|
9
|
+
adapter when the opencode reader became its second caller — two copies of
|
|
10
|
+
escape_uri_path would drift, and the bug it guards is subtle enough that a
|
|
11
|
+
drifted copy would look correct in review.
|
|
12
|
+
|
|
13
|
+
What differs between callers stays with them: the adapter raises
|
|
14
|
+
UnreadableStore on a query failure because a vanished database means no
|
|
15
|
+
sessions at all, while a reader warns and yields nothing because one
|
|
16
|
+
unreadable session must not take down a sweep (Layer 3 rule 2). This module
|
|
17
|
+
only opens; it never decides what a failure means.
|
|
18
|
+
|
|
19
|
+
## Public Class Methods
|
|
20
|
+
### `escape_uri_path(path)` <a id="method-c-escape_uri_path"></a> <a id="escape_uri_path-class_method"></a>
|
|
21
|
+
IMPORTANT, caught in review: SQLite's URI parser gives `%`, `#` and `?`
|
|
22
|
+
syntactic meaning, and the path was being interpolated raw. `#` starts a
|
|
23
|
+
fragment (silently truncating the path there); `?` starts the query string,
|
|
24
|
+
colliding with the `?mode=ro` open_readonly appends. The worst case, confirmed
|
|
25
|
+
directly: a path segment that merely CONTAINS a valid-looking percent-escape —
|
|
26
|
+
a directory literally named "a%23b" —gets that escape DECODED by the URI
|
|
27
|
+
parser into a different path ("a#b"), so a second, unrelated database sitting
|
|
28
|
+
at THAT path is read instead, silently, with no exception at all. One pass,
|
|
29
|
+
not two sequential gsubs: escaping # to %23 and THEN escaping the % that
|
|
30
|
+
produced would double-encode it to %2523.
|
|
31
|
+
|
|
32
|
+
### `open_readonly(path)` <a id="method-c-open_readonly"></a> <a id="open_readonly-class_method"></a>
|
|
33
|
+
No immutable=1: it tells SQLite to trust that the file will never change and
|
|
34
|
+
skip locking AND the WAL entirely — against a live, WAL-mode opencode.db that
|
|
35
|
+
means silently missing every committed-but-not-yet- checkpointed session.
|
|
36
|
+
Opening a WAL db even read-only touches its -shm and -wal sidecars (SQLite's
|
|
37
|
+
own reader bookkeeping, confirmed directly); the recorded sessions themselves
|
|
38
|
+
are never written.
|
|
39
|
+
|
|
40
|
+
busy_timeout gives SQLite up to 5s to retry internally against a lock held by
|
|
41
|
+
the agent's own live writer. A WAL writer's lock is normally held only for the
|
|
42
|
+
instant of a commit, so a lock that has not cleared within 5s is a stuck
|
|
43
|
+
process, not ordinary contention.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Class Agent::Sessions::Store <a id="class-Agent-Sessions-Store"></a>
|
|
2
|
+
|
|
3
|
+
| | |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| **Inherits** | Data |
|
|
6
|
+
| **Defined in** | lib/agent/sessions/store.rb |
|
|
7
|
+
|
|
8
|
+
## Attributes
|
|
9
|
+
### `agent` [R] <a id="attribute-i-agent"></a> <a id="agent-instance_method"></a>
|
|
10
|
+
Returns the value of attribute agent
|
|
11
|
+
- **@return** [Object] the current value of agent
|
|
12
|
+
|
|
13
|
+
### `documented` [R] <a id="attribute-i-documented"></a> <a id="documented-instance_method"></a>
|
|
14
|
+
Returns the value of attribute documented
|
|
15
|
+
- **@return** [Object] the current value of documented
|
|
16
|
+
|
|
17
|
+
### `effective` [R] <a id="attribute-i-effective"></a> <a id="effective-instance_method"></a>
|
|
18
|
+
Returns the value of attribute effective
|
|
19
|
+
- **@return** [Object] the current value of effective
|
|
20
|
+
|
|
21
|
+
### `env_overrides` [R] <a id="attribute-i-env_overrides"></a> <a id="env_overrides-instance_method"></a>
|
|
22
|
+
Returns the value of attribute env_overrides
|
|
23
|
+
- **@return** [Object] the current value of env_overrides
|
|
24
|
+
|
|
25
|
+
### `label` [R] <a id="attribute-i-label"></a> <a id="label-instance_method"></a>
|
|
26
|
+
Returns the value of attribute label
|
|
27
|
+
- **@return** [Object] the current value of label
|
|
28
|
+
|
|
29
|
+
### `layers` [R] <a id="attribute-i-layers"></a> <a id="layers-instance_method"></a>
|
|
30
|
+
Returns the value of attribute layers
|
|
31
|
+
- **@return** [Object] the current value of layers
|
|
32
|
+
|
|
33
|
+
### `retention` [R] <a id="attribute-i-retention"></a> <a id="retention-instance_method"></a>
|
|
34
|
+
Returns the value of attribute retention
|
|
35
|
+
- **@return** [Object] the current value of retention
|
|
36
|
+
|
|
37
|
+
### `retention_source` [R] <a id="attribute-i-retention_source"></a> <a id="retention_source-instance_method"></a>
|
|
38
|
+
Returns the value of attribute retention_source
|
|
39
|
+
- **@return** [Object] the current value of retention_source
|
|
40
|
+
|
|
41
|
+
### `verified_on` [R] <a id="attribute-i-verified_on"></a> <a id="verified_on-instance_method"></a>
|
|
42
|
+
Returns the value of attribute verified_on
|
|
43
|
+
- **@return** [Object] the current value of verified_on
|
|
44
|
+
|
|
45
|
+
### `warnings` [R] <a id="attribute-i-warnings"></a> <a id="warnings-instance_method"></a>
|
|
46
|
+
Returns the value of attribute warnings
|
|
47
|
+
- **@return** [Object] the current value of warnings
|
|
48
|
+
|
|
49
|
+
## Public Instance Methods
|
|
50
|
+
### `documented?()` <a id="method-i-documented-3F"></a> <a id="documented?-instance_method"></a>
|
|
51
|
+
- **@return** [Boolean]
|
|
52
|
+
|
|
53
|
+
### `format()` <a id="method-i-format"></a> <a id="format-instance_method"></a>
|
|
54
|
+
Not documented.
|
|
55
|
+
|
|
56
|
+
### `installed?()` <a id="method-i-installed-3F"></a> <a id="installed?-instance_method"></a>
|
|
57
|
+
- **@return** [Boolean]
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Class Agent::Sessions::Usage <a id="class-Agent-Sessions-Usage"></a>
|
|
2
|
+
|
|
3
|
+
| | |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| **Inherits** | Data |
|
|
6
|
+
| **Defined in** | lib/agent/sessions/usage.rb |
|
|
7
|
+
|
|
8
|
+
Token counts an agent reported, for one message or one whole session,
|
|
9
|
+
normalized to five DISJOINT buckets: `input` never includes what was read from
|
|
10
|
+
or written to cache, and `output` never includes `reasoning`. Agents disagree
|
|
11
|
+
here — Codex's input_tokens includes its cached_input_tokens, Claude's does
|
|
12
|
+
not (both verified against real stores on this machine, 2026-08-24) — and a
|
|
13
|
+
caller summing across agents needs one rule, not one per agent. Readers do the
|
|
14
|
+
subtraction; this object only holds the result.
|
|
15
|
+
|
|
16
|
+
nil means "this format does not record that dimension", and it is load-
|
|
17
|
+
bearing: absence must never read as zero, for the same reason
|
|
18
|
+
Agent::Sessions.read raises on a format with no reader. `cost` is reported by
|
|
19
|
+
the agent or absent — never derived from a pricing table, which would go stale
|
|
20
|
+
in a gem and is a consumer's decision anyway.
|
|
21
|
+
|
|
22
|
+
## Attributes
|
|
23
|
+
### `cache_creation` [R] <a id="attribute-i-cache_creation"></a> <a id="cache_creation-instance_method"></a>
|
|
24
|
+
Returns the value of attribute cache_creation
|
|
25
|
+
- **@return** [Object] the current value of cache_creation
|
|
26
|
+
|
|
27
|
+
### `cache_read` [R] <a id="attribute-i-cache_read"></a> <a id="cache_read-instance_method"></a>
|
|
28
|
+
Returns the value of attribute cache_read
|
|
29
|
+
- **@return** [Object] the current value of cache_read
|
|
30
|
+
|
|
31
|
+
### `cost` [R] <a id="attribute-i-cost"></a> <a id="cost-instance_method"></a>
|
|
32
|
+
Returns the value of attribute cost
|
|
33
|
+
- **@return** [Object] the current value of cost
|
|
34
|
+
|
|
35
|
+
### `input` [R] <a id="attribute-i-input"></a> <a id="input-instance_method"></a>
|
|
36
|
+
Returns the value of attribute input
|
|
37
|
+
- **@return** [Object] the current value of input
|
|
38
|
+
|
|
39
|
+
### `output` [R] <a id="attribute-i-output"></a> <a id="output-instance_method"></a>
|
|
40
|
+
Returns the value of attribute output
|
|
41
|
+
- **@return** [Object] the current value of output
|
|
42
|
+
|
|
43
|
+
### `reasoning` [R] <a id="attribute-i-reasoning"></a> <a id="reasoning-instance_method"></a>
|
|
44
|
+
Returns the value of attribute reasoning
|
|
45
|
+
- **@return** [Object] the current value of reasoning
|
|
46
|
+
|
|
47
|
+
## Public Instance Methods
|
|
48
|
+
### `+(other)` <a id="method-i--2B"></a> <a id="+-instance_method"></a>
|
|
49
|
+
Sums dimension-wise, keeping the nil/zero distinction: nil + nil stays nil
|
|
50
|
+
("neither side records this"), nil + n is n — one recorded value is a real
|
|
51
|
+
value, not a value plus an unknown, because per-message absence under a format
|
|
52
|
+
that does record the dimension means "none reported for this message", the one
|
|
53
|
+
place absence and zero do coincide.
|
|
54
|
+
|
|
55
|
+
### `initialize(input: nil, output: nil, cache_read: nil, cache_creation: nil, reasoning: nil, cost: nil)` <a id="method-i-initialize"></a> <a id="initialize-instance_method"></a>
|
|
56
|
+
- **@return** [Usage] a new instance of Usage
|