Skip to content

fix(mem0): replace falsified abort trigger; add namespace arms (f)/(g) - #6

Merged
YinkaMetrics merged 1 commit into
mainfrom
claude/quota-reset-diagnostics-8w7gh0
Aug 4, 2026
Merged

fix(mem0): replace falsified abort trigger; add namespace arms (f)/(g)#6
YinkaMetrics merged 1 commit into
mainfrom
claude/quota-reset-diagnostics-8w7gh0

Conversation

@YinkaMetrics

Copy link
Copy Markdown
Owner

Summary

Three changes. Internal tier (src/, diagnostics/, HANDOFF.md) — self-merges. No README.md, no examples/report_*.

  1. URGENT — the abort message named a falsified mechanism. Replaced with the evidenced one.
  2. Arms (f)/(g) — the delete_all namespace condition, which no prior arm tested.
  3. Credential correction — two merged entries claim a Mem0 key is present here. It isn't.

1. The falsified abort message

write() told operators:

Known trigger: re-adding text identical to a value deleted moments earlier (see HANDOFF, scenario 012)

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 — citing reset()'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: one delete_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.

  • (f) seeds 5 values so the namespace holds real residue, calls the same delete_all(app_id=...), polls until empty, then writes and polls for retrievability.
  • (g) identical, plus a 60s settle after empty.

A flaw caught by simulation before any spend

The first draft opened with a cleanup delete_all before 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 returned SETUP_FAILED for 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 one delete_all: the one under test.

delete_all behaviour (f) no settle (g) 60s settle
clean WRITE_VISIBLE WRITE_VISIBLE
reaps, transient WRITE_LOST WRITE_VISIBLE
reaps, persistent WRITE_LOST WRITE_LOST

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.json is 57 bytes holding a single user_id — the Mem0 CLI's identity file, not an API key. No platform.api_key, so resolve_api_key() returns None, and MEM0_API_KEY is 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 skipped
  • py_compile clean; --dry-run prices all seven arms without spending
  • Old message: 0 occurrences. New message cites 6/14: confirmed.

Flagged for the founder

The 003 abort 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 in adapters/mem0.py and could flip provider findings — gated tier, needs a ruling before merge.

External launch remains HELD.


Generated by Claude Code

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
YinkaMetrics marked this pull request as ready for review August 4, 2026 04:25
@YinkaMetrics
YinkaMetrics merged commit 3703123 into main Aug 4, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants