Skip to content
Memtru
Documentation menu
Reference

Memtru Afterlife

A delete can succeed, be confirmed, and still be undone later. An in-flight write lands after the delete, or an ingestion cycle rebuilds the memory from conversation history that was kept. Afterlife tests that: it deletes a synthetic marker, proves it is gone, then drives the lifecycle events that come later and checks every retrieval surface after each one.

memtru test --scenario afterlife      # both scenarios, against your configured adapter
memtru demo --afterlife               # broken store, then fixed store, then the comparison

Interactive walkthrough: /demo/afterlife.

Scenarios

MT-AF-001 Late write resurrection

Requires MEMORY_WRITE, MEMORY_SEARCH, LIFECYCLE_PENDING_WRITES, and MEMORY_DELETE or THREAD_DELETE.

  1. Write a memory containing a unique synthetic marker; confirm the owner can retrieve it (positive control).
  2. Start an update of that memory and hold it in flight (begin_pending_write).
  3. Delete the memory. Confirm the marker is absent on every surface (immediate_check).
  4. Release the held write (release_pending_writes). Poll every surface.
  5. If the adapter supports restart, restart the isolated instance and poll again.

FAIL late_write_resurrected when the marker is retrievable after step 4 or 5. The finding names the event that preceded the reappearance.

MT-AF-002 Session re-ingestion resurrection

Requires MEMORY_SEARCH, MEMORY_DELETE, LIFECYCLE_CONVERSATION, and LIFECYCLE_NEW_SESSION or LIFECYCLE_CONSOLIDATE.

  1. Record a conversation in which the user asks to remember a unique synthetic marker; run ingestion; confirm the derived memory is retrievable (positive control).
  2. Delete the derived memory. The conversation itself is retained. Confirm the marker is absent.
  3. Start a new session; poll every surface.
  4. Record an unrelated message and run ingestion; confirm the unrelated message became a memory (proof that ingestion ran); poll every surface.
  5. If supported, restart and poll again.

FAIL history_reingestion_resurrected when the marker reappears. The marker is never sent again after deletion, so a reappearance can only come from retained state.

When the result is not PASS or FAIL

Situation Result
The adapter lacks a required hook or deletion contract SKIPPED
The marker was never retrievable before deletion INCONCLUSIVE (positive control)
The marker was still visible right after deletion INCONCLUSIVE; run MT-DEL-003, this is not resurrection
A hook reports executed=False, or did not process the held write INCONCLUSIVE
Ingestion ran but did not turn the unrelated message into a memory INCONCLUSIVE
A hook raised a provider error ERROR

PASS is only possible when every lifecycle event in the run reported that it ran.

Deletion contracts

