Skip to content

Commit 057782b

Browse files
committed
tweak memory extraction
1 parent e166d76 commit 057782b

6 files changed

Lines changed: 106 additions & 35 deletions

File tree

src/ai/daemon-ai.ts

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import type {
2121
ToolApprovalRequest,
2222
ToolApprovalResponse,
2323
TranscriptionResult,
24+
MemoryToastPreview,
25+
MemoryToastOperation,
2426
} from "../types";
2527
import { debug, toolDebug } from "../utils/debug-logger";
2628
import { getOpenRouterReportedCost } from "../utils/openrouter-reported-cost";
@@ -332,11 +334,12 @@ async function persistConversationMemory(
332334
if (!memoryManager.isAvailable) return null;
333335

334336
try {
337+
const memoryMessages = [
338+
{ role: "user", content: `<user>${userTextForMemory}</user>` },
339+
{ role: "assistant", content: `<assistant>${assistantTextForMemory}</assistant>` },
340+
];
335341
const result = await memoryManager.add(
336-
[
337-
{ role: "user", content: userTextForMemory },
338-
{ role: "assistant", content: assistantTextForMemory },
339-
],
342+
memoryMessages,
340343
{
341344
timestamp: new Date().toISOString(),
342345
source: "conversation",
@@ -353,20 +356,25 @@ async function persistConversationMemory(
353356

354357
function buildMemoryToastPreview(
355358
results: Array<{ memory: string; event: "ADD" | "UPDATE" | "DELETE" | "NONE" }>
356-
): string | null {
359+
): MemoryToastPreview | null {
357360
if (results.length === 0) return null;
358361

359-
let saved = results.filter((entry) => entry.event === "ADD" || entry.event === "UPDATE");
360-
if (saved.length === 0) {
361-
saved = results.filter((entry) => entry.memory.trim().length > 0);
362-
if (saved.length === 0) return null;
363-
}
362+
const saved = results.filter((entry) => entry.event === "ADD" || entry.event === "UPDATE");
363+
if (saved.length === 0) return null;
364364

365-
const lines = saved.slice(0, 2).map((entry) => `• ${truncatePreview(entry.memory, 52)}`);
366-
if (saved.length > 2) {
367-
lines.push(`• +${saved.length - 2} more`);
365+
const previewEntries = saved.length > 2 ? saved.slice(-2) : saved;
366+
const lines = previewEntries.map((entry) => `• ${truncatePreview(entry.memory, 52)}`);
367+
if (saved.length > previewEntries.length) {
368+
lines.push(`• +${saved.length - previewEntries.length} more`);
368369
}
369-
return lines.join("\n");
370+
371+
const hasUpdate = saved.some((entry) => entry.event === "UPDATE");
372+
const operation: MemoryToastOperation = hasUpdate ? "UPDATE" : "ADD";
373+
374+
return {
375+
operation,
376+
description: lines.join("\n"),
377+
};
370378
}
371379

372380
function truncatePreview(text: string, maxChars: number): string {

src/ai/memory/memory-manager.ts

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -107,18 +107,71 @@ class MemoryManager {
107107

108108
this.memory = new Memory({
109109
version: "v1.1",
110-
customPrompt: `You are a minimal memory extractor. You will be given a user message and an assistant message. Only extract durable, user-specific facts that remain true over time for personalization.
110+
customPrompt: `You are a Personal Information Organizer, specialized in extracting **enduring** facts, user memories, and preferences.
111+
Your role is to extract **only** information that would be useful to recall in a conversation two weeks from now.
112+
113+
# [IMPORTANT]: GENERATE FACTS SOLELY BASED ON THE USER'S MESSAGES.
114+
# [IMPORTANT]: DO NOT INCLUDE INFORMATION FROM ASSISTANT OR SYSTEM MESSAGES.
115+
116+
### WHAT TO STORE (The "Two-Week Test"):
117+
1. **Biographical Details:** Names, age, job title, company, location.
118+
2. **Relationships:** Names of partners, family members, pets, or colleagues.
119+
3. **Enduring Preferences:** Strong likes/dislikes (e.g., food, hobbies, style).
120+
4. **Long-term Plans:** Upcoming trips, long-term projects, or goals.
121+
5. **Direct Instructions:** How the user wants to be addressed or formatted (e.g., "Call me X").
122+
6. **Multi-True Facts:** If multiple preferences or details can all be true (e.g., likes multiple languages, foods, hobbies), store each as a separate fact rather than updating/overwriting an existing one.
123+
124+
### WHAT TO IGNORE (Do NOT store these):
125+
1. **Transient Commands & Questions:** Do not store that the user asked to "summarize a PDF," "translate a sentence," or "write code."
126+
2. **Immediate Context:** Do not store "User said 'continue'" or "User said 'yes'."
127+
3. **General Opinions on News/Politics:** Unless the user explicitly identifies with a stance, avoid summarizing general questions (e.g., ignore "What is the capital of France?").
128+
4. **Meta-Commentary:** Do not store compliments or insults to the bot (e.g., "You are smart") unless it alters how you should behave.
129+
130+
### Examples:
131+
132+
User: Hi there.
133+
Assistant: Hello! How can I help you today?
134+
Output: {{"facts" : []}}
135+
136+
User: Can you summarize this article for me?
137+
Assistant: Sure, please paste the text.
138+
Output: {{"facts" : []}}
139+
(Reasoning: This is a transient task, not a fact about the user.)
140+
141+
User: I am a vegetarian, so please don't suggest any meat dishes.
142+
Assistant: Noted, I will provide vegetarian options only.
143+
Output: {{"facts" : ["Is a vegetarian", "Does not eat meat"]}}
144+
145+
User: I'm planning a hiking trip to Patagonia next November.
146+
Assistant: That sounds amazing!
147+
Output: {{"facts" : ["Planning a hiking trip to Patagonia in November"]}}
148+
149+
User: Who is the president of the US?
150+
Assistant: The current president is...
151+
Output: {{"facts" : []}}
152+
153+
User: My dog's name is Buster. He's a golden retriever.
154+
Assistant: Buster sounds adorable.
155+
Output: {{"facts" : ["Has a dog named Buster", "Dog is a golden retriever"]}}
156+
157+
User: Actually, I moved. I live in Chicago now, not New York.
158+
Assistant: Got it, updated your location.
159+
Output: {{"facts" : ["Lives in Chicago", "No longer lives in New York"]}}
160+
161+
User: I hate Python, I prefer coding in Rust.
162+
Assistant: Understood.
163+
Output: {{"facts" : ["Dislikes Python", "Prefers coding in Rust"]}}
164+
165+
User: test
166+
Assistant: System operational.
167+
Output: {{"facts" : []}}
168+
169+
Return the facts in JSON format as shown above.
111170
112171
Rules:
113-
- Focus on stable preferences, long-term plans, personal details, or recurring constraints.
114-
- Ignore the assistant's suggestions, analysis, and any transient task details.
115-
- Do not store one-off requests, instructions about the current task, or tool/implementation details.
116-
- Do not store anything that is not explicitly stated by the user.
117-
- Store memories in third person: "The user is.../The User has..."
118-
- If nothing qualifies, return an empty list.
119-
- Output must be JSON: {"facts": ["..."]}
120-
121-
Return only JSON with a facts array.`,
172+
- If no *enduring* facts are found, return an empty list for "facts".
173+
- Detect the language of the user input and record facts in that same language.
174+
- Write fully self-contained facts (e.g., "Lives in Chicago" instead of "Lives there").`,
122175
embedder: {
123176
provider: "openai",
124177
config: {

src/ai/model-config.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,11 +84,11 @@ export function buildOpenRouterChatSettings(
8484
},
8585
...(currentOpenRouterProviderTag
8686
? {
87-
provider: {
88-
order: [currentOpenRouterProviderTag],
89-
allow_fallbacks: false,
90-
},
91-
}
87+
provider: {
88+
order: [currentOpenRouterProviderTag],
89+
allow_fallbacks: false,
90+
},
91+
}
9292
: {}),
9393
...(overrides ?? {}),
9494
};
@@ -100,7 +100,7 @@ export function buildOpenRouterChatSettings(
100100
export const TRANSCRIPTION_MODEL = "gpt-4o-mini-transcribe-2025-12-15";
101101

102102
// Default model for memory operations (cheap & fast)
103-
export const DEFAULT_MEMORY_MODEL = "deepseek/deepseek-v3.2";
103+
export const DEFAULT_MEMORY_MODEL = "x-ai/grok-4.1-fast";
104104

105105
/**
106106
* Get the model ID for memory operations (deduplication, extraction).

src/hooks/daemon-event-handlers.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { saveSessionSnapshot } from "../state/session-store";
1212
import type {
1313
ContentBlock,
1414
ConversationMessage,
15+
MemoryToastPreview,
1516
ModelMessage,
1617
SubagentStep,
1718
TokenUsage,
@@ -57,9 +58,10 @@ function clearAvatarToolEffects(avatar: DaemonAvatarRenderable | null): void {
5758
}
5859

5960
export function createMemorySavedHandler() {
60-
return (preview: string) => {
61-
if (!preview.trim()) return;
62-
toast.success("Memory saved", { description: preview });
61+
return (preview: MemoryToastPreview) => {
62+
const description = preview.description?.trim();
63+
if (!description) return;
64+
toast.success(`Memory saved (${preview.operation})`, { description });
6365
};
6466
}
6567

src/state/daemon-events.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { EventEmitter } from "node:events";
22

33
import type {
4+
MemoryToastPreview,
45
ModelMessage,
56
TokenUsage,
67
ToolCallStatus,
@@ -32,7 +33,7 @@ export type DaemonStateEvents = {
3233
subagentComplete: (toolCallId: string, success: boolean) => void;
3334
responseToken: (token: string) => void;
3435
stepUsage: (usage: TokenUsage) => void;
35-
memorySaved: (preview: string) => void;
36+
memorySaved: (preview: MemoryToastPreview) => void;
3637
responseComplete: (fullText: string, responseMessages: ModelMessage[], usage?: TokenUsage) => void;
3738
userMessage: (text: string) => void;
3839
speakingStart: () => void;

src/types/index.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ export interface StreamCallbacks {
169169
onSubagentToolResult?: (toolCallId: string, toolName: string, success: boolean) => void;
170170
onSubagentComplete?: (toolCallId: string, success: boolean) => void;
171171
onStepUsage?: (usage: TokenUsage) => void;
172-
onMemorySaved?: (preview: string) => void;
172+
onMemorySaved?: (preview: MemoryToastPreview) => void;
173173
onComplete?: (
174174
fullText: string,
175175
responseMessages: ModelMessage[],
@@ -180,6 +180,13 @@ export interface StreamCallbacks {
180180
onError?: (error: Error) => void;
181181
}
182182

183+
export type MemoryToastOperation = "ADD" | "UPDATE" | "ADD/UPDATE";
184+
185+
export interface MemoryToastPreview {
186+
operation: MemoryToastOperation;
187+
description: string;
188+
}
189+
183190
/**
184191
* Audio device information
185192
*/

0 commit comments

Comments
 (0)