feat: Add memory reminders to improve memory use (#366)
This commit is contained in:
@@ -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<string, string> = {
|
||||
"persona.mdx": personaPrompt,
|
||||
|
||||
10
src/agent/prompts/memory_check_reminder.txt
Normal file
10
src/agent/prompts/memory_check_reminder.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
<system-reminder>
|
||||
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.
|
||||
</system-reminder>
|
||||
@@ -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<StaticItem[]>([]);
|
||||
|
||||
@@ -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] || "";
|
||||
|
||||
90
src/cli/helpers/memoryReminder.ts
Normal file
90
src/cli/helpers/memoryReminder.ts
Normal file
@@ -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<string> {
|
||||
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<string, string>,
|
||||
): 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;
|
||||
}
|
||||
@@ -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<string, string>; // DEPRECATED: kept for backwards compat
|
||||
profiles?: Record<string, string>; // 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<string, string>; // 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: {},
|
||||
};
|
||||
|
||||
|
||||
@@ -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?"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user