Skip to content

fix(extract_llm): book the saving the wire saw, not the projection's - #216

Merged
amiddavid merged 4 commits into
mainfrom
fix/extract-llm-wire-saved
Sep 7, 2026
Merged

fix(extract_llm): book the saving the wire saw, not the projection's#216
amiddavid merged 4 commits into
mainfrom
fix/extract-llm-wire-saved

Conversation

@amiddavid

Copy link
Copy Markdown
Collaborator

Closes #195.

extract_llm booked candidate − TextTokens(projection) for every removal, on both the fresh and
the replay path. What apply 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 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.

Both sites move to the sweep's basis.

Site Booked before Books now
fresh (out[k].saved) the projection, carried out of runCall cands[k].content − MessageText(req.Input[i]), in phase 3, once the splice is a fact
replay content − cached.Projected content − 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].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. 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: the gross_saved_tokens row now states the basis, and a paragraph
records 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 the
re-run is still open.

Also in the diff, and why

  • runCall no longer computes saved at all; only before rides along in the slot, as the ratio's
    denominator.
  • Phase 3 subtracts 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.
  • The replay path had to move with the fresh one. Correcting only the fresh path would leave 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).

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 asserted to land exactly once, each failing on its own test's own assertion with the
other test still green:

Mutation Result
M1 fresh path books the projection again (out[k].saved = before - TextTokens(res), phase 3's measurement removed) FAIL — "RecordExtractionSaving booked 6833 tokens; the message it sent shrank by 6787". Replay test still PASSES
M2 replay books cached.Projected again FAIL — "the replay credited 6833 tokens of value; the message it sent shrank by 6787". Fresh test still PASSES

M1 was also run against the whole components/offload package: it fails only the new fresh
test, 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 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), so
    the program keeps 40 of 400 lines;
  • a summary over clipSummary's 120 runes comes back ellipsized, so a verbatim Contains check
    would 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 / RecordExtractionValue
have exactly four call sites, two in each extraction component, and all four now measure the
message.

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 amiddavid left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 -l clean, go build ./... clean.
  • go test ./... -count=1: all 28 test packages ok (+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) in runCall) — builds and vets, then fails only TestExtractLLMBooksWhatTheSplicedMessageActuallySaved, 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 in components/offload still 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.

