TencentDB Agent Memory 的隐私边界:那个三选一的读取授权判定
README_CN.md 第 228 行有一句很好记的话:「新 Chat Memory 和 Skill 默认私有。分享是一个明确动作,不是默认泄漏。」这类句子在项目主页上到处都是,看多了容易滑过去。但它有个好处——它是可以被代码证伪或证实的。这篇就干一件事:把这句话往下追,追到 MemoryPanel 里那个真正决定「你能不能读到这条记忆」的函数上。
先把前提说清楚。我们看的是 TencentCloud/TencentDB-Agent-Memory 仓库,截至 2026-08-16 的快照 97f9465,分支是 feat/server_team——这个仓库的默认分支就是这个 feat/ 功能分支,不是 main 也不是 master,所以下文提到的每一个文件路径,都要按这个分支去找。另外,MemoryPanel 这个模块的 package.json 里版本号还是 0.1.0,包名叫 team-memory-control(目录名和包名不是一回事),仓库 ROADMAP_CN.md 开篇也写着「路线图列出的是团队正在推进的工作,不是承诺」。看代码时把这层限定带上。
一个函数,三条放行路径
决定 Chat Memory 读权限的函数叫 authorizeChatMemoryRead,位置是 MemoryPanel/src/panel/http/routes/chat-memory.ts 的第 1999 到 2036 行。它上面 :1992-1997 的注释把规则写死了:owner / team-shared / borrowed,三条任一命中即放行。
第一条是 asset.owner_user_id === meUserId(:2006)。自己的东西自己读,没有悬念。
第二条是 asset.visibility === "team" 且调用者是该 team 的成员(:2007-2009)。这条的注释里有一句值得单独拎出来:「外 team 用户即便知道 asset_id 也不算可读」。也就是说,visibility 是 team 并不意味着「凭 id 就能拿」,还得再过一次 team 成员校验。成员校验走的是 MemoryPanel/src/panel/http/routes/knowledge/common.ts 里的 isTeamMember(:70-83),实现是调内核 team-member/get,code === 0 && !!env.data 才算成员,异常时保守返回 false(:80-82)。
第三条最不直觉:借入关系(:2010-2031)。判定方式不是查一张授权表,而是遍历——先拉出调用者在这个 team 下 owner_user_id === meUserId 的 active agent,再对每个 agent 逐个调 agent-fixed-asset/list,只要有任何一个 agent 绑定过这条 asset,就放行。换句话说,「这条记忆已经被装配到我的某个 Agent 身上」本身就构成读权限。
三条都不命中,或者中间任何一步抛异常,都 fallthrough 到 deny(:2032-2035)。这个 catch 到 deny 的写法值得注意:网络抖动、内核返回非 0,结果都是「读不到」,而不是「放行」。
这个函数被两个端点共用:/chat-memory/layer(调用点 :1077)和 /chat-memory/search(调用点 :1691)。列表读和搜索读在业务上是两条不同的路径,在这份代码里它们收敛到了同一个判定函数上,也就是说读权限的口径只有这一处。你要核对读权限的行为,看这一个函数即可,不必分别去读两个端点的实现。
写和删为什么不走这条路
读可以借入,删不行。这一点在代码里是分开实现的,不是在同一个函数里加个 flag。
/chat-memory/clear 的校验在 :1391-1410:逐条调 asset/get,要求 asset_type === "chat_memory" 且 owner_user_id === meUserId,任一条不符就整批拒绝。三个错误码分别是 404 BLOCK_NOT_FOUND、400 NOT_CHAT_MEMORY、403 NOT_ASSET_OWNER。
/chat-memory/layer-delete 同样只认 Owner,:1437-1438 的注释把理由写出来了:读可以借入,但删除内容只允许 Owner,避免借入方清掉别人的记忆。
还有一条相邻的:/chat-memory/agent-fixed 在 agent.owner_user_id !== meUserId 时直接 403 NOT_YOUR_AGENT(:197-199)。
这里有个容易被忽略的层次问题。:1361-1363 的注释写得很直白:面板这一层只做前置拦截,最终权威校验在内核 /v3/chat-memory/clear。而 Python SDK 的文档 sdk/memory-core/python/README_CN.md:163 从另一头也说了同一件事——「内核不做用户级鉴权。需要『仅 Owner 可清空』请走面板后端 /api/v1/chat-memory/clear」。两处一对,读者能得到的信息是:这套 Owner 限制是面板层的语义,绕开面板直接打内核时,这层判定不在链路上。
「借入」上限是 2,且当前塞在 metadata 里
借入关系的数据模型在 MemoryPanel/src/panel/domain/chat-memory-governance.ts。
MAX_IMPORTED_AGENTS = 2(:16)——一个 Agent 最多借入 2 个其他 Agent 的记忆。这是配置里的常量值,不是任何运行表现的承诺,也别拿它去推算什么容量结论。
关系体 ChatMemoryAgentRel 只有两个字段(:18-23):memory_shared_with_team: boolean 和 imported_agent_ids: string[],默认值是 { memory_shared_with_team: true, imported_agent_ids: [] }(:25-28)。:20 的注释另外标了一句「本期 UI 锁定 true」。
validateImportedAgents(:33-52)逐条校验五件事:必须是数组、数量不超过 2、不能是自己、必须在同一 team、不能重复。
持久化方式是这份文件里最该照实写的一段。:9-13 的注释原文说明:后端 schema 还没落 chat_memory_rel 真字段,演示阶段把它塞进 Agent.metadata_json 的 "chat_memory" namespace(常量 METADATA_NS = 'chat_memory',:54),读写函数是 readChatMemoryRel(:62)和 writeChatMemoryRel(:77),并注明后端补字段后把这两个函数的实现切到真字段即可。这是项目自己标注的演示阶段方案,不是既成的存储结构。
可见性取值:三处口径不一样
顺着 visibility 往外看,会碰到三个不同的数字,这里只陈述差异、不推断原因。
后端 knowledge 侧的合法值是 5 种:MemoryPanel/src/panel/http/routes/knowledge/allocate-routes.ts:47 的 VALID_VISIBILITY 是 ['private', 'team', 'restricted', 'agent', 'task']。前端类型定义也是这 5 种(MemoryPanel/web/src/lib/api/types.ts:74 与 :94)。
根 README_CN.md:230-235 的可见性表只列 4 行:private(只有 Owner 可读,团队管理员也不例外)、team(团队成员可读,Owner / Admin 负责管理)、restricted(通过 User / Role / Agent ACL 精确授权)、agent(用于同团队 Agent 的定向装配)——没有 task。
而 Chat Memory 这条线上,前端只暴露二元 scope:MemoryPanel/web/src/lib/api/chat-memory.ts:140 的 patchScope 签名是 (blockId, scope: 'team' | 'private'),后端在 chat-memory.ts 里多处做 visibility === "private" ? "private" : "team" 的归一;前端服务层的 AssetConfigScope 同样是二值(MemoryPanel/web/src/services/asset-scope-store.ts:24)。
所以真要落到 Chat Memory 的读判定上,起作用的实际只有「是不是 team」这一个二元问题——authorizeChatMemoryRead 第二条分支判的就是这个字面量。5 种、4 种、2 种这三个口径同时存在于仓库里,以上述实读位置为准。
还有两处和边界有关的实现细节
一是固定资产 tab 的过滤放在前端。chat-memory.ts:201-206 的注释写明:该端点用 apply_visibility_filter=false 返回全部物理绑定,由前端按 scope + owner 把「其他人已切私密」的条目灰化;记忆内容与详情不允许查看(前端展示占位),解绑操作仍允许。也就是说,这一处的「看不见」是在返回全量之后由前端处理的。
二是入站门。MemoryPanel/src/panel/http/middleware/validate-panel-headers.ts 要求每个请求带 x-tdai-service-id(缺失 400 MISSING_INSTANCE_ID,:34-37)和 x-tdai-user-key(缺失 400 MISSING_USER_KEY,:50-53),唯一例外是 auth/verify,它的 user_key 放在 body 里(:9、:49)。但 /api/v1/knowledge/status-callback(MemoryPanel/src/panel/http/routes/knowledge/callback-routes.ts:117)注册时没有挂这个中间件,文件头注释自述原因是「S2S,无浏览器 session header」(:16),身份靠 payload 里的 service_id 从注册表解析实例凭证(:13-14);我们在该文件里没有 grep 到 Authorization / Bearer 之类的额外校验。与之配套的 KnowledgeTaskRegistry(MemoryPanel/src/panel/state/knowledge-task-registry.ts)把 owner_user_key 暂存在进程内存里(字段 :20,TTL 默认 1 小时,:25),文件头注释写「本期临时方案,下期持久化」(:2)。
你自己怎么核这几行
上面每个结论都给了文件和行号,照着去仓库里翻就行,顺序建议是:先看 chat-memory.ts:1992-2036(读判定本体),再看 :1077 和 :1691 两个调用点确认复用范围,然后看 :1391-1410 和 :1437-1438 确认写删口径,最后看 chat-memory-governance.ts 整个文件(它只有几十行,比路由文件好读得多)。在我们采集的这份快照(2026-08-16)里,chat-memory.ts 全文 2040 行,是 MemoryPanel/src/ 下最大的一个文件,行号也会随版本变,直接跳行号比通读省事,但对不上时以仓库当前内容为准。
有几件事这篇没法帮你回答,也说清楚:内核侧 /v3/chat-memory/* 到底怎么判、auth/verify 返回什么、ACL 表长什么样,都不在 MemoryPanel 这一层,我们没有在 MemoryCore/ 下核过;user_type 除了 system_admin 还有哪些取值,我们在 MemoryPanel 里也没找到完整枚举。这套判定接到真实数据上表现如何,更不是读源码能得出的结论。
回到开头那句 README。它在代码里确实有对应物,而且比一句口号具体得多——三条放行路径、一个异常即拒的 fallthrough、写删另走 Owner 校验、借入上限 2 且当前存在 metadata_json 里。这些是能被逐行指认的事实。至于这套边界够不够用,取决于你把什么数据交给它,以及你的合规要求怎么定——这个项目会采集并存储团队的对话、文档与代码,这件事本身就该先过一遍自家的评估流程,再谈配置细节。
延伸阅读
- 从头读起:TencentDB Agent Memory 是什么:团队级 Agent 记忆中枢怎么读
- 本专题共 40 篇,完整分组目录见专题页
- TencentDB Agent Memory 的数据模型:代码自述还在演示阶段
- TencentDB Agent Memory 面板能做哪些操作:从路由与菜单反推
本文依据 TencentDB Agent Memory 官方仓库(github.com/TencentCloud/TencentDB-Agent-Memory)
feat/server_team 分支上的 README、INSTALL、CHANGELOG、ROADMAP 与四个模块的源码整理,
核对日 2026-08-16,对应仓库快照 97f9465。该仓库的默认分支即为 feat/server_team。
本文内容为仓库源码与文档口径,我们没有部署、也没有运行过该项目的任何一个模块,
因此不涉及运行效果、检索质量与性能的任何描述。
该项目主模块处于 beta 阶段、其余模块版本号仍为 0.1.0,参数与接口随版本变动,请以仓库最新内容为准。
该项目会采集并存储团队的对话、文档与代码,属于敏感数据,是否使用请结合自身合规要求评估。
安全与合规相关做法请结合自身环境评估,本文不构成安全方案建议。