Skip to content

Commit cd4062b

Browse files
committed
feat: route reflection mapped rows through the uniform dedup/merge pipeline
1 parent ba9928f commit cd4062b

11 files changed

Lines changed: 2057 additions & 145 deletions

dist/index.js

Lines changed: 77 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import { storeReflectionToLanceDB, loadAgentReflectionSlicesFromEntries, DEFAULT
3636
import { parseReflectionMetadata } from "./src/reflection-metadata.js";
3737
import { extractReflectionLearningGovernanceCandidates, extractInjectableReflectionMappedMemoryItems, isRecallUsed, } from "./src/reflection-slices.js";
3838
import { createReflectionEventId } from "./src/reflection-event-store.js";
39-
import { buildReflectionMappedMetadata, getReflectionMappedStorageCategory } from "./src/reflection-mapped-metadata.js";
39+
import { buildReflectionMappedMetadata, getReflectionMappedMemoryCategory, getReflectionMappedStorageCategory } from "./src/reflection-mapped-metadata.js";
4040
import { buildFallbackCandidate, gateRegexFallbackCapture } from "./src/autocapture-fallback-admission.js";
4141
import { gateMappedReflectionEntries, resolveMappedRowAdmissionController } from "./src/reflection-mapped-admission.js";
4242
import { createMemoryCLI } from "./cli.js";
@@ -4704,8 +4704,9 @@ const memoryLanceDBProPlugin = {
47044704
const MAX_MAPPED_ENTRIES = 100;
47054705
const mappedReflectionMemories = extractInjectableReflectionMappedMemoryItems(reflectionText);
47064706
const mappedEntries = [];
4707-
// Per-row embed + near-duplicate pre-check first, collecting the
4708-
// gate-eligible rows so the whole burst can share one admission call.
4707+
const mappedGatedItems = [];
4708+
// Per-row embed first, collecting the gate-eligible rows so the
4709+
// whole burst can share one admission call.
47094710
const gateEligible = [];
47104711
for (const mapped of mappedReflectionMemories) {
47114712
if (gateEligible.length >= MAX_MAPPED_ENTRIES) {
@@ -4720,28 +4721,25 @@ const memoryLanceDBProPlugin = {
47204721
api.logger.warn(`memory-reflection: mapped row embedding failed after retry, skipping row: ${String(embedErr)}`);
47214722
continue;
47224723
}
4723-
let existing = [];
4724-
let searchFailed = false;
4725-
try {
4726-
existing = await store.vectorSearch(vector, 1, 0.1, [targetScope]);
4727-
}
4728-
catch (err) {
4729-
api.logger.warn(`memory-reflection: mapped memory duplicate pre-check failed, skip store: ${String(err)}`);
4730-
searchFailed = true;
4731-
}
4732-
if (searchFailed) {
4733-
continue;
4734-
}
4735-
// Near-duplicate pre-check ahead of admission gating. This is the only dedup mapped
4736-
// rows get: a single vector-similarity threshold, direct skip, no LLM-mediated
4737-
// merge/contextualize/contradict decision. Extraction candidates own deduplicate()
4738-
// (src/smart-extractor.ts) is a genuinely different, richer pipeline (a 0.7
4739-
// pre-filter feeding an LLM decision, not a single hard cutoff) - deliberately not
4740-
// reused here yet. AdmissionController's "pass_to_dedup" decision for a mapped row
4741-
// is therefore always treated as "admit, subject to this cheaper pre-check" below,
4742-
// not "route through the same merge pipeline extraction candidates get".
4743-
if (existing.length > 0 && existing[0].score > 0.95) {
4744-
continue;
4724+
// Extractor-backed runs take the SAME dedup/merge pipeline
4725+
// extraction candidates get (persistGatedCandidates below), so no
4726+
// bespoke similarity cutoff runs here. The no-extractor fallback
4727+
// keeps the historical near-duplicate pre-check, downgraded from
4728+
// fail-closed to fail-open: a search blip stores the row (worst
4729+
// case the near-duplicate lands as a separate row — this path
4730+
// only pre-checks, it has no merge step) instead of silently
4731+
// dropping it.
4732+
if (!smartExtractor) {
4733+
let existing = [];
4734+
try {
4735+
existing = await store.vectorSearch(vector, 1, 0.1, [targetScope]);
4736+
}
4737+
catch (err) {
4738+
api.logger.warn(`memory-reflection: mapped memory duplicate pre-check failed, storing without pre-check: ${String(err)}`);
4739+
}
4740+
if (existing.length > 0 && existing[0].score > 0.95) {
4741+
continue;
4742+
}
47454743
}
47464744
gateEligible.push({ mapped, vector });
47474745
}
@@ -4794,14 +4792,61 @@ const memoryLanceDBProPlugin = {
47944792
baseMetadata.admission_audit = mappedGate.auditJson;
47954793
}
47964794
const metadata = JSON.stringify(baseMetadata);
4797-
mappedEntries.push({
4798-
text: mapped.text,
4799-
vector,
4800-
importance,
4801-
category: getReflectionMappedStorageCategory(mapped.mappedKind),
4802-
scope: targetScope,
4803-
metadata,
4795+
if (smartExtractor) {
4796+
// Uniform pipeline: judge (done above) -> dedup -> merge-writer,
4797+
// identical to extraction candidates. The entry builder keeps
4798+
// the reflection metadata on CREATE-shaped verdicts.
4799+
mappedGatedItems.push({
4800+
candidate: {
4801+
category: getReflectionMappedMemoryCategory(mapped.mappedKind),
4802+
abstract: mapped.text,
4803+
overview: `## ${mapped.heading}`,
4804+
content: mapped.text,
4805+
},
4806+
vector,
4807+
buildEntry: (v) => ({
4808+
text: mapped.text,
4809+
vector: v,
4810+
importance,
4811+
category: getReflectionMappedStorageCategory(mapped.mappedKind),
4812+
scope: targetScope,
4813+
metadata,
4814+
}),
4815+
});
4816+
}
4817+
else {
4818+
mappedEntries.push({
4819+
text: mapped.text,
4820+
vector,
4821+
importance,
4822+
category: getReflectionMappedStorageCategory(mapped.mappedKind),
4823+
scope: targetScope,
4824+
metadata,
4825+
});
4826+
}
4827+
}
4828+
if (smartExtractor && mappedGatedItems.length > 0) {
4829+
const gatedResult = await smartExtractor.persistGatedCandidates(mappedGatedItems, {
4830+
sessionKey,
4831+
targetScope,
4832+
scopeFilter: [targetScope],
4833+
agentId: ownerAgentId,
4834+
conversationText: conversation,
48044835
});
4836+
api.logger.info(`memory-reflection: mapped rows through uniform pipeline: ${gatedResult.createdEntries.length} created, ${gatedResult.stats.merged} merged, ${gatedResult.stats.skipped} skipped`);
4837+
if (mdMirror) {
4838+
for (const stored of gatedResult.createdEntries) {
4839+
let heading = "unknown";
4840+
try {
4841+
const storedMeta = stored.metadata ? JSON.parse(stored.metadata) : {};
4842+
heading = storedMeta._reflectionHeading ?? "unknown";
4843+
}
4844+
catch {
4845+
api.logger.warn(`memory-reflection: failed to parse stored metadata for entry ${stored.id}, using "unknown"`);
4846+
}
4847+
await mdMirror({ text: stored.text, category: stored.category, scope: stored.scope, timestamp: stored.timestamp }, { source: `reflection:${heading}`, agentId: sourceAgentId });
4848+
}
4849+
}
48054850
}
48064851
if (mappedEntries.length > 0) {
48074852
const storedEntries = await store.bulkStore(mappedEntries, ({ index, reason }) => {

0 commit comments

Comments
 (0)