LearnNewsExamplesServices
Frontmatter
id15825
titleMailbox read-state resurfacing: identify the loss mechanism (four candidates already falsified — do not re-walk them)
stateClosed
labels
bugai
assigneesneo-gpt-emmy
createdAtJul 24, 2026, 6:46 PM
updatedAtAug 1, 2026, 6:07 PM
githubUrlhttps://github.com/neomjs/neo/issues/15825
authorneo-opus-grace
commentsCount15
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 1, 2026, 6:07 PM

Mailbox read-state resurfacing: identify the loss mechanism (four candidates already falsified — do not re-walk them)

neo-opus-grace
neo-opus-grace commented on Jul 24, 2026, 6:46 PM

Context

Carries forward the unresolved half of #15821. That ticket's titled defect — mark_read returning a receipt when the durable write was skipped — is fixed and red-proved by PR #15824, which therefore Resolves it. The trigger mechanism for the originally reported symptom is a separate deliverable and would be destroyed by that close, so it lives here.

Reported symptom (@neo-fable-clio, 2026-07-24, three occurrences): her mailbox resurfaced messages as UNREAD that she had verifiably mark_read earlier, each correlated with an MC server restart/reconnect. Pattern: recent marks vanish across the restart; older read-state survives.

Distinct from the restore-path loss fixed under #15448 / PR #15808 (--mode replace truncating the graph before re-apply). This is the restart/reconnect lifecycle, one over.

Do NOT re-walk these — four candidates already falsified or weakened

Static tracing eliminated four wrong fixes. Recording them so the next person spends their evidence budget on what is left:

candidate status why
mark_read interleaves with an autoSave = false window falsified all six autoSave = false sites in ai/graph/Database.mjs are synchronous — no await between toggle-off and toggle-back — so a JS mark_read cannot execute inside one
WAL-checkpoint loss across a plain restart falsified ai/graph/storage/SQLite.mjs:53 runs journal_mode = WAL; addEdges is a synchronous better-sqlite3 transaction, so a committed write is crash-safe
steady-state autoSave === false falsified mc-server builds its graph DB as Neo.create(CoreDatabase, {id, storage}) (GraphService.mjs:139) without passing autoSave, so it takes its config default of true (Database.mjs:34)
syncCache reload drops the readAt weakened Database.syncCache() only removes invalidated edges from the in-memory cache and relies on lazy reload from storage; it never touches storage. If the readAt is durably in storage, the reload restores it

Corroborating bound (@neo-opus-vega): a heavy-mark_read session showed acks appearing durable, independently consistent with autoSave defaulting true and the durable write running in steady state.

What PR #15824 changes about this investigation

It removes one way for "never persisted" to happen: the receipt write no longer sits behind autoSave (a flag whose purpose is suppressing load-echo writes, which a user-originated receipt can never be). It does not diagnose the symptom.

That makes this ticket cheaper to resolve, and gives it a free discriminator: a recurrence after #15824 merges is positive evidence for the persisted-then-dropped branch, because the never-persisted branch has one fewer route.

The discriminating probe (the only thing that separates the two branches)

At the moment of loss, does the affected DELIVERED_TO edge's storage row carry the readAt?

  • Storage row HAS the readAt → it was persisted and something dropped it on the way back in → the fix is on the rebuild/delta-log side, not the write path.
  • Storage row LACKS it → it was never persisted → a remaining write-skip route exists that static tracing did not find, and #15824 did not cover it.

Concretely: log in Database.syncCache when an entry in delta.invalidEdges is a DELIVERED_TO edge currently carrying a readAt, and log the readAt of its reloaded replacement.

Blocked on evidence, not on a ticket: @neo-fable-clio has the reproducing environment (hers is the only session that has seen it three times) and owns the runtime probe. Static reading has provably hit its limit here — it eliminated four candidates and cannot confirm the fifth.

Also worth excluding once the probe runs: the symlinked-WAL multi-writer topology (#15802) and any concurrent restore/reseed (the #15808 class), which would explain the reporter's odd "12:30Z marks resurfacing late at 14:19Z" anomaly via a second mechanism rather than this one.

Acceptance Criteria

  • The probe output states, for at least one observed loss, whether the storage row carried the readAt — a claim about the storage row, not the in-memory edge (the in-memory edge always carries it, so it cannot discriminate).
  • The mechanism is named with the code path that drops or skips the value, cited by file and line.
  • If a fix follows, it is verified by a control that turns the loss red — reading state back from storage, never from the in-memory cache.
  • If the probe shows the storage row DID carry the readAt, the syncCache "weakened" verdict above is revisited on the runtime evidence rather than the static reading.
  • If no recurrence is observable after #15824 merges, that is recorded as a bounded negative result (the never-persisted route was the live one) rather than the ticket being closed as unreproducible.

Out of Scope

  • The false-ack receipt shape itself — delivered by PR #15824 under #15821.
  • The --mode replace restore truncation — delivered by PR #15808 under #15448.

Refs #15821, #15448, #15802, #15808

Authored by Grace (@neo-opus-grace, Claude Opus 4.8). Filed to discharge an obligation stated in PR #15824's body: a Resolves #15821 must not bury the open question.

tobiu referenced in commit c1417fd - "feat(ai): the broadcast-delivery series reads shipped graph state, it does not count (#15919) (#15935) on Jul 25, 2026, 10:07 PM
tobiu referenced in commit 1079d68 - "docs(agentos): A2A.md states where read-state lives, not just the delivery topology (#15936) (#15939) on Jul 25, 2026, 10:34 PM
tobiu assigned to @neo-gpt on Jul 28, 2026, 10:19 AM
tobiu unassigned from @neo-opus-grace on Jul 28, 2026, 10:19 AM
tobiu referenced in commit 077ca94 - "fix(memory-core): persist hydrated broadcast receipts (#15825) (#16272)" on Aug 1, 2026, 6:07 PM
tobiu closed this issue on Aug 1, 2026, 6:07 PM