@@ -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" ;
@@ -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