feat(facts): compute a deterministic session-facts sheet from the spans - #112
Open
drewstone wants to merge 1 commit into
Open
feat(facts): compute a deterministic session-facts sheet from the spans#112drewstone wants to merge 1 commit into
drewstone wants to merge 1 commit into
Conversation
The bounded trace tools cannot return a whole-session fact. `viewTrace` degrades to a 20-entry name histogram above its 150,000-byte ceiling, `countTraces` counts traces rather than spans, `viewSpans` needs span ids the reader does not have, and `searchTrace` stops at 500 hits. A model asked for a tool-call total therefore adds up a capped histogram and decides by eye which names count. Measured over twelve private audit sessions, the model-backed analyst arm scored a deterministic mean of 0.389. Extracting the same answers mechanically from the OTLP spans those runs already wrote scores 0.858: the facts were present and exact the whole time. This adds that extraction as `src/session-facts.ts`, a `traces facts` command that prints it at $0, and the same sheet as prepared context for the built-in analyst kinds and every `traces ask` question. Two rules hold for every field: it names the span ids it was computed from, so a reader can check it; and a fact the spans cannot support is null with a stated reason, never a guess and never a silent zero. The sheet is not a span and cannot be cited — citations still resolve against the raw spans. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #108 (
feat/ask-questions), because this wires the sheet into theaskpath that PR introduces. Base it onmainonce #108 merges.What it implements
Ranked change 1 of
analysis.md, the largest measured item: a deterministic session-facts sheet computed from the spans, with no model call.Measured on twelve private audit sessions, over the OTLP files those runs already wrote:
Expected lift for this change alone: +0.414 deterministic mean (0.389 → 0.803), against an Opus subagent fleet at 0.906 dev / 0.917 holdout.
Nothing was missing from the spans. No tool returned a whole-session fact —
viewTracedegrades to a 20-entry name histogram above its 150,000-byte ceiling,countTracescounts traces rather than spans,viewSpansneeds span ids the reader does not have, andsearchTracestops at 500 hits — so a model asked for a tool-call total added up a capped histogram and decided by eye which names counted.The benchmark harness that produced those numbers is private and lives outside this repo. No session content from it appears here: every fixture is synthetic inline JSONL.
What is in the change
src/session-facts.ts.computeSessionFacts(spans)returns, per trace: tool calls excluding synthesized spans (with the excluded count kept separately, and a by-name histogram), everyspawn_agentwith its task name, humanuser.promptturns in order with actor and timestamp (plus every turn by actor), the last message of the session's own agent and of each subagent task kept apart, changed paths from patch headers and file-editing tool arguments, first and last record times, unread records from the session integrity receipt, and the harness token total when a span carriestraces.session.total_tokens.traces facts. JSON by default,--format textfor the short readable form,--outto write it, for one session or many. No model call, no engine, no budget: $0.askquestion throughTraceAnalystDefinition.prepareContext, bounded at 30,000 bytes — a fifth of the documentedperCallByteCeilingof 150,000 — with the shed reported inomitted_fieldsrather than applied silently.Two rules hold for every field: it names the span ids it came from, so a reader can open them and check the number; and a fact the spans cannot support is
nullwith a stated reason, never guessed and never a silent zero. The sheet is not a span and cannot be cited —trace://citations still resolve against the raw spans, which is why each fact carries its span ids.factsexits non-zero when a selected session produced no record spans, rather than printing a sheet of zeros for a session it could not read.Schema and behavior changes
SESSION_TOKEN_TOTAL_ATTR = 'traces.session.total_tokens', the span attribute the sheet reads for the harness token total. No adapter emits it yet, sotokenTotalis null with its reason; ranked change 2 emits it, and that is worth a further +0.125 on its own.SPAN_SYNTHESIZED_ATTR = 'traces.codex.span_synthesized', honored when present. Until ranked change 5 sets it, a synthesized subagent span is identified by thetraces.codex.subagent_thread_id+traces.codex.subagent_pathpair only that span carries.traces.session_factsandtraces.session_facts_report, bothschemaVersion: 1.+session-facts.1appended to its version.createTraceAnalystrecordsprepare_contextin the exact-run identity, so a changed prepared context must not hide behind an unchanged version.analyzeSpans({ sessionFactsContext: false })runs an analyst without it.ask's rules text now names the sheet and stays inside the 500-character DSPy preview head;--otlpis now a reading flag onfacts, and--current/--workfloware accepted.agent-eval.Tests
17 new tests in
tests/session-facts.test.tsover a synthetic Codex rollout (tests/session-facts-fixture.ts) whose gold is written by hand:tool.Agentspan does not inflate the tool count — 5 TOOL spans, 4 tool calls, 1 excluded;tokenTotalis null with its reason, and reports the value once a span carries the attribute;analyzeSpanshands the sheet to the built-in kinds and omits it undersessionFactsContext: false;traces factsprints both forms, keeps the facts and names the unread records for a partly corrupt session, and exits non-zero for a session that cannot be read and for an unknown--format.pnpm check:source,typecheck,test(845 passed),buildandcheck:packageare green locally.Documented in
README.md(Session facts) anddocs/trace-analysts.md(Session facts, including that the sheet is deterministic and free).🤖 Generated with Claude Code