Skip to content

Commit 48aaa6b

Browse files
committed
feat(memory): add cognitive mechanisms module — 8 neuroscience-grounded mechanisms
Adds an optional `mechanisms/` module with 8 individually configurable cognitive mechanisms backed by published research: Retrieval-time: - Reconsolidation drift (Nader et al., 2000) - Retrieval-induced forgetting (Anderson et al., 1994) - Involuntary recall (Berntsen, 2009) - Metacognitive feeling-of-knowing (Nelson & Narens, 1990) Consolidation-time: - Temporal gist extraction (Reyna & Brainerd, 1995) - Schema encoding (Bartlett, 1932; Tse et al., 2007) - Source confidence decay (Johnson et al., 1993) - Emotion regulation (Gross, 1998) All mechanisms are HEXACO personality-modulated and wired into the existing pipeline via 5 lifecycle hooks. 103 tests across 5 files.
1 parent a7e7846 commit 48aaa6b

24 files changed

Lines changed: 3053 additions & 0 deletions

docs/memory/COGNITIVE_MECHANISMS.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,17 @@ applySourceConfidenceDecay(traces, config): number
7070
applyEmotionRegulation(traces, config): number
7171
```
7272
73+
## HEXACO Personality Modulation
74+
75+
The `CognitiveMechanismsEngine` constructor accepts optional `HexacoTraits`. When provided, mechanism parameters are scaled by personality dimensions before any hooks fire:
76+
77+
```typescript
78+
// In CognitiveMemoryManager.initialize():
79+
this.mechanismsEngine = new CognitiveMechanismsEngine(config.cognitiveMechanisms, config.traits);
80+
```
81+
82+
Modulation is applied once at construction time via `applyPersonalityModulation()`. Each trait maps to a specific mechanism parameter via empirically-grounded scaling formulas documented in `CognitiveMechanismsEngine.ts`.
83+
7384
## Guard Conditions
7485
7586
All mechanisms share common guard patterns:

src/memory/CognitiveMemoryManager.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,9 @@ export class CognitiveMemoryManager implements ICognitiveMemoryManager {
203203
// Batch 3: Infinite Context (optional)
204204
private contextWindow: ContextWindowManager | null = null;
205205

206+
// Cognitive Mechanisms (optional)
207+
private mechanismsEngine: import('./mechanisms/CognitiveMechanismsEngine.js').CognitiveMechanismsEngine | null = null;
208+
206209
/**
207210
* Optional HyDE retriever for hypothesis-driven memory recall.
208211
*
@@ -216,13 +219,21 @@ export class CognitiveMemoryManager implements ICognitiveMemoryManager {
216219
async initialize(config: CognitiveMemoryConfig): Promise<void> {
217220
this.config = config;
218221

222+
// Cognitive Mechanisms (optional — dynamic import to avoid loading when unused)
223+
if (config.cognitiveMechanisms) {
224+
const { CognitiveMechanismsEngine } = await import('./mechanisms/CognitiveMechanismsEngine.js');
225+
this.mechanismsEngine = new CognitiveMechanismsEngine(config.cognitiveMechanisms, config.traits);
226+
}
227+
219228
// Memory store
220229
this.store = new MemoryStore({
221230
vectorStore: config.vectorStore,
222231
embeddingManager: config.embeddingManager,
223232
knowledgeGraph: config.knowledgeGraph,
224233
collectionPrefix: config.collectionPrefix ?? 'cogmem',
225234
decayConfig: config.decay ? { ...DEFAULT_DECAY_CONFIG, ...config.decay } : undefined,
235+
mechanismsEngine: this.mechanismsEngine ?? undefined,
236+
moodProvider: config.moodProvider,
226237
});
227238

228239
// Cognitive working memory (wraps the existing IWorkingMemory)
@@ -279,6 +290,7 @@ export class CognitiveMemoryManager implements ICognitiveMemoryManager {
279290
decay: config.decay,
280291
consolidation: config.consolidation,
281292
llmInvoker: config.reflector?.llmInvoker ?? config.featureDetectionLlmInvoker,
293+
mechanismsEngine: this.mechanismsEngine ?? undefined,
282294
});
283295
// Auto-start periodic consolidation
284296
this.consolidation.start();
@@ -394,6 +406,16 @@ export class CognitiveMemoryManager implements ICognitiveMemoryManager {
394406
isActive: true,
395407
};
396408

409+
// Cognitive mechanisms: schema encoding (before store, so adjusted strength persists)
410+
if (this.mechanismsEngine) {
411+
try {
412+
const embResp = await this.config.embeddingManager.generateEmbeddings({ texts: input });
413+
this.mechanismsEngine.onEncoding(trace, embResp.embeddings[0]);
414+
} catch {
415+
// Non-critical — schema encoding is best-effort
416+
}
417+
}
418+
397419
// Store in long-term memory
398420
await this.store.store(trace);
399421

src/memory/config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,10 @@ export interface CognitiveMemoryConfig {
210210
graph?: Partial<MemoryGraphConfig>;
211211
consolidation?: Partial<ConsolidationConfig>;
212212

213+
// --- Cognitive Mechanisms (optional, no-op when absent) ---
214+
/** Optional per-mechanism cognitive science extensions (reconsolidation, RIF, FOK, etc.). */
215+
cognitiveMechanisms?: import('./mechanisms/types.js').CognitiveMechanismsConfig;
216+
213217
// --- Batch 3: Infinite Context (optional, no-op when absent) ---
214218
/** Infinite context window config. Enables transparent compaction for forever conversations. */
215219
infiniteContext?: Partial<InfiniteContextConfig>;

src/memory/consolidation/ConsolidationPipeline.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ export interface ConsolidationPipelineConfig {
4949
consolidation?: Partial<ConsolidationConfig>;
5050
/** LLM invoker for schema integration (optional). */
5151
llmInvoker?: (systemPrompt: string, userPrompt: string) => Promise<string>;
52+
/** Optional cognitive mechanisms engine for consolidation-time hooks. */
53+
mechanismsEngine?: import('../mechanisms/CognitiveMechanismsEngine.js').CognitiveMechanismsEngine;
5254
}
5355

5456
// ---------------------------------------------------------------------------
@@ -153,6 +155,14 @@ export class ConsolidationPipeline {
153155
// --- Step 5: Spaced repetition reinforcement ---
154156
result.reinforcedCount = await this.spacedRepetitionSweep(batch, now);
155157

158+
// --- Step 6: Cognitive mechanisms (temporal gist, source decay, emotion regulation) ---
159+
if (this.config.mechanismsEngine) {
160+
const llmFn = this.config.llmInvoker
161+
? (prompt: string) => this.config.llmInvoker!('You are a memory consolidation assistant.', prompt)
162+
: undefined;
163+
await this.config.mechanismsEngine.onConsolidation(batch, llmFn);
164+
}
165+
156166
result.durationMs = Date.now() - startTime;
157167
this.lastRunAt = now;
158168
return result;

src/memory/index.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,3 +290,14 @@ export {
290290
MemorySearchTool,
291291
MemoryReflectTool,
292292
} from './tools/index.js';
293+
294+
// --- Cognitive Mechanisms (optional) ---
295+
export { CognitiveMechanismsEngine, DEFAULT_MECHANISMS_CONFIG } from './mechanisms/index.js';
296+
export { resolveConfig as resolveMechanismsConfig } from './mechanisms/index.js';
297+
export type {
298+
CognitiveMechanismsConfig,
299+
ResolvedMechanismsConfig,
300+
MetacognitiveSignal,
301+
MechanismMetadata,
302+
DriftEvent,
303+
} from './mechanisms/index.js';
Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
/**
2+
* @fileoverview CognitiveMechanismsEngine — lifecycle hook orchestrator.
3+
*
4+
* Instantiates and delegates to 8 cognitive mechanisms across 5 lifecycle
5+
* hooks: onAccess, onRetrieval, onEncoding, onConsolidation, onPromptAssembly.
6+
*
7+
* Cognitive science mechanisms:
8+
* - **Reconsolidation** (Nader, Schafe & Le Doux, 2000)
9+
* - **Retrieval-Induced Forgetting** (Anderson, Bjork & Bjork, 1994)
10+
* - **Involuntary Recall** (Berntsen, 2009)
11+
* - **Metacognitive FOK** (Nelson & Narens, 1990; Hart, 1965; Koriat, 1993)
12+
* - **Temporal Gist** (Reyna & Brainerd, 1995)
13+
* - **Schema Encoding** (Bartlett, 1932; Ghosh & Gilboa, 2014; Tse et al., 2007)
14+
* - **Source Confidence Decay** (Johnson, Hashtroudi & Lindsay, 1993)
15+
* - **Emotion Regulation** (Gross, 1998, 2015)
16+
*
17+
* @module agentos/memory/mechanisms/CognitiveMechanismsEngine
18+
*/
19+
20+
import type {
21+
CognitiveMechanismsConfig,
22+
ResolvedMechanismsConfig,
23+
MetacognitiveSignal,
24+
} from './types.js';
25+
import type { MemoryTrace, ScoredMemoryTrace, AssembledMemoryContext } from '../types.js';
26+
import type { PADState, HexacoTraits } from '../config.js';
27+
import type { CandidateTrace } from '../decay/RetrievalPriorityScorer.js';
28+
import { resolveConfig } from './defaults.js';
29+
import { applyReconsolidation } from './retrieval/Reconsolidation.js';
30+
import { applyRetrievalInducedForgetting } from './retrieval/RetrievalInducedForgetting.js';
31+
import { selectInvoluntaryMemory } from './retrieval/InvoluntaryRecall.js';
32+
import { detectFeelingOfKnowing } from './retrieval/MetacognitiveFOK.js';
33+
import { applyTemporalGist } from './consolidation/TemporalGist.js';
34+
import { applySchemaEncoding } from './consolidation/SchemaEncoding.js';
35+
import { applySourceConfidenceDecay } from './consolidation/SourceConfidenceDecay.js';
36+
import { applyEmotionRegulation } from './consolidation/EmotionRegulation.js';
37+
38+
// ---------------------------------------------------------------------------
39+
// Engine
40+
// ---------------------------------------------------------------------------
41+
42+
// ---------------------------------------------------------------------------
43+
// HEXACO → mechanism modulation
44+
// ---------------------------------------------------------------------------
45+
46+
const clamp01 = (v: number | undefined): number =>
47+
v == null ? 0.5 : Math.max(0, Math.min(1, v));
48+
49+
/**
50+
* Compute personality-modulated mechanism parameters from HEXACO traits.
51+
*
52+
* Cognitive science rationale for each mapping:
53+
* - **Emotionality → reconsolidation drift**: High-emotionality agents
54+
* experience stronger emotional reconsolidation (broader mood influence
55+
* on memory recall). Scales driftRate by 0.5 + E×1.0 (range 0.5x–1.5x).
56+
* - **Conscientiousness → RIF suppression**: High-conscientiousness agents
57+
* are more focused retrievers, producing stronger competitor suppression.
58+
* Scales suppressionFactor by 0.7 + C×0.6 (range 0.7x–1.3x).
59+
* - **Openness → involuntary recall**: High-openness agents are more
60+
* receptive to spontaneous associations and unbidden memories.
61+
* Scales probability by 0.5 + O×1.0 (range 0.5x–1.5x).
62+
* - **Honesty → source confidence decay**: High-honesty agents are more
63+
* critical of self-generated content, applying stronger source decay
64+
* to inferences/reflections. Scales reflection multiplier down by H×0.15.
65+
* - **Agreeableness → emotion regulation**: High-agreeableness agents
66+
* regulate emotional memories more actively (social harmony motive).
67+
* Scales reappraisalRate by 0.7 + A×0.6 (range 0.7x–1.3x).
68+
* - **Extraversion → FOK surfacing**: High-extraversion agents are more
69+
* willing to surface partial/uncertain memories (social disclosure).
70+
* Scales partialActivationThreshold down (lower = more signals).
71+
*/
72+
function applyPersonalityModulation(
73+
cfg: ResolvedMechanismsConfig,
74+
traits?: HexacoTraits,
75+
): ResolvedMechanismsConfig {
76+
if (!traits) return cfg;
77+
78+
const e = clamp01(traits.emotionality);
79+
const c = clamp01(traits.conscientiousness);
80+
const o = clamp01(traits.openness);
81+
const h = clamp01(traits.honesty);
82+
const a = clamp01(traits.agreeableness);
83+
const x = clamp01(traits.extraversion);
84+
85+
return {
86+
reconsolidation: {
87+
...cfg.reconsolidation,
88+
driftRate: cfg.reconsolidation.driftRate * (0.5 + e * 1.0),
89+
},
90+
retrievalInducedForgetting: {
91+
...cfg.retrievalInducedForgetting,
92+
suppressionFactor: cfg.retrievalInducedForgetting.suppressionFactor * (0.7 + c * 0.6),
93+
},
94+
involuntaryRecall: {
95+
...cfg.involuntaryRecall,
96+
probability: Math.min(1.0, cfg.involuntaryRecall.probability * (0.5 + o * 1.0)),
97+
},
98+
metacognitiveFOK: {
99+
...cfg.metacognitiveFOK,
100+
// Higher extraversion → lower threshold → more TOT signals surfaced
101+
partialActivationThreshold: cfg.metacognitiveFOK.partialActivationThreshold * (1.3 - x * 0.6),
102+
},
103+
temporalGist: cfg.temporalGist, // not personality-modulated (cognitive, not dispositional)
104+
schemaEncoding: {
105+
...cfg.schemaEncoding,
106+
// Higher openness → stronger novelty boost (more attention to schema violations)
107+
noveltyBoost: cfg.schemaEncoding.noveltyBoost * (0.8 + o * 0.4),
108+
},
109+
sourceConfidenceDecay: {
110+
...cfg.sourceConfidenceDecay,
111+
decayMultipliers: {
112+
...cfg.sourceConfidenceDecay.decayMultipliers,
113+
// Higher honesty → more skeptical of own inferences and reflections
114+
agent_inference: Math.max(0.5, cfg.sourceConfidenceDecay.decayMultipliers.agent_inference - h * 0.15),
115+
reflection: Math.max(0.4, cfg.sourceConfidenceDecay.decayMultipliers.reflection - h * 0.15),
116+
},
117+
},
118+
emotionRegulation: {
119+
...cfg.emotionRegulation,
120+
reappraisalRate: cfg.emotionRegulation.reappraisalRate * (0.7 + a * 0.6),
121+
},
122+
};
123+
}
124+
125+
// ---------------------------------------------------------------------------
126+
// Engine
127+
// ---------------------------------------------------------------------------
128+
129+
/**
130+
* Orchestrates 8 cognitive mechanisms across the memory pipeline lifecycle.
131+
*
132+
* When `cognitiveMechanisms` config is present on `CognitiveMemoryConfig`,
133+
* an instance is created during initialization. Existing pipeline files
134+
* call the lifecycle hooks at the appropriate points.
135+
*
136+
* If HEXACO traits are provided, mechanism parameters are personality-modulated:
137+
* emotionality → reconsolidation drift, conscientiousness → RIF strength,
138+
* openness → involuntary recall probability + novelty boost, honesty → source
139+
* skepticism, agreeableness → emotion regulation, extraversion → FOK surfacing.
140+
*/
141+
export class CognitiveMechanismsEngine {
142+
private readonly cfg: ResolvedMechanismsConfig;
143+
144+
/** Lazily populated cluster centroids for schema encoding. */
145+
private clusterCentroids: Map<string, number[]> = new Map();
146+
147+
constructor(config: CognitiveMechanismsConfig, traits?: HexacoTraits) {
148+
this.cfg = applyPersonalityModulation(resolveConfig(config), traits);
149+
}
150+
151+
// =========================================================================
152+
// Lifecycle hooks
153+
// =========================================================================
154+
155+
/**
156+
* Called by MemoryStore.recordAccess().
157+
* Applies reconsolidation drift to the trace's emotional context.
158+
*/
159+
onAccess(trace: MemoryTrace, currentMood: PADState): void {
160+
applyReconsolidation(trace, currentMood, this.cfg.reconsolidation);
161+
}
162+
163+
/**
164+
* Called by MemoryStore.query() after scoring.
165+
* Applies retrieval-induced forgetting to competitors and detects FOK signals.
166+
*
167+
* @returns Suppressed trace IDs and metacognitive signals.
168+
*/
169+
onRetrieval(
170+
results: ScoredMemoryTrace[],
171+
allCandidates: CandidateTrace[],
172+
retrievalCutoff: number,
173+
queryEntities: string[],
174+
): { suppressedIds: string[]; metacognitiveSignals: MetacognitiveSignal[] } {
175+
// RIF: competitors are candidates that didn't make the result set
176+
const resultIds = new Set(results.map((r) => r.id));
177+
const competitors = allCandidates
178+
.filter((c) => !resultIds.has(c.trace.id))
179+
.map((c) => c.trace);
180+
181+
const { suppressedIds } = applyRetrievalInducedForgetting(
182+
results,
183+
competitors,
184+
this.cfg.retrievalInducedForgetting,
185+
);
186+
187+
// FOK: detect partially activated traces
188+
// Build approximate ScoredMemoryTrace for non-retrieved candidates
189+
const allScored: ScoredMemoryTrace[] = allCandidates.map((c) => {
190+
const existing = results.find((r) => r.id === c.trace.id);
191+
if (existing) return existing;
192+
return {
193+
...c.trace,
194+
retrievalScore: c.vectorSimilarity * 0.35, // approximate composite
195+
scoreBreakdown: {
196+
strengthScore: 0,
197+
similarityScore: c.vectorSimilarity,
198+
recencyScore: 0,
199+
emotionalCongruenceScore: 0,
200+
graphActivationScore: 0,
201+
importanceScore: 0,
202+
},
203+
};
204+
});
205+
206+
const metacognitiveSignals = detectFeelingOfKnowing(
207+
allScored,
208+
retrievalCutoff,
209+
this.cfg.metacognitiveFOK,
210+
queryEntities,
211+
);
212+
213+
return { suppressedIds, metacognitiveSignals };
214+
}
215+
216+
/**
217+
* Called by EncodingModel.encode().
218+
* Classifies the trace as schema-congruent or schema-violating and adjusts
219+
* encoding strength accordingly.
220+
*/
221+
onEncoding(
222+
trace: MemoryTrace,
223+
traceEmbedding: number[],
224+
): void {
225+
if (this.clusterCentroids.size > 0) {
226+
applySchemaEncoding(trace, traceEmbedding, this.clusterCentroids, this.cfg.schemaEncoding);
227+
}
228+
}
229+
230+
/**
231+
* Called by ConsolidationLoop.run() after step 5 (compact).
232+
* Runs temporal gist, source confidence decay, and emotion regulation.
233+
*/
234+
async onConsolidation(
235+
traces: MemoryTrace[],
236+
llmFn?: (prompt: string) => Promise<string>,
237+
): Promise<{ gistedCount: number; sourceDecayedCount: number; regulatedCount: number }> {
238+
const gistedCount = await applyTemporalGist(traces, this.cfg.temporalGist, llmFn);
239+
const sourceDecayedCount = applySourceConfidenceDecay(traces, this.cfg.sourceConfidenceDecay);
240+
const regulatedCount = applyEmotionRegulation(traces, this.cfg.emotionRegulation);
241+
return { gistedCount, sourceDecayedCount, regulatedCount };
242+
}
243+
244+
/**
245+
* Called by MemoryPromptAssembler.
246+
* May inject an involuntary recall memory into the assembled context.
247+
*/
248+
onPromptAssembly(
249+
allTraces: MemoryTrace[],
250+
retrievedIds: Set<string>,
251+
): { involuntaryMemory: MemoryTrace | null } {
252+
const involuntaryMemory = selectInvoluntaryMemory(
253+
allTraces,
254+
retrievedIds,
255+
this.cfg.involuntaryRecall,
256+
);
257+
return { involuntaryMemory };
258+
}
259+
260+
// =========================================================================
261+
// Configuration
262+
// =========================================================================
263+
264+
/** Update cluster centroids (called after consolidation derive step). */
265+
setClusterCentroids(centroids: Map<string, number[]>): void {
266+
this.clusterCentroids = centroids;
267+
}
268+
269+
/** Get resolved config for diagnostics. */
270+
getConfig(): ResolvedMechanismsConfig {
271+
return this.cfg;
272+
}
273+
}

0 commit comments

Comments
 (0)