如果只把 memory 理解成“把历史对话塞回 prompt”,TencentDB-Agent-Memory 会显得很复杂:它有 Context Offload、L0-L3、Gateway、MemoryCore、Memory Hub、Memory Proxy、Skill、Wiki、CodeGraph、ACL 和 Loadout。 换一种读法就顺很多:它一直在回答两个问题:什么值得留下,下一位 Agent 凭什么可以使用。
同一条经验的生命周期:
1. 当前任务产生厚日志、报错、修复步骤和文档线索
2. v1 先保存原始证据,并把本轮 prompt 里的厚材料换成可下钻摘要
3. v1.0 把记忆引擎拆成独立 Gateway,其他 Agent 框架也能接入
4. v2 把 Chat Memory / Skill / Wiki / CodeGraph 登记成团队资产
5. Memory Hub 控制 owner、visibility、version、status 和绑定关系
6. Memory Proxy 在下一轮请求前,只把当前 Agent 有权使用的资产注入上下文
正文沿着上面这条路径读源码:先讲 v1 为什么要做 Context Offload 和 L0-L3;再讲 v1.0 的 Gateway 化解决了什么;最后讲 v2 的团队记忆为什么需要资产、权限和代理注入。
| 术语 | 先按这句话理解 |
|---|---|
| Context Offload | 把当前任务里的厚工具输出移出 prompt,留下摘要、任务节点和可找回原文的引用。 |
| MMD | Mermaid 任务图,用少量节点表达任务进展,并把节点连回具体工具调用。 |
| Memory Hub | 团队面板,管理资产的 owner、visibility、status、version 和绑定关系。 |
| Memory Proxy | 模型请求代理,在请求发给 LLM 前按 Team、User、Agent、Task 注入资产。 |
| Agent Loadout | 当前 Agent 被允许携带的一组资产,以及每个资产进入上下文的方式。 |
读完后应该能复述。
v1 解决的是单个 Agent 的长任务压力;v1.0 解决的是记忆能力被宿主插件绑住的问题;
v2 解决的是多人、多 Agent 之间如何复用经验又不泄露私有资产的问题。文档里的 /v3/*
是接口版本,不等于“产品 v3”。
本文依据。
v1 侧使用 v1.0.1
与 v1.0.0/v1.0.1 changelog;
v2 发布侧使用 v2.0.0 release notes
与 v2.0.1 release notes;
当前团队分支实现快照是
0468a2a。
feat/server 更接近 v1.0.x 的 Gateway 化主线;feat/server_team 才是 v2 Team Memory 主线。
一、先把版本关系理顺
1.1 v1 先处理单个 Agent 的上下文压力
代码 Agent 的上下文会被两类东西撑大。一类是当前任务里的工具输出:测试日志、搜索结果、文件片段、报错堆栈。 这些内容很厚,却经常只需要在关键时刻回看原文。另一类是跨会话信息:用户偏好、项目决策、稳定约束。 v1 的核心设计就是把两类信息分层处理:当前任务的厚材料先卸载,长期信息再逐层提炼。
Context Offload 的文件层很直白。不同 Agent 有独立目录,同一个 Agent 共享
mmds/、refs/、state.json,每个 session 有自己的
offload-<sessionId>.jsonl。源码注释把这个隔离关系写在
storage.ts
顶部;路径构造在 createStorageContext。
export interface StorageContext {
readonly dataRoot: string;
readonly dataDir: string;
readonly refsDir: string;
readonly mmdsDir: string;
readonly offloadJsonl: string;
readonly stateFile: string;
readonly agentName: string;
readonly sessionId: string;
}
一条工具结果不会直接消失。原文进入 refs/*.md,索引和摘要进入 JSONL,多个工具步骤再合成
MMD 任务图。模型平时读摘要和任务图,真要核对证据时再沿 result_ref 或 node_id
回到原文。这比单向 summary 更适合代码任务,因为“哪一行测试失败”经常比“测试失败了”更重要。
1.2 真正的卸载发生在进入模型前
v1 的 offload 写完文件后,还会在下一次模型调用前改写消息数组。before_prompt_build
的注释把流程分成三段:先快速重放已经确认的替换和删除;如果 token 仍然超阈值,就跑完整 L3 压缩;
最后把 MMD 注入消息。对应代码在
hook 顶部说明
和 fast-path 替换逻辑。
进入模型前:
1. 已确认可替换的 tool_result → 摘要
2. 超过 aggressive 阈值的旧工具消息 → 删除
3. 被删掉的任务过程 → 用 history MMD 补回结构
4. 当前 active MMD → 注入本轮消息
这一步让“省 token”和“可追溯”同时成立。模型不必每轮携带完整工具日志,但任务图仍然告诉它自己做过哪些关键动作。 如果后面发现摘要不够,系统还保留了下钻路径。
1.3 L0-L3 处理长期记忆,不处理团队共享
v1 的长期记忆同样按层组织:L0 是原始对话,L1 是可检索事实,L2 是场景,L3 是 persona 或核心画像。 召回时,代码会把 L3 persona、L2 scene navigation 和工具说明放进相对稳定的 system 区域,把每轮都变化的 L1 命中放进 user prompt 前缀。这个拆分写在 auto-recall 说明 和 上下文组装代码。
// stable: persona / scene navigation / memory tools guide
const appendSystemContext = stableParts.length > 0
? stableParts.join("\n\n")
: undefined;
// dynamic: L1 relevant memories for this turn
return { prependContext, appendSystemContext };
这套设计已经能让一个 Agent 更懂用户和任务,但它还没有回答团队问题。谁拥有这条 Skill? team admin 能不能看别人的私有记忆?Reviewer Agent 应该自动带上哪些 Wiki 和 CodeGraph? 这些问题已经越过 L0-L3 的范围,于是 v2 把“记忆记录”推进成“记忆资产”。
二、v1.0 Gateway 先把能力从插件里拆出来
2.1 如果记忆只活在一个插件里,别的 Agent 很难复用
v1.0.0 的 changelog 把变化说得很清楚:项目从 OpenClaw 专属插件演进成面向所有 Agent 的通用记忆服务, 记忆引擎拆成独立 Gateway 服务进程,提供 v2 HTTP API、TypeScript SDK、Python SDK,并继续保留 OpenClaw 与 Hermes 适配。见 v1.0.0 release notes。
| 阶段 | 主要变化 | 解决的问题 | 下一步为什么需要 |
|---|---|---|---|
| v0.x / v1 本地插件 | Context Offload、L0-L3、SQLite / 文件存储、OpenClaw / Hermes 入口。 | 单个 Agent 的长任务不被工具日志压垮,跨会话能召回用户信息。 | 能力仍贴着宿主插件,其他 Agent 框架要复用时需要标准服务入口。 |
| v1.0 Gateway | 独立服务进程、v2 HTTP API、SDK、Docker、观测指标。 | 记忆能力脱离单个宿主插件,其他 Agent 框架也能通过 API 接入。 | API 能共享能力,但还缺 owner、ACL、审核状态和 Agent Loadout 这些团队规则。 |
| v2 Team Memory | MemoryCore、Memory Hub、Memory Proxy、四类资产、ACL、Agent Loadout。 | 团队内多人、多 Agent 共享经验,同时保留 owner、权限和绑定边界。 | 后续重点转向客户端覆盖、冷启动、会话内命令和写入侧控制,v2.0.1 正在补这些体验。 |
所以 feat/server 这条线更像“把本地记忆服务化”。它的关键价值在于把
OpenClaw 插件里的 memory pipeline 抽成可被外部调用的服务。v2 的团队资产、Hub 和 Proxy,则在
feat/server_team 这条线继续展开。
2.2 团队协作还需要资产边界
一个 API 可以让多个客户端访问同一套能力,但团队协作还需要更多约束。比如,某位工程师在排障中生成的 Skill 是否默认全队可见?一个 Agent 的 user persona 能不能给另一个 Agent 读取?Wiki 和 CodeGraph 应该按项目、团队还是 Agent 绑定? 这些已经进入资产管理范畴,单纯的记忆 CRUD 没法给出稳定答案。
v2 的设计从这里接上:把可复用经验统一登记为资产,再让 Memory Hub 管理归属和可见性,让 Memory Proxy 在请求进入模型前按当前 session 的 Team、User、Agent、Task 装配上下文。
三、v2 把“记忆”升级成团队资产
3.1 四类资产覆盖经验、文档、代码和对话
v2.0.0 的 release notes 给出新的产品定位:让 Agent 的经验、文档、代码沉淀成可复用资产,让下一位 Agent 直接读档。 四类资产分别是 Chat Memory、Skill、Wiki、CodeGraph,见 v2.0.0 的资产说明。 这一步把“memory”的范围从聊天事实扩展到可复用工作资产。
| 资产 | 来自哪里 | 下一位 Agent 怎么用 |
|---|---|---|
| Chat Memory | 对话中的偏好、事实、决策和历史事件。 | 恢复用户、任务和团队的长期语境。 |
| Skill | 跑通过的任务步骤、触发条件、验证规则和资源文件。 | 遇到相似任务时直接按 SOP 执行或参考。 |
| Wiki | 产品文档、设计方案、运维手册等材料。 | 先读结构化页面和链接关系,少走文档迷宫。 |
| CodeGraph | 仓库符号、文件、调用关系和影响路径。 | 改代码前先看 callers、callees 和 impact analysis。 |
用前面的排障例子来看,测试日志和聊天过程会进入 Chat Memory;稳定的上线检查步骤可以沉淀成 Skill; 项目 README、架构文档和运维手册进入 Wiki;代码仓库的符号与调用关系进入 CodeGraph。 这些东西来源不同,但 v2 把它们放进同一个资产框架里管理。
3.2 资产记录先写清 owner、状态和可见性
团队记忆最怕含糊:看起来是共享经验,实际可能把个人对话、未审核步骤或过期文档塞给了不该看的 Agent。
v2 的元数据类型把这个边界落到字段上。AssetEntity 记录
team_id、asset_type、owner_user_id、version、
visibility、status、content_ref 等字段;
FixedAssetBindingEntity 再把资产绑定到某个 Agent,并记录注入方式和优先级。源码见
资产类型和注入模式
与 资产及固定绑定实体。
export type AssetType = "skill" | "llm_wiki" | "code_graph" | "chat_memory";
export type AssetVisibility = "private" | "team" | "restricted" | "agent" | "task";
export type InjectionMode = "direct" | "summary" | "tool" | "reference";
export interface AssetEntity {
asset_id: string;
team_id: string;
asset_type: AssetType;
owner_user_id: string;
version: number;
visibility: AssetVisibility;
status: AssetStatus;
content_ref?: string | null;
}
export interface FixedAssetBindingEntity {
agent_id: string;
asset_id: string;
injection_mode: InjectionMode;
priority: number;
}
排障 Skill 的一次资产化:
1. Coder Agent 跑通连接池超时排障流程
2. 系统生成 Skill,owner_user_id 指向创建者,status 先是 draft / candidate
3. 人工审核后 status 变成 approved,必要时 version 增加
4. owner 把 visibility 调成 team,或用 restricted + ACL 精确授权
5. Release Agent 通过 FixedAssetBinding 绑定这条 Skill
6. 下一轮请求里,Proxy 按权限和 Loadout 决定是否注入这条 Skill
这段类型说明了 v2 的责任边界:资产先有归属和版本,再谈能不能被 Agent 带进上下文。
例如,一个 Skill 可以先是 draft 或 candidate,审核后再变成团队可见;
一个私有 Chat Memory 仍然属于 owner,不能因为同属一个 Team 就自动暴露。
3.3 权限判断比“同一个团队”更细
权限规则在 permission-checker.ts 里以纯函数实现。它的顺序是:资源是否存在,当前用户是不是 owner,
是否为团队成员,visibility 是否允许,再看角色默认权限和显式 ACL。源码注释和实现见
设计要点
与 checkPermission。
一次 read 判断:
asset 不存在或 archived → 拒绝
user 是 owner → 放行
user 不是 active team member → 拒绝
visibility = private 且非 owner → 拒绝
visibility = restricted → 查 user / role / agent ACL
role 默认权限覆盖本动作 → 放行
显式 ACL 命中 → 放行
其余情况 → 拒绝
这里有一个容易忽略的细节:private 的语义很严格,团队管理员也不能直接读取别人的 private asset;
如果确实要共享,需要 owner 主动调整 visibility 或授权。绑定到 Agent 时也有单独规则:
team 和 agent 可在同 team 内绑定,private 需要同 owner,
task 和 restricted 不能直接做固定绑定。见
canBindAsset。
四、Memory Proxy 把团队资产送进不同 Agent
4.1 多客户端接入集中到 Proxy
v2.0.0 里,Memory Proxy 支持 Anthropic 和 OpenAI 两类协议入口;v2.0.1 又增加 OpenCode、DeepSeek Harness、 Codex CLI、WorkBuddy 等客户端接入,见 v2.0.0 Proxy 说明 与 v2.0.1 客户端列表。 README_CN 也明确写着:一套 Proxy,协议不变,把 Agent 的 base URL 指向 Proxy 即可,见 多 Agent 接入说明。
首轮请求时,Proxy 会让用户选择 Team、Agent、Task,并把这个绑定保存下来。之后每轮请求进入模型前, Proxy 根据当前绑定取该 Agent 的 loadout:哪些 Chat Memory、Skill、Wiki、CodeGraph 有权使用,哪些应该直接注入, 哪些只作为工具或引用出现。
4.2 注入管线把协议消息转成统一上下文
InjectionPipeline 的文件注释给了最短路径:raw body → Adapter.parse() → AgentContext →
execute hooks → Adapter.serialize() → modified body。实现里先按协议选择 adapter,再解析出统一的
AgentContext,随后按注入点执行 hook,最后序列化回原协议。见
pipeline 注释
与 process 主流程。
Proxy 每轮请求:
1. 用 URL path 或 system prompt 识别 agent profile
2. adapter.parse(body) 得到 AgentContext
3. 依次跑 system.prefix / system.before_tools / system.after_tools / user.* 等 hooks
4. hooks 返回 Chat Memory、Skill、Wiki、CodeGraph 等 ContextBlock
5. 优先按 agent profile 的 anchor 精确落位,失败则按注入点追加
6. adapter.serialize(ctx) 还原成上游 LLM 能识别的请求
一个极简请求形状:
注入前
system: Agent 的基础规则
user: 帮我修连接池超时
注入后
system: Agent 的基础规则
<session_context>当前 Team / Agent / Task</session_context>
<available_skills>发布检查 Skill 摘要或全文</available_skills>
<user_memory>相关 Chat Memory</user_memory>
<knowledge>Wiki / CodeGraph 的可读摘要或引用</knowledge>
user: 帮我修连接池超时
这里的 InjectionMode 决定资产进入上下文的姿势:direct 和
summary 更偏向直接放文本;tool 和 reference 更偏向告诉 Agent
有哪些工具或引用可按需读取。这样同一条资产可以被“直接带上”,也可以只作为可下钻线索出现。
注入可以避免每轮从头计算。hook 可以声明 cacheStrategy:none 每次执行,
session_init 读取预热缓存,hybrid 合并缓存和本轮新结果。相关逻辑在
hook 执行顺序
和 cacheStrategy 分支。
如果 hook 声明了 anchor,pipeline 会优先把内容放进该 Agent 的语义槽位;槽位解析失败时,才退回通用注入点,见
applyInjection。
4.3 Codex 路径展示了 Proxy 怎样适配新协议
v2.0.1 增加 Codex CLI 接入后,代码里有一条独立的 Responses API handler。
codexHandler.ts 顶部说明它处理 POST /v1/responses,并把
/responses/compact、/memories/trace_summarize、/realtime/calls
这类辅助请求作为 passthrough。主请求才走 session-init、asset injection 和转发。见
Codex handler 注释
与 auxiliary 判断。
Codex 的注入实现也很有代表性。它先构造一个 synthetic OpenAI body,让现有 injection pipeline 处理;
pipeline 把资产块追加到 system message;Codex handler 再把这些文本取出,包进 <tdai_injections>
放回 Responses API 的请求结构。这样可以复用原来的 hook、cache、prewarm 和 injector,不必为 Codex 再写一整套资产注入系统。
对应代码见
Codex asset injection。
五、写入侧也开始有团队边界
读侧的 ACL 和 Loadout 防止没权限的资产被注入;写侧则要防止噪声、临时上下文和高风险任务结果变成团队资产。 所以下面两个机制仍然属于 v2 的团队边界:一个清洗写入 L0 的用户问题,一个给 extraction 加总开关和白名单。
5.1 L0 写入只保留真正的用户问题
团队记忆不只要“读得准”,也要“写得干净”。最新团队分支里,tdai/recorder.ts 的注释解释了一个现实问题:
CodeBuddy、Claude Code、DSH 等 coding agent 的 user message 往往混入大量 harness 上下文,例如
<additional_data>、系统提醒、运行时快照。把整条 user message 原样写进 L0,会让记忆库被每轮都变化的噪声污染。
这段逻辑只抽取最后一条 user 消息里的真实问题,再写入 L0,见
写入动机
和 recordTdaiTurn。
export function extractLatestUserMessage(messages: unknown[]): TdaiMessage | null {
for (let i = messages.length - 1; i >= 0; i--) {
const msg = messages[i] as Record<string, unknown>;
if (msg?.role !== "user") continue;
const content = extractUserQueryText(extractContentText(msg.content));
if (content.trim()) return { role: "user", content };
}
return null;
}
这个细节很重要。Memory 系统如果把运行时塞进来的噪声都当成用户事实,后续检索再聪明也会越查越脏。 写入侧先做清洗,才有资格谈召回质量。
5.2 读和写可以分开开关
Proxy 的注入管线解决读侧:每轮给模型放什么上下文。写侧后来补了一个更小的 extraction-gate:
配置里可以关闭所有 extraction,也可以只允许 skill 或 tdai-memory 这类指定写入。
缺省时保持历史行为,配置缺失或字段写错不会悄悄把写入关掉。源码见
设计说明
与 判断函数。
这让生产系统多了一个实用模式:可以继续注入团队资产帮助 Agent 工作,但临时关闭写入,避免压测、迁移、演示或高风险任务把临时内容写进团队记忆。
5.3 回答用户和抽取记忆可以使用不同模型
写入开关决定“是否整理”,实例上游配置再决定“交给谁整理”。例如,团队可以让 Codex 的主对话继续使用原有模型,
把内部记忆抽取交给另一条统一配置的模型服务。配置按 conversation 与 extraction 分开保存;
查找时优先匹配指定 Agent 来源与类型,再退回同类型的 default。
Codex 主请求读取前者,
内部 system-user 的抽取转发路径读取后者。这里区分的是请求用途,团队资产的访问权限仍由前面的 ACL 与绑定决定。
| 配置模式 | 实例配置改变什么 | 抽取请求能否使用 |
|---|---|---|
official | 不覆盖原有上游选择。 | 可以。 |
custom_unified | 使用实例指定的地址和统一 API key;主请求可按配置替换 model。 | 可以。 |
custom_passthrough | 改变上游地址,在这个覆盖步骤保留已有鉴权头。 | 不可以,保存配置时拒绝。 |
因此,修改抽取配置后,不能只看下一次用户回答用了哪个模型来判断是否生效。 Proxy 按实例缓存配置 5 分钟,读取不会延长到期时间;刷新失败时继续使用最后一次成功值,首次获取失败则不做实例覆盖。 这意味着配置更新存在可见延迟,服务故障时还可能继续使用旧模型。排查应区分主对话和内部抽取请求,并检查实际转发目标。 这些行为分别来自配置缓存与选择函数以及保存时的模式校验。
六、接口 v3 是 v2 产品里的隔离契约
容易混淆的一点是:v2 产品线里出现了 /v3/* 接口。MemoryCore 的接口文档写明,v3 是 RPC 风格,
数据面接口通过 body 或 header 传入 team_id、agent_id、user_id、task_id,
并强制 team + agent + user 三元组隔离;接口目录覆盖 L0-L3、Skill、Knowledge、Chat-Memory、Memory-Prompt、Generation-Log、Meta 等模块。
见 鉴权与隔离字段
和 接口目录与 conversation/add。
所以这里的“v3”要按接口代际读。更精确的产品演进是:v1 本地记忆 → v1.0 Gateway 服务化 → v2 团队资产; API v3 是 v2 系统内部承接身份隔离和模块边界的接口代际。
七、放回 Agent Memory 系列看
TencentDB-Agent-Memory 在这个系列里最值得记住的地方,是它同时覆盖了两条路径。 第一条是运行时上下文:工具日志怎样从 prompt 里移出去,摘要和 MMD 怎样代表任务进展,原始证据怎样找回。 第二条是团队资产:一次任务里产出的聊天记忆、Skill、Wiki、CodeGraph,怎样有 owner、visibility、status 和 Agent 绑定。
| 项目 | 系列里的主问题 | TencentDB-Agent-Memory 的位置 |
|---|---|---|
| Mem0 | 长期记忆如何抽取、去重、排序和召回。 | TencentDB 也做长期记忆,但更强调工具日志、团队资产和 Proxy 注入。 |
| LangMem / LangGraph | 记忆写入放在 hot path 还是后台整理。 | TencentDB 把写入、注入和当前任务 offload 都接到 Agent 运行时旁边。 |
| OpenViking | Memory、Resource、Skill 如何进入统一上下文树。 | TencentDB 用资产和 loadout 管共享;OpenViking 更像统一资源文件系统。 |
| Cognee / Supermemory | 知识处理平台与 context API 怎样服务多个调用方。 | TencentDB 更靠近 coding agent 的请求代理、权限装配和团队协作场景。 |
如果只看 v1,它像一个很工程化的本地 memory 插件:把工具输出和长期画像都做成可追溯层级。 看到 v2 后,重点就变了:它试图把 Agent 做过的事,变成团队可以审核、授权、装配和复用的资产。 这也是它比单纯 vector memory 更重的原因。这份重量来自团队协作必须回答的归属、权限、版本、绑定和注入位置。
参考资料
- TencentCloud/TencentDB-Agent-Memory
- v1.0.0 / v1.0.1 changelog:Gateway、v2 API、SDK 与适配器
- v1.0.1 Context Offload storage
- v1.0.1 before_prompt_build 压缩流程
- v1.0.1 auto-recall 与 stable/dynamic context
- v2.0.0 release notes:四类资产、Memory Hub、Memory Proxy、SDK v3
- v2.0.1 release notes:更多 Agent 客户端、冷启动、Skill 与 Hub 改进
- MemoryCore metadata types:Asset、Visibility、Binding
- MemoryCore permission checker
- MemoryProxy InjectionPipeline
- MemoryProxy Codex Responses handler
- TDAI L0 recorder:只写真实用户问题
- MemoryProxy extraction gate
- MemoryCore v3 API:鉴权、隔离字段与数据面接口