fix(eve): scope forget, persist capture idempotency, and fail closed

Review findings: delete was not partitioned by scope.key, capture
replay after restart could double-write, and Mem0 errors were swallowed
as empty recall.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Himanshu-Sangshetti
2026-09-09 10:48:26 +08:00
co-authored by Cursor
parent 1d83bb0835
commit 10d6717068
24 changed files with 1013 additions and 165 deletions
+193 -14
View File
@@ -1,4 +1,5 @@
import type {
MemoryCompactionCompletedContext,
MemoryToolsContext,
MemoryTurnCompletedContext,
MemoryTurnStartedContext,
@@ -19,6 +20,15 @@ function completedContext(
return memoryContext(input) as unknown as MemoryTurnCompletedContext;
}
function compactionContext(
input?: Parameters<typeof memoryContext>[0],
): MemoryCompactionCompletedContext {
return memoryContext({
...input,
turn: input?.turn === undefined ? null : input.turn,
}) as unknown as MemoryCompactionCompletedContext;
}
function toolsContext(
input?: Parameters<typeof memoryContext>[0],
): MemoryToolsContext {
@@ -32,10 +42,20 @@ async function runTool(
return tool.execute(input as never, {} as never);
}
function requireCapture(
provider: ReturnType<typeof mem0Provider>,
): NonNullable<NonNullable<typeof provider.capture>["turn.completed"]> {
const capture = provider.capture?.["turn.completed"];
if (!capture) {
throw new Error("expected turn.completed");
}
return capture;
}
describe("mem0Provider", () => {
it("recalls memories for the locked scope on turn start", async () => {
const store = createFakeStore([{ id: "mem_1", memory: "User likes tea" }]);
const provider = mem0Provider({ apiKey: "m0-test", store });
const provider = mem0Provider({ store });
const result = await provider.recall["turn.started"](
startedContext({
turnInput: [{ role: "user", content: "What do I drink?" }],
@@ -43,31 +63,110 @@ describe("mem0Provider", () => {
);
expect(store.searched).toEqual([
{ query: "What do I drink?", userId: "scope_abc" },
{
query: "What do I drink?",
userId: "scope_abc",
topK: 5,
threshold: 0.1,
rerank: false,
},
]);
expect(result).toEqual({
messages: [{ id: "mem_1", content: "User likes tea" }],
});
});
it("does not fail the turn when search throws", async () => {
it("forwards recall search options", async () => {
const store = createFakeStore([{ id: "mem_1", memory: "User likes tea" }]);
const provider = mem0Provider({
store,
topK: 3,
threshold: 0.5,
rerank: true,
});
await provider.recall["turn.started"](
startedContext({
turnInput: [{ role: "user", content: "tea" }],
}),
);
expect(store.searched[0]).toMatchObject({
topK: 3,
threshold: 0.5,
rerank: true,
});
});
it("skips search when autoSearch is disabled", async () => {
const store = createFakeStore([{ id: "mem_1", memory: "User likes tea" }]);
const provider = mem0Provider({ store, autoSearch: { enabled: false } });
await expect(
provider.recall["turn.started"](startedContext()),
).resolves.toBeNull();
expect(store.searched).toEqual([]);
});
it("recalls after compaction when turn is null", async () => {
const store = createFakeStore([{ id: "mem_1", memory: "User likes tea" }]);
const provider = mem0Provider({ store });
const recallAfterCompaction = provider.recall["compaction.completed"];
if (!recallAfterCompaction) {
throw new Error("expected compaction.completed");
}
const result = await recallAfterCompaction(
compactionContext({
messages: [
{ role: "user", content: "What do I drink?" },
{ role: "assistant", content: "tea" },
],
}),
);
expect(store.searched).toEqual([
{
query: "What do I drink?",
userId: "scope_abc",
topK: 5,
threshold: 0.1,
rerank: false,
},
]);
expect(result).toEqual({
messages: [{ id: "mem_1", content: "User likes tea" }],
});
});
it("fails the turn when search throws", async () => {
const store = createFakeStore();
store.search = async () => {
throw new Error("mem0 down");
};
const provider = mem0Provider({ apiKey: "m0-test", store });
const provider = mem0Provider({ store });
const error = vi.spyOn(console, "error").mockImplementation(() => {});
await expect(
provider.recall["turn.started"](startedContext()),
).resolves.toBeNull();
try {
await expect(
provider.recall["turn.started"](startedContext()),
).rejects.toThrow("mem0 down");
} finally {
error.mockRestore();
}
});
error.mockRestore();
it("rethrows when recall is aborted", async () => {
const store = createFakeStore();
store.search = async () => {
throw new Error("cancelled");
};
const provider = mem0Provider({ store });
await expect(
provider.recall["turn.started"](startedContext({ aborted: true })),
).rejects.toThrow("cancelled");
});
it("captures a completed turn once per operation id", async () => {
const store = createFakeStore();
const provider = mem0Provider({ apiKey: "m0-test", store });
const provider = mem0Provider({ store });
const capture = provider.capture?.["turn.completed"];
expect(capture).toBeTypeOf("function");
@@ -76,12 +175,42 @@ describe("mem0Provider", () => {
await capture!(context);
expect(store.added).toHaveLength(1);
expect(store.added[0]?.userId).toBe("scope_abc");
expect(store.added[0]).toMatchObject({
userId: "scope_abc",
infer: true,
metadata: {
source: "eve",
operation_id: "op_replay",
session_id: "sess_1",
slot: "mem0",
},
});
});
it("does not recapture after process restart when operation metadata exists", async () => {
const store = createFakeStore();
const first = requireCapture(mem0Provider({ store }));
await first(completedContext({ operationId: "op_replay" }));
const restarted = requireCapture(mem0Provider({ store }));
await restarted(completedContext({ operationId: "op_replay" }));
expect(store.added).toHaveLength(1);
});
it("surfaces capture store failures", async () => {
const store = createFakeStore();
store.add = async () => {
throw new Error("write failed");
};
await expect(
requireCapture(mem0Provider({ store }))(completedContext()),
).rejects.toThrow("write failed");
});
it("omits capture when it is disabled", () => {
const provider = mem0Provider({
apiKey: "m0-test",
store: createFakeStore(),
capture: { enabled: false },
});
@@ -90,7 +219,13 @@ describe("mem0Provider", () => {
it("binds tools to the locked scope", async () => {
const store = createFakeStore([{ id: "mem_1", memory: "likes tea" }]);
const provider = mem0Provider({ apiKey: "m0-test", store });
const provider = mem0Provider({
store,
topK: 3,
threshold: 0.4,
rerank: true,
infer: false,
});
const tools = await provider.tools!(toolsContext());
if (!tools) {
throw new Error("expected tools");
@@ -114,8 +249,52 @@ describe("mem0Provider", () => {
});
expect(remember).toEqual({ saved: true });
expect(forget).toEqual({ deleted: true });
expect(store.searched[0]?.userId).toBe("scope_abc");
expect(store.added[0]?.userId).toBe("scope_abc");
expect(store.searched[0]).toMatchObject({
userId: "scope_abc",
topK: 3,
threshold: 0.4,
rerank: true,
});
expect(store.added[0]).toMatchObject({
userId: "scope_abc",
infer: false,
metadata: { source: "eve-tool" },
messages: [{ role: "user", content: "Allergic to peanuts" }],
});
expect(store.deleted).toEqual(["mem_1"]);
});
it("refuses to forget a memory from another scope", async () => {
const store = createFakeStore([
{ id: "mem_other", memory: "secret", userId: "scope_other" },
]);
const provider = mem0Provider({ store });
const tools = await provider.tools!(toolsContext());
const forgetTool = tools?.forget;
if (!forgetTool) {
throw new Error("expected forget tool");
}
await expect(runTool(forgetTool, { id: "mem_other" })).rejects.toThrow(
/Memory not found/,
);
expect(store.deleted).toEqual([]);
});
it("surfaces tool store failures", async () => {
const store = createFakeStore();
store.add = async () => {
throw new Error("add failed");
};
const provider = mem0Provider({ store });
const tools = await provider.tools!(toolsContext());
const rememberTool = tools?.remember;
if (!rememberTool) {
throw new Error("expected remember tool");
}
await expect(
runTool(rememberTool, { text: "remember this" }),
).rejects.toThrow("add failed");
});
});