fix(explore): fund the guard from room that exists, and cut the epilogue first (CG-31)

Two corrections found by measuring the first cut of the guard against the
6-repo suite. The first version held back the FULL sum of the reservations
below a file. On django that took 2,319 chars off a file the agent receives
and handed them to a section the hard ceiling then threw away — the guard's
own failure mode, one layer down. tokio lost 1,298 the same way.

1. `owedPayableBelow` — hold back only the prefix of what is owed below that
   the response can still PAY, in rank order. A promise the ceiling cannot
   reach is not a claim on this file's bytes.

2. The final truncation now spends the EPILOGUE before it spends a rendered
   file section. It used to cut at the last section header, dropping that
   section AND the trailing notes; dropping the notes alone is almost always
   enough. A section is source the agent otherwise has to Read; the epilogue
   is a pointer list and two reminders, and the note that replaces it carries
   the "explore these names" instruction forward.

Also count `flow.text` in `totalChars`. It is prepended to `lines` to make the
final output, so the render loop always spent against a ceiling it was ~2K
under on symbol-bag queries.

Deterministic, same clean-rebuilt indexes, both builds (baseline = CG-30 tip):

  repo         base source   new source      files
  django            20,033       20,791   5 trunc -> 6
  excalidraw        18,776       20,204   7 trunc -> 8
  okhttp            15,628       19,034   4 trunc -> 5
  tokio             20,340       21,521   4 trunc -> 5
  gin               10,776       10,776   4 -> 4   (byte-identical)
  alamofire         11,662       11,662   2 -> 2   (byte-identical)

No repo delivers less; four stop truncating. `funded` in the diagnostic now
reports the render CEILING the guard allows, which is what every render path
is actually bounded by.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Colby McHenry
2026-08-06 02:59:46 -05:00
co-authored by Claude Opus 5
parent 089dcc276f
commit f1fecb8232
3 changed files with 110 additions and 43 deletions
+12 -10
View File
@@ -104,12 +104,14 @@ interface FileRecord extends ExploreCandidateMeta {
*/
spendable: number | null;
/**
* The DISPLACEMENT-GUARDED bound (CG-31): how much this file may render
* The DISPLACEMENT-GUARDED ceiling (CG-31): the most this file may render
* without spending a reservation still owed to a file the loop has not
* reached. `spendable` is what the file was promised, this is what is
* actually still there to pay it with — when it sits below `spendable`, the
* difference is the overshoot the guard refused, and the files below this one
* in the table are the reason. `null` until the render loop reaches the file.
* reached AND can still pay. `spendable` is what the file was promised, this
* is what is actually still there to pay it with — every render path is
* bounded by it, so `emittedChars` above it is a bug. Sits ABOVE `spendable`
* when the room is there (the bounded overshoot a big cluster member may
* take) and BELOW it when the files underneath need the bytes. `null` until
* the render loop reaches the file.
*/
funded: number | null;
render?: ExploreRenderMode;
@@ -161,7 +163,7 @@ export interface ExploreDiagnosticFile extends ExploreCandidateMeta {
allowance: number | null;
/** Reservation + inherited slack — the bound the render paths actually use. */
spendable: number | null;
/** Same bound after holding back what is still owed to unreached files. */
/** Render ceiling after holding back what is still owed to unreached files. */
funded: number | null;
render: ExploreRenderMode | null;
skipped: ExploreSkipReason | null;
@@ -753,10 +755,10 @@ export function renderTable(report: ExploreDiagnosticReport): string {
if (f.spendable !== null && f.allowance !== null && f.spendable !== f.allowance) {
out.push(` spendable: ${num(f.spendable)} (reservation + inherited slack)`);
}
// Only when the displacement guard actually bit: the gap is what this file
// was refused so the files below it could still be paid.
if (f.funded !== null && f.spendable !== null && f.funded < f.spendable) {
out.push(` funded: ${num(f.funded)} (capped — ${num(f.spendable - f.funded)} held back for files not yet rendered)`);
// Only when the displacement guard actually bit: the gap is the overshoot
// this file was refused so the files below it could still be paid.
if (f.funded !== null && f.spendable !== null && f.funded < Math.round(f.spendable * 1.5)) {
out.push(` funded: ${num(f.funded)} (held to this so the files below keep their reservations)`);
}
if (f.dedupSavedChars > 0) {
const spans = f.dedupCovered.slice(0, 6).map(([a, b]) => (a === b ? `${a}` : `${a}-${b}`)).join(',');