From f8977a14c6dadd145a06d773c671872dc05b1e04 Mon Sep 17 00:00:00 2001 From: rhit-lid2 <59123825+NeoLi00@users.noreply.github.com> Date: Sun, 19 Jul 2026 16:08:55 +0530 Subject: [PATCH] refactor(gui): Use structured logging over string parsing --- apps/gui/src/components/play/PromptModal.tsx | 42 ++++++---- apps/gui/src/lib/simulation-types.ts | 5 ++ apps/gui/src/lib/simulation/turn-executor.ts | 14 ++-- packages/actor/src/actor-prompt-builder.ts | 81 +++++++++++++------- packages/actor/src/actor.ts | 12 ++- packages/scenario/tests/talking-room.test.ts | 10 ++- 6 files changed, 112 insertions(+), 52 deletions(-) diff --git a/apps/gui/src/components/play/PromptModal.tsx b/apps/gui/src/components/play/PromptModal.tsx index eb46f93..3d2c1fe 100644 --- a/apps/gui/src/components/play/PromptModal.tsx +++ b/apps/gui/src/components/play/PromptModal.tsx @@ -29,24 +29,37 @@ export function PromptModal({ entry, onClose }: PromptModalProps) { systemPrompt: string, userContext: string, inputTokens: number, + sectionsObj?: { + worldInfo: string; + memoryLedger: string; + cognitiveBuffer: string; + }, ) => { - const recentHeader = "=== COGNITIVE BUFFER ==="; - const ledgerHeader = "=== MEMORY LEDGER ==="; - - const recentIdx = userContext.indexOf(recentHeader); - let worldStr = userContext; + let worldStr = ""; let recentStr = ""; let ledgerStr = ""; - if (recentIdx !== -1) { - worldStr = userContext.substring(0, recentIdx).trim(); - const rest = userContext.substring(recentIdx).trim(); - const ledgerIdx = rest.indexOf(ledgerHeader); - if (ledgerIdx !== -1) { - recentStr = rest.substring(0, ledgerIdx).trim(); - ledgerStr = rest.substring(ledgerIdx).trim(); - } else { - recentStr = rest; + if (sectionsObj) { + worldStr = sectionsObj.worldInfo; + recentStr = sectionsObj.cognitiveBuffer; + ledgerStr = sectionsObj.memoryLedger; + } else { + const recentHeader = "=== COGNITIVE BUFFER ==="; + const ledgerHeader = "=== MEMORY LEDGER ==="; + + const recentIdx = userContext.indexOf(recentHeader); + worldStr = userContext; + + if (recentIdx !== -1) { + worldStr = userContext.substring(0, recentIdx).trim(); + const rest = userContext.substring(recentIdx).trim(); + const ledgerIdx = rest.indexOf(ledgerHeader); + if (ledgerIdx !== -1) { + recentStr = rest.substring(0, ledgerIdx).trim(); + ledgerStr = rest.substring(ledgerIdx).trim(); + } else { + recentStr = rest; + } } } @@ -144,6 +157,7 @@ export function PromptModal({ entry, onClose }: PromptModalProps) { entry.rawPrompt.systemPrompt, entry.rawPrompt.userContext, entry.usage.inputTokens, + entry.rawPrompt.sections, ) : null; const decoderBreakdown = diff --git a/apps/gui/src/lib/simulation-types.ts b/apps/gui/src/lib/simulation-types.ts index 3f201e0..11fa8ba 100644 --- a/apps/gui/src/lib/simulation-types.ts +++ b/apps/gui/src/lib/simulation-types.ts @@ -20,6 +20,11 @@ export interface LogEntry { rawPrompt?: { systemPrompt: string; userContext: string; + sections?: { + worldInfo: string; + memoryLedger: string; + cognitiveBuffer: string; + }; }; usage?: { inputTokens: number; diff --git a/apps/gui/src/lib/simulation/turn-executor.ts b/apps/gui/src/lib/simulation/turn-executor.ts index 57f6445..e489cd4 100644 --- a/apps/gui/src/lib/simulation/turn-executor.ts +++ b/apps/gui/src/lib/simulation/turn-executor.ts @@ -165,6 +165,11 @@ export async function processNpcTurn( narrativeProse: result.narrativeProse, intents: [], timestamp: worldState.clock.get().toISOString(), + rawPrompt: { + systemPrompt: result.systemPrompt || "", + userContext: result.userContext || "", + sections: result.promptComponents, + }, }; if ( @@ -175,10 +180,6 @@ export async function processNpcTurn( session.actorProvider.lastCalls[ session.actorProvider.lastCalls.length - 1 ]; - entry.rawPrompt = { - systemPrompt: actorCall.systemPrompt, - userContext: actorCall.userContext, - }; entry.usage = actorCall.usage; } @@ -243,8 +244,9 @@ export async function executePlayerAction( intents: [], timestamp: worldState.clock.get().toISOString(), rawPrompt: { - systemPrompt: ctx.systemPrompt, - userContext: ctx.userContext, + systemPrompt: result.systemPrompt || ctx.systemPrompt, + userContext: result.userContext || ctx.userContext, + sections: result.promptComponents, }, }; diff --git a/packages/actor/src/actor-prompt-builder.ts b/packages/actor/src/actor-prompt-builder.ts index e943794..e94eae7 100644 --- a/packages/actor/src/actor-prompt-builder.ts +++ b/packages/actor/src/actor-prompt-builder.ts @@ -54,16 +54,27 @@ export class ActorPromptBuilder { private ledgerLimit = 5, ) {} + /** + * Assembles the system prompt and user context for a given entity. + */ /** * Assembles the system prompt and user context for a given entity. */ build( worldState: WorldState, entity: Entity, - ): { systemPrompt: string; userContext: string } { + ): { + systemPrompt: string; + userContext: string; + sections: { + worldInfo: string; + memoryLedger: string; + cognitiveBuffer: string; + }; + } { const systemPrompt = this.buildSystemPrompt(); - const userContext = this.buildUserContext(worldState, entity); - return { systemPrompt, userContext }; + const { userContext, sections } = this.buildUserContext(worldState, entity); + return { systemPrompt, userContext, sections }; } private buildSystemPrompt(): string { @@ -84,23 +95,28 @@ Guidelines: - Stay strictly within what your character knows. Do not invent knowledge that doesn't exist or act on it. - You are limited by just your memory. If your memory is limited, then that's all you can remember. If you do make stuff up then that's lying. Which is allowed, but remember that you're lying - Only describe your character's own actions, spoken words, and internal reactions. Do NOT narrate the environment or your surroundings, or other characters' actions. +- Be clear about who or what you are interacting with. ". `.trim(); } - private buildUserContext(worldState: WorldState, entity: Entity): string { - const sections: string[] = []; + private buildUserContext( + worldState: WorldState, + entity: Entity, + ): { + userContext: string; + sections: { + worldInfo: string; + memoryLedger: string; + cognitiveBuffer: string; + }; + } { const now = worldState.clock.get(); - // --- Subjective present time --- - sections.push( - `=== CURRENT MOMENT ===\nIt is ${now.toISOString()} right now.`, - ); - - // --- Subjective world state (self + perceived entities + co-location) --- - sections.push( - `=== THE WORLD AS YOU PERCEIVE IT ===\n${serializeSubjectiveWorldState(worldState, entity.id)}`, - ); + // --- Subjective present time & world state --- + const momentStr = `=== CURRENT MOMENT ===\nIt is ${now.toISOString()} right now.`; + const perceivedStr = `=== THE WORLD AS YOU PERCEIVE IT ===\n${serializeSubjectiveWorldState(worldState, entity.id)}`; + const worldInfo = `${momentStr}\n\n${perceivedStr}`; // Fetch recent buffer entries once let recentEntries: BufferEntry[] = []; @@ -112,16 +128,6 @@ Guidelines: } } - // --- Cognitive Buffer --- - const memorySection = this.buildCognitiveBufferSection( - entity, - recentEntries, - now, - ); - if (memorySection) { - sections.push(memorySection); - } - // --- Recalled Memory Ledger --- const ledgerSection = this.buildLedgerSection( worldState, @@ -129,11 +135,30 @@ Guidelines: recentEntries, now, ); - if (ledgerSection) { - sections.push(ledgerSection); - } + const memoryLedger = ledgerSection || ""; - return sections.join("\n\n"); + // --- Cognitive Buffer --- + const memorySection = this.buildCognitiveBufferSection( + entity, + recentEntries, + now, + ); + const cognitiveBuffer = memorySection || ""; + + // Assemble final user context + const parts: string[] = [worldInfo]; + if (memoryLedger) parts.push(memoryLedger); + if (cognitiveBuffer) parts.push(cognitiveBuffer); + const userContext = parts.join("\n\n"); + + return { + userContext, + sections: { + worldInfo, + memoryLedger, + cognitiveBuffer, + }, + }; } private buildCognitiveBufferSection( diff --git a/packages/actor/src/actor.ts b/packages/actor/src/actor.ts index af05830..80a29c5 100644 --- a/packages/actor/src/actor.ts +++ b/packages/actor/src/actor.ts @@ -54,6 +54,13 @@ export interface ActorTurnResult { narrativeProse: string; /** The decoded intent sequence (split/classified from the prose). */ intents: IntentSequence; + systemPrompt?: string; + userContext?: string; + promptComponents?: { + worldInfo: string; + memoryLedger: string; + cognitiveBuffer: string; + }; } /** @@ -116,7 +123,7 @@ export class ActorAgent { ); } - const { systemPrompt, userContext } = this.promptBuilder.build( + const { systemPrompt, userContext, sections } = this.promptBuilder.build( worldState, entity, ); @@ -145,6 +152,9 @@ export class ActorAgent { return { narrativeProse, intents, + systemPrompt, + userContext, + promptComponents: sections, }; } } diff --git a/packages/scenario/tests/talking-room.test.ts b/packages/scenario/tests/talking-room.test.ts index 7837893..88a5ed7 100644 --- a/packages/scenario/tests/talking-room.test.ts +++ b/packages/scenario/tests/talking-room.test.ts @@ -128,14 +128,18 @@ describe("Talking Room Demo Scenario Test (Tier 1)", () => { // 7. Assert initial pre-seeded memories const alphaMemories = bufferRepo.listForOwner(alphaId); - expect(alphaMemories).toHaveLength(2); + expect(alphaMemories).toHaveLength(3); expect(alphaMemories[0].id).toBe("ab3f29d2-cf11-4111-9a99-b13c126d123e"); expect(alphaMemories[0].intent.type).toBe("monologue"); expect(alphaMemories[0].intent.content).toContain("jail"); - expect(alphaMemories[1].id).toBe("zz3f29d2-as11-9811-9a99-b13c126d123e"); + expect(alphaMemories[1].id).toBe("10ak29d2-as11-9811-9a99-b13c126d123e"); expect(alphaMemories[1].intent.type).toBe("action"); - expect(alphaMemories[1].intent.content).toContain("sleep"); + expect(alphaMemories[1].intent.content).toContain("another man"); + + expect(alphaMemories[2].id).toBe("zz3f29d2-as11-9811-9a99-b13c126d123e"); + expect(alphaMemories[2].intent.type).toBe("action"); + expect(alphaMemories[2].intent.content).toContain("sleep"); const betaMemories = bufferRepo.listForOwner(betaId); expect(betaMemories).toHaveLength(1);