Harness defect · characterised from one session transcript

The pending set that never empties

A file edited outside the agent's own tools joins a resync queue. The queue flushes whole, over and over, and only one specific action ever removes anything from it. Here is where it comes from and how it grows.

75re-injections
295,044bytes re-sent
66.3%byte-identical
15×worst single file
0after an Edit call

Measured over 296 turns of one session. The worst case is a 665-byte file delivered fifteen times, unchanged, across a span of 234 turns.


Where it starts

Two ways exist to change a file the agent is holding in context. They are not symmetric, and the asymmetry is the whole defect.

file F in context Edit / Write harness made it knows the diff tracked copy updated nothing queued 0 B 82 of 82 Bash · script · build cannot be attributed bytes differ added to pending set membership is permanent re-sent forever the split held across the whole session: 82 edit-tool calls, zero re-injections
The same change, made two ways. The harness can attribute its own edits and updates its tracked copy silently. It cannot attribute a shell-mediated write, so it queues the file for resynchronisation — and that queue entry is what never goes away.

How it grows

Each shell-mediated edit drops another slab onto the stack. Flushes are sparse — 21 turns out of 296 — but every flush emits the entire stack, in full, regardless of whether anything in it has changed since last time.

05-checklist.md entered turn 54 · sent 15× mid-session.md 665 B · sent 15× 04-reproduction.md entered turn 120 · 14× 01-observed-behaviour.md entered turn 120 · 14× shell edit adds a slab drain on flush does not happen every flush emits the whole stack
The pending set, drawn as a stack. Shell-mediated writes add slabs from above; the drain that should run on a successful flush does not exist — the marker on the dashed line attempts it and fades before reaching. Flush size therefore plateaus at the size of the stuck set — here, four files, emitted together on every one of the last fourteen flushes (flush 1 of 14, 14,190 B each time).

Watch it accumulate

Step through the session one flush at a time. Files enter the set and stay; each flush re-emits everything currently in it. Toggle the fix to see what a single content-hash check before emitting would have saved.

Pending set at turn 0
  • empty
Cumulative bytes re-injected
0 B
flushes0
duplicated0 B
wasted0%

Press Next flush to begin.


The progression, measured

Plotted against turn number, the shape is unmistakable. Early flushes carry one file. Once four files are stuck, every flush carries four — indefinitely, for files that had not been touched in hours.

012 345 turn 0 turn 296 FILES PER FLUSH plateau at 4 — the stuck set +1+1+2 Edit → evicted Edit → evicted
Twenty-one flushes across 296 turns. Amber ticks mark files entering the set; green lines mark the only two departures observed — each immediately after that file was written with the harness's own Edit tool. Nothing else removed anything.

Measured three times over the life of the same session, the duplicated share of re-injected bytes rose monotonically: 21.5%34.4%66.3%. The set accumulates, nothing leaves it unassisted, so the waste fraction climbs for as long as the session runs.


The one exit

Split every file that ever appeared in a flush by whether an Edit happened after it entered the set. The split is clean.

filelast Editflushes afteroutcome
README.md2000evicted
00-diagrams.md1620evicted
05-checklist.md2315stuck
mid-session.md4415stuck
04-reproduction.md2214stuck
01-observed-behaviour.md1114stuck

No Read occurred on any stuck file after it entered the set, so reading is not what evicts. The only observed exit is a write through the harness's own tool.

Minimal reproduction

  1. Create a small text file and read it, so it enters context.
  2. Modify it with a shell command — sed -i, a heredoc, a build step. It joins the pending set.
  3. Carry on with unrelated work elsewhere. Never touch it again. It is re-sent, byte-identical, on every flush, indefinitely.
  4. Modify it again by shell, in a different part of the file. The payload does not move to the new region — it expands to cover both, and stays larger for the rest of the session.
  5. Edit it once with the harness's own edit tool. It stops.

Steps 3 and 4 demonstrate the two dimensions separately: membership that never sheds files, and entries that never shed regions. Step 5 is both the diagnostic and the workaround available today. Cheaper still is avoiding step 2 — use the edit tool for anything already in context, and reserve shell-mediated writes for files that were never read.

The question that was open — and the answer

Every repeat was byte-identical to the first delivery, which is consistent with a snapshot frozen at entry. If that were true, the agent would be handed stale content while believing it current, and this would stop being an efficiency defect. So it was tested inside the same session, on a file that had already been stuck for seventeen flushes.

ARM A · prompts/mid-session.md · marker inserted at line 3 by shell 17 deliveries · hash f58064 · 665 B · lines 79–99 · no marker shell edit delivery 18 · 7c8b54 · 977 B lines 1–99 · MARKER PRESENT ARM B · docs/04-reproduction.md · untouched control every delivery identical · b58567 · 5236 B · lines 1–120
Seventeen provably constant deliveries, then a real change — and the payload refreshed. The marker appeared. The content is not stale.

The good news, stated plainly: the payload is not a frozen snapshot. The agent is not being handed outdated file contents. This stays an efficiency defect, and that is the less alarming of the two answers.

The new finding: the window did not move to line 3. It expanded to cover lines 1–99 — the new region and the original one together — and the entry grew from 665 B to 977 B permanently. Dirty regions accumulate inside an entry exactly as files accumulate inside the set.

dimensionwhat accumulateswhat clears it
set membershipfiles never leavean Edit to that file
entry payloadregions never retirenothing observed

So a file edited by shell in ten different places would carry all ten regions on every subsequent flush, for the rest of the session. The leak has two dimensions, and only one of them has a known exit.

One more exit, maybe: the session compacted

Everything above is measured. This last part is not, and it is marked as such because the distinction is the whole point of the exercise.

The session that produced these numbers eventually ran long enough to compact — the harness summarised the conversation and rewrote the transcript in place, 296 turns down to 48 lines. The boundary carried five named files across: three whole-file reads (startLine = 1, numLines == totalLines) and two name-only references. No flush payload crossed it, and three of the four files then stuck in the set do not appear in the rewritten transcript at all.

If compaction really does empty the set, the unbounded growth this page is about is in fact bounded by the compaction interval — which would make the defect meaningfully less severe than the rest of this page implies. So it was tested: three files queued by the same Read → sed -i sequence, in three directory scopes, with the three still-stuck files held untouched as controls.

readingturns queued, no flushprobability by chance
conservative390.057 — 1 in 18
generous560.016 — 1 in 62

Against the measured base rate of one flush per fourteen turns, the conservative reading sits right at the edge of the bar and on the wrong side of it. That makes it a trend, not a finding, and it is written down here as a trend so that nobody quotes it as one.

It is also worth being explicit about why waiting will not fix that. Every further quiet turn multiplies the probability by 0.929, so the number falls on its own, forever, whether or not compaction does anything. Reporting it from whichever moment it happened to dip below 0.05 would be optional stopping wearing a lab coat. The figure above is frozen at turn 70 and stays frozen. Settling it properly needs a fresh session with the turn budget fixed before the probe starts — which is the one experiment on this page that has not been run.

Whoever owns the code can answer it from the source in about a minute. The reasoning, the arithmetic and the two ways the probe could still be measuring nothing are in doc 11.