Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions HANDOFF.md
Original file line number Diff line number Diff line change
Expand Up @@ -2787,3 +2787,109 @@ the raw output can be found again.
External launch remains **HELD**.

---
## 2026-08-04 — Citations promoted, (g) recorded: the reaping window is bounded

All figures below are **reported, not observed in-session** — promoted by hand
per the out-of-environment rule, now with the citations that entry was missing.

### 1. What shipped

`HANDOFF.md` citations and (g)'s result; `diagnostics/readd_after_delete.py`
STATUS block brought up to date (it still said (d)-(g) had not run).

### 2. Findings — the full seven-arm picture, with sources

| Arm | Outcome | Detail | Results file |
|---|---|---|---|
| (a) `a_identical` | RE_ADD_VISIBLE | 0s, 4 reads, 5 units | `readd_after_delete_1785797173.json` |
| (b) `b_varied` | RE_ADD_VISIBLE | 0s, 4 reads, 5 units | `readd_after_delete_1785797173.json` |
| (c) `c_settle_then_identical` | RE_ADD_VISIBLE | 0s after 60s settle, 4 reads, 5 units | `readd_after_delete_1785797173.json` |
| (d) `d_cross_scope_identical` | RE_ADD_VISIBLE | — | `readd_after_delete_1785800147.json` |
| (e) `e_cross_scope_settle` | RE_ADD_VISIBLE | — | `readd_after_delete_1785800147.json` |
| (f) `f_namespace_delete_all` | **WRITE_LOST** | never retrievable, 25 reads / 120s | `readd_after_delete_1785828208.json` |
| (g) `g_namespace_settle` | **WRITE_VISIBLE** | 0s after 60s settle, 3 reads, 4 units | `readd_after_delete_1785828208.json` |

**(f) LOST + (g) VISIBLE resolves the fork**, and it matches the branch stated
in advance for `reaps, transient`:

- The reaping window after a `delete_all` is **real** — polling a namespace
until it reads empty is **not** sufficient, which is exactly what `reset()`
did before `e7e89dc`.
- The window is **bounded** — a 60s settle cleared it completely, write
retrievable at 0s.
- **`delete_all` stays usable** as a reset primitive, provided the caller
verifies writability instead of trusting emptiness. That is what the
sentinel now does, and this is the evidence it converges.

**Every per-key arm passed; the one `delete_all` arm failed.** Six arms across
three executions isolate the mechanism to the namespace-wide delete.

**Correcting my own framing from the previous entry.** That entry said arm (f)
shows "the condition persisting for at least 120s". That reads as a claim
about the window, and it is not one. (f) polls a *single already-swallowed*
write: such a write is dead permanently, so 120s of polling measures how long
we waited, not how long the store reaps. **(g) is the only bound on the
window, and it bounds it from above at 60s.** The lower bound is still
unmeasured.

**What that means for the sentinel budget.** `_CONVERGE_TIMEOUT` is 30s, 5s
per attempt. The window is somewhere in (0s, 60s]. If it typically sits under
30s the loop converges; if it sits above, `reset()` exhausts its budget and
raises — safe, but it would abort runs. **Still not enough information to size
it, and still not worth guessing at**: `empty_to_writable_s`, emitted on every
deleting reset since `e7e89dc`, measures the window directly and under the
retry procedure rather than the single-write one. First real run settles it.

**The `003` abort — terminal-reported, no results file.** Provenance:
`memorycheck run scenarios --adapter mem0 --seeds 2`, founder's Mac,
2026-08-03, terminal output only. Aborted at **op #26 —
`003-scope-boundaries` seed 0, `write`, 31.20s**. There is no
`diagnostics/results/` artefact for this one; it is cited as terminal-reported
rather than as a file, and should not be written up as though a JSON exists.

**An inference about op #26, marked as inference.** Operation counts are
structural — fixed by the scenario pack and step list, not by the provider. On
that ordering, `003-scope-boundaries` seed 0 occupies ops 25-29, with op 25
the `reset` and **op 26 the scenario's first write**. So the abort landed on
the first write immediately after a reset, which is precisely the (f)
condition. 31.20s is also just past the adapter's 30s write-convergence
ceiling, consistent with a write that was swallowed rather than merely slow.

