Skip to content
53 changes: 40 additions & 13 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3597,24 +3597,34 @@ const memoryLanceDBProPlugin = {
command: String(event.action || "unknown"),
});

const MAX_MAPPED_ENTRIES = 100;
const mappedReflectionMemories = extractInjectableReflectionMappedMemoryItems(reflectionText);
const mappedEntries: Array<{ text: string; vector: number[]; importance: number; category: string; scope: string; metadata: string }> = [];
for (const mapped of mappedReflectionMemories) {
if (mappedEntries.length >= MAX_MAPPED_ENTRIES) {
api.logger.warn(`memory-reflection: mapped entries cap (${MAX_MAPPED_ENTRIES}) reached, skipping remaining items`);
break;
}
const vector = await embedder.embedPassage(mapped.text);
let existing: Awaited<ReturnType<typeof store.vectorSearch>> = [];
let searchFailed = false;
try {
existing = await store.vectorSearch(vector, 1, 0.1, [targetScope]);
} catch (err) {
api.logger.warn(
`memory-reflection: mapped memory duplicate pre-check failed, continue store: ${String(err)}`,
`memory-reflection: mapped memory duplicate pre-check failed, skip store: ${String(err)}`,
);
searchFailed = true;
}
if (searchFailed) {
continue;
}

if (existing.length > 0 && existing[0].score > 0.95) {
continue;
}

const importance = mapped.category === "decision" ? 0.85 : 0.8;
const metadata = JSON.stringify(buildReflectionMappedMetadata({
const baseMetadata = buildReflectionMappedMetadata({
mappedItem: mapped,
eventId: reflectionEventId,
agentId: sourceAgentId,
Expand All @@ -3624,22 +3634,38 @@ const memoryLanceDBProPlugin = {
usedFallback: reflectionGenerated.usedFallback,
toolErrorSignals,
sourceReflectionPath: relPath,
}));
});
// embed heading in metadata JSON so it survives bulkStore round-trip to LanceDB
baseMetadata._reflectionHeading = mapped.heading;
const metadata = JSON.stringify(baseMetadata);

const storedEntry = await store.store({
mappedEntries.push({
text: mapped.text,
vector,
importance,
category: mapped.category,
scope: targetScope,
metadata,
});

}
if (mappedEntries.length > 0) {
const storedEntries = await store.bulkStore(mappedEntries);
if (mdMirror) {
await mdMirror(
{ text: mapped.text, category: mapped.category, scope: targetScope, timestamp: storedEntry.timestamp },
{ source: `reflection:${mapped.heading}`, agentId: sourceAgentId },
);
for (const stored of storedEntries) {
// retrieve heading from metadata JSON — critical when bulkStore filters entries
// because storedEntries[i] may not correspond to mappedEntries[i]
let heading = "unknown";
try {
const storedMeta = stored.metadata ? JSON.parse(stored.metadata) : {};
heading = storedMeta._reflectionHeading ?? "unknown";
} catch {
api.logger.warn(`memory-reflection: failed to parse stored metadata for entry ${stored.id}, using "unknown"`);
}
await mdMirror(
{ text: stored.text, category: stored.category, scope: stored.scope, timestamp: stored.timestamp },
{ source: `reflection:${heading}`, agentId: sourceAgentId },
);
}
}
}

Expand Down Expand Up @@ -3686,9 +3712,10 @@ const memoryLanceDBProPlugin = {
if (sessionKey) {
reflectionErrorStateBySession.delete(sessionKey);
getGlobalReflectionLock().delete(sessionKey);
if (reflectionRan) {
getSerialGuardMap().set(sessionKey, Date.now());
}
getSerialGuardMap().set(sessionKey, Date.now());
// NOTE: This guard is tested via inline simulation in
// test/memory-reflection-issue680-tdd.test.mjs "Bug #1: serial guard on early throw".
// The test verifies this runs unconditionally in finally (not gated by reflectionRan).
}
pruneReflectionSessionState();
}
Expand Down
9 changes: 8 additions & 1 deletion scripts/ci-test-manifest.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export const CI_TEST_MANIFEST = [
{ group: "core-regression", runner: "node", file: "test/to-import-specifier-windows.test.mjs", args: ["--test"] },
{ group: "storage-and-schema", runner: "node", file: "test/update-consistency-lancedb.test.mjs" },
{ group: "core-regression", runner: "node", file: "test/strip-envelope-metadata.test.mjs", args: ["--test"] },
{ group: "cli-smoke", runner: "node", file: "test/import-markdown/import-markdown.test.mjs", args: ["--test"] },
{ group: "cli-smoke", runner: "node", file: "test/cli-smoke.mjs" },
{ group: "cli-smoke", runner: "node", file: "test/functional-e2e.mjs" },
{ group: "storage-and-schema", runner: "node", file: "test/per-agent-auto-recall.test.mjs", args: ["--test"] },
Expand Down Expand Up @@ -53,6 +54,12 @@ export const CI_TEST_MANIFEST = [
// Issue #629 batch embedding fix
{ group: "llm-clients-and-auth", runner: "node", file: "test/embedder-ollama-batch-routing.test.mjs" },
// Issue #665 bulkStore tests
{ group: "storage-and-schema", runner: "node", file: "test/bulk-store.test.mjs", args: ["--test"] },
{ group: "storage-and-schema", runner: "node", file: "test/bulk-store-edge-cases.test.mjs", args: ["--test"] },
{ group: "storage-and-schema", runner: "node", file: "test/smart-extractor-bulk-store.test.mjs", args: ["--test"] },
{ group: "storage-and-schema", runner: "node", file: "test/smart-extractor-bulk-store-edge-cases.test.mjs", args: ["--test"] },
// Issue #680 regression tests
{ group: "core-regression", runner: "node", file: "test/memory-reflection-issue680-tdd.test.mjs", args: ["--test"] },
];

export function getEntriesForGroup(group) {
Expand All @@ -61,4 +68,4 @@ export function getEntriesForGroup(group) {
}

return CI_TEST_MANIFEST.filter((entry) => entry.group === group);
}
}
2 changes: 2 additions & 0 deletions scripts/verify-ci-test-manifest.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ const EXPECTED_BASELINE = [
{ group: "storage-and-schema", runner: "node", file: "test/bulk-store-edge-cases.test.mjs", args: ["--test"] },
{ group: "storage-and-schema", runner: "node", file: "test/smart-extractor-bulk-store.test.mjs", args: ["--test"] },
{ group: "storage-and-schema", runner: "node", file: "test/smart-extractor-bulk-store-edge-cases.test.mjs", args: ["--test"] },
// Issue #680 regression tests
{ group: "core-regression", runner: "node", file: "test/memory-reflection-issue680-tdd.test.mjs", args: ["--test"] },
];

function fail(message) {
Expand Down
2 changes: 2 additions & 0 deletions src/reflection-mapped-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export interface ReflectionMappedMetadata {
baseWeight: number;
quality: number;
sourceReflectionPath?: string;
// Issue #680: heading stored in entry for bulkStore filtering recovery
_reflectionHeading?: string;
}

export interface ReflectionMappedDecayDefaults {
Expand Down
Loading
Loading