fix(agent): gadfly round 3 — the pointer must be all there is
CI / Tidy (pull_request) Successful in 9m22s
CI / Build & Test (pull_request) Successful in 9m48s

Round 2 fixed this class on isSummaryCloser and left its twin standing
on isWeakFinal. Three of the four findings are that twin, and every one
of them reproduced:

    "That's the chain above. Ship Tuesday."          weak=true
    "See the summary above. Option B wins."          weak=true
    "Anything above 100 boils. See the note above."  weak=true
    "Anything above, say, 40 degrees is a problem."  weak=true

Each of those is 37-58 bytes with the answer sitting right next to the
pointer, and each was disposable — a >=200-byte earlier turn replaced it
and "Ship Tuesday" went in the bin. pointsAbove asks whether a pointer is
PRESENT; disposability needs it to be ALL THERE IS.

bareAbovePointer cuts the reference's own clause — from the end of the
previous sentence through the match — and requires what remains to be
filler: nothing, punctuation, or a throat-clearing "Done —". Cutting the
clause rather than testing position is what makes the other two shapes
safe for free: a mixed terminal keeps its first sentence, and a
comparative "above," with an interjection keeps the rest of its own.

The fourth finding (kimi) is the same idea one level up:
compressionMarkerRe matched anywhere, so "Given the analysis above, the
bottom line is that we need a different vendor" read as an announced
summary when it is a conclusion. The marker must now OPEN a sentence.

Both classes now enforce one rule from opposite ends: a terminal is
disposable only when it carries no answer of its own — proved in
isWeakFinal by nothing standing beside the pointer, and in
isSummaryCloser by the model declaring what stands beside it a
compression.

One of my own round-1b fixtures had to change: "…is above, so there is
no point repeating it" is a pure pointer to a human, but prose after the
reference is indistinguishable from an answer, so the rule correctly
stops treating it as disposable. Replaced with a genuinely bare 96-byte
pointer, still sized so the two recovery bars disagree about it.

Break-check: thirteen mutations, each killed by a named test, control
survives. M12 initially "passed" by failing to compile — reformulated so
it builds, and TestIsWeakFinal/prepositional-then-deictic-with-content
kills it properly.
This commit is contained in:
2026-08-22 00:16:47 -04:00
parent 3f1c016e74
commit 1756910ef0
2 changed files with 103 additions and 10 deletions
+44 -7
View File
@@ -27,9 +27,20 @@ func TestIsWeakFinal(t *testing.T) {
{"crisp-status", "It's down, restarting now.", false},
{"long-with-as-i-said", long, false}, // >120 chars: not weak despite the phrase
// The deictic half of the class (aboveRefRe), inside the length cap.
// The deictic half of the class, inside the length cap. A pointer only
// counts when it is ALL there is — see bareAbovePointer.
{"bare-above-pointer", "That's the full chain above.", true},
{"above-pointer-in-parens", "(the breakdown is above)", true},
{"bare-pointer-with-filler-opener", "Done. See the chain above.", true},
// A pointer sharing the terminal with real content is NOT weak: the
// content beside it is the answer, and discarding the terminal would
// throw it away.
{"pointer-plus-a-decision", "That's the chain above. Ship Tuesday.", false},
{"pointer-plus-a-choice", "See the summary above. Option B wins.", false},
{"pointer-mid-sentence-then-answer", "As shown above, the answer is sixty minutes.", false},
{"prepositional-then-deictic-with-content", "Anything above 100 boils. See the note above.", false},
{"comparative-with-an-interjection", "Anything above, say, 40 degrees is a problem for the pump.", false},
{"prepositional-above-not-weak", "Anything above 100 degrees boils off.", false},
{"1611-closer-too-long-for-weak", closer1611, false}, // 220 bytes: isSummaryCloser's job
}
@@ -200,6 +211,13 @@ func TestIsSummaryCloser(t *testing.T) {
{"above-pointer-plus-tldr", "That's the whole picture above. In short: the merger fell through.", true},
{"prepositional-above-is-not-a-closer", "Anything above 100 degrees boils off, which is why the sample evaporated.", false},
// The compression marker has to OPEN a sentence. Mid-sentence the same
// words are prose carrying NEW content, not an announcement that what
// follows is a condensation.
{"marker-mid-sentence-is-new-content", "Given the analysis above, the bottom line is that we need a different vendor entirely.", false},
{"marker-mid-sentence-in-short", "That is the chain above, and in short supply of alternatives we went with B.", false},
{"marker-opening-after-a-colon", "That's the chain above: in short, the merger fell through.", true},
// A deictic pointer WITHOUT a compression marker is not a summary
// closer: these state a conclusion the earlier turn never contained,
// so replacing them with that turn would discard the answer.
@@ -254,12 +272,16 @@ func TestFinalOutput(t *testing.T) {
bothMatchCloser := "Citations are logged. As I mentioned above, the full detail on the money sources is in my earlier message."
// The #1611 pair: a front-loaded analysis that dwarfs its 220-byte closer.
analysis := analysis1611()
// A 98-byte deictic closer: inside the weak-final cap (120), and sized so
// the two bars actually DISAGREE about it — 3x98 = 294 > longAnswer's 275,
// so the summary closer's dwarf ratio would reject longAnswer while the
// modeBackRef bar (>=200 bytes, no ratio) accepts it. A shorter closer
// would pass under either bar and prove nothing.
shortAbovePointer := "Done. The whole chain is above, so there is no point repeating all of that detail down here again."
// A 96-byte BARE deictic closer: nothing but filler and the pointer's own
// clause, inside the weak-final cap (120), and sized so the two bars
// actually DISAGREE — 3x96 = 288 > longAnswer's 275, so the summary
// closer's dwarf ratio would reject longAnswer while the modeBackRef bar
// (>=200 bytes, no ratio) accepts it. A shorter closer would pass under
// either bar and prove nothing. It must also stay BARE: an earlier draft
// ended "…so there is no point repeating it", and prose after the
// reference is indistinguishable from an answer, so bareAbovePointer
// correctly stopped treating it as disposable.
shortAbovePointer := "Done — that is the complete chain, start to finish, exactly as I worked it out for you, above."
// A deictic pointer followed by a NEW conclusion (no compression marker):
// >120 bytes so isWeakFinal cannot claim it, and it must not be treated as
// a summary closer either.
@@ -570,6 +592,21 @@ func TestFinalOutput(t *testing.T) {
terminal: shortAbovePointer,
want: longAnswer,
},
{
// The isWeakFinal twin of the case below: a SHORT pointer that
// shares its terminal with the decision. Before bareAbovePointer
// this was weak, so a >=200-byte prior turn replaced it and "Ship
// Tuesday" — the only thing the user needed — was discarded.
name: "short above-pointer sharing the terminal with the answer is not discarded",
msgs: []llm.Message{
llm.UserText("when do we ship?"),
asst(hugeAnswer, cite...),
llm.ToolResultsMessage(llm.ToolResult{ID: "c1", Name: "cite", Content: "ok"}),
asst("That's the chain above. Ship Tuesday."),
},
terminal: "That's the chain above. Ship Tuesday.",
want: "That's the chain above. Ship Tuesday.",
},
{
// A pointer plus a NEW conclusion is not a compression, so it must
// survive verbatim even though a much longer prior turn exists —