Skip to content

Commit 995bfbe

Browse files
committed
feat(memory): Stage E Phase 4.1 - typedNetwork config field + manager wiring
Adds optional typedNetwork config to CognitiveMemoryConfig with two variants: - 'minimal': bank routing at encode (no graph traversal at retrieve) - 'full': minimal + spreading activation per Hindsight Eq. 12 + 4-way RRF Manager now exposes getTypedNetworkStore(), getTypedNetworkObserver(), getTypedSpreadingActivation(). Returns null when typedNetwork is absent (zero-cost no-op for non-Stage-E callers). Phase 4.2 (encode integration) and 4.3 (retrieve integration) follow.
1 parent f4abd37 commit 995bfbe

3 files changed

Lines changed: 222 additions & 1 deletion

File tree

src/memory/CognitiveMemoryManager.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ import {
5656
ConsolidationPipeline,
5757
type ConsolidationResult,
5858
} from './pipeline/consolidation/ConsolidationPipeline.js';
59+
import {
60+
TypedNetworkStore,
61+
TypedNetworkObserver,
62+
TypedSpreadingActivation,
63+
} from './retrieval/typed-network/index.js';
5964

6065
// Batch 3: Infinite Context
6166
import { ContextWindowManager } from './pipeline/context/ContextWindowManager.js';
@@ -289,6 +294,13 @@ export class CognitiveMemoryManager implements ICognitiveMemoryManager {
289294
*/
290295
private hydeRetriever: HydeRetriever | null = null;
291296

297+
// Stage E: Hindsight 4-network typed observer wiring (optional)
298+
private typedNetworkStore: TypedNetworkStore | null = null;
299+
private typedNetworkObserver: TypedNetworkObserver | null = null;
300+
private typedSpreadingActivation: TypedSpreadingActivation | null = null;
301+
private typedNetworkVariant: 'minimal' | 'full' | null = null;
302+
private typedNetworkWeight = 0.5;
303+
292304
async initialize(config: CognitiveMemoryConfig): Promise<void> {
293305
this.config = config;
294306

@@ -454,9 +466,45 @@ export class CognitiveMemoryManager implements ICognitiveMemoryManager {
454466
this.archive = config.archive;
455467
}
456468

469+
// --- Stage E: Hindsight 4-network typed observer (optional) ---
470+
if (config.typedNetwork) {
471+
this.typedNetworkVariant = config.typedNetwork.variant;
472+
this.typedNetworkWeight = config.typedNetwork.weight ?? 0.5;
473+
this.typedNetworkStore = new TypedNetworkStore();
474+
this.typedNetworkObserver = new TypedNetworkObserver({
475+
llm: config.typedNetwork.observerLLM,
476+
});
477+
// Spreading activation only for the 'full' variant. 'minimal' performs
478+
// bank routing at encode but skips graph traversal at retrieve.
479+
if (config.typedNetwork.variant === 'full') {
480+
this.typedSpreadingActivation = new TypedSpreadingActivation({
481+
decay: config.typedNetwork.decay ?? 0.5,
482+
});
483+
}
484+
}
485+
457486
this.initialized = true;
458487
}
459488

489+
// ---------------------------------------------------------------------------
490+
// Stage E typed-network accessors
491+
// ---------------------------------------------------------------------------
492+
493+
/** Stage E: typed-network store, or `null` when typed-network not configured. */
494+
getTypedNetworkStore(): TypedNetworkStore | null {
495+
return this.typedNetworkStore;
496+
}
497+
498+
/** Stage E: typed-network observer (LLM extractor), or `null`. */
499+
getTypedNetworkObserver(): TypedNetworkObserver | null {
500+
return this.typedNetworkObserver;
501+
}
502+
503+
/** Stage E: typed spreading activation, or `null` (only set for 'full' variant). */
504+
getTypedSpreadingActivation(): TypedSpreadingActivation | null {
505+
return this.typedSpreadingActivation;
506+
}
507+
460508
// =========================================================================
461509
// Encode
462510
// =========================================================================
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/**
2+
* @fileoverview Stage E Phase 4.1: CognitiveMemoryManager `typedNetwork` config
3+
* field + initialization wiring tests.
4+
*
5+
* Verifies the manager exposes a TypedNetworkStore + Observer + spreading
6+
* activation when `config.typedNetwork` is provided, and exposes nulls when
7+
* the field is absent (zero-cost no-op for non-Stage-E callers).
8+
*
9+
* @module memory/__tests__/CognitiveMemoryManager.typedNetwork.test
10+
*/
11+
12+
import { describe, it, expect, vi, beforeEach } from 'vitest';
13+
import { CognitiveMemoryManager } from '../CognitiveMemoryManager.js';
14+
import {
15+
TypedNetworkStore,
16+
TypedNetworkObserver,
17+
TypedSpreadingActivation,
18+
type ITypedExtractionLLM,
19+
} from '../retrieval/typed-network/index.js';
20+
21+
function makeMinimalDeps() {
22+
const mockKnowledgeGraph = {
23+
initialize: vi.fn(),
24+
upsertEntity: vi.fn(),
25+
upsertRelation: vi.fn(),
26+
queryEntities: vi.fn().mockResolvedValue([]),
27+
getNeighborhood: vi.fn().mockResolvedValue({ entities: [], relations: [] }),
28+
getRelations: vi.fn().mockResolvedValue([]),
29+
deleteEntity: vi.fn(),
30+
deleteRelation: vi.fn(),
31+
traverse: vi.fn().mockResolvedValue({
32+
root: { id: 'r', type: 'memory', label: 'r', properties: {}, confidence: 1, source: { type: 'system', timestamp: '', method: '' } } as any,
33+
levels: [],
34+
totalEntities: 0,
35+
totalRelations: 0,
36+
}),
37+
recordMemory: vi.fn(),
38+
};
39+
40+
const mockVectorStore = {
41+
initialize: vi.fn(),
42+
upsert: vi.fn(),
43+
query: vi.fn().mockResolvedValue({ documents: [] }),
44+
collectionExists: vi.fn().mockResolvedValue(true),
45+
createCollection: vi.fn(),
46+
};
47+
48+
const mockEmbeddingManager = {
49+
generateEmbeddings: vi.fn().mockResolvedValue({
50+
embeddings: [[0.1, 0.2, 0.3]],
51+
modelId: 'test',
52+
providerId: 'test',
53+
usage: { totalTokens: 0 },
54+
}),
55+
getEmbeddingDimension: vi.fn().mockResolvedValue(3),
56+
getEmbeddingModelInfo: vi.fn().mockResolvedValue({
57+
dimension: 3,
58+
modelId: 'test',
59+
providerId: 'test',
60+
maxInputTokens: 8192,
61+
}),
62+
initialize: vi.fn(),
63+
checkHealth: vi.fn().mockResolvedValue({ isHealthy: true }),
64+
shutdown: vi.fn(),
65+
};
66+
67+
const mockWorkingMemory = {
68+
capacity: 7,
69+
store: vi.fn(),
70+
retrieve: vi.fn().mockResolvedValue([]),
71+
clear: vi.fn(),
72+
getSlots: vi.fn().mockReturnValue([]),
73+
};
74+
75+
return {
76+
workingMemory: mockWorkingMemory as any,
77+
knowledgeGraph: mockKnowledgeGraph as any,
78+
vectorStore: mockVectorStore as any,
79+
embeddingManager: mockEmbeddingManager as any,
80+
agentId: 'stage-e-test',
81+
traits: { emotionality: 0.5, conscientiousness: 0.5 },
82+
moodProvider: () => ({ valence: 0, arousal: 0.3, dominance: 0 }),
83+
featureDetectionStrategy: 'keyword' as const,
84+
};
85+
}
86+
87+
const stubLLM: ITypedExtractionLLM = {
88+
invoke: async () => JSON.stringify({ facts: [] }),
89+
};
90+
91+
describe('CognitiveMemoryManager: typedNetwork wiring', () => {
92+
let manager: CognitiveMemoryManager;
93+
94+
beforeEach(() => {
95+
manager = new CognitiveMemoryManager();
96+
});
97+
98+
it('exposes null typed-network components when config.typedNetwork is absent', async () => {
99+
await manager.initialize(makeMinimalDeps());
100+
expect(manager.getTypedNetworkStore()).toBeNull();
101+
expect(manager.getTypedNetworkObserver()).toBeNull();
102+
expect(manager.getTypedSpreadingActivation()).toBeNull();
103+
});
104+
105+
it('instantiates typed-network store/observer/activation when configured (full variant)', async () => {
106+
await manager.initialize({
107+
...makeMinimalDeps(),
108+
typedNetwork: {
109+
variant: 'full',
110+
observerLLM: stubLLM,
111+
},
112+
});
113+
expect(manager.getTypedNetworkStore()).toBeInstanceOf(TypedNetworkStore);
114+
expect(manager.getTypedNetworkObserver()).toBeInstanceOf(TypedNetworkObserver);
115+
expect(manager.getTypedSpreadingActivation()).toBeInstanceOf(TypedSpreadingActivation);
116+
});
117+
118+
it('instantiates only store + observer for minimal variant (no spreading activation)', async () => {
119+
await manager.initialize({
120+
...makeMinimalDeps(),
121+
typedNetwork: {
122+
variant: 'minimal',
123+
observerLLM: stubLLM,
124+
},
125+
});
126+
expect(manager.getTypedNetworkStore()).toBeInstanceOf(TypedNetworkStore);
127+
expect(manager.getTypedNetworkObserver()).toBeInstanceOf(TypedNetworkObserver);
128+
// Minimal variant skips spreading activation: bank routing only.
129+
expect(manager.getTypedSpreadingActivation()).toBeNull();
130+
});
131+
});

src/memory/core/config.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,10 +320,52 @@ export interface CognitiveMemoryConfig {
320320
* storage before overwriting with the gist. Enables on-demand rehydration
321321
* via `CognitiveMemoryManager.rehydrate()`.
322322
*
323-
* @default undefined (no archive gist is destructive)
323+
* @default undefined (no archive, gist is destructive)
324324
* @see {@link IMemoryArchive} — the archive contract
325325
*/
326326
archive?: import('../archive/IMemoryArchive.js').IMemoryArchive;
327+
328+
/**
329+
* Stage E: optional Hindsight 4-network typed observer wiring.
330+
*
331+
* When provided, `encode()` additionally extracts typed facts (World /
332+
* Experience / Opinion / Observation banks) via the configured LLM and
333+
* persists them in an in-memory `TypedNetworkStore`. `retrieve()` runs
334+
* typed-graph spreading activation (`'full'` variant only) and produces
335+
* a 4-way RRF fused ranking that's merged into the existing scoring.
336+
*
337+
* Variants:
338+
* - `'minimal'`: bank routing + observer only (no graph traversal at retrieve).
339+
* - `'full'`: minimal + spreading activation per Hindsight Eq. 12 + 4-way RRF.
340+
*
341+
* @default undefined (Stage E disabled, zero-cost no-op)
342+
* @see `packages/agentos-bench/docs/specs/2026-04-26-hindsight-4network-observer-design.md`
343+
*/
344+
typedNetwork?: TypedNetworkRuntimeConfig;
345+
}
346+
347+
/**
348+
* Stage E runtime config for the typed-network observer + retrieval fusion.
349+
*/
350+
export interface TypedNetworkRuntimeConfig {
351+
/**
352+
* Variant selector.
353+
* - `'minimal'`: 4-bank routing only at encode; no spreading activation.
354+
* - `'full'`: minimal + spreading activation + 4-way RRF at retrieve.
355+
*/
356+
variant: 'minimal' | 'full';
357+
/** LLM adapter for the 6-step extraction call. */
358+
observerLLM: import('../retrieval/typed-network/index.js').ITypedExtractionLLM;
359+
/**
360+
* Weight applied to the typed-network ranking when merging into the
361+
* standard cognitive score. 0.0 ignores typed-network; 1.0 uses only
362+
* typed-network. Default 0.5.
363+
*/
364+
weight?: number;
365+
/** Spreading-activation max depth. Default 3. */
366+
maxDepth?: number;
367+
/** Spreading-activation per-hop decay δ. Default 0.5. */
368+
decay?: number;
327369
}
328370

329371
// ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)