From cb59636cd92b8f5b53f0532bb4d7718107bab037 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 8 Mar 2026 22:04:52 +0000 Subject: [PATCH] feat(memory): add shared history context to social narration prompts When narrating social interactions, the LLM now receives up to 3 shared history events between the two NPCs, enabling more contextual narrations that reference past encounters. Co-Authored-By: Claude Opus 4.6 --- server/src/llm/__tests__/narrationService.test.ts | 1 + server/src/llm/narrationService.ts | 2 ++ server/src/llm/templates.ts | 1 + server/src/systems/narrationEmitter.ts | 15 +++++++++++++++ 4 files changed, 19 insertions(+) diff --git a/server/src/llm/__tests__/narrationService.test.ts b/server/src/llm/__tests__/narrationService.test.ts index df92281..7dbd415 100644 --- a/server/src/llm/__tests__/narrationService.test.ts +++ b/server/src/llm/__tests__/narrationService.test.ts @@ -205,6 +205,7 @@ describe('NarrationService', () => { outcome: 'negative', relationship: 'rival', sentiment: '-0.3', + recentHistory: '', }); }); }); diff --git a/server/src/llm/narrationService.ts b/server/src/llm/narrationService.ts index 764688e..d1ccee4 100644 --- a/server/src/llm/narrationService.ts +++ b/server/src/llm/narrationService.ts @@ -13,6 +13,7 @@ export interface InteractionRecord { npc1Personality?: string; npc2Personality?: string; sentiment?: number; + sharedHistory?: string; } export interface NarrationService { @@ -69,6 +70,7 @@ export function createNarrationService(llmService: LlmService): NarrationService outcome: record.outcome, relationship: record.classification, sentiment: String(record.sentiment ?? ''), + recentHistory: record.sharedHistory ?? '', }; llmService.generate('socialNarration', variables).then((result) => { diff --git a/server/src/llm/templates.ts b/server/src/llm/templates.ts index 2fcaac9..948e0fc 100644 --- a/server/src/llm/templates.ts +++ b/server/src/llm/templates.ts @@ -27,6 +27,7 @@ export const templates: Record = { '{{npc1Name}} ({{npc1Personality}}) had a {{outcome}} interaction with ' + '{{npc2Name}} ({{npc2Personality}}). ' + 'They are currently {{relationship}} (sentiment: {{sentiment}}/100).\n' + + 'Their recent shared history:\n{{recentHistory}}\n' + 'Describe what happened in one vivid sentence.', }, diff --git a/server/src/systems/narrationEmitter.ts b/server/src/systems/narrationEmitter.ts index 8904f3e..0f0795b 100644 --- a/server/src/systems/narrationEmitter.ts +++ b/server/src/systems/narrationEmitter.ts @@ -5,11 +5,13 @@ import type { World } from '../ecs/World.js'; import type { NarrationService } from '../llm/narrationService.js'; import { formatStatsForPrompt } from '../llm/backstoryGenerator.js'; import { classify } from './relationshipHelpers.js'; +import type { EventMemoryService } from '../llm/eventMemoryService.js'; export function narrationEmitter( world: World, narrationService: NarrationService, followedEntityIds: Set, + eventMemoryService?: EventMemoryService, ): void { const entities = world.query('socialState', 'name', 'stats'); const processed = new Set(); @@ -69,6 +71,18 @@ export function narrationEmitter( const [firstName, secondName] = entity < partnerId ? [name1, name2] : [name2, name1]; const [firstStats, secondStats] = entity < partnerId ? [stats1, stats2] : [stats2, stats1]; + let sharedHistory = ''; + if (eventMemoryService) { + const events = eventMemoryService.selectForPrompt(firstId, { + maxCount: 3, + preferEntityId: secondId, + preferTypes: ['social_positive', 'social_negative', 'tier_change'], + }); + if (events.length > 0) { + sharedHistory = events.map(e => `- ${e.detail}`).join('\n'); + } + } + narrationService.recordInteraction({ tick: social.lastOutcome.tick, entityIds: [firstId, secondId], @@ -80,6 +94,7 @@ export function narrationEmitter( npc1Personality: firstStats ? formatStatsForPrompt(firstStats) : undefined, npc2Personality: secondStats ? formatStatsForPrompt(secondStats) : undefined, sentiment: relValue, + sharedHistory, }); } }