@titan-design/session-analytics 0.4.1 → 0.5.0
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.
- package/README.md +130 -3
- package/dist/index.d.ts +1034 -4
- package/dist/index.js +1214 -46
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -29,10 +29,33 @@ priceRequest(
|
|
|
29
29
|
- `classifySession(facts)` — `agent_spawned`, `human_interactive`, `headless_sdk` or
|
|
30
30
|
`other_headless`, plus `coordinator` or `adhoc` for human sessions.
|
|
31
31
|
- `CONTEXT_BANDS`, `GAP_BANDS`, `bandOf`, `contextBand`, `gapBand`.
|
|
32
|
-
- `costReport(db, { since, until, days, top, transcriptsDiscovered, facetVersion
|
|
33
|
-
standing cost report as one JSON
|
|
32
|
+
- `costReport(db, { since, until, days, top, transcriptsDiscovered, facetVersion, actionRules,
|
|
33
|
+
mechanicalClasses, episodeRoles, noActionClasses, brokerLogLines, handoff })` — the standing cost report as one JSON
|
|
34
|
+
object, and `costReportSchema`, its zod schema. `byAction` gives each role's cost by action
|
|
35
|
+
class, and `mechanicalShare` the cost of the `mechanicalClasses` (default
|
|
36
|
+
`DEFAULT_MECHANICAL_CLASSES`) over the window total, with each role's share over that role's
|
|
37
|
+
cost. `wakeEpisodes` cuts the wakes of the `episodeRoles` (default `DEFAULT_EPISODE_ROLES`:
|
|
38
|
+
`coordinator` and `worker:coordinator`) into episodes; see "Wake episodes" below.
|
|
39
|
+
`handoffThreshold` fits the handoff model per role and model; see "Handoff threshold" below.
|
|
40
|
+
- `buildWakeEpisodes`, `summarizeWakeEpisodes`, `episodeNames`, `wakeEpisodesSchema`,
|
|
41
|
+
`WAKE_FROM_KINDS`, `DEFAULT_NO_ACTION_CLASSES` — the pure pieces behind `wakeEpisodes`.
|
|
42
|
+
- `handoffThreshold(rows, teleports, agentSessions, options?)`, `sweepK`, `costPerRequest`,
|
|
43
|
+
`isBootAction`, `parseTeleportEvents`, `handoffThresholdSchema` — the pure pieces behind
|
|
44
|
+
`handoffThreshold`.
|
|
45
|
+
- `ACTION_CLASSES`, `DEFAULT_ACTION_RULES`, `DEFAULT_MECHANICAL_CLASSES`,
|
|
46
|
+
`classifyRequest(calls, rules?)` — one action class per request from its tool calls: the
|
|
47
|
+
first rule in list order that any call matches, `text-only` with no calls, `other` with no
|
|
48
|
+
match. Rules match a tool name, a Bash command head, or a read or written path.
|
|
49
|
+
- `readRequestToolCalls(db, window)` — each request's tool calls as `ActionCall`s, with the
|
|
50
|
+
`command_heads`, `file_read` and `file_write` signals session-read extracted for them.
|
|
34
51
|
- `renderCostReportText(report)` — the same report as plain-text tables, ending with
|
|
35
52
|
`LIST_PRICE_CAVEAT` and the price-table and coverage footer.
|
|
53
|
+
- `renderCostReportSections(report, sections)`, `COST_REPORT_SECTIONS` — chosen sections of that
|
|
54
|
+
text under the same header, with the caveat and footer last; one question's answer, not the
|
|
55
|
+
whole report.
|
|
56
|
+
- `scope` on `costReport` and `cacheTtlReport` options (`ReportScope`: `sessionIds`,
|
|
57
|
+
`agentPrefix` on agent-chat names, `roles`), and `scopeFilter(db, scope)` behind it — narrows
|
|
58
|
+
every request-keyed field to some sessions. Compactions and coverage stay window-wide.
|
|
36
59
|
- `roleFromProfile`, `workerRole(facts)`, `sessionRole(classification, facts)` — worker-v1
|
|
37
60
|
roles, including the standing-peer overlay.
|
|
38
61
|
- `buildEpisodes(input, "worker-v1" | "coordinator-v1")` (pure), `readEpisodeInput`,
|
|
@@ -49,6 +72,89 @@ const report = costReport(openDatabase(graphPath, { readonly: true }), { days: 7
|
|
|
49
72
|
process.stdout.write(renderCostReportText(report));
|
|
50
73
|
```
|
|
51
74
|
|
|
75
|
+
## Wake episodes
|
|
76
|
+
|
|
77
|
+
An episode is one arrival that wakes a session and the requests after it, up to the next
|
|
78
|
+
arrival in the same transcript. An arrival is a turn-start record or a mid-loop delivery, the
|
|
79
|
+
`queued_command` that reaches a busy seat inside a tool loop. A tool result is never an arrival.
|
|
80
|
+
Mid-loop deliveries are most of a busy coordinator's events, so leaving them out would show a
|
|
81
|
+
handful of expensive wakes and hide the rest.
|
|
82
|
+
|
|
83
|
+
`wakeEpisodes.byCause` gives, per cause, episodes, mid-loop episodes, requests, cost,
|
|
84
|
+
`requestsPerEpisode`, `costPerEpisode`, and the no-action count and cost. Causes are
|
|
85
|
+
session-read's, except that `channel_system` is reported as `agent_lifecycle`: agent-chat's
|
|
86
|
+
notice that an agent exited or changed state. Requests in the window whose arrival came before
|
|
87
|
+
it are counted under `unattributed`.
|
|
88
|
+
|
|
89
|
+
**No-action rule.** An episode is no-action when every one of its requests has an action class
|
|
90
|
+
in `noActionClasses`, by default `read-investigate`, `text-only` and `other`. An episode with no
|
|
91
|
+
requests, such as the second report of a burst, is no-action. Anything else, including a
|
|
92
|
+
journal write, a PR check or a message, counts as action. The rule reads the TP-501 facet
|
|
93
|
+
rather than regexes over tool input, so the default action rules and a seat's own `actionRules`
|
|
94
|
+
decide it the same way they decide `byAction`.
|
|
95
|
+
|
|
96
|
+
**From.** Each episode has a sender kind. `broker` is agent-chat itself, the sender of the
|
|
97
|
+
lifecycle notice. `broadcast` is a message whose `msg_id` reached more than one session. `seat`
|
|
98
|
+
is a name carried by a top-level session or by a session spawned with a coordinator profile.
|
|
99
|
+
`agent` is any other named sender, and `none` is an arrival with no sender. `pairs` is the
|
|
100
|
+
sender-by-receiver matrix. A seat sender is named, and every other sender collapses to its kind,
|
|
101
|
+
because spawned agents have one-off names.
|
|
102
|
+
|
|
103
|
+
## Handoff threshold
|
|
104
|
+
|
|
105
|
+
When should a session hand over to a fresh one? Each session's terms come from its own requests,
|
|
106
|
+
main thread only, priced from `PRICE_TABLE` rather than the graph's stored price table:
|
|
107
|
+
|
|
108
|
+
- **Boot cost B**: the cost of every request up to and including the first action, a dispatch,
|
|
109
|
+
a send (`BOOT_TOOL`) or a file write.
|
|
110
|
+
- **Boot fill f0**: the context tokens at that request.
|
|
111
|
+
- **Growth g**: the mean rise in fill per request after boot. Only rises count, so a
|
|
112
|
+
compaction's drop does not cancel the growth before it.
|
|
113
|
+
- **Read price p**: the model's cache-read rate.
|
|
114
|
+
|
|
115
|
+
A cycle runs from f0 to the threshold K in n = (K - f0) / g requests, so a request costs
|
|
116
|
+
B / n + p x (f0 + K) / 2. `cohorts` averages the terms over each role and model, sweeps K over
|
|
117
|
+
`DEFAULT_K_SWEEP` and reports the best K, the extra cost per request at each `configuredK`
|
|
118
|
+
(the charter's `teleport_k` and `retire_k`), and the same sweep in `halfBoot`, where only half
|
|
119
|
+
the boot is overhead. `sessions` carries the per-session terms and best K. A session with no
|
|
120
|
+
boot action, or on an unpriced model, counts in `unbootedSessions`.
|
|
121
|
+
|
|
122
|
+
`teleports` gives the exit fill of each handover: the fill of the outgoing session's last
|
|
123
|
+
request at or before the broker's `teleport_started` line. The package does not open the broker
|
|
124
|
+
log. Pass its lines as `brokerLogLines`; the `from` agent id maps to sessions through
|
|
125
|
+
`session_origin.agent_id`.
|
|
126
|
+
|
|
127
|
+
`reviewers` compares `reviewerPrs` reviews (default 10). A fresh reviewer per PR pays its boot
|
|
128
|
+
and its own reads each time. A standing reviewer, priced from the `standingRole` cohort, boots
|
|
129
|
+
once and then reads a context that every earlier PR grew. A row's requests per PR come from the
|
|
130
|
+
reviewer sessions on its own model; a standing model with no reviewers of its own takes the
|
|
131
|
+
mean of the newest reviewer cohort, the model whose latest session ends last; `requestsFrom`
|
|
132
|
+
names that model. With no reviewer session at all it is 0 and `requestsFrom` is `"pooled"`.
|
|
133
|
+
A session with no request after boot does not count toward its cohort's growth. A reviewer's
|
|
134
|
+
review often sits inside its boot (its first write or send comes late), so requests per PR
|
|
135
|
+
count only what follows it and can understate a reviewer that does its work before it writes.
|
|
136
|
+
|
|
137
|
+
The report reads a window, so a session that started before it has its boot cut short.
|
|
138
|
+
|
|
139
|
+
## Cache TTL what-if
|
|
140
|
+
|
|
141
|
+
`cacheTtlReport(db, { since, until, days })` asks what `CLAUDE_CODE_PROMPT_CACHE_TTL=5m` would
|
|
142
|
+
have saved against the 1h TTL. `cacheTtlWhatIf(rows, prices?)` is the pure core over
|
|
143
|
+
`TtlRequestRow`s, which `readTtlRows(db, window)` reads with the cost report's roles and each
|
|
144
|
+
session's spawn profile. `cacheTtlWhatIfSchema` is its zod schema and `renderCacheTtlText` its
|
|
145
|
+
text form.
|
|
146
|
+
|
|
147
|
+
- **Reprice.** Every 1h write is priced at the 5m write rate instead, from `findPrice`.
|
|
148
|
+
- **Rebuild.** A request whose gap falls in `REBUILD_GAP_BANDS` (the `gapBand`s from 5 minutes
|
|
149
|
+
up) finds a 5m cache gone. Its cache read is charged again at the 5m write rate, less the read
|
|
150
|
+
it no longer pays. Past an hour the 1h cache had expired too, so the read is already near zero
|
|
151
|
+
and only the reprice applies.
|
|
152
|
+
- **Net.** Per role and per profile: reprice saving less rebuild cost, and the same per session.
|
|
153
|
+
`lossRoles` lists the roles whose net is negative, typically seats that wait on CI.
|
|
154
|
+
|
|
155
|
+
The gap is the request's `gap_ms` when the miner stored one. Otherwise it is the time since the
|
|
156
|
+
session's previous request on the same thread, which may fall before the window.
|
|
157
|
+
|
|
52
158
|
## Things that will bite you
|
|
53
159
|
|
|
54
160
|
Fable's cache read is **0.025** of its input rate, not the 0.1 every other model uses.
|
|
@@ -59,6 +165,27 @@ because defaulting bills a new model at an old model's rate without saying so. T
|
|
|
59
165
|
report lists such models under `unpricedModels`.
|
|
60
166
|
|
|
61
167
|
The cost report prices through the graph's `price` table, not through `PRICE_TABLE`. A graph
|
|
62
|
-
whose price rows were never synced reports every request as unpriced
|
|
168
|
+
whose price rows were never synced reports every request as unpriced, and one synced from an
|
|
169
|
+
older `PRICE_TABLE` keeps pricing at the old rates. The cost report takes a caller-opened,
|
|
170
|
+
read-only graph and never writes it. Before reporting, the caller must run session-graph's
|
|
171
|
+
`reconcilePrices(graph, PRICE_TABLE, { tableVersion: PRICE_TABLE_VERSION, source: "session-analytics" })`
|
|
172
|
+
through a writable connection; `titan-miner` does so on every open, other openers do not.
|
|
173
|
+
|
|
174
|
+
The default action rules are generic. Rules that name a seat's own journal files or scorer
|
|
175
|
+
scripts belong in the caller's config, passed as `actionRules`, never in this package. The
|
|
176
|
+
defaults read session-read's `command_heads` signal, which keeps only a path's shape, so
|
|
177
|
+
`gh api -X PUT repos/o/r/pulls/5/merge` reaches the classifier as `gh api PUT pulls/merge`. No
|
|
178
|
+
default rule matches that head yet, so it is not a merge.
|
|
179
|
+
|
|
180
|
+
A tool call belongs to the latest request at or before it in its transcript, the request that
|
|
181
|
+
issued it. The `context_contribution` view maps the other way, to the request a block feeds.
|
|
182
|
+
|
|
183
|
+
session-read keeps `from` and `msg_id` from a channel tag but not its `broadcast` attribute, so
|
|
184
|
+
a broadcast is inferred from its `msg_id` reaching more than one session. A multicast to named
|
|
185
|
+
recipients counts as a broadcast too.
|
|
186
|
+
|
|
187
|
+
Coordinator seats spawned with the `opus-coordinator` profile report as `worker:coordinator`.
|
|
188
|
+
Before that profile was mapped they fell into `worker:unknown`, and the `coordinator` role held
|
|
189
|
+
only human-driven seats.
|
|
63
190
|
|
|
64
191
|
Full reference: `site/reference/session-analytics.md`.
|