Skip to content

Commit 8562c8a

Browse files
committed
feat(memory): add response guidance preamble to MemoryPromptAssembler
Teaches LLMs how to use each memory type differently: semantic as background truth, episodic woven naturally, prospective as action items, relational as tone modulators. Personality-aware variants (structured, narrative, emotional). Section headers include inline usage hints. Preamble skipped when token budget is too small.
1 parent 381902f commit 8562c8a

1 file changed

Lines changed: 63 additions & 5 deletions

File tree

src/memory/core/prompt/MemoryPromptAssembler.ts

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,14 @@ function estimateTokens(text: string): number {
3838
const clamp01 = (v: number | undefined): number =>
3939
v == null ? 0.5 : Math.max(0, Math.min(1, v));
4040

41+
/**
42+
* Select the memory formatting style based on HEXACO personality traits.
43+
* Highest trait wins: conscientiousness → structured, openness → narrative,
44+
* emotionality → emotional. Ties favor structured > narrative > emotional.
45+
*
46+
* @param traits - HEXACO personality traits
47+
* @returns Formatting style for memory trace presentation
48+
*/
4149
function selectFormattingStyle(traits: HexacoTraits): FormattingStyle {
4250
const c = clamp01(traits.conscientiousness);
4351
const o = clamp01(traits.openness);
@@ -49,6 +57,45 @@ function selectFormattingStyle(traits: HexacoTraits): FormattingStyle {
4957
return 'emotional';
5058
}
5159

60+
/**
61+
* Build the memory usage preamble that teaches the LLM how to use
62+
* each memory type differently in its response.
63+
*
64+
* This preamble is critical for memory-aware behavior. Without it,
65+
* LLMs treat all memory sections as flat context and may announce
66+
* facts, list memories, or state relationships explicitly — all of
67+
* which feel unnatural.
68+
*
69+
* Personality variants add style-specific guidance:
70+
* - Structured (high conscientiousness): logical, organized references
71+
* - Narrative (high openness): flowing, associative references
72+
* - Emotional (high emotionality): empathetic, mood-aware references
73+
*
74+
* @param traits - HEXACO traits for personality-aware variant selection
75+
* @returns Formatted preamble string (~200 tokens)
76+
*/
77+
function buildMemoryPreamble(traits: HexacoTraits): string {
78+
const style = selectFormattingStyle(traits);
79+
80+
// Personality-specific guidance on how to reference memories in conversation
81+
const styleGuidance = style === 'structured'
82+
? '\nOrganize your references to past knowledge logically. Connect facts to the current discussion with clear reasoning.'
83+
: style === 'narrative'
84+
? '\nLet memories flow into your responses as stories and associations. Draw creative connections between past and present.'
85+
: '\nLet emotional memories influence your mood and empathy. If you recall a time the user was struggling, let that inform your warmth now.';
86+
87+
return `## How To Use Your Memories
88+
89+
- **Working Memory**: Reference directly — this is your active context.
90+
- **Semantic Recall** (facts): Background truth. Don't announce — let it shape responses naturally.
91+
- **Episodic Memories** (experiences): Weave in naturally. Never list.
92+
- **Prospective Alerts** (reminders): Act on these. Bring up naturally.
93+
- **Relational Signals** (trust/bonds): Modulate TONE, not words. Never state explicitly.
94+
- **Graph Associations** (connected memories): Use for richer context and connection depth.
95+
- **Partial Memories** (feeling-of-knowing): Express uncertainty naturally — "I feel like we talked about..."
96+
${styleGuidance}`;
97+
}
98+
5299
// ---------------------------------------------------------------------------
53100
// Assembler
54101
// ---------------------------------------------------------------------------
@@ -108,6 +155,17 @@ export function assembleMemoryContext(input: MemoryAssemblerInput): AssembledMem
108155
const includedIds: string[] = [];
109156
let totalTokens = 0;
110157

158+
// --- Memory Usage Preamble ---
159+
// Teaches the LLM how to use each memory type differently (~200 tokens).
160+
// Deducted from total budget before section allocation. Skipped when
161+
// budget is too small to fit the preamble meaningfully.
162+
const preamble = buildMemoryPreamble(input.traits);
163+
const preambleTokens = estimateTokens(preamble);
164+
if (budget >= preambleTokens + 100) {
165+
sections.push(preamble);
166+
totalTokens += preambleTokens;
167+
}
168+
111169
// --- Persistent Memory (MEMORY.md) ---
112170
if (input.persistentMemoryText && pmBudget > 0) {
113171
let pmText = input.persistentMemoryText;
@@ -124,7 +182,7 @@ export function assembleMemoryContext(input: MemoryAssemblerInput): AssembledMem
124182
const wmTokens = estimateTokens(wmText);
125183
let wmUsed = 0;
126184
if (wmText && wmTokens <= wmBudget) {
127-
sections.push(`## Active Context\n${wmText}`);
185+
sections.push(`## Active Context (in focus — reference directly)\n${wmText}`);
128186
wmUsed = wmTokens;
129187
totalTokens += wmUsed;
130188
}
@@ -160,7 +218,7 @@ export function assembleMemoryContext(input: MemoryAssemblerInput): AssembledMem
160218
includedIds.push(trace.id);
161219
}
162220
if (semanticLines.length > 0) {
163-
sections.push(`## Relevant Memories\n${semanticLines.join('\n')}`);
221+
sections.push(`## Relevant Memories (facts — use as background truth, don't announce)\n${semanticLines.join('\n')}`);
164222
totalTokens += semanticUsed;
165223
}
166224
}
@@ -178,7 +236,7 @@ export function assembleMemoryContext(input: MemoryAssemblerInput): AssembledMem
178236
includedIds.push(trace.id);
179237
}
180238
if (episodicLines.length > 0) {
181-
sections.push(`## Recent Experiences\n${episodicLines.join('\n')}`);
239+
sections.push(`## Recent Experiences (events — weave in naturally, never list)\n${episodicLines.join('\n')}`);
182240
totalTokens += episodicUsed;
183241
}
184242
}
@@ -194,7 +252,7 @@ export function assembleMemoryContext(input: MemoryAssemblerInput): AssembledMem
194252
prospectiveUsed += tokens;
195253
}
196254
if (prospectiveLines.length > 0) {
197-
sections.push(`## Reminders\n${prospectiveLines.join('\n')}`);
255+
sections.push(`## Reminders (act on these — bring up naturally)\n${prospectiveLines.join('\n')}`);
198256
totalTokens += prospectiveUsed;
199257
}
200258
}
@@ -210,7 +268,7 @@ export function assembleMemoryContext(input: MemoryAssemblerInput): AssembledMem
210268
graphUsed += tokens;
211269
}
212270
if (graphLines.length > 0) {
213-
sections.push(`## Related Context\n${graphLines.join('\n')}`);
271+
sections.push(`## Related Context (connected memories — use for depth)\n${graphLines.join('\n')}`);
214272
totalTokens += graphUsed;
215273
}
216274
}

0 commit comments

Comments
 (0)