深度解析:持久记忆的判定、写入与预算管理)
SurfSense 主 Agent 记忆协议Memory Protocol深度解析持久记忆的判定、写入与预算管理【免费下载链接】SurfSenseOpen-source NotebookLM alternative. Research the open web with live data(Reddit, YT, IG, TikTok, Indeed, Google Search, Maps etc) through one platform, API or MCP server. Join our Discord: https://discord.gg/ejRNvftDp9项目地址: https://gitcode.com/GitHub_Trending/su/SurfSensememory_protocol 是 SurfSense 多 Agent 聊天系统中主 Agentmain_agent系统提示的固定组成片段之一。它回答了一个关键问题在多轮对话中哪些信息值得被记住、何时写入、以什么格式写入、以及如何在不撑爆上下文的前提下长期积累用户画像。本文将基于 memory_protocol/private.md 展开结合仓库中update_memory工具定义、内存服务app/services/memory/的校验管线与注入中间件源码完整还原这套协议的判定规则、文档格式规范、预算上限与底层实现帮助你理解 SurfSense 如何实现跨会话持久记忆。记忆协议在主 Agent 系统提示中的位置SurfSense 的主 Agent 系统提示由 builder/compose.py 中的build_main_agent_system_prompt按固定顺序组装agent_identity [users custom_system_instructions, if any] core_behavior knowledge_base_first dynamic_context routing specialists tools memory_protocol # 默认启用default body citations output_format refusal_and_limits reminder从源码结构看memory_protocol属于default body的一部分——当use_default_system_instructionsTrue时被加入见 compose.py。其位置紧跟在工具说明之后作用是告诉模型在响应每条用户消息时都要并行执行一次是否值得记忆的判定。该片段的加载逻辑位于 builder/sections/memory_protocol.py它是一个**可见性感知visibility-aware**的构建器根据聊天线程的可见性加载不同变体私有线程ChatVisibility.PRIVATE→ 加载memory_protocol/private.md对应个人记忆工作区共享线程ChatVisibility.SEARCH_SPACE→ 加载memory_protocol/team.md对应团队记忆。两套变体共享同一套协议骨架判定 → 调用 → 过滤 → 预算 → 格式仅在记忆主体与允许的 heading上有所区分。本文以私有变体为主线团队变体的差异在文末单独说明。协议核心规则什么值得记、什么时候记、记什么格式private.md全文将协议浓缩为四条硬规则规则一先判定再响应——durable facts 检测After understanding each user message, check: does it reveal durable facts about the user — role, interests, preferences, projects, background, or standing instructions?模型在理解每条用户消息之后需要先回答一个布尔问题这条消息是否揭示了关于用户的持久事实durable facts协议给出了六类典型候选类别示例role身份/角色我是一名自由摄影师interests兴趣我是太空爱好者preferences偏好我更喜欢简洁的回答projects项目我在做一个自然纪录片background背景我上个月搬到了东京standing instructions长期指令永远用要点形式回复我规则二即时写入不推迟If yes, callupdate_memoryalongsideyour normal response — dont defer it to a later turn.一旦判定为持久事实模型必须在当轮与正常回复并行调用update_memory工具严禁推迟到后续轮次。这一alongside设计避免了两种常见失败模式一是记忆被后续对话冲淡而遗忘写入二是把记忆写操作延迟到会话结束时集中处理导致上下文窗口中的事实过期。规则三过滤临时噪音Skip ephemeral chat noise (one-off Q/A, greetings, session logistics).协议明确列出三类必须跳过的内容一次性问答one-off Q/A、问候语greetings、会话后勤信息session logistics如稍等、下次继续之类。这与规则一形成互补判定不只看是否是事实还要看是否值得持久保存。规则四受预算约束Stay within the budget shown inuser_memory.每次调用update_memory都必须遵守user_memory中展示的字符预算。预算的数值定义见后文预算与限额小节其核心目的是防止记忆文档无限膨胀、挤占上下文窗口。update_memory 工具契约全量替换而非追加协议要求调用update_memory工具其完整契约定义在 tools/update_memory/private/description.md用途维护该用户的个人长期记忆文档触发时机用户要求记住/遗忘某事或对话中出现持久事实、偏好、指令时调用人称规范写入条目时必须使用user_name中的名字例如写 Alex prefers… 而非 The user prefers…且不允许单独存储名字作为一条记忆噪音过滤跳过一次性问答、问候、会话后勤参数updated_memory——完整替换后的 markdown 文档merge and curate不只是 append。也就是说模型每次都要基于user_memory中看到的当前全文输出合并、去重、整理后的整份新文档格式heading-based markdown条目位于##标题之下推荐标题为## Facts、## Preferences、## Instructions也允许更清晰的自然标题新 bullet 形如- YYYY-MM-DD: text遗留标记迁移若旧记忆使用(YYYY-MM-DD) [fact|pref|instr]标记必须保留其中的信息但新文档改用新格式书写。参数语义updated_memory为什么是全量替换从工具实现 main_agent/tools/update_memory.py 可以确认这一点update_memory是一个异步 LangChain 工具接收updated_memory: str后调用save_memory(scopeMemoryScope.USER, target_iduid, contentupdated_memory, ...)其 docstring 明确写道Pass the FULL updated markdown document, not a diff. 工具每次调用都会打开一个全新的短生命周期数据库会话async_session_maker()以避免编译后的 Agent 缓存持有过期的请求级会话——这保证了即使在 Agent 被缓存复用的场景下记忆写入也始终落到最新数据上。官方示例逐一解读private/example.md 给出了五种典型写入场景正好覆盖协议的六类持久事实示例 1随手暴露的兴趣casual durable factuser_nameAlex/user_name, user_memory is empty. user: Im a space enthusiast, explain astrophage to me → update_memory(updated_memory## Facts\n- 2025-03-15: Alex is a space enthusiast\n)要点即使是顺便提及的兴趣只要持久就值得记录用名字、用中性标题。示例 2明确的偏好指令durable preference mergeuser: Remember that I prefer concise answers over detailed explanations → update_memory(updated_memory## Facts\n- 2025-03-15: Alex is a space enthusiast\n\n## Preferences\n- 2025-03-15: Alex prefers concise answers over detailed explanations\n)要点新偏好并入既有文档旧事实space enthusiast必须保留——体现merge and curate而非覆盖。示例 3事实更新updated factuser: I actually moved to Tokyo last month → update_memory(updated_memory...\n\n## Facts\n- 2025-03-15: Alex lives in Tokyo (previously London)\n...)要点更新事实时保留历史previously London日期记录的是写入当天而非事件发生日。示例 4自然标题natural headinguser: Im a freelance photographer working on a nature documentary → update_memory(updated_memory...\n\n## Current Focus\n- 2025-03-15: Alex is a freelance photographer\n- 2025-03-15: Alex is working on a nature documentary\n)要点除推荐标题外允许使用更贴切的自然标题## Current Focus证明格式是语义驱动而非死板枚举。示例 5长期指令standing instructionuser: Always respond in bullet points → update_memory(updated_memory...\n\n## Instructions\n- 2025-03-15: Always respond to Alex in bullet points\n)要点明确的长期指令归入## Instructions。记忆文档格式规范heading-based Markdown协议规定记忆是heading-based markdown其正式解析与渲染契约实现在 services/memory/document.py。该文件是记忆的小 markdown 契约解析器只识别两种结构##开头的章节标题is_section_heading以##开头且##之后有非空内容-开头的日期 bulletparse_bullet_line分为两种写法规范写法canonical- YYYY-MM-DD: text解析规则_parse_canonical_bullet前 10 个字符必须是 ISO 日期date.fromisoformat且第 11、12 个字符必须是:之后才是条目文本。遗留写法legacy marker- (YYYY-MM-DD) [fact] text对应协议中提到的(YYYY-MM-DD) [fact|pref|instr]旧标记LEGACY_MARKERS frozenset({fact, pref, instr})。解析器通过_parse_legacy_bullet识别并在渲染时统一归一化为新格式。遗留标记的自动迁移test_memory_service.py 中的test_save_memory_accepts_legacy_marker_payload直接验证了这一行为输入- (2026-05-19) [fact] Legacy marker memorysave_memory输出## Memory\n- 2026-05-19: Legacy marker memory——遗留 bullet 被归入默认## Memory章节并转写为规范格式。这与协议中preserve the information but write new saves in the heading-based format的要求完全一致。未知行保留原则document.py的模块 docstring 特别注明Unknown lines are preserved so user edits are not lost。即解析器只识别标题和 bullet其余行原样保留——这意味着如果用户或模型手动编辑过记忆文档编辑内容不会被格式化过程抹掉。预算与限额软限制 18,000 / 硬限制 25,000协议要求stay within the budget shown inuser_memory具体数值定义在 services/memory/validation.py常量值含义MEMORY_SOFT_LIMIT18,000 字符超过后触发软限制警告MEMORY_HARD_LIMIT25,000 字符超过后保存失败除非可被 LLM 强制重写软限制警告soft_limit_warningvalidation.py在文档超过 18,000 字符时返回警告提示合并重复项、删除过时条目但不阻止保存。同时注入中间件也会在接近上限时向模型追加memory_warning提示见下文注入机制。硬限制与强制重写在 service.py 的save_memory中如果len(next_content) MEMORY_HARD_LIMIT且传入了llm系统会调用forced_rewrite尝试用 LLM 将内容压缩到限制以内重写成功后自动添加 noticeMemory was automatically rewritten to fit within limits.。若重写失败或仍超限validate_memory_size会返回 errorMemory exceeds 25,000 character limit ... Consolidate by merging related items, removing outdated entries, and shortening descriptions.且不执行任何写入。软限制检查的时机需要特别指出软限制警告在保存成功后才被计算并随SaveResult返回service.py而硬限制校验发生在保存前——所以模型看到的user_memory中的 usage/limit 与警告是分阶段生效的。底层实现save_memory 的完整校验管线update_memory工具最终落到 services/memory/service.py 的save_memory。理解这条管线能帮助你预测模型写入的每一步结果Scope 归一化MemoryScope.USER/MemoryScope.TEAMStrEnum加载目标user 场景查询User表按 UUIDteam 场景查询Workspace表按 int 工作区 id用户/工作区不存在则返回 error读取旧记忆user 读User.memory_mdteam 读Workspace.shared_memory_md迁移121_add_memory_md_columns.py、122_migrate_and_drop_old_memory_tables.py引入的存储列剥离前导杂文strip_preamble_to_first_heading丢弃第一个##标题之前的模型 preamble防止推理文本被误存NO_UPDATE 哨兵内容为NO_UPDATE/NO_CHANGE等时返回no_op不写库_NO_UPDATE_SENTINELS超限强制重写超过硬限制且有llm时调用forced_rewrite见 services/memory/rewrite.py顺序校验validate_memory_size硬限制→validate_heading_sanity要求至少有##标题→validate_memory_scope团队记忆禁止个人标题→render_memory_document(parse_memory_document(...))归一化渲染提交写回目标对象的memory_md/shared_memory_md字段并 commit事后审计validate_diff检测被意外删除的章节、超过 40% 的异常缩水、validate_bullet_format检测非规范 bullet、soft_limit_warning软限制警告全部作为 warnings 返回。validate_heading_sanityvalidation.py的规则值得单独说明空内容、长度 ≤ 40 字符、或已包含##标题的内容可通过超过 40 字符的无标题长文 blob 会被拒绝报错Memory must be markdown with at least one ## heading.。这一校验正是协议heading-based markdown在服务端的强制落地。SaveResultservice.py将上述所有状态saved/error/no_op、消息、warnings、diff_warnings、format_warnings、notice 统一打包返回给工具工具再以 dict 形式回传给模型。记忆注入机制user_memory 从哪来协议中反复引用的user_memory与预算信息由 main_agent/middleware/memory/middleware.py 的MemoryInjectionMiddleware在每个回合注入私有线程注入个人记忆包装为team_memory的对称结构user_memory chars... limit...其中limit即MEMORY_HARD_LIMIT当记忆超过MEMORY_SOFT_LIMIT时额外注入memory_warning指示模型在下次update_memory调用时合并重复项、删除过时条目、压缩到限制以内注入仅在最后一条消息是HumanMessage时执行避免在工具结果之后重复注入。同时prompts/dynamic_context/private.md 对user_memory做了语义定位它携带用户跨会话积累的持久上下文角色、兴趣、偏好、项目、背景、长期指令并报告当前字符使用量与硬限制系统提示同时强调把它当作回答的背景色background colour而不是任务本身——即记忆用于塑造回答不喧宾夺主。与团队记忆team.md的差异作为对照私有协议存在一个team 变体memory_protocol/team.md适用于工作区共享线程ChatVisibility.SEARCH_SPACE。差异集中在三个维度记忆主体个人记忆面向 userrole、interests、preferences团队记忆面向 workspacedecisions、conventions、architecture notes、processes、key facts。允许的章节团队变体推荐## Product Decisions、## Engineering Conventions、## Project Facts、## Open Questions并明确禁止创建个人类标题## Preferences、## Instructions、## Personal Notes、## Personal Instructions。这一约束在服务端同样强制执行validate_memory_scopevalidation.py会拒绝向团队记忆新引入这些禁止标题_FORBIDDEN_TEAM_HEADINGS但对旧文档中已存在的个人标题采用grandfather策略——只给警告、不阻断并提示将其并入团队安全标题。工具侧 team/description.md 还补充了裁剪优先级decisions/conventions key facts current priorities。示例差异team/example.md 展示了团队场景的典型条目如 Weekly standup meetings happen on Mondays归入## Product Decisions、Office location is downtown Seattle, 5th floor归入## Project Facts。测试与验证协议行为的可观测保障记忆服务的核心行为均有单测覆盖tests/unit/services/test_memory_service.pytest_save_memory_saves_heading_based_memory规范 heading 格式正常保存commit 恰好一次test_save_memory_accepts_legacy_marker_payload遗留 marker 自动迁移为新格式test_save_memory_rejects_long_no_heading_payload无标题长文被拒绝旧记忆保持不变、零提交test_save_memory_no_update_sentinel_is_no_opNO_UPDATE哨兵不产生写入。此外tests/unit/agents/new_chat/tools/test_update_memory_scope.py 覆盖工具层面的 scope 行为。这些测试共同保证了协议文本中判定 → 调用 → 过滤 → 预算 → 格式的每一条规则在服务端都有对应的可执行校验而不是仅靠提示词自律。实践要点总结把memory_protocol/private.md与仓库实现对照可以提炼出这套记忆系统的四条最佳实践判定先于响应每条消息都做持久事实检测role/interests/preferences/projects/background/standing instructions命中即当轮调用update_memory与回复并行绝不推迟严格过滤噪音一次性问答、问候、会话后勤一律不入库保持记忆文档的信息密度全量合并写入updated_memory参数要求提交完整替换文档merge and curate必须保留旧事实、用写入日期标记、优先使用## Facts/## Preferences/## Instructions等规范标题敬畏预算时刻关注user_memory中的 usage/limit18,000 字符是软警告线25,000 字符是硬限制接近上限时主动合并重复项、删除过时条目——否则要么触发 LLM 自动重写要么保存失败。这套协议的价值在于把记忆从黑盒变成了一个可校验、有预算、格式统一的一等公民子系统提示词层负责判定与时机update_memory工具负责契约化写入app/services/memory/负责解析、校验、迁移与审计中间件负责每轮注入。理解它你就理解了 SurfSense 跨会话个性化以及团队共享工作区背后的完整机制。【免费下载链接】SurfSenseOpen-source NotebookLM alternative. Research the open web with live data(Reddit, YT, IG, TikTok, Indeed, Google Search, Maps etc) through one platform, API or MCP server. Join our Discord: https://discord.gg/ejRNvftDp9项目地址: https://gitcode.com/GitHub_Trending/su/SurfSense创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考