From 54f0f233b3366a79950a6459761db40ecf5f7847 Mon Sep 17 00:00:00 2001 From: Devansh Jain <31609257+devanshrj@users.noreply.github.com> Date: Tue, 23 Dec 2025 12:00:34 -0800 Subject: [PATCH] feat: Add memory reminders to improve memory use (#366) --- src/agent/promptAssets.ts | 2 + src/agent/prompts/memory_check_reminder.txt | 10 +++ src/cli/App.tsx | 29 +++++- src/cli/helpers/memoryReminder.ts | 90 +++++++++++++++++++ src/settings-manager.ts | 3 + .../builtin/initializing-memory/SKILL.md | 2 +- 6 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 src/agent/prompts/memory_check_reminder.txt create mode 100644 src/cli/helpers/memoryReminder.ts diff --git a/src/agent/promptAssets.ts b/src/agent/promptAssets.ts index f4f9d46..6c58b98 100644 --- a/src/agent/promptAssets.ts +++ b/src/agent/promptAssets.ts @@ -9,6 +9,7 @@ import lettaAnthropicPrompt from "./prompts/letta_claude.md"; import lettaCodexPrompt from "./prompts/letta_codex.md"; import lettaGeminiPrompt from "./prompts/letta_gemini.md"; import loadedSkillsPrompt from "./prompts/loaded_skills.mdx"; +import memoryCheckReminder from "./prompts/memory_check_reminder.txt"; import personaPrompt from "./prompts/persona.mdx"; import personaClaudePrompt from "./prompts/persona_claude.mdx"; import personaKawaiiPrompt from "./prompts/persona_kawaii.mdx"; @@ -26,6 +27,7 @@ export const PLAN_MODE_REMINDER = planModeReminder; export const SKILL_UNLOAD_REMINDER = skillUnloadReminder; export const SKILL_CREATOR_PROMPT = skillCreatorModePrompt; export const REMEMBER_PROMPT = rememberPrompt; +export const MEMORY_CHECK_REMINDER = memoryCheckReminder; export const MEMORY_PROMPTS: Record = { "persona.mdx": personaPrompt, diff --git a/src/agent/prompts/memory_check_reminder.txt b/src/agent/prompts/memory_check_reminder.txt new file mode 100644 index 0000000..da20314 --- /dev/null +++ b/src/agent/prompts/memory_check_reminder.txt @@ -0,0 +1,10 @@ + +MEMORY CHECK: Review this conversation for information worth storing in your memory blocks. Update memory silently (no confirmation needed) if you learned: + +- **User info**: Name, role, preferences, working style, current work/goals +- **Project details**: Architecture, patterns, gotchas, dependencies, conventions, workflow rules +- **Corrections**: User corrected you or clarified something important +- **Preferences**: How they want you to behave, communicate, or approach tasks + +Ask yourself: "If I started a new session tomorrow, what from this conversation would I want to remember?" If the answer is meaningful, update the appropriate memory block(s) now. + \ No newline at end of file diff --git a/src/cli/App.tsx b/src/cli/App.tsx index 244373d..c23021e 100644 --- a/src/cli/App.tsx +++ b/src/cli/App.tsx @@ -89,6 +89,10 @@ import { } from "./helpers/accumulator"; import { backfillBuffers } from "./helpers/backfill"; import { formatErrorDetails } from "./helpers/errorFormatter"; +import { + buildMemoryReminder, + parseMemoryPreference, +} from "./helpers/memoryReminder"; import { buildMessageContentFromDisplay, clearPlaceholdersInText, @@ -505,6 +509,9 @@ export default function App({ // Track if we've sent the session context for this CLI session const hasSentSessionContextRef = useRef(false); + // Track conversation turn count for periodic memory reminders + const turnCountRef = useRef(0); + // Static items (things that are done rendering and can be frozen) const [staticItems, setStaticItems] = useState([]); @@ -1674,6 +1681,9 @@ export default function App({ setStaticItems([]); setStaticRenderEpoch((e) => e + 1); + // Reset turn counter for memory reminders when switching agents + turnCountRef.current = 0; + // Update agent state - also update ref immediately for any code that runs before re-render agentIdRef.current = targetAgentId; setAgentId(targetAgentId); @@ -2287,6 +2297,9 @@ export default function App({ // emittedIdsRef.current.clear(); // setStaticItems([]); + // Reset turn counter for memory reminders + turnCountRef.current = 0; + // Update command with success buffersRef.current.byId.set(cmdId, { kind: "command", @@ -3246,12 +3259,21 @@ DO NOT respond to these messages or otherwise consider them in your response unl bashCommandCacheRef.current = []; } - // Combine reminders with content (session context first, then plan mode, then skill unload, then bash commands) + // Build memory reminder if interval is set and we've reached the Nth turn + const memoryReminderContent = await buildMemoryReminder( + turnCountRef.current, + ); + + // Increment turn count for next iteration + turnCountRef.current += 1; + + // Combine reminders with content (session context first, then plan mode, then skill unload, then bash commands, then memory reminder) const allReminders = sessionContextReminder + planModeReminder + skillUnloadReminder + - bashCommandPrefix; + bashCommandPrefix + + memoryReminderContent; const messageContent = allReminders && typeof contentParts === "string" ? allReminders + contentParts @@ -4476,6 +4498,9 @@ DO NOT respond to these messages or otherwise consider them in your response unl // Get questions from approval args const questions = getQuestionsFromApproval(approval); + // Check for memory preference question and update setting + parseMemoryPreference(questions, answers); + // Format the answer string like Claude Code does const answerParts = questions.map((q) => { const answer = answers[q.question] || ""; diff --git a/src/cli/helpers/memoryReminder.ts b/src/cli/helpers/memoryReminder.ts new file mode 100644 index 0000000..dad3742 --- /dev/null +++ b/src/cli/helpers/memoryReminder.ts @@ -0,0 +1,90 @@ +// src/cli/helpers/memoryReminder.ts +// Handles periodic memory reminder logic and preference parsing + +import { settingsManager } from "../../settings-manager"; + +// Memory reminder interval presets +const MEMORY_INTERVAL_FREQUENT = 3; +const MEMORY_INTERVAL_OCCASIONAL = 8; + +/** + * Get the effective memory reminder interval (local setting takes precedence over global) + * @returns The memory interval setting, or null if disabled + */ +function getMemoryInterval(): number | null { + // Check local settings first (may not be loaded, so catch errors) + try { + const localSettings = settingsManager.getLocalProjectSettings(); + if (localSettings.memoryReminderInterval !== undefined) { + return localSettings.memoryReminderInterval; + } + } catch { + // Local settings not loaded, fall through to global + } + + // Fall back to global setting + return settingsManager.getSetting("memoryReminderInterval"); +} + +/** + * Build a memory check reminder if the turn count matches the interval + * @param turnCount - Current conversation turn count + * @returns Promise resolving to the reminder string (empty if not applicable) + */ +export async function buildMemoryReminder(turnCount: number): Promise { + const memoryInterval = getMemoryInterval(); + + if (memoryInterval && turnCount > 0 && turnCount % memoryInterval === 0) { + const { MEMORY_CHECK_REMINDER } = await import( + "../../agent/promptAssets.js" + ); + return MEMORY_CHECK_REMINDER; + } + + return ""; +} + +interface Question { + question: string; + header?: string; +} + +/** + * Parse user's answer to a memory preference question and update settings + * @param questions - Array of questions that were asked + * @param answers - Record of question -> answer + * @returns true if a memory preference was detected and setting was updated + */ +export function parseMemoryPreference( + questions: Question[], + answers: Record, +): boolean { + for (const q of questions) { + const questionLower = q.question.toLowerCase(); + const headerLower = q.header?.toLowerCase() || ""; + + // Match memory-related questions + if ( + questionLower.includes("memory") || + questionLower.includes("remember") || + headerLower.includes("memory") + ) { + const answer = answers[q.question]?.toLowerCase() || ""; + + // Parse answer: "frequent" → MEMORY_INTERVAL_FREQUENT, "occasional" → MEMORY_INTERVAL_OCCASIONAL + if (answer.includes("frequent")) { + settingsManager.updateLocalProjectSettings({ + memoryReminderInterval: MEMORY_INTERVAL_FREQUENT, + }); + return true; + } else if (answer.includes("occasional")) { + settingsManager.updateLocalProjectSettings({ + memoryReminderInterval: MEMORY_INTERVAL_OCCASIONAL, + }); + return true; + } + break; // Only process first matching question + } + } + return false; +} diff --git a/src/settings-manager.ts b/src/settings-manager.ts index ccbc568..9bea32e 100644 --- a/src/settings-manager.ts +++ b/src/settings-manager.ts @@ -11,6 +11,7 @@ export interface Settings { tokenStreaming: boolean; enableSleeptime: boolean; sessionContextEnabled: boolean; // Send device/agent context on first message of each session + memoryReminderInterval: number | null; // null = disabled, number = prompt memory check every N turns globalSharedBlockIds: Record; // DEPRECATED: kept for backwards compat profiles?: Record; // DEPRECATED: old format, kept for migration pinnedAgents?: string[]; // Array of agent IDs pinned globally @@ -47,6 +48,7 @@ export interface LocalProjectSettings { permissions?: PermissionRules; profiles?: Record; // DEPRECATED: old format, kept for migration pinnedAgents?: string[]; // Array of agent IDs pinned locally + memoryReminderInterval?: number | null; // null = disabled, number = overrides global } const DEFAULT_SETTINGS: Settings = { @@ -54,6 +56,7 @@ const DEFAULT_SETTINGS: Settings = { tokenStreaming: false, enableSleeptime: false, sessionContextEnabled: true, + memoryReminderInterval: 1, // number = prompt memory check every N turns globalSharedBlockIds: {}, }; diff --git a/src/skills/builtin/initializing-memory/SKILL.md b/src/skills/builtin/initializing-memory/SKILL.md index c0e528c..4d7e4ee 100644 --- a/src/skills/builtin/initializing-memory/SKILL.md +++ b/src/skills/builtin/initializing-memory/SKILL.md @@ -215,7 +215,7 @@ You should ask these questions at the start (bundle them together in one AskUser 1. **Research depth**: "Standard or deep research (comprehensive, as long as needed)?" 2. **Identity**: "Which contributor are you?" (You can often infer this from git logs - e.g., if git shows "cpacker" as a top contributor, ask "Are you cpacker?") 3. **Related repos**: "Are there other repositories I should know about and consider in my research?" (e.g., backend monorepo, shared libraries) -4. **Workflow style**: "How proactive should I be?" (auto-commit vs ask-first) +4. **Memory updates**: "How often should I check if I should update my memory?" with options "Frequent (every 3-5 turns)" and "Occasional (every 8-10 turns)". This should be a binary question with "Memory" as the header. 5. **Communication style**: "Terse or detailed responses?" 6. **Any specific rules**: "Rules I should always follow?"