fix(agent): gadfly round 1 — the hyphen made "above-board" a back-reference
CI / Tidy (pull_request) Successful in 9m25s
CI / Build & Test (pull_request) Successful in 10m43s

Three of four reviewers independently found the same defect: `\b` holds
between "above" and "-", so the literal hyphen in aboveRefRe's terminator
class made every hyphenated compound clause-final. "above-average",
"above-board", "above-ground" all read as deictic pointers, putting a
legitimate short closer at risk of being discarded. My own test used the
SPACE-separated "above average" — it tested the neighbour, not the named
path, which is exactly why the class survived a round of review. Hyphen
dropped from the class; the four compounds are now table cases.

Two more findings, both real:

- backRefHeadChars and weakFinalMaxChars were two unlinked 120 literals
  that the comment called "the same guard". Now defined by reference,
  with a test pinning the identity.
- isBackRef claimed to be the shared extension point for both shapes but
  had a single caller — isSummaryCloser deliberately uses pointsAbove
  directly, because backRefRe's fixed phrases match ANYWHERE and are only
  safe under the 120-byte weak cap. Folding the two together for
  tidiness would widen the gate, not deduplicate it. Helper deleted, the
  doc moved onto pointsAbove where the real sharing is, and it now says
  why backRefRe is not shared.

Also shared the front-loaded-analysis fixture between the table and the
end-to-end test (kimi), and de-hollowed the offset-bound cases: they were
sized as backRefHeadChars±n, so they moved with the constant they were
meant to pin — a break-check that widened the bound to 100000 sailed
through. Literal lengths now, plus an explicit identity assertion.

Break-check: six mutations, each killed by a named test; control survives.
This commit is contained in:
2026-08-21 23:41:46 -04:00
parent bcba9667bd
commit f97c2b78c2
2 changed files with 64 additions and 25 deletions
+27 -18
View File
@@ -36,9 +36,9 @@ import (
// carry answer content (see modeSummary).
//
// The last two shapes share one signal — the terminal DEFERS: it tells us the
// answer is somewhere the user cannot see (isBackRef, or the bookkeeping ack).
// They differ only in whether the terminal also carries content of its own, so
// a new deferral phrase added to that shared signal is covered in both the
// answer is somewhere the user cannot see (pointsAbove, or the bookkeeping
// ack). They differ only in whether the terminal also carries content of its
// own, so a new deferral phrasing added to pointsAbove is covered in both the
// bare and the "+ compression" variant at once.
//
// A citations addendum is tested first and wins over the other two (a short
@@ -130,26 +130,33 @@ var backRefRe = regexp.MustCompile(`(?i)(already answered|see above|as (i )?(sai
// back-reference family is covered without enumerating every phrasing a model
// might invent — backRefRe's fixed list kept missing new ones (mort issue
// #1611: "Done — that's the full chain above.").
var aboveRefRe = regexp.MustCompile(`(?i)\babove\b[ \t]*([.,;:!?)\]"'’”—-]|\n|$)`)
//
// The terminator set deliberately excludes the ASCII hyphen. `\b` holds
// between "above" and "-", so a literal '-' in the class made every
// hyphenated compound — "above-average", "above-board", "above-ground" —
// read as a clause-final deictic and put a legitimate short answer at risk of
// being discarded. The em dash stays: a model writes "…above — see the
// links", never "above-" as a separator.
var aboveRefRe = regexp.MustCompile(`(?i)\babove\b[ \t]*([.,;:!?)\]"'’”—]|\n|$)`)
// pointsAbove reports whether a deictic "above" appears in the terminal's
// OPENING. The offset bound is what makes a bare "above" safe to key on: with
// almost no text before it in THIS message, the reference cannot be pointing
// at the message's own content, so it must point at a turn the user never saw
// (the harness delivers only the final turn).
//
// This is the DEFERRAL SIGNAL SHARED by both recovery shapes — isWeakFinal and
// isSummaryCloser both call it, so a widening here reaches the bare closer and
// the "+ compression" closer at once. Its sibling backRefRe is deliberately
// NOT shared: those fixed phrases are matched anywhere in the text, which is
// only safe under isWeakFinal's 120-byte cap. Folding the two into one
// predicate for tidiness would hand isSummaryCloser an unanchored match across
// 300 bytes — widening the gate, not deduplicating it.
func pointsAbove(t string) bool {
loc := aboveRefRe.FindStringIndex(t)
return loc != nil && loc[0] <= backRefHeadChars
}
// isBackRef reports whether a terminal turn defers to earlier content instead
// of stating the answer — the signal shared by the back-reference and
// summary-closer shapes (see finalOutput). Extend the class here, once, rather
// than in either caller.
func isBackRef(t string) bool {
return backRefRe.MatchString(t) || pointsAbove(t)
}
// summaryCloserRe matches a terminal turn that OPENS with a bookkeeping
// acknowledgment of the citation round — "Citations are logged.", "Sources
// cited.", "Logged the citations." — the shape a model produces when it
@@ -219,11 +226,13 @@ const (
weakFinalMaxChars = 120
// backRefHeadChars bounds how far into the terminal a deictic "above" may
// sit and still read as pointing OUTSIDE this message (see pointsAbove).
// Same guard as weakFinalMaxChars — "there is not enough text before the
// reference for it to be pointing at content inside this turn" — but
// expressed as an offset, because a summary closer carries a compression
// AFTER the pointer and so is not itself short.
backRefHeadChars = 120
// It IS weakFinalMaxChars — the same guard ("there is not enough text
// before the reference for it to be pointing at content inside this
// turn"), expressed as an offset because a summary closer carries a
// compression AFTER the pointer and so is not itself short. Defined by
// reference, not by repeating the literal: tuning the weak cap without the
// offset following it would split one rule into two.
backRefHeadChars = weakFinalMaxChars
// recoverMinChars: a prior assistant turn this long is treated as a real
// answer regardless of how it opens (the preamble filter is not applied at
// this length — see isSubstantiveAnswer).
@@ -256,7 +265,7 @@ func isWeakFinal(s string) bool {
if t == "" {
return true
}
return len(t) <= weakFinalMaxChars && isBackRef(t)
return len(t) <= weakFinalMaxChars && (backRefRe.MatchString(t) || pointsAbove(t))
}
// isCitationsOnly reports whether a terminal turn is essentially just a