Comment thread components/offload/extract_llm.go Outdated
Comment on lines +1499 to +1500
out[k].saved = schema.TextTokens(cands[k].content) -
schema.TextTokens(schema.MessageText(req.Input[cands[k].i]))

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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].saved is 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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_extraction and model.calls preconditions rather than passing vacuously — so a future red says whether the guard broke or the race was simply lost. Your 50/50 at -count=50 is 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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread components/offload/extract_llm.go Outdated
// 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

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread docs/components/extract_llm.md Outdated
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

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"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:

  1. metrics.RecordExtractionSaving documents 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 the called guard. Per that contract the behaviour is correct; but it means the figure is per-distinct-compaction, not per-message-sent.
  2. Replays feed RecordExtractionValue only, never RecordExtractionSaving — both components, symmetric, so arm-to-arm comparability is intact. But gross_saved_tokens therefore counts fresh removals only, while gross_value_usd beside it counts fresh plus replay. Since this doc already had to teach acted_fresh vs acted_replay for 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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 amiddavid left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 -l clean, go vet ./... clean, go test ./... -count=1 all packages ok.
  • -race on the three savedbasis tests: clean, all pass.
  • M3 (my own: if out[k].called {if true {) builds, vets, and fails only TestExtractLLMDoesNotBookASingleFlightFollowersSaving, at 15628 booked for 3907 of 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 the Component filter 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 (3953 projection vs 3907 wire). 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:

  1. Removing the before field costs no extra work. schema.TextTokens is tokens.Count, which memoizes by content hash above minCacheLen (internal/tokens), so phase 3's second Count on cands[k].content is 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.
  2. The rewritten guard paragraph's arithmetic is exactly right. Two of the three zero-guards do lapse — ratioTracker.observe's totalTok <= 0 early return and the recorders' no-op-at-0, both because a follower's before and saved are now positive — and the third, the Component filter 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>
@OsherElhadad

Copy link
Copy Markdown
Collaborator

Review of PR #216 — "fix(extract_llm): book the saving the wire saw, not the projection's"

Reviewing at head 7a2b035a4697ea9f8bb6a53be815b4cdfb1ed7e4 in /tmp/cgfix/pr216 (detached worktree).

Setup

cd /tmp/cgfix/pr216 && git log --oneline -5
7a2b035 docs(extract_llm): name the second cause a follower precondition can go red for
7963330 docs(extract_llm): record what the follower test also guards, and the race it rides
dcad41c fix(extract_llm): say that the follower guard is now load-bearing, and test it
5ac6e61 fix(extract_llm): book the saving the wire saw, not the projection's
cc4754d Merge pull request #208 from rossoctl/fix/expand-cross-turn-201

Go 1.26.4, go env GOCACHE=/home/vpcuser/.cache/go-build GOPATH=/home/vpcuser/go.

PR body and review threads

Read gh pr view 216 and gh pr view 216 --comments. Note: the PR is authored by
amiddavid, and the two review passes visible on the PR (gh pr view 216 --comments and
gh api repos/rossoctl/context-guru/pulls/216/comments) are also authored by amiddavid
(GitHub disallows REQUEST_CHANGES/approval on your own PR, so these are posted as plain
comments — but they are from the same account as the PR author, not an independent
reviewer). Treating those threads as evidence of independent review would be a mistake;
I re-derived every claim from source myself rather than trusting them. Where my own
findings match what's already in those threads, I say so, but I did not take the numbers
on trust.

1) Basis divergence claim (extract_llm vs extract_llm_sweep)

  • extract_llm_sweep (pre-existing, unchanged by this PR) subtracts against the spliced
    message at components/offload/extract_sweep.go:526-527 (fresh) and :339 (replay):
    schema.TextTokens(cands[k].content) - schema.TextTokens(schema.MessageText(req.Input[cands[k].i])).
  • Before this PR, extract_llm's fresh path booked before - schema.TextTokens(res) inside
    runCall's accept branch (res is the raw model projection, no summary/marker/hint), and
    its replay path booked content - TextTokens(cached.Projected). Both are read directly off
    git show cc4754d:components/offload/extract_llm.go (the merge-base). Confirmed: this is a
    real, pre-existing divergence from the sweep's basis, not a claim I had to take on trust.
  • The PR moves both extract_llm sites to schema.MessageText(req.Input[...]) — i.e. the same
    basis as the sweep. Verified by reading extract_llm.go:1505 (fresh, phase 3) and
    extract_llm.go:910 (replay, phase 1) against the sweep's two sites side by side. Confirmed.

2) "What the wire saw" — ordering guarantee

  • apply(...) (the closure at extract_llm.go:795) calls schema.SetMessageText(&req.Input[i], newText)
    only after tryMark + commitMark/commitRefresh succeed, and returns true only then.
  • Phase 3 is documented and structured as serial (for k := range cands { ... }, after
    wg.Wait() — no goroutines run in this loop). The read schema.MessageText(req.Input[cands[k].i])
    at line 1505 happens in the very next statement after apply(...) returns true in the same
    loop iteration, with no intervening code that could touch req.Input for that i before the
    read. Phase 1 (the replay call site) is likewise serial (for _, i := range tools at the top of
    Offload, no concurrency). Each candidate maps to a distinct message index i, so no candidate's
    read can be clobbered by another candidate's write within the same request.
  • Confirmed: nothing mutates the spliced message between the successful apply() and the
    MessageText read that measures it, on both the fresh and replay paths.

3) Numbers — 6,833 booked vs 6,787 sent

Reproduced by running the new test directly (see below), not just read from the PR body:

go test -count=1 -v -run TestExtractLLMBooksWhatTheSplicedMessageActuallySaved ./components/offload/...

Passes as written. To see the OLD figures, mutation M1 (below) reverts the fresh path to the
projection basis and prints them on failure:

extract_llm_savedbasis_test.go:109: RecordExtractionSaving booked 6833 tokens; the message it
sent shrank by 6787 (the projection-only figure is 6833).

Confirmed exactly: 6,833 vs 6,787, a 46-token gap, reproduces byte-for-byte against my own
run, not copied from the PR body.

