Skip to content

fix(store): stash payloads are evicted while pinned decisions survive, so a reversible removal silently becomes irreversible #187

Description

@amiddavid

Symptom

expand_unresolved_missing is non-zero, and it correlates with the pipeline doing more work:

iteration arm unresolved missing
024 (150 runs, 64k) B — +evidence, +econ_trigger 209
024 A — baseline, sweep inert 0
023 Cp1 (+coref) 60 of that pass's 112 expand calls
023 every other pass 0

By this repo's own definition that is a defect, not a tuning artifact — expand/unresolved.go:

MISSING — the id is one this proxy could have minted, and nothing is behind it. That is a context-guru DEFECT: a cut was advertised as reversible and it was not. The stash expired, the store did not persist, or the key was written under a session id nothing reads.

209 times the agent asked for removed content back and got a placeholder instead.

Suspected cause: the pin list protects the decisions and evicts the payloads

store.DefaultPinPrefixes = {FrozenPrefix, ResultPrefix, LenPrefix, XResultPrefix, TTLPrefix, SeenPrefix}.

The stashed original payload — the content behind a <<cg:HASH>> marker — does not appear in that list. The default store holds 1000 entries (store.Options defaults; DefaultTTL is 10,000 s, far longer than these runs, so expiry is not the cause).

So under volume the store keeps "this output was dropped" (frozen + result entries, pinned) while evicting "here is what it was" (the stash, unpinned). A well-formed marker id with nothing behind it is exactly missing.

The volume fits the arm split. Arm B made 629 sweep calls, removed 2,407,680 tokens, and extract_llm logged 4,749 calls avoided — implying thousands of result-cache entries — against a 1000-entry store that the benchmark rig never configures. Arm A stashes nothing through the sweep and scores 0.

One assertion left to verify, and the diagnosis rests on it: that the stash payload key carries none of the pinned prefixes. Confirm that before fixing — if it is pinned, the cause is one of the other two in the comment above (non-persisting store, or a key written under a session id nothing reads) and the fix is different.

Why this is worse than a counter going up

The guarantee degrades precisely as the mechanism succeeds. Every additional drop both consumes a stash slot and adds a pinned decision entry, so the more content a configuration removes, the more of its own reversibility it destroys — and nothing warns. Fail open, always and "every lossy Offload must be reversible (a <<cg:HASH>> marker + the stashed original in the Store)" are stated as hard boundaries in CLAUDE.md; this silently converts a reversible removal into an irreversible one at exactly the load where reversibility matters most.

Adjacent to #47 (pin-budget exhaustion reopening the TailOnly fail-open on /compact), which suggests the pin budget is under-specified generally rather than only here.

How it affected iteration 024's published results

Recorded because the numbers are already written up in docs/experiments/loca/iter024/results.md, and this changes how two of them read:

  1. The reward result is CONSERVATIVE, not threatened. Arm B won 8 tasks to 0 (clustered p = 0.0078, harm bound 11.2%) while carrying 209 failed expands. A handicap the winning arm bore. With reversibility working it would plausibly do at least as well.
  2. The latency conclusion is WEAKENED and was stated too strongly. That write-up says arm B takes 13% more turns on pairs where both arms solved, and calls the latency hypothesis "refuted". But 209 failed expands over 75 runs is ~2.8 per run against ~33 turns/run — so failed-expand round-trips could account for a meaningful share of that 13%. "Refuted" should be "not supported, and partly confounded by this defect."
  3. Cost figures are mildly contaminated. A failed expand still costs the tokens and the turn spent asking, while delivering nothing.
  4. Most generally: iteration 024 measured "the mechanism plus a reversibility bug", not the mechanism. Re-running after the fix is the only way to separate them.

Suggested shape

Whichever cause verification confirms, the fix should make the failure loud rather than only fixed:

  • pin the stash payload, or size the store to the stash volume a configuration will generate;
  • and warn — or refuse the removal — when the budget is exhausted, rather than stamping a marker whose content cannot be produced. A removal that cannot be reversed should not be advertised as reversible.

expand_unresolved_missing exists and is documented as the alertable counter; the gap is that nothing acts on it, and the removal proceeds regardless.

Found while analysing iteration 024. Filed against main and kept off the co-reference branch (#186) because it is unrelated to evidence/econ_trigger and should reach production independently.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions