Skip to content

Latest commit

 

History

History
212 lines (152 loc) · 5.84 KB

File metadata and controls

212 lines (152 loc) · 5.84 KB

8. Memory — 让 Agent 跨会话记住重要信息

本章参考代码:

本章目标

Ch7 的 /compact 解决的是同一会话越来越长的问题。Memory 解决的是另一件事:哪些信息值得跨会话保留

本章实现一个最小但可用的文件系统记忆:

memory/
├── MEMORY.md          # 自动生成的索引
├── preferences.md     # 用户偏好
└── project.md         # 项目事实

Agent 不会自动记住所有对话。它只在模型明确写入 memory/*.md 时保存长期信息,并通过 MEMORY.md 索引把可用记忆注入 system prompt。

Memory 文件格式

每条记忆是一个 Markdown 文件,顶部带 frontmatter:

---
type: preference
summary: 用户喜欢中文回答
updated: 2026-07-02
---

回答时默认使用中文,除非用户明确要求英文。

三个字段的含义:

字段 用途
type 记忆类型,例如 preferenceprojectworkflow
summary 写入 MEMORY.md 的短摘要
updated 最后更新时间,方便判断信息是否过期

不要把短期任务状态、密钥、令牌、私人联系方式写进 memory。Memory 是长期上下文,不是临时草稿。

memory.py

本章新增 memory.py,负责三件事。

第一,解析 frontmatter:

def parse_frontmatter(text: str) -> tuple[dict[str, str], str]:
    if not text.startswith("---\n"):
        return {}, text

    end = text.find("\n---", 4)
    if end == -1:
        return {}, text

    raw = text[4:end]
    body = text[end + 4 :].lstrip("\n")
    metadata = {}
    for line in raw.splitlines():
        if ":" in line:
            key, value = line.split(":", 1)
            metadata[key.strip()] = value.strip().strip("\"'")
    return metadata, body

第二,扫描 memory/*.md 并生成索引:

def refresh_memory_index() -> str:
    MEMORY_DIR.mkdir(parents=True, exist_ok=True)
    lines = ["# Memory Index", ""]

    for path in sorted(MEMORY_DIR.glob("*.md")):
        if path.name == "MEMORY.md":
            continue
        metadata, _ = parse_frontmatter(path.read_text(encoding="utf-8"))
        memory_type = metadata.get("type", "note")
        summary = metadata.get("summary", path.stem)
        lines.append(f"- {memory_type}: {summary} (`{path.name}`)")

    index = "\n".join(lines).rstrip() + "\n"
    (MEMORY_DIR / "MEMORY.md").write_text(index, encoding="utf-8")
    return index

第三,生成 system prompt 片段:

def build_memory_prompt_section() -> str:
    index = load_memory_index()
    return (
        "# Memory System\n"
        "Use memory files only when they are relevant to the current task.\n"
        "To save durable preferences or project facts, write Markdown files "
        f"under `{MEMORY_DIR}` with frontmatter fields `type`, `summary`, "
        "and `updated`.\n\n"
        f"{index}"
    )

接入 Prompt

prompt.py 在模板末尾新增 {{memory}}

from memory import build_memory_prompt_section

SYSTEM_PROMPT_TEMPLATE = """\
...
{{claude_md}}
{{memory}}"""

构建 prompt 时替换这个占位符:

replacements = {
    "{{memory}}": build_memory_prompt_section(),
}

这样每次请求模型前,它都能看到当前 memory 索引,以及什么时候该保存长期信息。

写入后刷新索引

如果模型用 write_file 写入 memory/*.md,索引应该立刻更新。否则本轮后续请求仍然看不到新记忆。

tools.py 在成功写入或编辑文件后调用:

def maybe_refresh_memory_index(file_path: str) -> None:
    path = Path(file_path).resolve()
    memory_dir = memory.MEMORY_DIR.resolve()

    if path.suffix == ".md" and memory_dir in path.parents:
        memory.refresh_memory_index()

注意它只刷新 memory 目录下的 Markdown 文件。普通项目文件不应该触发 memory 索引重建。

/memory 命令

agent.py 在 REPL 中新增一个辅助命令:

if user_input == "/memory":
    print_info(load_memory_index())
    continue

它不调用模型,只把当前 MEMORY.md 打印出来,方便你确认长期记忆里到底有什么。

怎么验证

先运行 Chapter 8:

cd examples/chapter-08
python agent.py

让模型保存一条偏好:

请记住:这个项目的交流默认使用中文。把它保存到 memory/preferences.md。

然后输入:

/memory

预期能看到类似内容:

# Memory Index

- preference: 这个项目的交流默认使用中文 (`preferences.md`) - updated 2026-07-02

退出后重新运行 python agent.py,system prompt 仍会加载 memory/MEMORY.md。这就是本章的跨会话记忆。

本章完成检查

  • memory.py 能解析 frontmatter。
  • refresh_memory_index() 能生成 memory/MEMORY.md
  • build_memory_prompt_section() 会把索引注入 system prompt。
  • write_file / edit_file 修改 memory Markdown 后会刷新索引。
  • REPL 支持 /memory

暂时省略的能力

省略内容 原因 后续落点
语义召回 需要额外模型调用和排序策略,本章先只做索引注入 进阶练习
后台记忆提取 需要 Sub-Agent 才自然 Ch11 之后
多级 memory 目录 当前教程保持一个项目级 memory/ 进阶练习

下一章:Skills — 把可复用工作流按需加载进 Agent。