Cherry Studio 内置 Agent 长期记忆 FACT.md:设计规范与持久化实现解析
【免费下载链接】cherry-studio🍒 Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端项目地址: https://gitcode.com/CherryHQ/cherry-studio
Cherry Studio 为其内置 Agent(Cherry 小助手cherry-assistant、Cherry 支持助手cherry-support)提供了一套基于文件的长期记忆机制,其中memory/FACT.md是承载"长期知识"的核心文件。本文以该文件的官方规范(resources/builtin-agents/cherry-assistant/memory/FACT.md)为骨架,结合主进程源码与测试用例,讲解 FACT.md 的用途边界、持久化保证、防陈旧机制,以及底层原子写入与系统提示词内联加载的实现原理,帮助你在定制 Agent 或扩展记忆能力时做到事实准确、边界清晰。
FACT.md 的定位:跨会话的"用户长期知识"文件
FACT.md 的标题是# Long-term knowledge(长期知识),它被设计为存放Agent 跨会话学到的关于用户的稳定事实,包括但不限于:
- 偏好(preferences):用户喜欢的语气、命名习惯、工具选择、工作流偏好等;
- 环境怪癖(environment quirks):用户机器或工作区中的特殊环境、目录布局、命令习惯等;
- 已解决的问题(resolved issues):过去排查完成的故障、达成的技术决策、沉淀下来的经验。
从源码结构看,这一设计在 src/main/ai/agents/prompt.ts 的"Agent Data"清单中有明确对应——FACT.md 被描述为 "WHAT you know",即 Agent"知道什么",具体涵盖活跃项目、技术决策、6 个月以上的持久知识(durable knowledge, 6+ months)。其"回忆侧"(recall side)实现在同一文件的loadMemory逻辑中:会话启动时读取memory/FACT.md内容,将其内联进系统提示词供模型直接阅读;"写入侧"(write side)则由memory工具的updateaction 负责,二者共同构成"读-写闭环"。
核心保证:应用更新不会覆盖用户定制
FACT.md 规范中最关键的一条保证是:
It isnotoverwritten on app updates - your customizations persist. (它不会在应用更新时被覆盖——你的自定义内容会持久保留。)
这一保证由内置 Agent 的模板-实例分离机制在实现层面落地。内置 Agent 的源模板存放在仓库的 resources/builtin-agents/cherry-assistant/(及 resources/builtin-agents/cherry-support/)目录下;当用户在应用内首次使用该 Agent 时,系统会将其复制到独立的 Agent 数据目录({agentData}/memory/),此后 FACT.md 的生命周期就脱离应用安装目录了。
这一行为由测试 src/main/ai/agents/builtin/tests/BuiltinAgentProvisioner.test.ts 明确锁定:测试先向模板目录写入memory/FACT.md内容为TEMPLATE_FACT,断言初始化后 Agent 数据目录中出现了内容一致的 FACT.md;随后(第 204-210 行)将实例中的 FACT.md 改写为CUSTOM_FACT,验证再次初始化不会覆盖这份自定义内容——这正是"app updates 不覆盖用户定制"的代码级证据。
核心规范:产品知识不写入 FACT.md,走 skill + MCP 查询
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-guide技能,并通过mcp__assistant__product_info工具查询当前版本的包清单; - 清单边界:包清单(product manifest)不包含发布历史(release history),发布历史需要另行获取;
- 防陈旧原则:不要把产品事实复制进 FACT.md——因为产品会持续演进,写在静态记忆文件里的事实会在不发出任何信号的情况下"静默过期"(go stale silently),导致 Agent 向用户传播过时信息。
这一约束背后是**单一事实来源(Single Source of Truth)**的设计思想:FACT.md 只存"关于用户"的稳定事实,而"关于产品"的动态事实一律实时查询。当前版本的包清单实体见 resources/builtin-agents/cherry-assistant/product-manifest.json,其中包含package(名称与版本)、routes(应用内路由)、commands(快捷键命令)、providers(62 个模型提供商条目)、locales(13 种语言)、agents(渠道类型与代码 CLI 工具)以及features(上下文压缩、MCP 服务器类型、知识库支持的文件扩展名等能力边界)。由于该清单会随版本更新,Agent 每次查询都能拿到与用户当前安装版本一致的答案,这正是"不重复产品事实"的工程价值所在。
底层实现一:memory 工具的 update / append / search
FACT.md 的写入并非由模型直接编辑文件,而是通过统一的memory工具完成。工具定义位于 src/main/ai/agents/tools/memoryTools.ts,其输入模式(MEMORY_INPUT_SCHEMA)定义了三个互斥 action:
| action | 作用对象 | 语义 | 必要参数 |
|---|---|---|---|
update | memory/FACT.md | 整体覆盖写入长期知识(仅限持久知识) | content:FACT.md 的完整 Markdown 内容 |
append | memory/JOURNAL.jsonl | 追加一条日志(一次性事件、完成任务、会话笔记) | text:条目文本;可选tags:标签数组 |
search | memory/JOURNAL.jsonl | 查询日志(大小写不敏感的子串匹配) | query:查询串;可选tag标签过滤、limit结果上限(默认 20) |
工具描述(memoryTools.ts)中还内置了一条决策准则:写入 FACT.md 之前先问自己——"这条信息 6 个月后还有意义吗?(will this still matter in 6 months?)"如果没有,应该用append记入日志而不是update覆盖事实文件。这与 resources/skills/cherry-tool-guide/references/memory.md 中"按寿命选择updatevsappend"的指引完全一致,同时也明确提示:update会整体覆盖 FACT.md,重写时应当保留已有内容(先读后写、增量追加),不能粗暴清空。
值得注意的是,update与append的职责严格分离:FACT.md 只承载"长期知识",而一次性事件、已完成任务、会话笔记则进入JOURNAL.jsonl(追加式事件日志)。这也解释了为什么search只检索日志、不检索事实文件——事实文件本身已在会话启动时内联进提示词,无需再查。
底层实现二:FACT.md 的原子写入与安全约束
从实现细节看,FACT.md 的更新被刻意设计为原子替换 + 符号链接防护,防止写入中途崩溃导致文件损坏:
- 原子替换:
memoryUpdate(memoryTools.ts)先将新内容写入同目录下的临时文件.FACT.md.{uuid}.tmp(open使用wx独占创建标志,权限0o600),随后用rename一次性替换目标文件。任何一步失败都会在catch中清理临时文件,保证 FACT.md 要么是旧内容、要么是新内容,绝不出现半写状态; - 符号链接防护:多个辅助函数(
assertRegularFileOrMissing、assertMemoryDirectory、resolveFileCI)都坚持"必须是真实文件/目录"的校验,非 Windows 平台还通过O_NOFOLLOW标志拒绝跟随符号链接;resolveFileCI甚至在文件名匹配上做到了大小写不敏感(目录枚举时按小写比对),确保在大小写不敏感的文件系统上也行为一致; - 权限收敛:FACT.md 与 JOURNAL.jsonl 的写入均使用
0o600权限(仅属主可读写),日志追加使用O_APPEND标志。
这些行为由测试 src/main/ai/agents/tools/tests/memoryTools.test.ts 验证:调用update后断言memory/FACT.md内容即为传入的# Facts文本。此外 src/main/ai/agents/tests/prompt.test.ts 从提示词侧验证了:FACT.md 存在时会被包含进 memories 区块、以 "Agent Knowledge" 块包裹、大小写不敏感解析(/workspace/memory/FACT.md能被解析到),而文件缺失或为空时该区块会被安全省略(第 506 行、第 543 行)。
底层实现三:系统提示词中的内联加载
FACT.md 的"回忆"机制在 src/main/ai/agents/prompt.ts 中实现。会话启动时,Agent 数据目录下的四个文件会被协同加载:
| 文件 | 承载内容 | 说明 |
|---|---|---|
SOUL.md | 人格/语气 | Agent 如何呈现自己 |
USER.md | 用户是谁 | 偏好、上下文,来自 USER.md 模板 |
memory/FACT.md | Agent 知道什么 | 活跃项目、技术决策、持久知识,内联读取 +memory工具update写入 |
memory/JOURNAL.jsonl | 发生了什么 | 追加式事件日志,通过memory工具append/search维护 |
prompt.ts 中的loadMemory逻辑会读取memory/FACT.md并生成一段带明确指令的知识块——其措辞大意是:这些是 Agent 过去会话中积累的持久事实与经验,应作为 ground truth 信任,除非有直接证据证明其错误;若发现错误,应通过memory工具update修正 FACT.md,使下一个会话同样受益。这一"信任 + 可修正 + 跨会话传播"的设计,正是 FACT.md 与普通会话上下文最本质的区别:它不是聊天记录,而是 Agent 的长期工作记忆。
FACT.md 与其他记忆机制的边界
要正确使用 FACT.md,还需厘清它与 Cherry Studio 其他记忆/检索机制的分工(详见 docs/references/memory/overview.md):
- Agent File Memory(含 FACT.md):仅作用于单个 Agent,以文件读写持久化在
{agentData}/memory/,跨会话但不跨 Agent; - Knowledge Base(知识库):作用于助手与 Agent,通过"摄取 + 向量/查询"索引检索,跨会话且跨 Agent,适合用户主动整理的可检索参考资料;
- MCP Memory:通过内置
@cherry/memoryMCP 服务器(src/main/ai/mcp/servers/memory.ts)以知识图谱(实体/关系/观察)形式存储,持久化与共享取决于服务器实现。
三者的选择建议是:单个 Agent 的人格与长期项目知识 → Agent File Memory;需要检索的整理型参考资料 → Knowledge Base;由 MCP 驱动的结构化实体/关系记忆 → MCP Memory。FACT.md 属于第一种,它与后两者互不影响——启用知识库不会改变 FACT.md 的行为,反之亦然。
实践建议与注意事项
结合 FACT.md 的官方规范与源码实现,使用与维护 FACT.md 时应注意:
- 只写"关于用户"的持久事实:偏好、环境怪癖、已解决问题是合格的候选;一次性事件请写入
JOURNAL.jsonl; - 产品知识一律实时查询:遵循
cherry-assistant-guideskill,通过mcp__assistant__product_info读取当前清单,切勿复制进 FACT.md,避免静默过期;发布历史不在清单内,需另行获取; - 重写时先读后写:
update是整体覆盖语义,务必保留既有有效内容,只做增量合并; - 不必担心应用更新:实例化的 FACT.md 位于 Agent 数据目录,与模板分离,更新应用不会清空你的定制;
- 记忆会进入提示词:FACT.md 内容在会话启动时内联加载,因此它直接影响每次对话的上下文质量——写得精炼、准确,比写得冗长更有价值。
对于希望深入了解或扩展这套机制的开发者,推荐按以下路径阅读源码:先看 prompt.ts 了解记忆如何进入系统提示词,再看 memoryTools.ts 掌握工具层的行为与安全约束,最后对照 BuiltinAgentProvisioner.test.ts 与 prompt.test.ts 中的用例,即可完整还原"模板分发 → 实例化 → 读取内联 → 工具写入"的全链路。
【免费下载链接】cherry-studio🍒 Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端项目地址: https://gitcode.com/CherryHQ/cherry-studio
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考