Skip to content

feat(telemetry): a flow the tool named is a flow - #768

Merged
blafourcade merged 1 commit into
nextfrom
fix/a-flow-the-tool-named-is-a-flow
Sep 4, 2026
Merged

blafourcade merged 1 commit into
nextfrom
fix/a-flow-the-tool-named-is-a-flow

Conversation

@blafourcade

@blafourcade blafourcade commented Sep 4, 2026 •

Copy link
Copy Markdown
Contributor

What

by_flow decided a record's flow from journal intervals and nothing else (cost-report.ts's flowKeyOf). A record whose own transcript states the orchestrating skill it ran under was reported in the unnamed row, however plainly it said so.

Measured on this machine's sink, --days 30, 30,222 requests, on the binary built from 9fc819ef:

Axis aidd-orchestrator:01-sdlc Attribution
by_step 2,220 tool-stated
by_step 1 journal-interval
by_flow 1,052 journal interval, the only source it had

The two axes were not disagreeing about one flow. They were looking at two sessions: the 1,052 come from the session holding the corpus's only orchestrating step_start; the 2,220 come from a second session whose journal has six step_end lines and no step_start at all.

Why a session states a step its journal never opened

step_start is written by a hook when a skill is invoked. A session resumed after its context was compacted carries its skills forward in the restored context, so nothing is invoked again and no hook fires — while the transcript goes on stating the step on every record it produces. That journal shows exactly this: ends without starts.

Not an error to repair at the write path. It is a capture the journal cannot make, and a second source that already made it.

Change

flowKeyOf gains one fallback. A record no interval covers, whose own step_attribution is tool-stated and whose step names an orchestrating skill, joins a row for that skill.

Row Keyed on attribution started_at
a flow the journal witnessed the FlowInterval object, by reference journal-interval the interval's own start
a flow only the tool named the skill name tool-stated absent

Keeping them apart preserves what buildFlowIntervals argues for: two orchestrated runs of one skill in one session are two rows, because two intervals are two objects. A name is not a run, so the tool-stated row cannot make that distinction and does not pretend to.

An interval wins over a tool-stated step, and the reason is granularity, not strength. withStepBackfill prefers a tool-stated step over a journal-interval one; this preference runs the other way, and the two are not in conflict. There the question is which skill — the tool naming its own beats an inference from a moment. Here it is which run — and only an interval can say.

Proof, two binaries against one sink

by_flow row 9fc819ef this branch
aidd-orchestrator:01-sdlc, journal-interval 1,052 1,052
aidd-orchestrator:01-sdlc, tool-stated — 2,208
no flow 29,170 26,962

The unnamed row drops by exactly 2,208, and all eleven breakdowns sum to 30,222 on both. 2,208, not 2,220: the twelve missing are tool-stated records that fall inside the journal's own interval — the precedence rule visible in real data, staying on the interval's row rather than being moved or double-counted.

Guards

Guard Mutation that killed it
a tool-stated orchestrating record with no interval joins a row named for its skill drop the fallback — 2 unit, 4 e2e
a record inside an interval stays on the interval's row let the fallback run first — 1
a tool-stated step naming no orchestrating skill joins no flow row drop the ORCHESTRATING_SKILLS check — 1
an orchestrating step the reader merely inferred opens no flow accept any attribution — 1
by_flow still sums to the period total fold the row into nothing — 2
the --axis flow table's own four-column shape put the three-column header back — 1 e2e
the journal's two limits print only where a journal row does restore the old flow !== undefined gate — 1
the tool-stated limit prints only where such a row does — (paired with the above)

The limits each row kind states

flowLimits used to gate on "a named row exists". A tool-stated row has a name, so both of the journal's own limits would have printed for a period whose only flow came from a record's own tool — describing a walk that never ran. They are now gated on a journal-interval row, and a tool-stated row states the limit that is actually its own: it is every run of that skill at once, so its total is not one orchestration's.

Envelope

No bump. The flow row gains attribution, a field a consumer may ignore, and no existing field changes meaning — the rule stated at cost-report-envelope.ts. Every consumer updated in the same commit: the envelope type and mapper, the --axis flow artefact (which gains an Attribution column, the same third column stepArtefact already carries and for the same reason), the contract, and the cost skill's own reading instructions. The terminal display renders no flow axis.

The contract also carried a stale sentence from #763 — a flow closing at "the next orchestrating step_start or a turn_end". Corrected here, in the section this PR rewrites.

Not in scope

by_prompt's doc comment calls it "the one breakdown that is complete by construction". Measured the same day on the same sink: 843 of 30,714 records carry no prompt_id. Recorded in this PR's plan with its evidence; it is a different axis and its own argument.

Bundle is at 592.9 KB against a 593 KB budget — 0.1 KB of headroom. Not raised here; the next change to touch the bundle will have to.

Gates: 3443 tests / 308 files, tsc, biome ci (2 pre-existing warnings, 0 new), knip, jscpd, bundle within budget, 369 repository script tests, 0 broken links, cli layering clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

@blafourcade
blafourcade requested a review from a team as a code owner September 4, 2026 22:26
`by_flow` decided a record's flow from journal intervals and nothing else. A
record whose own transcript states the orchestrating skill it ran under was
reported as belonging to no flow, however plainly it said so.

Measured on this machine's sink, 30 days, 30,222 requests: `by_step` names
`aidd-orchestrator:01-sdlc` on 2,220 requests as `tool-stated`, and `by_flow`
placed 2,208 of them outside every flow. They come from a session whose journal
holds six `step_end` lines and no `step_start` at all — the shape a session
resumed after its context was compacted leaves behind, since nothing is invoked
again and no hook fires, while the transcript goes on stating the step on every
record it produces.

`flowKeyOf` gains one fallback: a record no interval covers, whose own
`step_attribution` is `tool-stated` and whose `step` names an orchestrating
skill, joins a row for that skill. The row is a distinct kind, keyed on the
name, carrying `attribution: "tool-stated"` and no `started_at` — a name is not
a run, so it can neither tell two runs apart nor claim a single opening moment.

An interval still wins where a record falls inside one, and the reason is
granularity, not strength: `withStepBackfill` prefers a tool-stated step over a
journal-interval one because there the question is *which skill*; here it is
*which run*, and only an interval can say. Twelve records show it in the real
data — tool-stated and inside the interval, they stay on the interval's row.

`by_flow` still reconciles: the unnamed row drops by exactly what the new row
gains. No envelope bump — the flow row gains a field a consumer may ignore, and
no existing field changes meaning.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
@blafourcade
blafourcade force-pushed the fix/a-flow-the-tool-named-is-a-flow branch from 22f6f4b to 56c03e2 Compare September 4, 2026 22:30
@blafourcade
blafourcade merged commit 37a6b2c into next Sep 4, 2026
17 checks passed
@blafourcade
blafourcade deleted the fix/a-flow-the-tool-named-is-a-flow branch September 6, 2026 06:22
@aidd-bot aidd-bot Bot mentioned this pull request Sep 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant