fix(extract_llm): book the saving the wire saw, not the projection's - #216
Conversation
extract_llm booked `candidate - TextTokens(projection)` for every removal, on
both the fresh and the replay path. What `apply` actually writes is
projected + "\n[" + summary + "] " + marker + recovery hint
so the figure omitted the summary segment, the marker and the recovery hint.
extract_llm_sweep already subtracts the message AS SPLICED, so after #188 the
two extraction components were measuring "saved tokens" against different
baselines while both feeding metrics.RecordExtractionSaving /
RecordExtractionValue and both surfacing in /stats under by_component. Two arms
of a comparison were not measuring the same thing. #195.
THE GAP IS SUMMARY-DOMINATED, not the marker's fixed ~23 tokens. #195's
description put it at the marker's overhead; that is true of the sweep's old
figure and wrong for this component, where the summary is the dominant term and
is a MODEL OUTPUT — so the overstatement varies per candidate and does not
average out across a run. Measured on this change's fixture: 6,833 booked
against 6,787 actually sent, 46 tokens on one candidate whose summary is 98
characters; a real 120-rune summary on a smaller candidate is a far larger
fraction.
IT IS NOT ONLY REPORTING. out[k].saved feeds e.ratios.observe, and the ratio
tracker is what the economic gate consults to decide whether a call is worth
making at all — so an optimistic saving biased the decision to spend, in the
direction of spending more.
Both sites move to the sweep's basis:
- fresh: phase 3 subtracts schema.MessageText(req.Input[i]) once the splice is
a fact. runCall no longer computes `saved` at all; only `before` rides along
in the slot, as the ratio's denominator. Taken from cands[k].content rather
than out[k].before, which is 0 in a single-flight FOLLOWER's slot — and a
follower does reach the splice, so that subtraction would store a negative.
Nothing books it today (the guard is out[k].called), but a stored negative
saving is one refactor away from being read.
- replay: credits `content - MessageText(req.Input[i])` at the repeat rate.
Correcting only the fresh path would have left the component contradicting
itself — the same compaction worth more on every replay turn than on the
turn it was made — and replays are the steady state, so most of the reported
value would come from the overstated side. That is the shape the sweep was
in for exactly one commit (c442670).
SEQUENCING, which is a maintainer's call and not mine: #195 asks for this to
land AFTER the iteration-024 re-run, or with an explicit note beside its
results, so a savings-basis change does not land in the middle of the comparison
it would invalidate. Iteration 024's numbers are not in the tree, so the durable
note went into docs/components/extract_llm.md, which now states the basis in the
`gross_saved_tokens` row and records why it moved. Anyone reading 024's two arms
across this commit must know extract_llm's figure got smaller without the
mechanism getting worse.
WHAT THE BROKEN VERSION DID, so the tests can be audited from outside: on the
new fixture it booked TextTokens(body) - TextTokens("<40 body lines + elision
notes>") = 6,833, while the message it sent was those lines plus
"\n[worker log: 400 identical INFO batch lines, …] <<cg:HASH>> [full output:
call cg_expand]", which is 6,787 smaller than the body. Both figures are large
and positive; they differ by summary + marker + hint.
VERIFICATION (Go 1.26.4, eval box): gofmt -l clean, go vet ./... clean,
go test ./... 28/28 packages pass.
Two mutations, each reverting ONE site, each confirmed to BUILD first
(go build ./... + go vet), each landing exactly once, and each failing on its
own test's own assertion with the other test still green:
M1 fresh path books the projection again -> FAIL "booked 6833 tokens; the
(out[k].saved = before - TextTokens(res), message it sent shrank by 6787"
phase 3's measurement removed) replay test still PASSES
M2 replay books cached.Projected again -> FAIL "credited 6833 tokens of
value; the message it sent
shrank by 6787"
fresh test still PASSES
M1 was also run against the whole package: it fails ONLY the fresh test, so no
existing test was silently depending on either basis.
The fixture needed a model that sets both OUTPUT and SUMMARY. shrinkingModel
does not — its reply defines a `transform` function, never assigns OUTPUT, and
the deterministic fallback then supplies the projection with an empty summary,
which exercises the marker overhead but NOT the term this change is about. Two
things the new fixture had to clear to put the subject inside the shape: a
one-line OUTPUT is refused by the acceptance check's keep-ratio floor (~5% of
the body), and a summary over clipSummary's 120 runes comes back ellipsized so a
verbatim Contains check fails for an unrelated reason. Both are asserted as
preconditions rather than assumed, and each test fatals if the projection-based
figure is not strictly larger than the wire figure — without that the fixture
could not tell the two implementations apart.
Signed-off-by: DAVID AMID <DAVIDA@il.ibm.com>
Assisted-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amiddavid
left a comment
There was a problem hiding this comment.
Reviewed at commit 5ac6e61 (note: your message said 04622cf — I reviewed what is actually on the branch).
Independently verified, not taken from the PR body. On the eval box, Go 1.26.4, from a clean git archive of 5ac6e61:
gofmt -lclean,go build ./...clean.go test ./... -count=1: all 28 test packagesok(+3 with no test files). Matches your claim.- M1 (my own mutation: delete the phase-3 measurement, restore
out[k].saved = before - schema.TextTokens(res)inrunCall) — builds and vets, then fails onlyTestExtractLLMBooksWhatTheSplicedMessageActuallySaved, on both of its assertions:booked 6833 tokens; the message it sent shrank by 6787, and the ledger assertion at the same numbers. Every other test incomponents/offloadstill passes, so nothing else was depending on either basis. - M2 (revert the replay site to
cached.Projected) — builds and vets, fails only the replay test:credited 6833 tokens of value; the message it sent shrank by 6787.
So both tests are non-vacuous, each pins its own site, and the numbers reproduce exactly. The projected <= wire precondition in each test is the right guard — without it neither fixture could separate the two implementations, and I confirmed by mutation that it does.
The change itself is correct and the basis now matches extract_sweep.go:531-532 and :339. I also confirmed the claim that RecordExtractionSaving/RecordExtractionValue have no other bookings: four sites, two per component, all four now subtract the spliced message.
Verdict: changes requested — posted as a COMMENT review only because GitHub refuses REQUEST_CHANGES on a PR owned by the same account, not because it is optional. One thing to fix before merge (the first inline comment, on lines 1499-1500) — it is a stale premise this diff itself creates, in the same file, and it is the failure mode #195 was written about. Two smaller notes besides.
I have no view on the sequencing hold; agreed that it is the maintainer's call, and putting the durable note in docs/components/extract_llm.md rather than nowhere is the right answer given 024's numbers are not in the tree.
| out[k].saved = schema.TextTokens(cands[k].content) - | ||
| schema.TextTokens(schema.MessageText(req.Input[cands[k].i])) |
There was a problem hiding this comment.
This assignment makes the comment at line 1544 false, and that comment is load-bearing for the argument right below it.
Line 1543-1549 justifies the out[k].called guard as "A DEFENCE, NOT A FIX: nothing was escaping before it", and enumerates three zero-guards that make it so. One of them is:
out[k].savedis already 0 in a follower slot
That was true before this diff, because out[k].saved was assigned inside runCall's accept branch, which a single-flight follower returns before reaching (if !executed { ...; return }, line 1281-1287). It is false after this diff. The follower's slot is filled with out[k] = outT{projected: res, summary: sum} — projected is non-empty, so phase 3 does not continue at the out[k].projected == "" check, the follower reaches apply, and this assignment then stores a positive wire-measured saving in its slot. tryMark guarantees the spliced text is strictly smaller than the original, so it is positive rather than merely non-zero.
Your own new comment eight lines up says exactly this — "a follower does reach this splice" — so the file now contains two comments 45 lines apart that contradict each other on the same fact, and the wrong one is the one a reader consults to decide whether the guard matters.
The consequence is not cosmetic: the called guard is promoted from a defence to the only thing standing between a follower's positive saved and RecordExtractionSaving, e.ratios.observe (which prices future calls) and calls[k].SavedTokens. Anyone who reads 1543-1549, believes "nothing was escaping before it", and relaxes or refactors that guard now books a saving whose model call was never made by this request — which is the shape the guard was written to prevent.
This is the same defect class #195 itself identifies as the reason the sweep's figure had to move: "a comment added in the same commit asserted it was the wire figure, which made it wrong rather than merely inconsistent." Here the diff invalidates a neighbouring comment's premise rather than adding a wrong one, but the cost to the next reader is identical.
Suggest amending 1543-1549 to say what is now true: two of the three zero-guards still hold, out[k].saved no longer does, and the called guard is therefore load-bearing as of this change — not a defence. If you would rather not restate the whole paragraph, the minimum is dropping the out[k].saved is already 0 clause and the "nothing was escaping" framing that rests on it.
There was a problem hiding this comment.
Fixed in dcad41c, and you were right that the cost is not cosmetic.
The paragraph now states what is true: it was a defence while saved was computed in runCall's accept branch, two of the three zero-guards lapsed when the measurement moved to phase 3, and out[k].called is therefore the only thing between a follower's positive saving and RecordExtractionSaving / e.ratios.observe / calls[k].SavedTokens. It also names the mechanism you traced — non-empty projected, so no projected == "" skip, so it splices and the assignment stores a positive number.
One thing beyond the ask, because a load-bearing guard with no test is one refactor from the defect: TestExtractLLMDoesNotBookASingleFlightFollowersSaving. Four byte-identical outputs, one leader and three followers, and it asserts all four were spliced — a fixture whose followers never splice would be testing a slot that never reached phase 3. It pins RecordExtractionSaving's own contract: one compaction derived by one call, spliced into four messages, books once.
M3, the guard replaced by if true: builds, vets, lands once, and fails only that test — 15,628 tokens booked for 3,907 of compaction, the exact 4× over-count. Worth noting for anyone auditing it: the ledger assertion does not catch M3, because a follower's row is still dropped by the Component filter. The counter assertion is the one doing the work.
Also re-ran M1 and M2 against the restructured code (the before field is gone, see the thread below): each builds, vets, lands once, fails only its own test, 6833 vs 6787 both times.
There was a problem hiding this comment.
Confirmed independently at dcad41c, and the paragraph now says what is true — I re-derived the "two of the three" count rather than take it: observe's totalTok <= 0 early return and the recorders' no-op-at-0 both lapse because a follower's before and saved are now positive, and the Component filter is the one that still holds, as you say it does.
TestExtractLLMDoesNotBookASingleFlightFollowersSaving earns its place. My own M3 (if true in place of the guard) fails only that test, at 15,628 for 3,907 — and your note about the ledger assertion is exactly right: I checked which assertions fired, and it is line 263 alone. Neither ledger assertion fires, because the follower rows never survive the Component filter to be counted. A reader who assumed all three assertions were load-bearing would have mis-read the test's strength, so saying so in the PR is worth more than the extra assertions would have been.
One incidental result worth having in the record: a different mutation of the fresh path — changing the basis in place (before - schema.TextTokens(out[k].projected)) rather than removing phase 3's measurement — fails this follower test too, at 3,953 projection against 3,907 wire. So it guards the wire basis as well as the guard, which is more than its doc comment claims for it.
There was a problem hiding this comment.
Round 2 noted, and both of your for-the-record items are now in the tree rather than only in this thread — 7963330, comment-only, one test file.
- The follower test's doc comment now says it guards the basis as well as the guard, with your variant M1's numbers (3,953 against 3,907) and where they came from. That was the more valuable of the two: its name and comment described less than it protects, so someone deciding what could be changed safely would have under-read it.
- The scheduling race is written down too, including why it is left as is — a lost race fatals on the
deduped_inflight_extractionandmodel.callspreconditions rather than passing vacuously — so a future red says whether the guard broke or the race was simply lost. Your 50/50 at-count=50is recorded.
No assertion moved and no behaviour changed, so I did not re-run the mutations: the round-2 evidence stands, including your independent M1 variant. gofmt -l and go vet clean, the three savedbasis tests pass at -count=5.
Thanks for checking the tokens.Count memoization before I shipped the field removal — I had assumed rather than confirmed that the second Count was cheap, and it was your suggestion, so that was the right thing to verify rather than take on faith.
The sequencing hold on the iteration-024 re-run stays open and is the maintainer's call; nothing here touches it.
There was a problem hiding this comment.
Confirmed comment-only at 7963330, mechanically rather than by eye: one file, 12 insertions, zero deletions, and every added line is a // comment appended to the doc block directly above TestExtractLLMDoesNotBookASingleFlightFollowersSaving — top-level, not inside a raw string literal, which is the only way a pure comment insertion could have changed behaviour. Semantically identical, so not re-running the mutations is right and the round-2 evidence stands unchanged. Approval unaffected.
The recorded numbers match my runs: 3,953 against 3,907 for the basis variant, 50/50 at -count=50, and the two preconditions named are indeed t.Fatalf.
One optional wording note, take it or leave it — it changes nothing and needs no reply if you disagree. The last sentence reads:
Do not read a failure on those two lines as a defect in the guard.
Literally true, and I can see why you wrote it. But deduped_inflight_extraction == 3 and model.calls == 1 can go red for two different reasons: the race was lost, or single-flight dedup itself regressed — a broken result key, a change to the concurrency semaphore that serialises the candidates, getResultGlobal starting to answer them. Only the first is benign. The second is a real defect, just in a different mechanism than the one this test is named for, and it is one a reader who has been told "not a defect" may well shrug off.
If you touch the file again, something like "a failure there means the fixture produced no followers — either the race was lost or single-flight dedup itself regressed; neither is a defect in out[k].called, but the second is a defect worth chasing" keeps the reassurance you intended without inviting a shrug. Not worth a commit on its own.
There was a problem hiding this comment.
Taken, not left — 7a2b035, comment-only, the one sentence.
You called it optional and it was not, quite: "do not read this as a defect in the guard" is the kind of sentence that stops someone looking, and one of the two causes is a real defect. Both are now named — race lost, or single-flight dedup itself regressed (changed result key, a semaphore that serialises the candidates, getResultGlobal answering them) — with the reassurance kept on out[k].called and the second explicitly marked worth chasing. Your phrasing, near enough verbatim.
A new commit rather than an amend of 7963330 deliberately: you verified that SHA mechanically and said so on this thread, and force-pushing over it would leave your verification pointing at a tree that no longer exists in the branch's history. Cheaper to add six lines than to invalidate an audit record.
gofmt -l and go vet clean, follower test at -count=3. Comment lines only, no assertion touched, so round 2's mutation evidence still stands.
Nothing else from me. The only thing left on this PR is the sequencing decision, which is the maintainer's.
There was a problem hiding this comment.
Verified at 7a2b035, same mechanical check: one file, 6 insertions and 2 deletions, every changed line a // comment inside the doc block above the func — no code, no assertion, semantically identical to 7963330. git merge-base --is-ancestor 7963330 7a2b035 confirms the tree I verified is still in the branch history, so my earlier confirmation still points at something real. Deliberately not amending for that reason was the right instinct and I would not have thought to ask for it.
The new wording does the job: both causes named, the reassurance still scoped to out[k].called, and the second cause marked worth chasing rather than covered by a blanket "not a defect".
Approval stands, now at 7a2b035 — recording the SHA so the approved state is unambiguous rather than pointing at an earlier head. Nothing outstanding from me on this PR.
The only open item is not a review item: whether to hold the merge for the iteration-024 re-run, per #195's sequencing request. The durable note in docs/components/extract_llm.md covers the case where it lands first; the timing is the maintainer's call.
| // consults to decide whether the NEXT call is worth making, so an optimistic saving | ||
| // biased that decision towards spending. | ||
| // | ||
| // From cands[k].content rather than out[k].before, which is the same number for a slot |
There was a problem hiding this comment.
Non-blocking, and a consequence of this diff being right: out[k].before now has exactly one reader left, e.ratios.observe at line 1557, and this comment correctly observes it is the same number as schema.TextTokens(cands[k].content) wherever it is read — before := schema.TextTokens(cands[k].content) at line 1244, and every read is under out[k].called.
calls[k].CandidateTokens takes the local before, not the slot's. So once saved stopped riding in the slot, the before field is carrying a value that phase 3 can derive on the spot, and the only reason the slot still has the field is the ratio denominator. Passing schema.TextTokens(cands[k].content) to observe would let the field go, and would remove the last place where a follower's slot holds a number (0) that means something different from what the field name says.
Genuinely optional — it is churn in a hot loop's struct for a readability gain, and the comment already tells the reader the two are equal. Raising it only because the field's remaining purpose is now non-obvious enough to need that comment at all.
There was a problem hiding this comment.
Taken in dcad41c — the field is gone. Phase 3 derives before := schema.TextTokens(cands[k].content) at the point of use and passes it to observe.
Your last paragraph is what decided it: the field needed a comment explaining that its value equals something else, and a follower's slot held a 0 that did not mean what the field name says. Both are gone with it, and outT now carries only saved (filled by phase 3, documented as such) and called.
One knock-on: outT.called's comment cited before > 0 as a rejected alternative predicate, and that field no longer exists. It now says so rather than referring to something a reader cannot find.
| summary the dominant term. Since the summary is a model output, the overstatement varied per | ||
| candidate rather than averaging out, and two arms of a comparison read side by side were not | ||
| measuring the same thing (#195). Both components now subtract the message that was actually sent, | ||
| which is the number an operator can check against their bill. It matters beyond reporting: the same |
There was a problem hiding this comment.
"which is the number an operator can check against their bill" is stronger than gross_saved_tokens can support, and this paragraph is the one a reader will trust on that point.
Two reasons the counter is not the bill delta, both pre-existing and neither introduced here:
metrics.RecordExtractionSavingdocuments its own contract as "count each distinct compaction once — the caller dedups by content key" (metrics/extract.go:233). Single-flight dedups by result key, so when two concurrent requests carry byte-identical content, two messages shrink and one saving is booked — the follower's is deliberately dropped by thecalledguard. Per that contract the behaviour is correct; but it means the figure is per-distinct-compaction, not per-message-sent.- Replays feed
RecordExtractionValueonly, neverRecordExtractionSaving— both components, symmetric, so arm-to-arm comparability is intact. Butgross_saved_tokenstherefore counts fresh removals only, whilegross_value_usdbeside it counts fresh plus replay. Since this doc already had to teachacted_freshvsacted_replayfor exactly that confusion two paragraphs up, a reader is primed to assume the token row spans both.
The basis claim you added is the important one and it is now correct. It is the bill sentence that overshoots: the number is what the measured, deduped, fresh removals shrank the wire by. Given #173's precedent about not presenting a figure as more than it measures, I would soften it to something like "the number of tokens the requests actually shrank by, for the removals it counts" and leave the bill comparison to gross_value_usd.
Same for the table row at line 714 — "the candidate minus what actually went upstream" is exact and I would keep it as is; it is only the prose paragraph that generalises.
There was a problem hiding this comment.
Taken in dcad41c, both reasons stated rather than just softened — the paragraph now says the row is not the bill delta, that RecordExtractionSaving counts each distinct compaction once (so single-flight makes two messages shrink for one booked saving), and that replays feed gross_value_usd but never this row, so the tokens are fresh removals while the dollars beside them are fresh plus replay. Framed as the acted_fresh / acted_replay split one column over, since as you say the reader is already primed for it two paragraphs up, and the money question is pointed at gross_value_usd / net_value_usd.
The table row at 714 stands as written. The same bill claim was in the code comment on the phase-3 assignment; that is gone too — it now says "what this request's message actually shrank by", which is the claim the line can support.
…d test it Review round 1 on #216: one blocker about a comment the previous commit invalidated, and two non-blocking findings, all three taken. THE BLOCKER: THE PREVIOUS COMMIT MADE A NEIGHBOURING COMMENT FALSE, which is the same defect class #195 exists for — there, a comment added in the same commit asserted the sweep's figure was the wire figure, and that is what made it wrong rather than merely inconsistent. The booking guard's own paragraph read "A DEFENCE, NOT A FIX: nothing was escaping before it", and listed `out[k].saved is already 0 in a follower slot` as one of three zero-guards holding that up. True while `saved` was computed in runCall's accept branch, which a single-flight FOLLOWER returns before reaching. False as of the wire measurement: a follower's slot carries a NON-EMPTY `projected`, so it does not take phase 3's `projected == ""` skip, it reaches apply, it splices, and the new assignment stores a POSITIVE saving there (tryMark guarantees the spliced text is strictly smaller). So two of the three zero-guards stopped applying and `out[k].called` was promoted from a defence to the only thing between a follower's saving and RecordExtractionSaving, e.ratios.observe and calls[k].SavedTokens — while the paragraph a reader consults before relaxing it still said it prevented nothing. The file also contradicted itself: my own comment 45 lines up already said "a follower does reach this splice". The paragraph now states what is true, and says which two guards lapsed and why. AND THE GUARD HAD NO TEST, which is the half the review did not ask for. A guard that is load-bearing and untested is one refactor from booking a saving for a request that made no call, so: TestExtractLLMDoesNotBookASingleFlightFollowersSaving — four byte-identical outputs in one request, one leader and three followers, ALL FOUR spliced (asserted, because a fixture whose followers never splice would be about a slot that never reached phase 3). It pins RecordExtractionSaving's own contract, "count each distinct compaction once": one compaction derived by one call and spliced into four messages books once. M3, the guard replaced by `if true`: 15,628 tokens booked for 3,907 of compaction — the exact 4x over-count — and it fails ONLY that test. Note the ledger assertion does not catch M3: a follower's row is still dropped by the Component filter, so the counter assertion is the one doing the work, which is why it is there. NON-BLOCKING 1, taken: `out[k].before` had one reader left and was derivable at the point of use. The field is gone; phase 3 derives `before` from cands[k].content — the same number runCall computes for CandidateTokens — and passes it to observe. This also removes the last place a follower's slot held a number (0) meaning something other than what the field name says. outT.called's comment referenced that field as a rejected alternative predicate; it now says the field itself is gone. NON-BLOCKING 2, taken: "the number an operator can check against their bill" overshot what gross_saved_tokens measures, in the one paragraph a reader would trust on the point. RecordExtractionSaving counts each DISTINCT compaction once (metrics/extract.go:233), so single-flight makes two messages shrink for one booked saving; and replays feed Value only, so the tokens are fresh removals while the dollars beside them are fresh plus replay — the same acted_fresh / acted_replay split this doc already had to teach, one column over. Both stated, and the money question is pointed at gross_value_usd / net_value_usd. The table row's "the candidate minus what actually went upstream" is exact and stands. The code comment lost the bill claim too. VERIFICATION (Go 1.26.4, eval box): gofmt -l clean, go vet ./... clean, go test ./... 28/28 packages, go test -race ./components/offload/ ok (91.8s). All three mutations re-run against the restructured code, each confirmed to BUILD and vet first, each landing exactly once, each failing only its own test: M1 fresh path books the projection again -> 6833 booked vs 6787 sent M2 replay books cached.Projected again -> 6833 credited vs 6787 sent M3 the `called` guard relaxed -> 15628 booked vs 3907 (4x) Signed-off-by: DAVID AMID <DAVIDA@il.ibm.com> Assisted-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amiddavid
left a comment
There was a problem hiding this comment.
Round 2 — all three findings resolved. Approving on the substance.
Verified at dcad41c from a clean git archive, eval box, Go 1.26.4 — independently, not from your numbers:
gofmt -lclean,go vet ./...clean,go test ./... -count=1all packages ok.-raceon the threesavedbasistests: clean, all pass.- M3 (my own:
if out[k].called {→if true {) builds, vets, and fails onlyTestExtractLLMDoesNotBookASingleFlightFollowersSaving, at15628booked for3907of compaction — the exact 4×. And your disclosure is accurate: the failure is on the counter assertion at line 263 only; neither ledger assertion fired, because theComponentfilter still drops the follower rows. Flagging that yourself is the right call — it is the difference between a test with three assertions and a test with one that matters. - M2 re-verified on the restructured code: fails only the replay test, 6833 vs 6787.
- M1 variant of my own — I changed the basis in place (
out[k].saved = before - schema.TextTokens(out[k].projected)) rather than removing the phase-3 line as you did. It fails the fresh test and the follower test (3953projection vs3907wire). Not a discrepancy with your M1; it means the follower test independently guards the wire basis as well as the guard, which is coverage beyond its stated purpose.
Two things I checked because they were consequences of my own suggestions, and both are fine:
- Removing the
beforefield costs no extra work.schema.TextTokensistokens.Count, which memoizes by content hash aboveminCacheLen(internal/tokens), so phase 3's secondCountoncands[k].contentis a hash plus a map lookup, not a re-tokenization — and extraction candidates are large by construction, so they are always on the cached side. I would not have suggested dropping the field if it had meant re-tokenizing on the serial hot path, so I confirmed rather than assumed. - The rewritten guard paragraph's arithmetic is exactly right. Two of the three zero-guards do lapse —
ratioTracker.observe'stotalTok <= 0early return and the recorders' no-op-at-0, both because a follower'sbeforeandsavedare now positive — and the third, theComponentfilter on the ledger append, still applies and is stated as still applying. The paragraph no longer rests on a premise this diff invalidates.
One note on the new test, not a change request. Its dedup depends on a scheduling race: the leader must still hold the single-flight key when the three followers reach Do, and summarizingModel.Complete is pure string work that returns immediately. I ran it -count=50 — 50/50 pass — and the reason I am not asking for anything is that a lost race fatals on the deduped_inflight_extraction == 3 and model.calls == 1 preconditions rather than passing vacuously. So if it ever does flake in CI it will say why, which is the property that matters. Worth knowing it is there if it ever goes red for reasons unrelated to the guard.
The sequencing hold on the iteration-024 re-run remains the maintainer's call and is unchanged by any of this; the durable note in docs/components/extract_llm.md is the right place for it given 024's numbers are not in the tree.
No further findings. Posted as a COMMENT review again only because GitHub will not take an approval on a PR owned by the same account — read this as an approve.
… race it rides Comment-only, in one test file. Round 2 of #216's review raised no change requests and cleared the branch; these are the two things the reviewer put "for the record", written where the next reader will look rather than left in a PR thread. IT ALSO GUARDS THE BASIS. The reviewer's variant M1 changed phase 3's basis in place — `before - TextTokens(out[k].projected)` instead of removing the line as my own M1 did — and TestExtractLLMDoesNotBookASingleFlightFollowersSaving fails on it too, 3,953 against 3,907. So the test protects more than the guard its name and comment describe, and a reader deciding what may be changed safely would otherwise under-read it. THE DEDUP RIDES A SCHEDULING RACE. The leader must still hold the single-flight key when the three followers reach extractInflight.Do, and summarizingModel.Complete is pure string work that returns immediately. Left as is deliberately, and the comment says why: a lost race FATALS on the deduped_inflight_extraction and model.calls preconditions rather than passing vacuously, so a red distinguishes "the guard broke" from "the race was lost". Verified 50/50 at -count=50 by the reviewer. Neither note changes behaviour and no assertion moved. VERIFICATION (Go 1.26.4, eval box): gofmt -l clean, go vet ./components/offload/ clean, the three savedbasis tests pass at -count=5. No mutation re-run: the diff touches only comment lines, so the round-2 mutation evidence (M1, M2, M3, and the reviewer's independent M1 variant) stands unchanged. Signed-off-by: DAVID AMID <DAVIDA@il.ibm.com> Assisted-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…go red for Comment-only, same test file, one sentence. Taking the reviewer's optional wording note from round 3 of #216, because the sentence it is about could suppress an investigation rather than merely read oddly. The previous commit ended the race note with "Do not read a failure on those two lines as a defect in the guard". Literally true and the wrong emphasis: `deduped_inflight_extraction == 3` and `model.calls == 1` go red when the fixture produced no followers, and that has two causes — the scheduling race was lost, or SINGLE-FLIGHT DEDUP ITSELF REGRESSED (a changed result key, a concurrency semaphore that now serialises the candidates, getResultGlobal starting to answer them). Only the first is benign. Telling a reader "not a defect" invites them to shrug at the second, which is a real defect in a different mechanism than the one this test is named for. Both causes are now named, with the reassurance kept: neither is a defect in out[k].called, but the second is worth chasing. VERIFICATION (Go 1.26.4, eval box): gofmt -l clean, go vet ./components/offload/ clean, the follower test passes at -count=3. No mutation re-run — comment lines only, no assertion touched, so #216's round-2 evidence stands. Signed-off-by: DAVID AMID <DAVIDA@il.ibm.com> Assisted-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review of PR #216 — "fix(extract_llm): book the saving the wire saw, not the projection's"Reviewing at head SetupGo 1.26.4, PR body and review threadsRead 1) Basis divergence claim (
|
metrics-booked (gross_saved_tokens) |
wire-accurate (components.saved_tokens) |
gap | |
|---|---|---|---|
| BASE (unpatched) | 682 | 656 | 26 (booked MORE than actually shrank) |
| HEAD (this PR) | 652 | 652 | 0 |
This is the live, real-session version of the same defect and fix the unit tests pin
synthetically: on the unpatched code the metrics figure overstates the wire-observed saving by 26
tokens (this run's summary + marker + hint overhead); on the PR head the two numbers are
identical, because they are now the same computation. The two runs are not byte-identical
conversations (the model's own replies and the LLM extraction's summary text differ slightly run
to run — tokens_before 20,400 vs 20,419 — this is a real session, not a replayed fixture), so the
comparison that matters is the INTERNAL one on each run (booked vs wire), not the absolute totals
across runs, and that internal comparison shows exactly what the PR claims: base diverges, head
does not. The booked figure is also smaller on head (652 vs 682) without the mechanism getting
worse — same shape of extraction, same acceptance, one call each, llm_truncated: 0,
llm_errors: 0 on both.
Correctness and marker resolution
- BASE run answer: "All 400 lines in worker.log are identical... with no errors, warnings, or
latency variation, so there are no anomalies in this log." Correct. - HEAD run answer: the model initially flagged the compacted tool result's marker/recovery-hint
text as looking like an injected instruction, said so, used the expand mechanism to recover the
full original content, and then gave the same correct answer: "all 400 lines are identical
INFO ... status=okentries at a constantlatency_ms=118, so there are no errors, warnings, or
unusual latency." This is a genuine, unprompted exercise of the marker/expand round trip on a
real session (not something I engineered), and it resolved correctly:expand_unresolved_malformed: 0,expand_unresolved_missing: 0,frozen_repaired: 0,frozen_flips: 0,stash_refused: 0on
both runs. Both sessions' final answers were correct, and the marker resolved cleanly on the
run where the model actually exercised it.
Teardown
Both proxies killed by PID (309131 base, 309132 head — captured from $!, never by name/pattern),
confirmed gone (ps -p <pid> returns no rows). The extra merge-base worktree
(/tmp/cgfix/pr216-base, created only to build the comparison binary) has been removed
(git worktree remove /tmp/cgfix/pr216-base --force); both built binaries deleted afterward.
git status in /tmp/cgfix/pr216 (the assigned workspace) is clean.
Ranked findings
Blocker: none found.
Major: none found.
Minor:
docs/components/extract_llm.md— the PR body says "the durable note went into
docs/components/extract_llm.md", which is true for the basis-change explanation, but the
doc does not itself carry the sequencing caveat ("land after the iteration-024 re-run, or note
it beside the results") that metrics: extract_llm and extract_llm_sweep measure saved tokens against different baselines, so their per-arm savings are not comparable #195 asked for. A reader of the doc with no access to this PR or
metrics: extract_llm and extract_llm_sweep measure saved tokens against different baselines, so their per-arm savings are not comparable #195 has no way to know a comparison spanning this commit needs a footnote. Suggest folding one
sentence of that caveat into the same paragraph, e.g. noting that a saved-tokens comparison
spanning this change is not apples-to-apples. Not blocking — the maintainer's sequencing
decision is explicitly out of scope for this PR, and the paragraph as written is accurate on
what it does say.
Nit:
aggressiveness: aggressiveis silently rejected by config validation (must be
low|medium|high) — pre-existing, unrelated to this PR, but worth a bug report since it's an
easy value to guess wrong from the field name.- The two review threads visible on this PR (
gh pr view 216 --comments) are all authored by
amiddavid, the same account as the PR author — GitHub blocksREQUEST_CHANGES/approval on
one's own PR, which the threads themselves note, but that also means there is no independent
reviewer's approval on record yet, only self-review. Not a defect in the change, but worth the
maintainer knowing before merge that "two rounds of review" here is one person auditing their
own diff twice, however rigorously (and it is rigorous — every mutation claim in those threads
reproduced exactly when I re-ran it independently).
What I could not verify / left as unverified
- Iteration-024's actual re-run numbers — not in the tree, so I cannot confirm what "land
after" would compare against; this is explicitly the maintainer's call per the PR body and metrics: extract_llm and extract_llm_sweep measure saved tokens against different baselines, so their per-arm savings are not comparable #195,
and is outside what this review can settle. - Whether disabling
economic_gatefor the live-session proof changed anything about claim 4's
mechanism — it did not need to, since claim 4 was verified from source (ratio tracker /
evaluateGatewiring) rather than from the live run, and I said so above rather than implying
the live run covers it. - Production data — nothing here touched
/var/lib/context-guru/cg.db,/etc/context-guru/**,
or any tenant data; the two proxies used their own in-memory store (--store false) under
/tmp/cgfix/.
Verdict
Approve. Every claim in the PR body that I could check independently reproduced exactly,
including two independent forms of the fresh-path mutation (the PR's own runCall-based M1 and an
in-place phase-3 basis mutation) that both reproduce the same 6,833-vs-6,787 gap and both
additionally fail the follower test at 3,953-vs-3,907 — a stronger result than the PR body's table
claims for the runCall-based M1 alone, and consistent with what the review thread's "M1 variant"
already found. All 386 offload-package tests pass under both -count=1 and -race, the full
go test ./... (30 packages) and make lint are clean, and a real, paired Claude Code session
through this repo's own proxy shows the exact defect and fix live: the pre-fix binary books 682
tokens against a wire-measured 656 (a 26-token overstatement), the PR-head binary books 652 against
a wire-measured 652 (exact match), with both sessions' final answers correct and the marker/expand
round trip resolving cleanly on the run that exercised it. One minor doc-completeness note above,
non-blocking.
Closes #195.
extract_llmbookedcandidate − TextTokens(projection)for every removal, on both the fresh andthe replay path. What
applywrites isso the figure omitted the summary segment, the marker and the recovery hint.
extract_llm_sweepalready subtracts the message as spliced, so the two extraction components were measuring
"saved tokens" against different baselines while both feeding
metrics.RecordExtractionSaving/RecordExtractionValueand both surfacing in/statsunderby_component.Both sites move to the sweep's basis.
out[k].saved)runCallcands[k].content − MessageText(req.Input[i]), in phase 3, once the splice is a factcontent − cached.Projectedcontent − MessageText(req.Input[i])The gap is summary-dominated, not the marker's ~23 tokens
#195's description put the overhead at the marker's; that is true of the sweep's old figure and
wrong for this component. The summary is the dominant term and it is a model output, so the
overstatement varies per candidate and does not average out across a run. On this change's fixture:
6,833 booked against 6,787 actually sent — 46 tokens on one candidate whose summary is 98
characters, and a full 120-rune summary on a smaller candidate is a far larger fraction.
It is not only reporting
out[k].savedfeedse.ratios.observe, and the ratio tracker is what the economic gate consults todecide whether a call is worth making at all. An optimistic saving biased the decision to spend, in
the direction of spending more.
Sequencing — a maintainer's call, flagged rather than decided
#195 asks for this to land after the iteration-024 re-run, or with an explicit note beside its
results, so a savings-basis change does not land in the middle of the comparison it would
invalidate. Iteration 024's numbers are not in the tree, so the durable note went into
docs/components/extract_llm.md: thegross_saved_tokensrow now states the basis, and a paragraphrecords why it moved. Anyone reading 024's two arms across this commit needs to know
extract_llm's figure got smaller without the mechanism getting worse. Hold the merge if there-run is still open.
Also in the diff, and why
runCallno longer computessavedat all; onlybeforerides along in the slot, as the ratio'sdenominator.
cands[k].contentrather thanout[k].before, which is0in asingle-flight follower's slot — and a follower does reach the splice, so that subtraction
would store a negative. Nothing books it today (the guard is
out[k].called), but a storednegative saving is one refactor away from being read.
component contradicting itself — the same compaction worth more on every replay turn than on the
turn it was made — and replays are the steady state, so most of the reported value would come from
the overstated side. That is the shape the sweep was in for exactly one commit (
c442670).What the broken version did
So the tests can be audited from outside: on the new fixture it booked
TextTokens(body) − TextTokens("<40 body lines + elision notes>")= 6,833, while the message itsent was those lines plus
\n[worker log: 400 identical INFO batch lines, …] <<cg:HASH>> [full output: call cg_expand], which is 6,787 smaller than the body. Both figures are large and positive;they differ by summary + marker + hint.
Verification
Go 1.26.4, eval box:
gofmt -lclean,go vet ./...clean,go test ./...28/28 packages pass.Two mutations, each reverting ONE site, each confirmed to build first (
go build ./...+go vet), each asserted to land exactly once, each failing on its own test's own assertion with theother test still green:
out[k].saved = before - TextTokens(res), phase 3's measurement removed)cached.ProjectedagainM1 was also run against the whole
components/offloadpackage: it fails only the new freshtest, so no existing test was silently depending on either basis.
The fixture, and what it took to make it able to fail
The tests needed a model that sets both
OUTPUTandSUMMARY.shrinkingModeldoes not — itsreply defines a
transformfunction, never assignsOUTPUT, and the deterministic fallback thensupplies the projection with an empty summary, which exercises the marker overhead but not the
term this change is about. Two things the new fixture had to clear to put the subject inside the
shape:
OUTPUTis refused by the acceptance check's keep-ratio floor (~5% of the body), sothe program keeps 40 of 400 lines;
clipSummary's 120 runes comes back ellipsized, so a verbatimContainscheckwould fail for a reason unrelated to the subject.
Both are asserted as preconditions rather than assumed, and each test fatals if the projection-based
figure is not strictly larger than the wire figure — without that guard the fixture could not tell
the two implementations apart.
Not touched here
The other components' savings paths were checked:
RecordExtractionSaving/RecordExtractionValuehave exactly four call sites, two in each extraction component, and all four now measure the
message.