feat(explore): point at source this session already sent, don't send it twice (CG-18)
A later explore call re-served whatever it re-ranked, so on the #1500 report the 4th call spent its envelope on the spine the 1st call had already delivered. CG-17 recorded what was served; this acts on it. What a withheld span becomes is the whole design: a POINTER, never a silence. An insufficient-feeling response is what sends an agent to Read, and one or two of those early in a session teach it to abandon codegraph — so the replacement names the file, the symbols and the line spans, and says both that the source came from THIS conversation and that the file has not changed since. - Content fingerprint, not the drift flag, gates it. They answer different questions: two calls inside one drift window served the same current bytes, while a file edited AND re-synced between calls is never "stale" and yet the agent's copy is now wrong. An edited file re-emits in full. - Only a covered run of >= 8 lines is replaced, and a remainder under 160 chars folds into the pointer. Below those the pointer costs more than the source and the block reads as shredded — a fence holding `228\t` is a broken-looking response, which is the expensive failure. - The reclaimed bytes go to files the agent has NOT seen, two ways: a smaller `sourceSpent` hands slack down CG-21's carry-forward pool, and a fully back-referenced file gives up its maxFiles slot the way a cliffed one does. Within a file, the cluster shrink now reads the DEDUPED length, so it never drops new symbols to make room for source it isn't sending. - If dedup suppresses everything and nothing new takes its place, the top suppressed file is spliced back in whole. An all-pointer response is the shape that reads as "codegraph found nothing"; one re-served file is the cheaper mistake. Kill switch: CODEGRAPH_EXPLORE_DEDUP=0.
This commit is contained in:
@@ -70,6 +70,13 @@ export interface ExploreFileEmission {
|
||||
ranges: ExploreLineRange[];
|
||||
/** Source chars emitted for this file (excludes headers / fences). */
|
||||
bytes: number;
|
||||
/**
|
||||
* Identity of the bytes those ranges were sliced from (CG-18). Cross-call
|
||||
* dedup withholds a span only when the file still hashes to this, so an edit
|
||||
* between two calls re-serves instead of pointing at source the agent holds a
|
||||
* now-wrong copy of. Absent = unprovable, which dedup treats as "re-serve".
|
||||
*/
|
||||
fingerprint?: string;
|
||||
/** Set when ranges were dropped to stay under the per-file bound. */
|
||||
rangesTruncated?: boolean;
|
||||
}
|
||||
@@ -309,6 +316,7 @@ export class ExploreSessionState {
|
||||
.map((f) => {
|
||||
const { ranges, truncated } = coalesceRanges(f.ranges ?? []);
|
||||
const out: ExploreFileEmission = { path: f.path, ranges, bytes: Math.max(0, f.bytes || 0) };
|
||||
if (typeof f.fingerprint === 'string' && f.fingerprint) out.fingerprint = f.fingerprint;
|
||||
if (truncated) out.rangesTruncated = true;
|
||||
return out;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user