@@ -36,7 +36,7 @@ import { storeReflectionToLanceDB, loadAgentReflectionSlicesFromEntries, DEFAULT
3636import { parseReflectionMetadata } from "./src/reflection-metadata.js" ;
3737import { extractReflectionLearningGovernanceCandidates , extractInjectableReflectionMappedMemoryItems , isRecallUsed , } from "./src/reflection-slices.js" ;
3838import { 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" ;
4040import { buildFallbackCandidate , gateRegexFallbackCapture } from "./src/autocapture-fallback-admission.js" ;
4141import { gateMappedReflectionEntries , resolveMappedRowAdmissionController } from "./src/reflection-mapped-admission.js" ;
4242import { createMemoryCLI } from "./cli.js" ;
@@ -4619,8 +4619,9 @@ const memoryLanceDBProPlugin = {
46194619 const MAX_MAPPED_ENTRIES = 100 ;
46204620 const mappedReflectionMemories = extractInjectableReflectionMappedMemoryItems ( reflectionText ) ;
46214621 const mappedEntries = [ ] ;
4622- // Per-row embed + near-duplicate pre-check first, collecting the
4623- // gate-eligible rows so the whole burst can share one admission call.
4622+ const mappedGatedItems = [ ] ;
4623+ // Per-row embed first, collecting the gate-eligible rows so the
4624+ // whole burst can share one admission call.
46244625 const gateEligible = [ ] ;
46254626 for ( const mapped of mappedReflectionMemories ) {
46264627 if ( gateEligible . length >= MAX_MAPPED_ENTRIES ) {
@@ -4635,28 +4636,25 @@ const memoryLanceDBProPlugin = {
46354636 api . logger . warn ( `memory-reflection: mapped row embedding failed after retry, skipping row: ${ String ( embedErr ) } ` ) ;
46364637 continue ;
46374638 }
4638- let existing = [ ] ;
4639- let searchFailed = false ;
4640- try {
4641- existing = await store . vectorSearch ( vector , 1 , 0.1 , [ targetScope ] ) ;
4642- }
4643- catch ( err ) {
4644- api . logger . warn ( `memory-reflection: mapped memory duplicate pre-check failed, skip store: ${ String ( err ) } ` ) ;
4645- searchFailed = true ;
4646- }
4647- if ( searchFailed ) {
4648- continue ;
4649- }
4650- // Near-duplicate pre-check ahead of admission gating. This is the only dedup mapped
4651- // rows get: a single vector-similarity threshold, direct skip, no LLM-mediated
4652- // merge/contextualize/contradict decision. Extraction candidates own deduplicate()
4653- // (src/smart-extractor.ts) is a genuinely different, richer pipeline (a 0.7
4654- // pre-filter feeding an LLM decision, not a single hard cutoff) - deliberately not
4655- // reused here yet. AdmissionController's "pass_to_dedup" decision for a mapped row
4656- // is therefore always treated as "admit, subject to this cheaper pre-check" below,
4657- // not "route through the same merge pipeline extraction candidates get".
4658- if ( existing . length > 0 && existing [ 0 ] . score > 0.95 ) {
4659- continue ;
4639+ // Extractor-backed runs take the SAME dedup/merge pipeline
4640+ // extraction candidates get (persistGatedCandidates below), so no
4641+ // bespoke similarity cutoff runs here. The no-extractor fallback
4642+ // keeps the historical near-duplicate pre-check, downgraded from
4643+ // fail-closed to fail-open: a search blip stores the row (worst
4644+ // case the near-duplicate lands as a separate row — this path
4645+ // only pre-checks, it has no merge step) instead of silently
4646+ // dropping it.
4647+ if ( ! smartExtractor ) {
4648+ let existing = [ ] ;
4649+ try {
4650+ existing = await store . vectorSearch ( vector , 1 , 0.1 , [ targetScope ] ) ;
4651+ }
4652+ catch ( err ) {
4653+ api . logger . warn ( `memory-reflection: mapped memory duplicate pre-check failed, storing without pre-check: ${ String ( err ) } ` ) ;
4654+ }
4655+ if ( existing . length > 0 && existing [ 0 ] . score > 0.95 ) {
4656+ continue ;
4657+ }
46604658 }
46614659 gateEligible . push ( { mapped, vector } ) ;
46624660 }
@@ -4709,14 +4707,61 @@ const memoryLanceDBProPlugin = {
47094707 baseMetadata . admission_audit = mappedGate . auditJson ;
47104708 }
47114709 const metadata = JSON . stringify ( baseMetadata ) ;
4712- mappedEntries . push ( {
4713- text : mapped . text ,
4714- vector,
4715- importance,
4716- category : getReflectionMappedStorageCategory ( mapped . mappedKind ) ,
4717- scope : targetScope ,
4718- metadata,
4710+ if ( smartExtractor ) {
4711+ // Uniform pipeline: judge (done above) -> dedup -> merge-writer,
4712+ // identical to extraction candidates. The entry builder keeps
4713+ // the reflection metadata on CREATE-shaped verdicts.
4714+ mappedGatedItems . push ( {
4715+ candidate : {
4716+ category : getReflectionMappedMemoryCategory ( mapped . mappedKind ) ,
4717+ abstract : mapped . text ,
4718+ overview : `## ${ mapped . heading } ` ,
4719+ content : mapped . text ,
4720+ } ,
4721+ vector,
4722+ buildEntry : ( v ) => ( {
4723+ text : mapped . text ,
4724+ vector : v ,
4725+ importance,
4726+ category : getReflectionMappedStorageCategory ( mapped . mappedKind ) ,
4727+ scope : targetScope ,
4728+ metadata,
4729+ } ) ,
4730+ } ) ;
4731+ }
4732+ else {
4733+ mappedEntries . push ( {
4734+ text : mapped . text ,
4735+ vector,
4736+ importance,
4737+ category : getReflectionMappedStorageCategory ( mapped . mappedKind ) ,
4738+ scope : targetScope ,
4739+ metadata,
4740+ } ) ;
4741+ }
4742+ }
4743+ if ( smartExtractor && mappedGatedItems . length > 0 ) {
4744+ const gatedResult = await smartExtractor . persistGatedCandidates ( mappedGatedItems , {
4745+ sessionKey,
4746+ targetScope,
4747+ scopeFilter : [ targetScope ] ,
4748+ agentId : ownerAgentId ,
4749+ conversationText : conversation ,
47194750 } ) ;
4751+ api . logger . info ( `memory-reflection: mapped rows through uniform pipeline: ${ gatedResult . createdEntries . length } created, ${ gatedResult . stats . merged } merged, ${ gatedResult . stats . skipped } skipped` ) ;
4752+ if ( mdMirror ) {
4753+ for ( const stored of gatedResult . createdEntries ) {
4754+ let heading = "unknown" ;
4755+ try {
4756+ const storedMeta = stored . metadata ? JSON . parse ( stored . metadata ) : { } ;
4757+ heading = storedMeta . _reflectionHeading ?? "unknown" ;
4758+ }
4759+ catch {
4760+ api . logger . warn ( `memory-reflection: failed to parse stored metadata for entry ${ stored . id } , using "unknown"` ) ;
4761+ }
4762+ await mdMirror ( { text : stored . text , category : stored . category , scope : stored . scope , timestamp : stored . timestamp } , { source : `reflection:${ heading } ` , agentId : sourceAgentId } ) ;
4763+ }
4764+ }
47204765 }
47214766 if ( mappedEntries . length > 0 ) {
47224767 const storedEntries = await store . bulkStore ( mappedEntries , ( { index, reason } ) => {
0 commit comments