4) "It is not only reporting" — the ratio tracker / economic gate path

Traced end to end in source (components/offload/extract_econ.go, extract_llm.go):

  • ratioTracker.observe(removedTok, totalTok) (extract_econ.go:488) accumulates
    r.removed += removedTok; r.total += totalTok under a mutex. No gate logic here — it's
    a running accumulator.
  • ratioTracker.ratio() (extract_econ.go:507) derives a shrinkage-weighted estimate from
    r.removed/r.total, capped at maxLearnedRatio (0.60), falling back to
    defaultCompressionRatio (0.12) until minRatioSampleTokens tokens of evidence exist.
  • Offload computes ratio := e.ratios.ratio() once per request (extract_llm.go:745,
    before any of this request's own calls run) and passes it into evaluateGate(sz, candRatio, ...)
    per candidate (extract_llm.go:1095) — evaluateGate (extract_econ.go:391) is what decides
    whether a candidate is even worth an extraction call (comparing estimated savings value against
    call cost).
  • Phase 3 then calls e.ratios.observe(out[k].saved, before) (extract_llm.go:1574) for every
    slot where out[k].called is true — feeding THIS request's outcome into the tracker that PRICES
    FUTURE calls (this request's own gate decisions already happened at line 1095, using the
    pre-update ratio).
  • Before this PR, out[k].saved fed into that same observe call was the projection-basis figure
    (systematically inflated by the summary+marker+hint). After this PR it is the wire figure.

Confirmed, not merely reporting: an inflated saved pushes r.removed up relative to
r.total, which pushes ratio() up, which (via evaluateGate's cost/value comparison) biases
future gate decisions toward making more calls. This is a real behaviour path, and the PR's basis
fix changes the learned ratio, not just a displayed number. I did not find any place where the
inflated number was ONLY read for display — every one of the four call sites
(RecordExtractionSaving/RecordExtractionValue, both components) is either a /stats counter or
this ratio feed; there is no third "reporting-only" path that would make the claim an overstatement.

5) Follower / negative-saving argument

  • A single-flight follower (extractInflight.Do, extract_llm.go:1274) returns early via
    if !executed { ...; out[k] = outT{projected: res, summary: sum}; return } (extract_llm.go:1284-1287).
    Its slot's projected field is filled (non-empty, from the shared singleflight result) and its
    called field is left at its zero value (false), since the whole struct is replaced by a
    fresh literal that doesn't set it.
  • In phase 3, the skip condition is if out[k].projected == "" { continue } (extract_llm.go:1471)
    — since a follower's projected is non-empty, it does NOT skip; it reaches apply(...)
    (extract_llm.go:1486) and splices, same as the leader.
  • The booking block is gated by if out[k].called { (extract_llm.go:1571) — a follower's
    called is false, so RecordExtractionSaving/e.ratios.observe/calls[k].SavedTokens are
    skipped for it. Confirmed: a follower does reach the splice, and the guard is out[k].called,
    exactly as claimed.
  • "Nothing books it today" — verified by mutation M3 (if out[k].called {if true {
    at extract_llm.go:1571): builds and vets clean, and fails only
    TestExtractLLMDoesNotBookASingleFlightFollowersSaving:
    extract_llm_savedbasis_test.go:279: RecordExtractionSaving booked 15628 tokens for 4 messages
    spliced from ONE model call; one compaction is worth 3907. ...
    
    15,628 = 4 × 3,907 exactly — the 4x over-count the PR predicts. Only the counter assertion
    (line 279) fires; the ledger assertion (line 289) does NOT fire, because a follower's
    ModelCall slot is never appended to rep.Calls at all (see for k := range calls { if calls[k].Component != "" { rep.Calls = append(...) } }, and a follower's calls[k] is the
    zero-value components.ModelCall{} with Component == ""). This matches the review thread's
    own disclosure and I independently confirmed which assertion fired rather than taking that on
    trust. Reverted after the run; tree confirmed clean.

6) Replay path moving with the fresh path

  • Confirmed via mutation M2: reverting the replay site (extract_llm.go:910) to
    schema.TextTokens(cached.Projected) builds clean and fails only
    TestExtractLLMReplayBooksWhatTheReplayedMessageActuallySaved:
    extract_llm_savedbasis_test.go:189: the replay credited 6833 tokens of value; the message it
    sent shrank by 6787 (projection only: 6833). ...
    
    6,833 vs 6,787 — the same gap as the fresh-path fixture, as expected (same fixture body/summary).
    The fresh test and the follower test both still PASS with only the replay site reverted.
  • The "component would contradict itself" argument: with only the fresh path fixed, a fresh
    extraction books the (correct, smaller) wire figure once, then every replay of the SAME frozen
    compaction on later turns would book the (uncorrected, larger) projection-based figure via
    RecordExtractionValue. Read docs/components/extract_llm.md:719-722: replays are documented as
    the common case (reapplied_same_session: 2,291 beside acted: 239 in the doc's own worked
    example) — so yes, replays are structurally the steady state for a component whose whole value
    proposition is "compact once, replay for free on every subsequent turn," and leaving replay on
    the old basis would mean most of the reported gross_value_usd for this component came from the
    overstated side. Confirmed both the mechanism and the "replays are the steady state" premise.

7) Docs

docs/components/extract_llm.md:714 (table row) reads:

gross_saved_tokens | Tokens removed, measured on the message as spliced — the candidate
minus what actually went upstream, marker and summary segment included

and the added paragraph at :722-728 states the basis change, and explicitly says the row is
not the bill delta (single-flight dedup counts one compaction once even though two messages
shrank; replays feed gross_value_usd but never gross_saved_tokens). Matches the PR body's
description exactly — verified by reading the file at 7a2b035, not by trusting the diff summary.
The sequencing note (land after iteration-024, or beside it) is NOT itself in
docs/components/extract_llm.md as a literal sentence — the doc only carries the basis-change
paragraph, not an explicit "hold for iteration-024" instruction. That sequencing ask lives in the
PR body and issue #195, not in the docs file. This is a minor gap between what the PR body implies
("the durable note went into docs/components/extract_llm.md") and what's literally there: the note
explains WHY the number moved, but does not itself flag the sequencing risk for a reader of the doc
who has no access to this PR or #195. Not blocking, but worth naming (see findings below).

Real Claude Code sessions — paired head vs merge-base

Gates found in source, and effective values used

  • extract_llm's own economic gate (extract_econ.go:415, if val.cached && !allowCached { decline })
    declines any candidate that is already inside the request's cached prefix, unless
    allow_on_caching_backend/economic_gate: false is set. Per prior measurement on this service
    (housellm preset's own comment in config/config.go) this path has never fired in production
    traffic under Claude Code specifically because of that check plus the ratio-based cost/value comparison in
    evaluateGate. To get a REAL, non-synthetic session to exercise the mechanism this PR touches
    (not the sweep component, which is a different code path this PR does not change), I set
    economic_gate: false in the test config — a real, existing config knob, not a code patch —
    so the gate's own value/cost arithmetic and cache-state check are bypassed and only the
    acceptance/never-worse checks (untouched by config) decide whether a call is made and accepted.
    This means the live run does NOT independently re-verify claim 4 (the ratio/gate bias) — that
    was verified from source instead (see above). It DOES verify the accounting change end to end
    on real tool output through a real Claude Code session and a real upstream model call.
  • min_tokens: 500 (explicit) is the per-output floor (e.outputFloor, extract_llm.go
    minTokensSet: explicit path) — the 400-line repetitive log fixture is ~8,700+ tokens, well
    clear of it.
  • strategy: code (deterministic Starlark keep/trim pass, LLM only for the summary),
    aggressiveness: high (rejected aggressive as an invalid value — the component only accepts
    low|medium|high, worth noting as a minor UX trap for anyone hand-writing this config; not
    introduced by this PR).
  • model.source: incoming — the extraction call rides the SAME upstream credentials/model as the
    session's own request, through the same proxy, no separate cheap-model credential needed.

What I built

  • Two builds from this worktree: /tmp/cgfix/cg-proxy-base (merge-base cc4754d, via
    git worktree add --detach /tmp/cgfix/pr216-base cc4754d) and /tmp/cgfix/cg-proxy-pr216
    (PR head 7a2b035), both CGO_ENABLED=0 go build -o ... ./cmd/context-guru-proxy.
  • Config (/tmp/cgfix/extract-llm-test.yaml, identical for both runs):
    pipeline: [extract_llm]
    components:
      extract_llm:
        strategy: code
        min_tokens: 500
        economic_gate: false
        aggressiveness: high
        llm_max_per_request: 8
        model:
          source: incoming
  • A real fixture file worker.log: 400 near-identical INFO worker: processed batch N status=ok items=42 latency_ms=118 lines (~8,700 tokens), the shape a tool call (cat) would produce for
    a real noisy log — highly compressible, which is what makes it worth extracting.
  • Two proxies, bound to 127.0.0.1:4041 (base) and 127.0.0.1:4042 (head) — both inside the
    4040-4049 range — each with ANTHROPIC_API_KEY/ANTHROPIC_UPSTREAM set from this session's own
    gateway credentials (read from ~/.claude/settings.json's env block at run time via a Python
    one-liner into shell variables, never echoed or written to any file; confirmed
    sha256sum /home/vpcuser/.claude/settings.json unchanged before/after:
    ff1de4913bee48586aecfb1c80a6772f940d9b82eb0456f021023ecfaa1604c8).
  • Two throwaway Claude Code config dirs (CLAUDE_CONFIG_DIR=/tmp/cgfix/ccdir216-base /
    -head), each with a settings.json pointing ANTHROPIC_BASE_URL at its own proxy's
    /anthropic prefix with a placeholder auth token (the proxy injects the real gateway key
    server-side; no credential reaches the Claude Code config file).
  • Two identical workspaces (/tmp/cgfix/livework216-base, -head), each with a copy of
    worker.log.
  • Ran, headless, the SAME prompt against each:
    CLAUDE_CONFIG_DIR=/tmp/cgfix/ccdir216-base claude -p \
      "Read worker.log with cat and tell me in one sentence whether there are any anomalies \
       (errors, warnings, or unusual latency) in it." \
      --settings /tmp/cgfix/ccdir216-base/settings.json \
      --allowedTools "Bash(cat *)" --permission-prompts none --output-format text
    
    (repeated with -head config dir and workspace). --dangerously-skip-permissions was refused
    by this session's own auto-mode classifier as an unauthorized safety-gate bypass for a subagent;
    --allowedTools "Bash(cat *)" + --permission-prompts none is the non-bypass equivalent that
    still lets the single needed tool call through without a live approval prompt.

Results — the paired numbers

$ curl -s http://127.0.0.1:4041/stats   # BASE (cc4754d, unpatched)
...
"extract": {
  "by_component": { "extract_llm": { "gross_saved_tokens": 682, ... "extraction_cost_usd": 0.0243, ... } }
},
"components": { "extract_llm": { "saved_tokens": 656, "acted": 1, "acted_fresh": 1 } }

$ curl -s http://127.0.0.1:4042/stats   # HEAD (7a2b035, this PR)
...
"extract": {
  "by_component": { "extract_llm": { "gross_saved_tokens": 652, ... "extraction_cost_usd": 0.0173, ... } }
},
"components": { "extract_llm": { "saved_tokens": 652, "acted": 1, "acted_fresh": 1 } }

components.extract_llm.saved_tokens is computed generically by the pipeline harness from the
actual before/after message byte diff (unrelated to metrics.RecordExtractionSaving and untouched
by this PR) — it is independently wire-accurate on both runs. extract.by_component.extract_llm.gross_saved_tokens
is what metrics.RecordExtractionSaving books, which is exactly the figure this PR changes the
basis of.

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=ok entries at a constant latency_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: 0 on
    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:

  1. 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:

  1. aggressiveness: aggressive is 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.
  2. 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 blocks REQUEST_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_gate for 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 /
    evaluateGate wiring) 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.

@amiddavid
amiddavid merged commit 4737762 into main Sep 7, 2026
6 checks passed
@github-project-automation github-project-automation Bot moved this from New/ToDo to Done in Rossoctl Issue Prioritization Sep 7, 2026
@amiddavid
amiddavid deleted the fix/extract-llm-wire-saved branch September 7, 2026 13:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

metrics: extract_llm and extract_llm_sweep measure saved tokens against different baselines, so their per-arm savings are not comparable

3 participants