fix(memory-lancedb): improve autoCapture with turn-by-turn processing
Two fixes for autoCapture reliability: 1. Strip injected memory context before capture filtering - autoRecall prepends <relevant-memories> to user messages - This was causing shouldCapture() to skip all user messages - Now strips the context before evaluating capture criteria 2. Process only current turn instead of full history - Previously scanned all messages and picked arbitrary 3 - In long sessions (100+ messages), recent content was missed - Now captures only last user + last assistant message - Previous turns were already captured when they occurred These fixes ensure that autoCapture reliably stores each conversation turn as it happens, rather than missing recent exchanges.
This commit is contained in:
parent
01e0d3a320
commit
068e9085b1
@ -185,18 +185,28 @@ const MEMORY_TRIGGERS = [
|
||||
/always|never|important/i,
|
||||
];
|
||||
|
||||
// Strip injected memory context from message text before processing
|
||||
function stripMemoryContext(text: string): string {
|
||||
const memoryBlockEnd = text.indexOf("</relevant-memories>");
|
||||
if (memoryBlockEnd !== -1) {
|
||||
return text.slice(memoryBlockEnd + "</relevant-memories>".length).trim();
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
function shouldCapture(text: string): boolean {
|
||||
if (text.length < 10 || text.length > 500) return false;
|
||||
// Skip injected context from memory recall
|
||||
if (text.includes("<relevant-memories>")) return false;
|
||||
// Skip system-generated content
|
||||
if (text.startsWith("<") && text.includes("</")) return false;
|
||||
// Strip any injected memory context first
|
||||
const cleanText = stripMemoryContext(text);
|
||||
|
||||
if (cleanText.length < 10 || cleanText.length > 500) return false;
|
||||
// Skip system-generated content (pure XML)
|
||||
if (cleanText.startsWith("<") && cleanText.includes("</")) return false;
|
||||
// Skip agent summary responses (contain markdown formatting)
|
||||
if (text.includes("**") && text.includes("\n-")) return false;
|
||||
if (cleanText.includes("**") && cleanText.includes("\n-")) return false;
|
||||
// Skip emoji-heavy responses (likely agent output)
|
||||
const emojiCount = (text.match(/[\u{1F300}-\u{1F9FF}]/gu) || []).length;
|
||||
const emojiCount = (cleanText.match(/[\u{1F300}-\u{1F9FF}]/gu) || []).length;
|
||||
if (emojiCount > 3) return false;
|
||||
return MEMORY_TRIGGERS.some((r) => r.test(text));
|
||||
return MEMORY_TRIGGERS.some((r) => r.test(cleanText));
|
||||
}
|
||||
|
||||
function detectCategory(text: string): MemoryCategory {
|
||||
@ -491,7 +501,8 @@ const memoryPlugin = {
|
||||
});
|
||||
}
|
||||
|
||||
// Auto-capture: analyze and store important information after agent ends
|
||||
// Auto-capture: store important information from the current turn
|
||||
// Only processes the last user message and last assistant message (not full history)
|
||||
if (cfg.autoCapture) {
|
||||
api.on("agent_end", async (event) => {
|
||||
if (!event.success || !event.messages || event.messages.length === 0) {
|
||||
@ -499,26 +510,9 @@ const memoryPlugin = {
|
||||
}
|
||||
|
||||
try {
|
||||
// Extract text content from messages (handling unknown[] type)
|
||||
const texts: string[] = [];
|
||||
for (const msg of event.messages) {
|
||||
// Type guard for message object
|
||||
if (!msg || typeof msg !== "object") continue;
|
||||
const msgObj = msg as Record<string, unknown>;
|
||||
|
||||
// Only process user and assistant messages
|
||||
const role = msgObj.role;
|
||||
if (role !== "user" && role !== "assistant") continue;
|
||||
|
||||
const content = msgObj.content;
|
||||
|
||||
// Handle string content directly
|
||||
if (typeof content === "string") {
|
||||
texts.push(content);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle array content (content blocks)
|
||||
// Helper to extract text from message content
|
||||
const extractText = (content: unknown): string | null => {
|
||||
if (typeof content === "string") return content;
|
||||
if (Array.isArray(content)) {
|
||||
for (const block of content) {
|
||||
if (
|
||||
@ -529,21 +523,46 @@ const memoryPlugin = {
|
||||
"text" in block &&
|
||||
typeof (block as Record<string, unknown>).text === "string"
|
||||
) {
|
||||
texts.push((block as Record<string, unknown>).text as string);
|
||||
return (block as Record<string, unknown>).text as string;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// Find the LAST user message and LAST assistant message (current turn only)
|
||||
// Previous turns were already captured when they happened
|
||||
let lastUserText: string | null = null;
|
||||
let lastAssistantText: string | null = null;
|
||||
|
||||
for (const msg of event.messages) {
|
||||
if (!msg || typeof msg !== "object") continue;
|
||||
const msgObj = msg as Record<string, unknown>;
|
||||
const role = msgObj.role;
|
||||
const text = extractText(msgObj.content);
|
||||
|
||||
if (role === "user" && text) lastUserText = text;
|
||||
if (role === "assistant" && text) lastAssistantText = text;
|
||||
}
|
||||
|
||||
// Collect texts from this turn only
|
||||
const turnTexts: string[] = [];
|
||||
if (lastUserText) turnTexts.push(lastUserText);
|
||||
if (lastAssistantText) turnTexts.push(lastAssistantText);
|
||||
|
||||
// Filter for capturable content
|
||||
const toCapture = texts.filter(
|
||||
const toCapture = turnTexts.filter(
|
||||
(text) => text && shouldCapture(text),
|
||||
);
|
||||
if (toCapture.length === 0) return;
|
||||
|
||||
// Store each capturable piece (limit to 3 per conversation)
|
||||
// Store each capturable piece from this turn
|
||||
let stored = 0;
|
||||
for (const text of toCapture.slice(0, 3)) {
|
||||
for (const rawText of toCapture) {
|
||||
// Clean the text before storing (strip injected memory context)
|
||||
const text = stripMemoryContext(rawText);
|
||||
if (text.length < 10) continue; // Re-check length after cleaning
|
||||
|
||||
const category = detectCategory(text);
|
||||
const vector = await embeddings.embed(text);
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user