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 <noreply@anthropic.com>
This commit is contained in:
root
2026-03-08 22:04:52 +00:00
parent 1db8935de3
commit cb59636cd9
4 changed files with 19 additions and 0 deletions
@@ -205,6 +205,7 @@ describe('NarrationService', () => {
outcome: 'negative',
relationship: 'rival',
sentiment: '-0.3',
recentHistory: '',
});
});
});
+2
View File
@@ -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) => {
+1
View File
@@ -27,6 +27,7 @@ export const templates: Record<string, PromptTemplate> = {
'{{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.',
},
+15
View File
@@ -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<EntityId>,
eventMemoryService?: EventMemoryService,
): void {
const entities = world.query('socialState', 'name', 'stats');
const processed = new Set<string>();
@@ -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,
});
}
}