Cherry Studio 内置 Agent 的长期记忆机制:深入解析 FACT.md 的设计与实现
【免费下载链接】cherry-studioAI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs项目地址: https://gitcode.com/GitHub_Trending/ch/cherry-studio
本指南聚焦 Cherry Studio 内置 Agent(Cherry Assistant 与 Cherry Support)的跨会话长期记忆文件memory/FACT.md,说明它的职责边界、持久化保证、写入与回读机制,以及它和JOURNAL.jsonl、产品清单(manifest)之间的分工。读完本文,你将理解内置 Agent 记忆目录的完整文件布局,掌握哪些信息该写入 FACT.md、哪些信息必须走memory工具或查询 manifest,并能从源码层面验证这套机制的实现原理。
FACT.md 是什么
在 Cherry Studio 的 Agent 数据目录中,memory/FACT.md被定义为Long-term knowledge(长期知识)文件,其唯一用途是存放跨会话学习到的关于用户的稳定事实,例如:
- 用户偏好(preferences);
- 环境特性(environment quirks);
- 已解决的问题(resolved issues)。
该文件的头部注释对此做了明确声明:
This file is for facts you learn about the user across sessions (preferences, environment quirks, resolved issues, etc.). It isnotoverwritten on app updates - your customizations persist.
即:它不会在应用升级时被覆盖,用户的自定义内容会一直保留。这也是 FACT.md 与一般缓存/临时文件最本质的区别——它属于“用户数据”而非“产品数据”。
当前仓库中,两个内置 Agent 均携带同构的 FACT.md 模板:
- cherry-assistant 的 FACT.md
- cherry-support 的 FACT.md
两者内容完全一致,说明 FACT.md 是内置 Agent 的通用记忆契约,而非某个 Agent 的专属配置。
记忆目录的完整布局:四类文件的职责分工
从 PromptBuilder 的源码注释 可以完整还原 Agent 数据目录的文件布局:
| 文件 | 语义 | 用途 | 更新方式 |
|---|---|---|---|
SOUL.md | 你是谁(HOW) | 名字、性格、语气、沟通风格;未配置 System Prompt 时的角色定义 | Read + Edit 工具直接编辑 |
USER.md | 用户是谁(WHO) | 姓名、偏好、时区、个人上下文 | Read + Edit 工具直接编辑 |
memory/FACT.md | 你知道什么(WHAT) | 活跃项目、技术决策、持久知识(6 个月以上仍有效) | 仅通过memory工具update动作写入 |
memory/JOURNAL.jsonl | 何时发生了什么(WHEN) | 一次性事件、会话笔记(追加式日志) | 仅通过memory工具append/search |
这套布局的核心原则是每个文件有独占职责,禁止跨文件重复信息。其中 FACT.md 与 JOURNAL.jsonl 的区别尤其关键:
- FACT.md:存放“6 个月后仍然重要的知识”,由
memory工具的update动作整体覆盖写; - JOURNAL.jsonl:存放“一次性事件、已完成任务、会话笔记”,由
memory工具的append动作逐条追加,且不加载进上下文,需要时通过search查询。
memory工具的描述里有一条非常实用的判据:
Before writing to FACT.md, ask: will this still matter in 6 months? If not, use append instead.
(写 FACT.md 之前先问自己:这件事 6 个月后还重要吗?如果否,就用 append 写入日志。)
为什么 FACT.md 不会被应用更新覆盖
这一保证并非凭空承诺,而是由“内置 Agent 预置(provisioning)”机制的实现决定的。内置 Agent 的模板目录与用户数据目录是分离的:
- 模板存放在
feature.agents.builtin路径下(即仓库中的 resources/builtin-agents 目录); - 用户实例化后产生的实际数据(SOUL.md、USER.md、memory/ 等)位于独立的 Agent 数据目录。
builtinAgentDefinition.ts中通过getBuiltinAgentTemplateDirectory读取模板、loadBuiltinAgentDefinition解析 agent.json,预置过程只负责初始化模板文件,并不会在应用升级时重新覆盖已经存在的用户数据文件。这一点与 FACT.md 中 "not overwritten on app updates" 的声明相互印证——升级流程只同步产品侧模板,尊重用户侧的既有记忆。
产品知识不入 FACT.md:与 manifest 的分工
FACT.md 还给出了一条硬性约束:
For Cherry Studio product knowledge, follow the
cherry-assistant-guideskill and query the current package manifest throughmcp__assistant__product_info. The manifest does not include release history. Do not duplicate product facts here, or they will go stale silently.
翻译过来即:
- 关于 Cherry Studio 的产品知识,应遵循
cherry-assistant-guideskill; - 通过 MCP 工具
mcp__assistant__product_info查询当前安装包的产品清单(manifest); - manifest 不包含版本发布历史;
- 不要把产品事实复制到 FACT.md,否则它们会在没有提示的情况下悄悄过期(go stale silently)。
这一设计背后是“单一事实来源(single source of truth)”原则:产品事实由安装包内的 manifest 动态提供,会随版本更新而自动刷新;FACT.md 是用户记忆,一旦写入产品事实,就会与安装包脱节,造成“记忆与现状不符”的陈旧问题。同理,builtinAgentDefinition.ts中也有类似的设计意图:内置 Agent 的展示/搜索描述由 i18n 拥有(agent.builtin.cherry_assistant.description),而不是在 bundle 中复制一份,因为 bundle 副本会成为“易漂移的第二个事实来源”。
结合内置 Agent 的职责来看:cherry-support(产品反馈 Agent)的指令明确要求“产品回答必须以当前安装包为准”(见 cherry-support 的 agent.json),这正是 FACT.md 要求查询 manifest 的落地场景。
写入机制:memory 工具的 update/append/search
FACT.md 的写入不是通过普通文件编辑,而是必须经过memory工具。该工具定义在 src/main/ai/agents/tools/memoryTools.ts,输入 schema 支持三种动作:
| 参数 | 类型 | 说明 |
|---|---|---|
action | string(必填) | update:整体覆盖 FACT.md(仅持久知识);append:追加 JOURNAL 日志条目;search:查询日志 |
content | string | FACT.md 的完整 Markdown 内容(update必填) |
text | string | 日志条目文本(append必填) |
tags | string[] | 日志条目标签,可选项(用于append) |
query | string | 搜索词——大小写不敏感的子串匹配(用于search) |
tag | string | 按标签过滤(可选,用于search) |
limit | integer | 最大返回条数,默认 20(用于search) |
update:原子化覆盖 FACT.md
memoryUpdate的实现值得关注(memoryTools.ts#L113-L139):
- 校验
content必须为非空字符串; - 解析
memory目录并定位FACT.md(大小写不敏感匹配); - 在同一目录下创建临时文件
.FACT.md.<uuid>.tmp,以0o600权限写入新内容; - 再次校验目录与目标文件均为“真实文件且非符号链接”;
- 通过
rename(tmpPath, factPath)原子替换。
这种“先写临时文件再 rename”的流程保证了即使在写入中途发生异常,也不会留下半截损坏的 FACT.md;异常路径中临时文件会被清理。同时,withNoFollow在非 Windows 平台加入O_NOFOLLOW标志,配合lstat检查isSymbolicLink(),从安全角度拒绝了符号链接指向文件——防止记忆文件被链接到任意路径。
测试用例 memoryTools.test.ts#L46-L54 验证了update的原子写入:调用后 FACT.md 内容精确等于传入的content,且返回Memory updated.。
append:追加式事件日志
memoryAppend使用O_APPEND | O_CREAT | O_WRONLY打开JOURNAL.jsonl(同样拒绝符号链接),将形如{ ts: <ISO 时间戳>, tags: [...], text: "..." }的 JSON 对象追加为一行。该文件采用JSONL(JSON Lines)格式,天然支持追加与逐行解析。
search:日志检索
memorySearch对每行执行JSON.parse,按query(大小写不敏感子串)与tag(精确匹配)过滤,最后取最后limit条并逆序返回(即最新的在前)。损坏的行会被跳过并记录 warning。测试 memoryTools.test.ts#L56-L64 验证了“追加三条、按 tag 搜索后返回最新优先”的行为(['v2', 'v1'])。
安全护栏
memory工具还包含多项路径安全校验:
getAgentDataPath通过agentService.getAgent校验 Agent 存在性,并用assertAgentDataDirectory强制解析后的路径必须与上下文中的agentDataPath完全一致(路径不匹配抛InternalError);memory目录必须是“真实目录且非符号链接”;- 文件名解析使用大小写不敏感匹配(跨平台一致体验)。
这些逻辑均有测试覆盖,例如 memoryTools.test.ts#L78-L83 验证了路径不匹配时抛错。
回读机制:FACT.md 如何进入 Agent 上下文
记忆不仅要“写得进”,还要“读得出”。FACT.md 的回读由 PromptBuilder 负责,包含两条路径:
- Memories 区块(
buildMemoriesSection):始终生成## Memories小节,加载 SOUL.md / USER.md / FACT.md 的内容,分别包裹在<soul>、<user>、<facts>标签中; - Agent Knowledge 区块(
buildFactsSection):专门回读memory/FACT.md,生成## Agent Knowledge小节,并把内容放入<facts>...</facts>。源码注释将其描述为“跨会话学习回路的回忆侧(recall side)”:
agents write durable knowledge to FACT.md via
mcp__agent-memory__memoryaction="update", and this method loads it back into the system prompt at the start of the next session so the agent remembers what it learned.
(Agent 通过memory工具的 update 动作把持久知识写入 FACT.md;下一次会话开始时,本方法把它重新加载进系统提示,让 Agent 记住它学到的东西——例如之前失败的参数形状、项目约定、用户的纠正。)
该区块还明确要求 Agent 把 FACT.md 内容当作 ground truth(基准事实),除非有直接证据表明其错误,此时应通过memory工具update更新 FACT.md,使后续会话同样受益。这构成了一个完整的学习闭环:会话中沉淀 → 写入 FACT.md → 下个会话回读 → 纠错后再写入。
此外,readCachedFile实现了基于 mtime 的缓存(TTL 30 分钟),并对文件做了真实文件校验(拒绝符号链接)、路径越界校验(realpath 后必须位于期望根目录内),回读路径同样具备安全防护。
写入准则速查
综合 FACT.md 模板、memory工具说明与 PromptBuilder 的实现,可归纳出以下可执行的判据:
| 信息类型 | 是否写入 FACT.md | 正确去向 |
|---|---|---|
| 用户偏好、环境特性、已解决问题 | ✅ 是 | memory工具update |
| 6 个月后仍重要的技术决策/项目知识 | ✅ 是 | memory工具update |
| 一次性事件、已完成任务、会话笔记 | ❌ 否 | memory工具append到 JOURNAL.jsonl |
| Cherry Studio 产品知识(功能、概念、限制) | ❌ 否 | cherry-assistant-guideskill +mcp__assistant__product_info查询 manifest |
| 产品发布历史 | ❌ 否(manifest 也不包含) | 不写入记忆,直接面向用户说明 |
在 Cherry Support 场景中的实际意义
本文开头提到的 FACT.md 来自 cherry-support 的模板。作为内置的“产品反馈”Agent(其职责为答疑解惑、使用帮助、问题排查、反馈整理,见 agent.json),它面对的是大量重复出现的用户环境问题——某用户的代理配置、某平台的特有行为、某个已排查过的报错。这些正是 FACT.md 的典型适用场景:跨会话记住“这位用户的网络环境”“上次排查到一半的问题状态”“用户偏好的沟通方式”,从而让下一次对话无需从零开始。
而“Cherry Studio 产品知识必须查 manifest”的约束,则确保 Support Agent 的回答始终跟随当前安装包版本,不会因为 FACT.md 里残留的旧知识而给出过时指引——这与 src/main/ai/agents/tools/memoryTools.ts 中“update 覆盖写 + 不重复产品事实”的设计互为表里。
总结
FACT.md 是 Cherry Studio 内置 Agent 长期记忆体系的核心文件,它通过与 SOUL.md(人格)、USER.md(用户画像)、JOURNAL.jsonl(事件日志)的职责切分,以及“产品事实查 manifest、用户事实写 FACT.md”的边界约定,实现了三个关键保证:跨会话持久、应用升级不丢失、永不陈旧。其写入路径(memory工具)与回读路径(PromptBuilder)均经过源码级安全校验(符号链接拒绝、路径越界检查、原子替换、mtime 缓存),并有完整的单元测试支撑。对开发者而言,这套设计是一份可复用的“Agent 长期记忆”参考实现:用独占文件划分记忆类型、用专用工具封装写入契约、用动态数据源取代静态复制,从而避免知识过期。
【免费下载链接】cherry-studioAI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs项目地址: https://gitcode.com/GitHub_Trending/ch/cherry-studio
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考