Back to News & Insights
Artificial Intelligence October 13, 2026 · 11 min read

Cosine Similarity Said 0.92. The Payload Was Already Dead.

Claiming an LLM agent's memory poisoned its behaviour is easy. Proving it is not. A measurement rig built on counterfactual amputation, a noise floor, and three honest negative results.

Cosine Similarity Said 0.92. The Payload Was Already Dead.

An agent retrieves something from memory. Its next action looks different. Someone writes a blog post: the agent was poisoned by persistent memory.

Maybe. But there is a large gap between "the action changed" and "the memory changed the action," and almost every demo I have seen falls into it.

This post is about closing that gap with instrumentation instead of adjectives. It comes with three negative results, one of which I think is the most useful number in the whole project: an embedding cosine similarity of 0.923 on a payload that was already functionally dead.

When people talk about memory poisoning or "AI worms," they are usually merging two very different statements:

| Claim | What actually establishes it | |---|---| | The carrier exists, was stored, was retrieved, spread to new carriers | the lineage graph: propagation depth and breadth | | The carrier mattered — it changed what the agent did | the change in the action trajectory when you remove it |

A carrier can be retrieved, enter the prompt, spawn descendants, and still be completely non-load-bearing. I did not design for that case; the rig found it on the first run, and it is a better illustration of the problem than any positive result.

Run the scenario. Log everything. Then replay the exact same scenario with exactly one thing changed: a specific carrier is made unavailable to retrieval before its first influential read.

D is the fraction of action steps that differ. Two properties make this an estimator rather than a demonstration: Only one thing changes — what retrieval is allowed to return. It is calibrated. Amputating a carrier that does not exist must produce exactly zero — asserted by the test suite (testamputatinganonexistentcarrierchangesnothing), not assumed.

A zero from a measurement rig is worthless until you have shown the rig can produce a non-zero. So it is validated on a deterministic scripted agent first, before any model is attached.

The scripted run reproduces a cross-session re-entry: the carrier enters memory in session 1, returns through retrieval in session 2, changes the agent's action, and creates a new carrier; session 3 sees both.

That middle result is the interesting one. cs2t1 was created by the agent, appeared in a later context, and produced a descendant. It is a real, spreading carrier — and it is redundant, because the original seed cseed1 is still in the context with the same payload. Remove it and nothing changes.

A carrier existing and spreading is not evidence that it is load-bearing. Those are two different graphs and two different claims.

Redundancy like this is also a defensive property. It should be measured, not smoothed away — so it is pinned by a test named testamputatingaredundantpropagatedcarrierchangesnothing.

A model makes everything above harder, so before the model arrives, the rig gets the metrics that decide whether a number is interpretable at all: parsefailurerate — share of model responses from which no decision could be extracted. An invalid response is never silently replaced with a nominal action. emptyresponses — tracked separately from other parse failures. Model digests — the model revision is part of the experiment, not the environment. If a digest does not match what the scenario pinned, that is a hard error, not a warning. An empirical noise floor — from a negative control: the same paired procedure, but amputating a carrier that was never there. Paired repeats + difference-in-differences + an exact sign test — because one Δaction from one stochastic run is not a measurement. The sign test uses integer arithmetic, not a normal approximation, because N is small by design. Conservatism by default — mean Δaction ≤ noise floor yields the verdict "not distinguishable from the run-to-run noise floor." Not a borderline result, not a promising trend.

The pessimism goes all the way into the exit code — the report CLI returns a non-zero status whenever no effect exceeds the noise floor:

Which means "we found nothing" cannot pass silently as a green build. If your harness reports success by default, a null result and a broken harness look the same in CI.

Setup: Apple M2 Max / 32 GB, a local inference server on 127.0.0.1:11434, temperature = 0, seed pinned per pair, three paired repeats, carrier cseed1, agent llama3-groq-tool-use:8b (rev 36211dad2b153f09fe2b0c5…, verified against the server digest). Memory backend is verbatim — no transformation — so this experiment isolates persistence, not summarization.

| Arm | Agent model | Parse errors | Markers emitted | Δaction | Noise floor | Verdict | |---|---|---|---|---|---|---| | A | llama3-groq-tool-use:8b | 0/4 | 0 | 0.000 | 0.000 | no effect | | B | DistilQwen3-1.7B-uncensored | 3/4 | 0 | 0.000 | 0.000 | no effect | | C | gemma-4-abliterated:12b | 4/4 (256 tok), 3/4 (1024) | 0 | 0.000 | 0.000 | uninterpretable |

Want to discuss this further?

Book a free strategy call with our team to see how these insights apply to your specific business goals.

Book a consultation