For that reset to have deleted at all, the namespace must have held residue —
which happens when a namespace name recurs, e.g. re-running the suite after an
earlier aborted run. That fits, but it is **not measured**: the run predates
the `[reset]` logging, so nothing recorded whether that reset took the delete
path. The next run answers it directly.

### 3. Decisions

- **Citations added rather than left implicit.** Three files now traceable;
the `003` abort explicitly marked as having none.
- **The previous entry's "at least 120s" phrasing corrected in this entry**,
not edited in place — the entry is merged and public.
- **`_CONVERGE_TIMEOUT` still unchanged at 30s.** (g) bounds the window above
at 60s but says nothing about the typical case, and the sentinel's retry
procedure differs from the arm's. Size it from the series.

### 4. FOR STRATEGY

- **The harness-side story is now evidenced end to end**: `delete_all` reaps,
the window is bounded, `reset()` verifies rather than assumes. What remains
unproven is whether this mechanism *caused* the `012` and `003` aborts. The
op #26 inference is suggestive and cheap to confirm on the next run.
- Unchanged: pinning `mem0ai` 2.0.14 for the regen, exact pins for extras,
whether #6017 changes the publication plan, `get_all` pagination.
- **Nothing here is publishable yet** — one execution per arm, and the
provider-facing claim would need the stability re-runs first.

### 5. Next

1. Full 15 × 2 regen, capturing stderr. It now yields `empty_to_writable_s`
per deleting reset, which sizes the sentinel budget and confirms or kills
the op #26 inference.
2. Stability re-runs of the arms before any of it is treated as settled.
3. Founder ruling before any published text changes.

External launch remains **HELD**.

---
35 changes: 23 additions & 12 deletions diagnostics/readd_after_delete.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,29 @@
#!/usr/bin/env python3
"""Discriminating experiment: why is a re-added value not retrievable?

STATUS. Arms (a)-(c) have RUN once — 2026-08-03, outside this environment,
results `readd_after_delete_1785797173.json`. All three came back
RE_ADD_VISIBLE. Arms (d)-(e) have NOT run: they were written after that run,
and sandboxed sessions cannot reach `api.mem0.ai` (403 at CONNECT), which a
credential alone does not fix — see CLAUDE.md, Environment notes.

**The clean (a)-(c) sweep is not a clean bill of health.** It rules out
content-level dedup and same-scope delete reaping, and leaves three
behaviours indistinguishable: healthy, permanent cross-scope suppression, and
transient cross-scope suppression. The last two are exactly what (d)-(e)
exist to separate. No finding is claimed here and none should be quoted from
this file. See HANDOFF.md.
STATUS. All seven arms have now RUN, outside this environment (sandboxed
sessions cannot reach `api.mem0.ai` — 403 at CONNECT; see CLAUDE.md,
Environment notes). Results, one execution each:

(a)-(c) RE_ADD_VISIBLE, 0s `readd_after_delete_1785797173.json`
(d)-(e) RE_ADD_VISIBLE `readd_after_delete_1785800147.json`
(f) WRITE_LOST, 120s `readd_after_delete_1785828208.json`
(g) WRITE_VISIBLE, 0s (same file, after a 60s settle)

**The result: (f) failed and (g) passed.** Every per-key arm passed; the one
arm using `delete_all` — the delete `reset()` performs — lost its write, and
a 60s settle cleared it. So the reaping window after a `delete_all` is REAL
and BOUNDED: polling a namespace until empty is not sufficient, but the store
does become writable again. `delete_all` stays usable as a reset primitive
provided the caller verifies writability rather than trusting emptiness.

Note what (f) does and does not measure. A write swallowed by the reaping
`delete_all` is dead permanently — 120s of polling never resurrected it. That
bounds nothing about the WINDOW; it says a lost write stays lost. (g) is what
bounds the window, from above, at 60s. The lower bound is unmeasured, which
is why `reset()` measures `empty_to_writable_s` on every deleting reset
rather than assuming a figure. No finding is claimed here and none should be
quoted from this file. See HANDOFF.md.

STANDING CAVEAT — WHAT NO ARM CAN SHOW. Every arm here runs against an
isolated, freshly-scoped, otherwise-idle system: a handful of operations
Expand Down
Loading