From 2c56d72b5412a7f49b6fb3958ab76e083a174633 Mon Sep 17 00:00:00 2001 From: fepu08 Date: Sat, 20 Dec 2025 16:13:28 +0100 Subject: [PATCH 1/2] improved summarization of the db --- src/memory.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/memory.ts b/src/memory.ts index acda0f2..7f3edf9 100644 --- a/src/memory.ts +++ b/src/memory.ts @@ -41,9 +41,14 @@ export const addMessages = async (messages: AIMessage[]) => { db.data.messages.push(...messages.map(addMetadata)) if (db.data.messages.length >= 10) { - const oldestMessages = db.data.messages.slice(0, 5).map(removeMetadata) - const summary = await summarizeMessages(oldestMessages) - db.data.summary = summary + let lastNum = -5 + if (db.data.messages[db.data.messages.length + lastNum].role === 'tool') { + lastNum++ + } + const oldestMessages = db.data.messages + .slice(0, lastNum) + .map(removeMetadata) + db.data.summary = await summarizeMessages(oldestMessages) } await db.write() @@ -72,7 +77,7 @@ export const getSummary = async () => { export const saveToolResponse = async ( toolCallId: string, - toolResponse: string + toolResponse: string, ) => { return addMessages([ { From c4e1be27df738fc2ea98e21cab3f7e719d3b62e9 Mon Sep 17 00:00:00 2001 From: fepu08 Date: Sat, 20 Dec 2025 16:20:18 +0100 Subject: [PATCH 2/2] fix logic --- src/memory.ts | 80 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 54 insertions(+), 26 deletions(-) diff --git a/src/memory.ts b/src/memory.ts index 7f3edf9..5e4708e 100644 --- a/src/memory.ts +++ b/src/memory.ts @@ -3,6 +3,8 @@ import type { AIMessage } from '../types' import { v4 as uuidv4 } from 'uuid' import { summarizeMessages } from './llm' +const WINDOW_SIZE = 5 + export type MessageWithMetadata = AIMessage & { id: string createdAt: string @@ -13,6 +15,11 @@ type Data = { summary: string } +const defaultData: Data = { + messages: [], + summary: '', +} + export const addMetadata = (message: AIMessage) => { return { ...message, @@ -26,29 +33,39 @@ export const removeMetadata = (message: MessageWithMetadata) => { return rest } -const defaultData: Data = { - messages: [], - summary: '', -} - export const getDb = async () => { - const db = await JSONFilePreset('db.json', defaultData) - return db + return await JSONFilePreset('db.json', defaultData) } -export const addMessages = async (messages: AIMessage[]) => { +export const addMessages = async (newMessages: AIMessage[]) => { const db = await getDb() - db.data.messages.push(...messages.map(addMetadata)) + db.data.messages.push(...newMessages.map(addMetadata)) + + const messages = db.data.messages + const len = messages.length + + // We only have a "previous window of size N" to summarize once we have at least 2N messages. + if (len >= 2 * WINDOW_SIZE) { + // Tail = raw messages we will return as-is (N, or N+1 if tool-boundary adjustment kicks in) + const tailStart = computeTailStartIndex(messages, WINDOW_SIZE) - if (db.data.messages.length >= 10) { - let lastNum = -5 - if (db.data.messages[db.data.messages.length + lastNum].role === 'tool') { - lastNum++ + // Summary window is the N messages immediately before the raw tail. + let summaryStart = tailStart - WINDOW_SIZE + let summaryEndExclusive = tailStart + + // If the summary window starts with a tool response, shift start left by 1 + // so we don't begin a summarized chunk with a tool message detached from its context. + if (summaryStart > 0 && messages[summaryStart]?.role === 'tool') { + summaryStart -= 1 } - const oldestMessages = db.data.messages - .slice(0, lastNum) + + summaryStart = Math.max(0, summaryStart) + + const messagesToSummarize = messages + .slice(summaryStart, summaryEndExclusive) .map(removeMetadata) - db.data.summary = await summarizeMessages(oldestMessages) + + db.data.summary = await summarizeMessages(messagesToSummarize) } await db.write() @@ -57,17 +74,9 @@ export const addMessages = async (messages: AIMessage[]) => { export const getMessages = async () => { const db = await getDb() const messages = db.data.messages.map(removeMetadata) - const lastFive = messages.slice(-5) - - // If first message is a tool response, get one more message before it - if (lastFive[0]?.role === 'tool') { - const sixthMessage = messages[messages.length - 6] - if (sixthMessage) { - return [...[sixthMessage], ...lastFive] - } - } - return lastFive + const tailStart = computeTailStartIndex(messages, WINDOW_SIZE) + return messages.slice(tailStart) } export const getSummary = async () => { @@ -87,3 +96,22 @@ export const saveToolResponse = async ( }, ]) } + +function computeTailStartIndex( + messages: AIMessage[], + keepLastN: number, +): number { + const len = messages.length + if (len <= keepLastN) return 0 + + // Nominally keep the last N raw messages + let tailStart = len - keepLastN + + // If the kept raw tail starts with a tool response, shift tailStart left by 1 + // so the tool response is not the first raw item (we include the message before it). + if (messages[tailStart]?.role === 'tool') { + tailStart = Math.max(0, tailStart - 1) + } + + return tailStart +}