fix(eve): address review — async-write status, reasoning filter, turn-bounded capture, best-effort dedup

Review by @kartik-mem0. Root cause across all four: Mem0's hosted add with
infer:true is async and returns a PENDING event before the memory is visible.

- store.ts/tools.ts: add() returns AddResult{status,eventId} via parseAddResult;
  the v3 add path returns {status:"PENDING", event_id}, so remember reports
  {status:"queued", eventId} for async writes and {status:"saved"} only when
  resolved. FAILED/unknown status maps to completed, not queued.
- messages.ts: drop typed non-text parts (reasoning no longer leaks into memory);
  bound the captured assistant reply to the current turn and collect all of its
  text segments (text->tool->text) instead of a stale prior answer.
- capture.ts + README + eve.mdx: document operationId dedup as best-effort
  (lookup+add is not atomic; pending/concurrent windows can double-write);
  removed the "restart replays cannot write twice" claim.

Tests: 68 pass (+15). Added reasoning, tool-only/cross-turn, multi-segment,
pending/concurrent capture replay, and PENDING/FAILED add-result cases.
Typecheck + build clean.
This commit is contained in:
Himanshu-Sangshetti
2026-09-12 00:36:29 +09:00
parent 10d6717068
commit 75e9efa261
11 changed files with 327 additions and 22 deletions
+11 -1
View File
@@ -23,7 +23,10 @@ export type FakeSearch = SearchMemoryInput & {
query: string;
};
export function createFakeStore(hits: FakeHit[] = []): MemoryStore & {
export function createFakeStore(
hits: FakeHit[] = [],
options: { pendingWrites?: boolean } = {},
): MemoryStore & {
added: FakeAdd[];
searched: FakeSearch[];
deleted: string[];
@@ -50,11 +53,18 @@ export function createFakeStore(hits: FakeHit[] = []): MemoryStore & {
infer: input.infer,
metadata: input.metadata,
});
// Model Mem0's async extraction: a pending write is accepted but its
// memory is not yet visible to listByMetadata/get, so it cannot dedupe a
// replay. A resolved write becomes immediately visible.
if (options.pendingWrites) {
return { status: "queued" as const, eventId: `evt_${added.length}` };
}
records.push({
id: `mem_added_${added.length}`,
userId: input.userId,
metadata: input.metadata,
});
return { status: "completed" as const };
},
async search(query, input) {
searched.push({ query, ...input });