Skip to content

Commit a604d86

Browse files
committed
feat(memory): Stage E Phase 4.2 - encode() routes through typed-network observer
CognitiveMemoryManager.encode() now invokes TypedNetworkObserver.extract() after the standard trace creation when typed-network is configured. Resulting facts are namespaced by the parent trace ID and persisted into the manager's TypedNetworkStore. Best-effort: extraction failures are logged to stderr but do not abort the encode call. The standard trace lifecycle is unaffected when typed-network is not configured (zero-cost no-op).
1 parent cac63c2 commit a604d86

3 files changed

Lines changed: 83 additions & 1 deletion

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@framers/agentos",
3-
"version": "0.3.2",
3+
"version": "0.3.3",
44
"description": "Modular AgentOS orchestration library",
55
"type": "module",
66
"main": "dist/index.js",

src/memory/CognitiveMemoryManager.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,24 @@ export class CognitiveMemoryManager implements ICognitiveMemoryManager {
598598
});
599599
}
600600

601+
// --- Stage E: typed-network extraction (optional) ---
602+
// Routes the encoded content through the LLM observer to produce 0+
603+
// typed facts (W/E/O/S banks). Facts are namespaced by the parent
604+
// trace ID so they can be cross-referenced at retrieval time. Best
605+
// effort: extraction failures are logged via stderr but do not abort
606+
// the encode (the trace is already persisted at this point).
607+
if (this.typedNetworkObserver && this.typedNetworkStore) {
608+
try {
609+
const facts = await this.typedNetworkObserver.extract(input, trace.id);
610+
for (const fact of facts) {
611+
this.typedNetworkStore.addFact(fact);
612+
}
613+
} catch (err) {
614+
const msg = err instanceof Error ? err.message : String(err);
615+
process.stderr.write(`[CognitiveMemoryManager.encode typed-network extraction failed] ${msg}\n`);
616+
}
617+
}
618+
601619
return trace;
602620
}
603621

src/memory/__tests__/CognitiveMemoryManager.typedNetwork.test.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,4 +128,68 @@ describe('CognitiveMemoryManager: typedNetwork wiring', () => {
128128
// Minimal variant skips spreading activation: bank routing only.
129129
expect(manager.getTypedSpreadingActivation()).toBeNull();
130130
});
131+
132+
it('encode() extracts typed facts via observer and persists into the typed-network store', async () => {
133+
// The stub LLM returns 2 typed facts: one WORLD, one OPINION.
134+
const extractingLLM: ITypedExtractionLLM = {
135+
invoke: async () =>
136+
JSON.stringify({
137+
facts: [
138+
{
139+
text: 'Berlin is in Germany',
140+
bank: 'WORLD',
141+
temporal: { mention: '2026-04-25T12:00:00Z' },
142+
participants: [],
143+
reasoning_markers: [],
144+
entities: ['Berlin', 'Germany'],
145+
confidence: 1.0,
146+
},
147+
{
148+
text: 'The user prefers TypeScript',
149+
bank: 'OPINION',
150+
temporal: { mention: '2026-04-25T12:00:00Z' },
151+
participants: [{ name: 'user', role: 'subject' }],
152+
reasoning_markers: ['because'],
153+
entities: ['TypeScript'],
154+
confidence: 0.7,
155+
},
156+
],
157+
}),
158+
};
159+
await manager.initialize({
160+
...makeMinimalDeps(),
161+
typedNetwork: { variant: 'full', observerLLM: extractingLLM },
162+
});
163+
164+
// Encode triggers extraction.
165+
const trace = await manager.encode(
166+
'Berlin is in Germany. I think the user prefers TypeScript because it is statically typed.',
167+
{ valence: 0, arousal: 0.3, dominance: 0 },
168+
'neutral',
169+
);
170+
171+
const store = manager.getTypedNetworkStore()!;
172+
expect(store.getBank('WORLD').size).toBe(1);
173+
expect(store.getBank('OPINION').size).toBe(1);
174+
expect(store.getBank('EXPERIENCE').size).toBe(0);
175+
expect(store.getBank('OBSERVATION').size).toBe(0);
176+
177+
// Trace ID should be the namespace anchor for the extracted facts so the
178+
// facts are linked back to the parent encoding event.
179+
const expectedFactId = `${trace.id}-fact-0`;
180+
expect(store.getFact(expectedFactId)).toBeDefined();
181+
expect(store.getFact(expectedFactId)?.text).toBe('Berlin is in Germany');
182+
});
183+
184+
it('encode() is a no-op (no extraction call) when typedNetwork not configured', async () => {
185+
await manager.initialize(makeMinimalDeps());
186+
// No typedNetwork: extraction must never be called.
187+
await manager.encode(
188+
'Sample text',
189+
{ valence: 0, arousal: 0.3, dominance: 0 },
190+
'neutral',
191+
);
192+
// No store to inspect; verifying via absence of error + null state above.
193+
expect(manager.getTypedNetworkStore()).toBeNull();
194+
});
131195
});

0 commit comments

Comments
 (0)