2. 记忆,让 Agent 记得发生了什么 - Memory

最简单的记忆存储 - 一个 Markdown 文件

没有数据库、没有RAG向量检索,先做一个最简单的记忆存储,把每次Agent的执行时间、任务和结果追加到一个Markdown文件中。

/**
 * 保存记忆到 memory.md 文件
 * @param task 用户输入
 * @param result 执行结果
 */
export function saveMemory(task: string, result: string): void {
  const timestamp = new Date().toISOString();
  const entry = `## ${timestamp}\n\n**Task:** ${task}\n\n**Result:**\n${result}\n\n---\n\n`;
 
  if (fs.existsSync(MEMORY_FILE)) {
    fs.appendFileSync(MEMORY_FILE, entry, "utf-8");
  } else {
    fs.writeFileSync(MEMORY_FILE, "# Agent Memory\n\n" + entry, "utf-8");
  }
}
# Agent Memory
 
## 2026-03-30T07:51:28.740Z
 
**Task:** -- 在当前目录下,创建一个HelloWorld.txt文件,然后在里面写入HelloWorld
 
**Result:**
任务已完成!已在当前目录下创建HelloWorld.txt文件,并写入内容"HelloWorld"。

记忆读取

通过滑动窗口的方式读取该Markdown文件的最新记录,因为上下文的容量是有限的,记录的memory会随着执行次数的增多越来越多,必须读取有限的最新记忆。

/**
 * 通过滑动窗口方式加载 memory.md 文件
 * @param windowSize 窗口大小(字符数),默认 1000
 * @returns 记忆内容字符串
 */
export function loadMemory(windowSize: number = 1000): string {
  if (!fs.existsSync(MEMORY_FILE)) {
    return "";
  }
 
  const content = fs.readFileSync(MEMORY_FILE, "utf-8");
  if (content.length <= windowSize) {
    return content;
  }
 
  // 从末尾截取指定大小的内容
  const startIndex = content.length - windowSize;
  // 尝试从最近的标题开始,避免截断中间的内容
  const headerMatch = content.lastIndexOf("## ", startIndex);
  const finalStartIndex = headerMatch !== -1 ? headerMatch : startIndex;
 
  return content.slice(finalStartIndex);
}
 

记忆注入

把加载出来的记忆拼接到system prompt的末尾,并告诉LLM这是之前的内容,这样LLM在处理新任务的时候就能看到之前的历史了。

async function getSystemMessage(planText?: string): Promise<ChatMessage[]> {
  const memory = loadMemory();
  let systemPrompt = `You are a powerful code assistant. First, figure out what kind of project & system this is. Last, Be concise and helpful.`;
  
  if (planText) {
    systemPrompt += `\n\nTask Steps:\n${planText}`;
  }
  
  if (memory) {
    systemPrompt += `\n\nPrevious context:\n${memory}`;
  }
 
  return [
    {
      role: "system",
      content: systemPrompt,
    },
  ];
}

Agent 记忆的本质

使用Markdown记录记忆的方式揭露了记忆机制的根本原理:LLM 本身没有真正的记忆,所有的记忆都是通过在 Prompt 中注入历史信息来实现的。无论是 Openclaw 的 Memory.md 还是更复杂的 RAG 系统,底层都是通过上下文的注入来实现的。