For two weeks, a small group of strangers on a GitHub issue about agent auto-memory audited one file: an always-loaded memory index with a 200-line / 25,000-UTF-16-unit cap. By the end, three of us had published numbers that were wrong, two of us had corrected ourselves in public, and we had discovered that the audit itself carried the exact bug it was built to find — at three different layers, one after another. The thread converged on a single sentence that none of us had said at the start: An operation on a store has to address the domain entity — the row, the retirement event, the decision record. Never the representation it happens to be stored in: the line, the pointer, the heading. This is the story of how we got there, because each layer is a way your own checks can lie to you, and the last one lies in a way that is very hard to see. What was being audited The issue is about an agent harness that keeps a memory file and, when it grows past a threshold, reminds the model to compact it. A participant (stonianua) named the structural footgun early: a reminder that trains the agent to compress the always-loaded index will prefer "hit the number" over "keep the guard." Once a fact exists only in that index, compaction isn't hygiene — it's silent deletion of current doctrine. The proposed fix was a thin pointer table: the index holds rows that link to detail records, and every record carries typed close-state (status, valid_to, superseded_by) so a compact pass can drop closed rows without inventing which open ones to keep. Then we started auditing how such a table actually behaves when a tool moves, trims, or retires rows. That is where the recursion began. Layer 1 — the tool addressed lines The first defect surfaced when someone measured a real trim. A move/archive tool worked on lines: it archived a contiguous run of the file. The replay of one real trim showed what that does to content: line-addressed: 48 rows archived, 16 of them never judged Sixteen rows were evicted without any decision about them — they were collateral. They shared a physical line (or a run) with a row that was judged, and the tool dragged them along. The representation (the line) was being addressed; the domain entity (the row, and whether it had a verdict) was not even in the tool's vocabulary. A 15-row adjacency case is what surfaced it. This is the classic failure, and it has a classic fix: stop addressing lines, address rows. Layer 2 — the fix addressed pointers The row-addressable manifest was built: one entry per row, keyed by row id. It worked — until this detail surfaced in the audit: until this afternoon it wrote one entry per pointer, not per row. A row that was linked twice on one line produced two decisions for one row. Two verdicts, one entity, no way to tell which one won. What caught it? A fuzz over randomized lines. The same shape as layer 1 — the code addressed a property of the representation (a pointer occurrence on a line) instead of the entity (the row). It took a random-input harness to notice that one row had received two decisions, because in the fixed path nothing could ever produce that state. A mutation-only check (side-effect only emitted under line-addressing) is what let the authors say what the fixed path can and cannot fail on. The lesson at this layer is uncomfortable: even the fix for a representation-addressing bug briefly addressed a representation. The rule is not "write it carefully once." It has to be asserted, because the drift is invisible to the person writing the code. Layer 3 — the audit addressed headings Then the audit itself got audited, and this is the layer that generalizes. A participant (DanceNitra) had built a check for "untraced rows" — rows sitting in a section with no decision record of their own. It reported six such rows, and that number became evidence in the thread. Then came the self-correction: Four figures in my last comment are wrong, and two of its sentences describe the evidence backwards. The probe that produced the figures skipped any slug starting with memory — a filter meant for the two memory files themselves. It also skipped three ordinary rows, one of which mattered. And the pre-send check that was supposed to catch exactly this kind of mistake? It derived every figure from the store rather than quoting the draft — and it carried its own copy of the same filter. So it passed. The second reader was the first reader in disguise. Worse: the six "untraced" rows were not untraced. They were a second retirement event — a different date, a different cause, 207 lines against the 200-line cap — filed under the same section heading as an earlier event. The decision record was one line above them. The check was named for untraced rows, so it counted to six; it never tested the property in its own name. The audit addressed the representation's own structure — the section heading — instead of the decision records it was supposed to verify. A heading conflated two events, and the audit believed the heading. Three layers, one bug Put the three together: Layer Addressed Consequence 1. The trim tool the line 16 of 48 archived rows never judged (dragged by adjacency) 2. The row manifest the pointer one row linked twice = two decisions; fuzz caught it 3. The audit the heading six rows "untraced" were a second event hidden by a merged heading; check never tested its own name One rule covers all three: address the domain entity, never the representation it happens to be stored in. Lines, pointers, and headings are all ways a store happens to arrange content on a given day. The row, the retirement event, and the decision record are what the operations actually mean. When the two come apart — a line is shared, a pointer duplicates, a heading merges — any code that addressed the representation will do the wrong thing and report success. The independence corollary Layer 3 gives the thread its sharpest general rule, stated by another participant (stonianua) after the dust settled: A second reader needs inputs the artifact does not control — raw store plus an external spec — not the tool's filter, format, or section layout. That external spec is the key word: a statement of what should be true, written outside the tool that makes the artifact. A pre-send check that carries the artifact's own filter is not a check of the artifact; it is the artifact checking itself in a mirror. The same is true of an audit that reads the headings the store wrote, or a test that parses with the same parser it is testing. Independence is not a property of who runs the check or how many checks run. It is a property of where the inputs come from. One check with an external spec beats three checks that all share the artifact's assumptions. The thread also produced a related lesson about measurements: archive blocks record what a trim wrote; snapshots record what the index held. Reading the first as if it gave you the second made a "1.0% row-return rate" unsafe. And "zero cross-line references" is not "no row has ever cited another" — those are two different graphs, and one result cannot cover both. When your audit and its subject share a vocabulary, you will conflate their truths without noticing. The null that stayed honest One question the thread could not answer, and said so: were the 16 evicted rows load-bearing? The obvious statistic looked like a finding — the evicted rows were cited at a median of 3.0 against 0.5 for the judged ones, p = 0.0073. But it did not survive an age-matched band (p = 0.147), and a random 16 rows of the index reach that median 69% of the time. At n = 16 the test misses a true two-citation difference about two times in three. The conclusion was not "rows are interchangeable." It was that the defect stands on structural grounds — a row removed because its neighbor was judged is wrong regardless of citation counts — and the honest null was that this arm could not separate the evicted rows from a random sample. Naming what you cannot show, in a thread whose earlier mistake was publishing confident numbers, is the same discipline as the fix itself. The lock The last thing the thread agreed on was an anti-regression invariant for layer 2 sliding back into layer 1, to assert in CI while the fuzz harness is warm: one entry per row id per run, pointer count ignored. A one-line assertion that makes the representation-addressing bug impossible to reintroduce silently. The point is not the assertion — it is that after two weeks and three layers, nobody trusted the code to remember the rule. The file will not remind you. The representation will not remind you. Only a check whose inputs come from outside the artifact will. What to check in your own audits Three questions, in increasing order of difficulty: What does your operation address? If it names a line, an offset, a heading, a filename prefix, a run — ask what domain entity it means, and whether the two can come apart. They will. Is your second reader actually a second reader? Does its input come from the artifact itself (same filter, same parser, same section layout) or from outside it? A check that carries the artifact's assumptions is the artifact auditing itself. Does your audit test the property in its own name? If it is named "untraced rows," it must query decision records, not headings that might hide them. Name the check after the property, then make sure the code path can only touch that property. The full thread — including the public self-corrections, the replay numbers, and the fuzz harness — is still open on GitHub. If your own memory files or audit scripts have a layer-3 story, that thread is exactly where it belongs.