SurfSense 团队记忆协议(Team 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/SurfSense
SurfSense 是一个开源的 NotebookLM 替代方案,其核心是运行在surfsense_backend中基于 LangGraph 构建的多智能体聊天架构。在系统提示词中,<memory_protocol>的 team 变体(team.md)定义了一套完整的团队级长期记忆写入规范:主智能体如何识别、判定并持久化关于团队本身的持久事实——决策、约定、架构笔记、流程与关键事实。读完本文你将掌握:团队记忆的判定标准、update_memory工具的正确调用时机与参数契约、heading-based Markdown 记忆格式规范、软/硬容量限制的处理策略,以及这套协议从系统提示词组装、每轮记忆注入、工具执行到后端校验的完整落地链路与源码证据。
一、协议概述:团队记忆是什么
team.md定义的是workspace(工作空间)维度的共享长期记忆。与用户个人记忆(private.md)相对,团队记忆存储的是关于这个团队/工作空间的持久事实,而不是关于某个具体用户的个人信息。
协议的核心判定逻辑可以浓缩为一句话:
在理解每一条用户消息之后,检查它是否揭示了关于团队的持久事实——决策(decisions)、约定(conventions)、架构笔记(architecture notes)、流程(processes)或关键事实(key facts)。
这意味着团队记忆关注的是团队成员之间共享、跨会话仍然有意义的信息,例如:
- 团队决定了每周一开例会(应归入
## Product Decisions); - 项目约定使用某种数据库或技术栈(应归入
## Engineering Conventions); - 办公室地址等团队级事实(应归入
## Project Facts)。
反过来,协议明确排除的类别是瞬时聊天噪音(ephemeral chat noise),包括:
- 一次性问答(one-off Q/A);
- 问候语(greetings);
- 会话物流(session logistics,例如"我们这周先把文档整理完"这类临时安排)。
这些内容不入库,避免记忆文档被噪声污染。
与用户个人记忆(private.md)的边界
SurfSense 将记忆按可见性划分为两类,二者的协议模板并列存放在同一目录下:
| 维度 | 团队记忆(team) | 用户个人记忆(private) |
|---|---|---|
| 协议文件 | team.md | private.md |
| 作用域 | Workspace(工作空间)共享 | 单个用户 |
| 记录对象 | 决策、约定、架构笔记、流程、关键事实 | 角色、兴趣、偏好、项目、背景、长期指示 |
| 禁止项 | 个人资料、个人偏好、仅针对个人的长期指示 | 无(本身就是个人维度) |
| 建议 heading | ## Product Decisions、## Engineering Conventions、## Project Facts、## Open Questions | ## Facts、## Preferences、## Instructions |
这条边界在代码层面被严格强制执行:update_memory工具说明明确要求 "NEVER store personal memory in team memory",而后端校验服务validate_memory_scope(见下文"后端校验"一节)会在落库前拒绝在团队记忆中新增个人类 heading,从提示词到运行时形成双重防线。
二、触发时机与调用姿势:alongside,而不是 defer
协议对调用时机的规定非常明确:
If yes, call
update_memoryalongsideyour normal response — don't defer it to a later turn.
也就是说,主智能体一旦判定消息中蕴含持久团队事实,应当在同一轮次内与正常回复并行调用update_memory,而不是推迟到后续轮次。这一"同步写入"约束避免了两个工程问题:
- 上下文漂移导致记忆丢失:推迟意味着后续轮次的模型可能不再持有这条信息,记忆写入就落空了;
- 重复扫描成本:每轮都做一次"是否值得记忆"的判定,保证记忆写入与对话进程同步推进,而不是事后回溯。
团队记忆工具的 Prompt 契约
update_memory的 team 变体提示词位于 prompts/tools/update_memory/team/description.md,它定义了完整的工具语义:
- 职责:维护当前 workspace 的共享长期记忆文档;
- 当前记忆展示:已有记忆会以
<team_memory>块注入系统提示词,其中携带用量与上限(usage vs limit); - 触发条件:团队成员要求记住/忘记某事,或对话中浮现持久的团队决策、约定、架构笔记、流程或关键事实;
- 禁止项:绝不把个人记忆写入团队记忆(个人简介、个人偏好、仅针对个人的长期指示);
- 跳过项:瞬时聊天噪音(一次性问答、问候、会话物流);
- 参数契约:
updated_memory参数必须是完整的替换版 Markdown(FULL replacement),需要做合并与策展(merge and curate),而不是只追加新条目; - 格式要求:heading-based Markdown,新条目形如
- YYYY-MM-DD: text,并保持在<team_memory>中显示的容量限制内; - 兼容性:若旧记忆存在
(YYYY-MM-DD) [fact]形式的遗留标记,须保留其中的信息,但新保存的内容统一写成新格式; - 禁止创建个人 heading:如
## Preferences、## Instructions、## Personal Notes、## Personal Instructions; - 裁剪优先级:决策/约定(decisions/conventions)> 关键事实(key facts)> 当前优先级(current priorities)。
示例佐证
prompts/tools/update_memory/team/example.md 给出了两条最直接的调用示例:
user: "Let's remember that we decided to do weekly standup meetings on Mondays" → update_memory(updated_memory="...\n\n## Product Decisions\n- 2025-03-15: Weekly standup meetings happen on Mondays\n...") user: "Our office is in downtown Seattle, 5th floor" → update_memory(updated_memory="...\n\n## Project Facts\n- 2025-03-15: Office location is downtown Seattle, 5th floor\n...")注意两条示例的共同点:日期由模型填写为记录当天(2025-03-15),内容是对团队事实的中性陈述,heading 使用协议推荐值。这正是YYYY-MM-DD: text规范在真实调用中的标准形态——新增条目时传...省略号表示"保留既有内容",实际调用时是完整文档的替换。
三、记忆格式规范:heading-based Markdown
协议规定了记忆文档的存储格式,这是团队记忆能否被稳定读写、校验和渲染的基础。
3.1 结构约定
- 顶层结构:使用
##级别的 heading 分节,推荐## Product Decisions、## Engineering Conventions、## Project Facts、## Open Questions; - 条目格式:
- YYYY-MM-DD: text,例如- 2025-03-15: Weekly standup meetings happen on Mondays; - 禁止项:不得创建
## Preferences、## Instructions等个人类 heading(团队记忆只能容纳团队级信息)。
3.2 遗留格式迁移
协议明确要求:如果旧记忆中存在(YYYY-MM-DD) [fact]这类遗留标记(legacy markers),需要保留其中的信息、改写为新格式——新保存的内容全部使用 heading-based 格式,遗留标记中的信息被并入对应章节,而不是简单丢弃。
3.3 解析与渲染的源码实现
记忆文档模型在 surfsense_backend/app/services/memory/document.py 中实现。它刻意只解析 SurfSense 记忆的小型 Markdown 契约:##分节 + 带日期的列表项。关键行为包括:
parse_bullet_line同时识别标准格式(- 2025-03-15: text,要求body[10:12] == ": "且日期可被date.fromisoformat解析)和遗留格式(- (2025-03-15) [fact] text);- 无法解析为记忆条目的未知行会被保留为
MemoryRawLine,避免用户手工编辑的内容在写入时丢失; render_memory_document在渲染时会自动规范化:遗留格式子弹统一输出为- YYYY-MM-DD: text;extract_headings通过normalize_heading做归一化(小写、非字母数字字符折叠为空格),用于后续 heading 校验。
这也解释了协议中"write new saves in the heading-based format"的深层原因:即使历史遗留数据存在,save_memory在保存时也会经render_memory_document将文档规范化输出,新写入的内容天然符合协议格式。
3.4 预算控制:软/硬容量限制
<team_memory>注入块会携带chars(当前字符数)与limit(上限)。具体数值定义在 surfsense_backend/app/services/memory/validation.py:
MEMORY_SOFT_LIMIT = 18_000:软上限。超过后提示模型在下次update_memory时合并重复项、移除过期条目、缩短描述;MEMORY_HARD_LIMIT = 25_000:硬上限。超过后写入失败,返回错误消息要求压缩。
协议中的 "Stay within the budget shown in<team_memory>" 指向的正是这套限制机制——模型需要基于注入块中的实时用量自行判断是直接追加,还是先策展合并再写入。
四、从协议到运行时:完整落地链路
team.md并非孤立文档,它是 SurfSense 多智能体系统提示词的组成部分,被"组装 → 注入 → 执行 → 校验"四个环节调用。理解这条链路,才能明白协议规则是如何被真正执行到位的。
4.1 组装:visibility 感知的协议注入
系统提示词由 compose.py 组装,<memory_protocol>一节位于默认流程中<tools>之后。组装逻辑实现在 sections/memory_protocol.py:
def build_memory_protocol_section(*, visibility: ChatVisibility) -> str: variant = "team" if visibility == ChatVisibility.SEARCH_SPACE else "private" fragment = read_prompt_md(f"memory_protocol/{variant}.md") return f"\n{fragment}\n" if fragment else ""即:当线程可见性为SEARCH_SPACE(共享/工作空间线程)时注入team.md,否则注入private.md。同理,update_memory工具说明也按可见性选择变体——_MEMORY_VARIANT_TOOLS集合中的工具会从tools/update_memory/{team|private}/子目录读取 description 与 example(见 tool_instruction_block.py)。也就是说,同样的对话,在私人线程里走用户记忆协议,在共享线程里自动切换为团队记忆协议。
4.2 注入:MemoryInjectionMiddleware 每轮注入
团队记忆的实际注入由 middleware/memory/middleware.py 中的MemoryInjectionMiddleware完成。它在每个 turn 的abefore_agent阶段执行:
- 仅处理以
HumanMessage结尾的轮次; - 根据
ChatVisibility决定 scope:SEARCH_SPACE→ team,否则 → user; - 从
Workspace.shared_memory_md字段读取团队记忆(_load_team_memory),组装为如下注入块:
<team_memory chars="..." limit="25000"> ... </team_memory>- 当字符数超过
MEMORY_SOFT_LIMIT(18000)时,额外注入<memory_warning>块,明确要求模型在下次调用时"合并重复项、移除过期条目、缩短描述"后再新增内容; - 记忆块以
SystemMessage形式插入消息序列(位于第二条消息位置),保证模型每次推理都能看到最新记忆。
这意味着<team_memory>中的 usage vs limit 是实时的——模型据此判断当前预算空间,决定是直接追加还是需要先策展。
4.3 执行:update_memory 工具的两条实现路径
update_memory工具在代码库中有两个等价实现:
- 主智能体直调:
main_agent/tools/update_memory.py中的create_update_team_memory_tool(workspace_id, ...)。它刻意使用一次性短生命周期数据库会话(fresh short-lived session per call),避免 compiled-agent 缓存持有过期请求级会话(见update_memory.py的 docstring); - 记忆子智能体:
subagents/builtins/memory/tools/update_memory.py中的create_update_team_memory_tool,服务于专门的 memory 子智能体,供 supervisor 委托记忆操作(其系统提示词见 subagents/builtins/memory/system_prompt.md)。
两者最终都调用app.services.memory.service.save_memory(scope=MemoryScope.TEAM, target_id=workspace_id, ...),将内容写入Workspace.shared_memory_md字段(见_set_memory)。注意MemoryScope枚举定义了USER = "user"与TEAM = "team"两个值:团队记忆的 target 是 workspace_id,用户记忆的 target 是 user_id,两者在数据库层面分属Workspace.shared_memory_md与User.memory_md两个字段。
五、后端校验:什么能写进团队记忆
协议中的规则并非只停留在提示词层面——save_memory会执行一系列校验,违反规则将导致写入失败并返回错误。全部校验定义在 surfsense_backend/app/services/memory/validation.py。
5.1 校验清单
| 校验器 | 规则 | 违反后果 |
|---|---|---|
validate_memory_size | 长度 ≤ 25000 字符 | error:要求合并相关条目、移除过期条目、缩短描述 |
validate_heading_sanity | 超过 40 字符的长内容必须至少含一个##heading | error:"Memory must be markdown with at least one ## heading." |
validate_memory_scope | 团队记忆不得引入个人 heading(preferences、instructions、personal notes、personal instructions) | error:"Team memory cannot introduce personal headings..." |
validate_diff | 检测被删除的分节、显著缩水(新内容 < 原内容 40% 时提示可能的数据丢失) | warning |
validate_bullet_format | 所有-开头的行都应是标准或遗留格式 | warning:"Non-standard memory bullet: ..." |
soft_limit_warning | 长度 > 18000 时提示合并 | warning |
此外还有两个值得注意的预处理行为(实现在 service.py):
- preamble 剥离:
strip_preamble_to_first_heading会丢弃模型输出中第一个##heading 之前的所有前言文本——即使模型在updated_memory中夹带了"好的,以下是更新:"之类的开场白,也不会污染存储内容; - NO_UPDATE 哨兵:当内容为
NO_UPDATE/NO CHANGE等哨兵字符串时,直接返回status="no_op",不产生任何写入。
5.2 作用域校验的细节:grandfather 机制
validate_memory_scope有一个值得注意的设计:历史遗留的个人 heading 被允许保留(grandfathered),但新增的一律拒绝。实现逻辑是:
- 计算新内容中的
_FORBIDDEN_TEAM_HEADINGS(即preferences、instructions、personal notes、personal instructions)与旧记忆 heading 集合的交集; grandfathered(新旧同时存在)→ 只产生 warning,提示尽快合并到团队安全 heading;introduced(只在新内容中出现)→ 直接 error,拒绝写入。
这保证了协议中"不要创建个人 heading"的规则在历史数据迁移场景下不会破坏既有内容——老数据可以继续存在并逐步收敛,但新数据必须守规矩。
5.3 超限自动重写(forced rewrite)
当内容超过硬上限(25000 字符)且调用时提供了llm参数,save_memory会调用forced_rewrite(rewrite.py),用一次聚焦的 LLM 调用把记忆压缩到限制以内;当重写成功且结果更短时,使用重写结果,并附带 notice:"Memory was automatically rewritten to fit within limits."。这为协议中的 "Stay within the budget" 提供了自动兜底,模型无需总是手动压缩。
5.4 测试佐证
上述校验行为有完整的单元测试覆盖,见 tests/unit/agents/new_chat/tools/test_update_memory_scope.py 与 tests/unit/services/test_memory_service.py。其中可直接验证的关键行为包括:
test_validate_memory_scope_rejects_new_personal_heading_in_team:在团队记忆中新增## Preferences返回 error;test_validate_memory_scope_allows_old_marker_payload_in_team_scope:遗留(2026-04-10) [pref]标记可以被读取、不报错;test_save_memory_blocks_new_personal_heading_in_team_before_commit:含个人 heading 的团队写入不触发 commit,shared_memory_md保持为空——校验发生在落库之前;test_save_memory_allows_grandfathered_personal_heading_in_team:旧记忆已含## Preferences时再次写入成功,但产生 warning;test_save_memory_normalizes_legacy_marker_bullets:遗留标记子弹在保存时被规范化为标准 heading-based 格式(## Memory\n- 2026-04-10: Legacy fact is preserved);test_save_memory_strips_preamble_before_heading:模型输出中首个##heading 之前的 preamble 被剥离,只保留## Facts\n- 2026-04-10: Likes cats。
六、记忆子智能体:委托路径的补充约束
除主智能体直接调用工具外,SurfSense 还提供了专门的 memory 子智能体,其系统提示词(subagents/builtins/memory/system_prompt.md)从操作者视角复述并强化了team.md的原则:
- 目标:用
update_memory持久化 durable 的偏好/事实/指示,同时避免瞬态或不安全的存储; - 可见性:记忆是 workspace 作用域的,不得假设跨 workspace 可见;
- 工具策略:只保存对未来有价值的持久信息;不存瞬时聊天;不存 secrets(除非被明确指示);意图不明时返回
status=blocked并附缺失的意图信号字段; - 输出契约:仅返回一个 JSON 对象,包含
status(success/partial/blocked/error)、action_summary、evidence(含memory_updated布尔值与memory_category语义分类)、next_step、missing_fields、assumptions。其中明确注明:memory_category只是供 supervisor 日志使用的语义分类,不是存储格式,不得强制在保存内容中插入[fact|preference|instruction]标记。
这从侧面解释了为什么team.md中 "do not create personal headings" 的约束会传导到保存内容的实际格式:即使子智能体在内部做了偏好/事实的语义分类,持久化的文档本身仍必须遵循 heading-based 的团队格式,分类只存在于日志层面。
七、总结:团队记忆协议的关键设计要点
回顾整个协议与其代码实现,可以提炼出以下设计要点,它们共同保证了团队记忆在 SurfSense 多智能体系统中的可靠性:
- 判定先行、同步写入:每条用户消息先判定是否蕴含团队级持久事实,是则同轮并行调用
update_memory,绝不 defer 到后续轮次; - 格式统一、自动规范化:heading-based Markdown(
##分节 +- YYYY-MM-DD: text),遗留(YYYY-MM-DD) [fact]标记保留信息但由render_memory_document统一规范化; - 边界刚性、双保险:团队记忆禁止个人 heading,提示词层由
description.md约束,运行时层由validate_memory_scope强制(新增即拒、遗留可迁移); - 预算显式、软硬分层:
<team_memory>实时携带字符数与上限(软 18000 / 硬 25000),超软限注入<memory_warning>,超硬限触发 forced rewrite 自动重写或直接写入失败; - 整链落地、可测试:协议 → 组装(visibility 感知)→ 注入(每轮中间件)→ 执行(主智能体/子智能体双实现)→ 校验(服务层),每一环都有源码与单元测试可查证。
如需深入探索,建议继续阅读以下仓库文件:
- 协议本体:team.md、private.md
- 工具说明与示例:update_memory/team/description.md、update_memory/team/example.md
- 运行时实现:middleware.py、main_agent/tools/update_memory.py
- 存储与校验服务:service.py、validation.py、document.py、rewrite.py
- 测试证据:test_update_memory_scope.py、test_memory_service.py
【免费下载链接】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),仅供参考