Memory deletion and thread deletion are different promises.

  • memory_record: delete(ctx, id) removes a memory. Afterlife expects it to stay removed.
  • thread_checkpoint: delete_thread(ctx) removes conversation / checkpoint state (for example LangGraph's checkpointer.delete_thread). It is not expected to remove long-term memories derived from that thread.

MT-AF-001 uses memory_record when MEMORY_DELETE is available and thread_checkpoint otherwise, and the report says which. MT-AF-002 always requires memory_record. See tests/test_langgraph_afterlife.py::test_thread_deletion_is_not_memory_deletion.

Lifecycle hooks (memtru.adapters.lifecycle)

Adapters opt in by also inheriting MemoryLifecycleHooks and declaring the matching capabilities. Every hook is optional.

Hook Capability Must do
begin_pending_write(ctx, content, target_id=) LIFECYCLE_PENDING_WRITES Start a real write and hold it before it is applied.
release_pending_writes(ctx) LIFECYCLE_PENDING_WRITES Let held writes complete exactly as the system would.
record_conversation(ctx, messages) LIFECYCLE_CONVERSATION Append user messages to retained history.
start_session(ctx) LIFECYCLE_NEW_SESSION Begin a session, including any session-start ingestion.
consolidate(ctx) LIFECYCLE_CONSOLIDATE Run one ingestion / consolidation cycle.
restart() LIFECYCLE_RESTART Restart the isolated test instance.
retrieval_surfaces(), inspect_surface(ctx, surface, query) LIFECYCLE_SURFACES Expose further surfaces an agent reads (for example a profile summary).
delete_thread(ctx) THREAD_DELETE Delete thread / checkpoint state.

Rules:

  • Return a HookReceipt from every hook. executed=False makes the scenario INCONCLUSIVE.
  • Put every record a hook creates in receipt.created; Memtru tracks it and deletes it during cleanup.
  • Act only inside ctx.namespace, and restart only an isolated instance.
  • Declare a capability only if the hook drives the real mechanism. The engine drops lifecycle capabilities from adapters that do not implement MemoryLifecycleHooks. The http adapter implements them through optional lifecycle endpoints, so a service in any language can run Afterlife by implementing those endpoints and advertising the capabilities.

Report format

Afterlife results carry an optional afterlife object (schema 1.0, additive). Results of other scenarios omit the key, so earlier readers are unaffected.

"afterlife": {
  "deletion_contract": "memory_record",
  "hooks_executed": ["release_pending_writes"],
  "surfaces_checked": ["profile"],
  "events": [
    {"seq": 4, "phase": "delete", "action": "delete", "detail": "memory mem_000003 deleted", "elapsed_ms": 1, "evidence_step": 6},
    {"seq": 5, "phase": "immediate_check", "action": "immediate_check", "detail": "marker absent from every checked surface", "marker_visible": false, "surfaces": [], "elapsed_ms": 2},
    {"seq": 7, "phase": "observe", "action": "after_release", "detail": "marker visible on search, profile", "marker_visible": true, "surfaces": ["search", "profile"], "elapsed_ms": 4}
  ],
  "resurrected": true,
  "first_resurrection_event": 7,
  "resurrection_trigger": "release_pending_writes",
  "reproduce": ["memtru test --scenario MT-AF-001", "memtru demo --afterlife"]
}

Phases: create, positive_control, pending_write, delete, immediate_check, lifecycle, observe. The dashboard shows the timeline inside the run's result row and on the finding page; the HTML report shows it under the finding and in an "Afterlife timelines" section.

Verified integrations

Target MT-AF-001 MT-AF-002 Evidence
Built-in store, correct PASS PASS tests/test_afterlife.py
Built-in store, late_write_resurrection FAIL (release) PASS tests/test_afterlife.py
Built-in store, history_reingestion PASS FAIL (session start) tests/test_afterlife.py
LangGraph 1.2.16 app, no tombstones FAIL (release) FAIL (session start) tests/test_langgraph_afterlife.py
Same app, tombstones honoured PASS PASS tests/test_langgraph_afterlife.py

Mem0. The Mem0 adapter supports MT-AF-001 through its pending-write hook; MT-AF-002 is skipped because Mem0 ingestion requires an LLM. Results against Mem0 are withheld from this page while they are handled with the maintainers.

LangGraph 1.2.16. LangGraph does not extract memories itself. examples/afterlife-langgraph is an application built on real LangGraph primitives (StateGraph, InMemorySaver, InMemoryStore, checkpointer.delete_thread) with a deterministic memory manager. The failing variant rebuilds the profile from every retained thread and applies deferred writes blindly; the fixed variant writes tombstones. The defect is an application pattern, not a claim about LangGraph.

pip install -e "packages/memtru[langgraph]"
cd examples/afterlife-langgraph && memtru test      # FAIL, exit 1
# set adapter.options.honour_tombstones: true       # PASS, exit 0

Limitations

  • Observes retrieval surfaces the adapter exposes. It does not prove erasure from backups, replicas, logs, embeddings, caches or other copies Memtru cannot see.
  • Exercises only the lifecycle events the adapter exposes as hooks. A nightly job that is not exposed is not tested.
  • A PASS holds for the configured consistency window after each event, not for every timing.
  • Pending writes are controlled fixtures. Real races can involve timings a hook does not reproduce.
  • Ingestion is driven through hooks; the extraction quality of a real model is out of scope.
  • Over HTTP, lifecycle events are only as faithful as the service's endpoints: an endpoint that reports executed: true without driving the real mechanism cannot be detected.