@@ -38,6 +38,14 @@ function estimateTokens(text: string): number {
3838const 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+ */
4149function 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