fix(mem0): replace falsified abort trigger; add namespace arms (f)/(g) - #6
Merged
Merged
Conversation
The write() abort message named "re-adding text identical to a value deleted moments earlier (see HANDOFF, scenario 012)" as the known trigger. That is falsified: the 2026-08-03 abort fired at 003-scope-boundaries op #26, which does not delete at all, and arms (a)-(e) were built to test that mechanism. Replaced with the evidenced trigger — a write issued after a delete_all even when the namespace was polled until it read empty — citing reset()'s own measurement of 6/14 writes lost after a delete_all versus 0/10 without. That measurement sat one method above the message that ignored it. Arms (f)/(g) test the delete the harness actually performs. Every prior arm used a per-key delete by id; reset() issues a single delete_all(app_id=...) across the namespace, polls until empty, returns, and the runner writes immediately. That per-key substitution was mine, made to keep the delete mechanism fixed for comparability, and it is a plausible reason all five earlier arms passed. (f) seeds five values so the namespace holds real residue, calls the same delete_all, polls until empty, then writes. (g) adds a 60s settle after empty. Together they separate "reads empty means propagated" from "reads empty but still reaping". Simulation caught a flaw before any spend: the first draft opened with a cleanup delete_all, which under the hypothesis being tested would swallow the seed writes and abort in setup having measured nothing. Each namespace arm now uses a fresh app_id, so it contains exactly one delete_all — the one under test. Validated against clean, transient-reap and persistent-reap providers; all three discriminate, and in every case the namespace read empty before the write. Also corrects two prior HANDOFF entries claiming a Mem0 credential is present here. It is not: ~/.mem0/config.json holds only a user_id, not an API key. Both blockers are live — no key and no egress. The claims are struck through in place rather than deleted, since the entries are merged and public. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EqxND2pAdrMMubjBnp3VLR
YinkaMetrics
marked this pull request as ready for review
August 4, 2026 04:25
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three changes. Internal tier (
src/,diagnostics/,HANDOFF.md) — self-merges. NoREADME.md, noexamples/report_*.delete_allnamespace condition, which no prior arm tested.1. The falsified abort message
write()told operators:Three things contradict it: the 2026-08-03 abort fired at
003-scope-boundaries, op #26, which does not delete at all; there was no re-add of deleted text near it; and arms (a)–(e) were built to test that mechanism.Replaced with the evidenced trigger — a write issued after a
delete_all, even when the namespace was polled until it read empty — citingreset()'s own measurement of 6/14 writes lost following a delete_all versus 0/10 without.That measurement was sitting one method above the message that ignored it. This isn't cosmetic: the message is what an operator reads at the moment a run dies, and it pointed at a mechanism five arms had already excluded.
2. Arms (f)/(g) — and why (a)–(e) were the wrong instrument
Every prior arm uses a per-key delete (fetch scope, filter on metadata key, delete by id).
reset()does something categorically different: onedelete_all(app_id=...)wiping the namespace across every scope, a poll until empty, then return — after which the runner immediately writes.That substitution was my call when (d)/(e) were added. The brief said
delete_all; I used per-key to hold the delete mechanism fixed for comparability. Comparability was preserved and the mechanism under investigation went untested. It's a plausible reason all five passed.delete_all(app_id=...), polls until empty, then writes and polls for retrievability.A flaw caught by simulation before any spend
The first draft opened with a cleanup
delete_allbefore seeding. Under the very hypothesis being tested, that call would swallow the seed writes and the arm would die in setup having measured nothing — the simulation returnedSETUP_FAILEDfor both reaping behaviours.Fixed by giving each namespace arm a fresh
app_id, so the namespace is empty by construction and the arm contains exactly onedelete_all: the one under test.In every simulated case the namespace read empty before the write — which is exactly what
reset()currently trusts.Reading stated in advance
If (f) fails and (g) passes, polling a namespace until empty is not sufficient. Since that is precisely what
reset()does before returning, every run is exposed: the first writes after any reset can be reaped. That would be a harness-side defect as much as a provider behaviour, and the fix is a settle sized from the measured empty-to-safe gap, not a fixed sleep.Cost: all seven arms ~228 units worst case; (f)+(g) ~66 of that, ~10–12 if they pass.
3. Credential correction
Two merged entries state a Mem0 credential is present in this environment. It is not.
~/.mem0/config.jsonis 57 bytes holding a singleuser_id— the Mem0 CLI's identity file, not an API key. Noplatform.api_key, soresolve_api_key()returnsNone, andMEM0_API_KEYis unset.I inferred it from the file existing and never opened it. Both claims are struck through in place and point to the new entry — not deleted, since the entries are merged and public, and silently rewriting them would hide that the log was wrong for several hours.
Both blockers are live, not one: no key, and no egress (
connect_rejected api.mem0.ai:443, re-verified).Verification
pytest -q: 67 passed, 4 skippedpy_compileclean;--dry-runprices all seven arms without spendingFlagged for the founder
The
003abort is a stronger data point than anything the arms have produced, and it's second-hand here — one sentence, no results filename, no latency curve. Per the promotion rule an unlogged run is invisible to the next session; worth promoting properly with the curve the new instrumentation now captures.If (f)/(g) implicate
reset(), the fix lands inadapters/mem0.pyand could flip provider findings — gated tier, needs a ruling before merge.External launch remains HELD.
Generated by Claude Code