TencentDB Agent Memory 的 L0 到 L3:分层记忆在代码里对应什么

2026-08-16

读 TencentDB Agent Memory 的 README,最容易记住的就是那一行箭头:L0 Conversation → L1 Atom → L2 Scenario → L3 Persona。看上去像四级火箭,一层一层往上提炼。但真去仓库里找这四层的时候会发现,它们在代码里的存在方式差别很大:有的是一组路由,有的是一个结构体,有的是一个目录名,有的干脆和上一层共用同一个类型。

这篇就沿着 feat/server_team 分支上的文件走一遍,把文档里的四个概念名逐个对到代码里的位置。

先交代前提:本文全部内容来自静态读文件。我们没有部署、没有启动过该项目的任何一个容器,也没有发过一次请求。截至 2026-08-16 我们采集的快照是 97f9465,该仓库的默认分支就是 feat/server_team(不是 main,也不是 master),下文所有路径都以这个分支为准。主模块 MemoryCore/package.jsonversion2.0.0-beta.1,另外三个模块的 package.json 版本仍是 0.1.0,接口与默认值随版本变动。

文档那一头:一张四行表和三种不同写法

分层模型的原表在 README_CN.md:246-257,小节标题原文是「记忆不是平铺记录,而是逐层生长」,引导句写「对话首先作为 L0 保存,再由异步 Pipeline 提炼为不同粒度的记忆」。表格逐格照抄如下:

层级保存什么主要用途
L0 Conversation原始对话与完整上下文核对原话、时间和来源
L1 Atom从对话提取的事实、偏好、约束与事件精确召回可执行信息
L2 Scenario围绕项目或场景组织的知识块快速恢复一个工作场景
L3 Core / Persona长期画像、稳定模式与高层认知让 Agent 迅速进入用户和团队语境

表下还有一句(README_CN.md:257):「生成和召回都分层:平时用 L2 / L3 快速进入语境,需要具体事实时通过 BM25、向量检索与 RRF 回到 L1 / L0。」

同一套分层在仓库里另有三种写法:CHANGELOG.md:57-58[2.0.0] 条目写「L0 原始记录 → L1 事实 → L2 场景 → L3 长期认知」;MemoryCore/README_CN.md:5 写「L0 对话、L1 原子记忆、L2 场景记忆和 L3 核心画像」;同文件 :15 又重复一遍。四层的编号一致,每一层的中文名字则每处略有出入——记住编号比记住名字可靠。

这套词元在代码里铺得挺开。我们用正则 \bL[0-3]\bMemoryCore/src,命中 115 个 .ts 文件(该目录 2026-08-16 共 272 个 TS 文件),其中 L0 出现 331 次、L1 806 次、L2 431 次、L3 365 次。

第一处落点:路由层,四层各有自己的动词

MemoryCore/src/gateway/v2-router.ts 的文件头注释(:6-9)直接把四层和动词写出来了:

 *   L0 Conversation: add / query / search / delete
 *   L1 Atomic:       update / query / search / delete
 *   L2 Scenario:     ls / read / write / rm
 *   L3 Core:         read / write

值得注意的是动词的形状:L0 和 L1 是数据库式的 query / search,L2 用的是文件系统式的 ls / read / write / rm,L3 只有 read / write 两个。这不是随手写的排版,后面在落盘层能看到对应。

不过这段注释和实际路由对不上。同文件 :153-172/v3 强隔离数据面白名单 V3_ALLOWED_SUBPATHS 我们数出 18 条(conversation 5 / atomic 5 / scenario 5 / core 3),handler 映射去重后同样是 18 条;而头注释列的是 4+4+4+2=14 个动作。差的四个是每层各多出来的一个 count:158:163:168:171)。两处不一致,以我们实读的仓库状态为准,这里只陈述差异。

访问约束也分层::104:121-126/v3 必填 team_id + agent_id + user_id,缺一即 422,而「L2 scenario/* 与 L3 core/*:原本就是 team+agent 级 profile 聚合,session 忽略」。审计更明确,recordAuditlayer 字段类型是 "L1" | "L2" | "L3":188),注释写「L0 不参与(不可变流水)」(:180)。

第二处落点:存储类型层,L2 和 L3 是同一个类型

MemoryCore/src/core/store/types.ts 就会发现四层并不对称。

L1 有一整族类型:L1SearchResult:56)、L1FtsResult:78)、L1QueryFilter:100)、L1RecordRow:115),注释块标题写「L1 Types (Structured Memories)」(:51-53)。

L0 只有一个 L0Record:141,注释块「L0 Types (Raw Conversations)」在 :136-138),字段是 sessionKey / sessionId / role / messageText / recordedAt / timestamp,外加三维隔离字段 teamId? / userId? / agentId?:153-155)。

而 L2 和 L3 在存储层共用一个 ProfileRecord:266),注释原文是「Canonical L2/L3 profile row shared between local cache and remote store.」(:265),靠一个判别字段区分:type: "l2" | "l3";:269)。稳定 ID 的注释公式是 profile:v1:${sha256(scope + "\0" + type + "\0" + filename)}:267),:273-274 补一句「L2/L3 profile identity is team+agent scoped.」——和路由层「session 忽略」那条对得上。

这种「四层其实是三种存储形态」的结构,在清空接口上体现得最直白:MemoryContentClearResult:446-452)只有三个计数字段,l0Deleted / l1Deleted / profilesDeleted,最后一个的注释就是「L2/L3 profile 行数」。想按层删数据的人,看这三个计数比看四行表更省事。

第三处落点:落盘层,四层各占一个路径

MemoryCore/src/core/storage/types.ts:250-282StoragePaths 常量把四层摊成了目录结构:

常量
L3personapersona.md
L2sceneBlocksDirscene_blocks/
L1recordsDirrecords/
L0conversationsDirconversations/

L2 的单文件路径是 scene_blocks/${name}.md:273),L0 与 L1 则按日期切成 conversations/${date}.jsonl:275)与 records/${date}.jsonl:277)。L3 的 persona 那条注释还多说了一句:「Chat mode stores persona; code mode stores Team Operating Doctrine in the same file.」(:252)——同一个文件名,在两种模式下装的是两种东西。

为什么 L2 是 .md、L0/L1 是 .jsonl,同文件 :15-18 的文件头注释给了分工:「IMemoryStore = database abstraction (L0/L1 structured data → VDB/SQLite)」、「IStorageBackend = file storage abstraction (L2/L3 Markdown files → COS/local-fs)」。回头再看路由层那组动词——L2 用 ls / rm、L3 只有 read / write——就不奇怪了:那两层本来就是当文件在管。

第四处落点:调度层,以及三处对不上的默认值

层与层之间靠什么往上走?MemoryCore/src/config.ts:66PipelineTriggerConfig 注释写得很直接:「Pipeline trigger settings (L1→L2→L3 scheduling)」。它的字段与代码里 ?? 兜底的实际默认值(:563-569)如下:

字段JSDoc 里写的默认值代码实际默认值
everyNConversations55
enableWarmuptruetrue
l1IdleTimeoutSeconds30600
l2DelayAfterL1Seconds9010
l2MinIntervalSeconds900900
l2MaxIntervalSeconds36003600
sessionActiveWindowHours2424

L2/L3 生成侧另有 PersonaConfig:50-64,注释写「controls scene extraction (L2) and user profile generation (L3)」):triggerEveryN 注释与代码都是 50,backupCount 都是 3,sceneBackupCount 都是 10;maxScenes 的 JSDoc 写「default: 20」(:55),代码里是 ?? 15:556)。

于是同一个文件里出现了三处 JSDoc 与代码对不上:l1IdleTimeoutSeconds(注释 30 / 代码 600)、l2DelayAfterL1Seconds(注释 90 / 代码 10)、maxScenes(注释 20 / 代码 15)。这三处只是文本差异的并列陈述,我们不推断哪个是对的,也不推断原因。要照着注释调参的人,最好先打开 config.ts 自己对一眼。

还得强调一句:以上全部是配置里的默认值,不是「你用起来会怎样」的保证。这些数字该改成多少取决于你的用法,项目没有给通用值,我们也没有任何运行依据可以推荐。

L1 的「type」并不是 README 那四个词

README 表里 L1 那格写的是「事实、偏好、约束与事件」。但代码里 L1 抽取出来的 type 枚举是另一套,而且分两家。

MemoryCore/src/config.ts:15 定义 export type MemoryPromptMode = "chat" | "code";。在 MemoryCore/src/core/prompts/l1-extraction.ts 里,两种模式的 prompt 各带一套 type

  • chat 家族(:37-56,小标题原文「【支持提取的三大类型】(必须严格遵守类型规则)」)是三类:persona(用户的稳定属性、偏好、技能、价值观、习惯)、episodic(客观发生的动作、决定、计划或达成结果,注释明写「绝不包含纯主观感受」)、instruction(用户对 AI 提出的长期行为规则、格式偏好、语气控制)。输出 JSON 里写死 "type": "persona|episodic|instruction":79)。
  • code 家族(EXTRACT_WORK_MEMORIES_SYSTEM_PROMPT:105 起)是四类:work_fact / work_task / work_method / work_artifact,输出 JSON 写 "type": "work_fact|work_task|work_method|work_artifact":345)。其中 work_method 还可带 method_typesop|principle|constraint|anti_pattern|heuristic|evaluation_criterion:284)。

也就是说,README 的四个中文词和代码里的 3 或 4 个英文枚举不是一一对应关系,两者不一致。要按类型过滤 L1 的人,得先确定自己跑的是 chat 还是 code 模式。

另外 chat 家族给 instructionpriority 打分区间里有一个负数取值「-1(极其严格的全局死命令)」(:56),这类细节文档侧没有提。

一个容易混的地方:L3 内部还有一套 Layer 1-4

如果你去读 L3 的生成 prompt,会在 MemoryCore/src/core/prompts/persona-generation.ts:69 看到「执行以下四层深度扫描」,四层分别是「Layer 1: 基础锚点」(:71)、「Layer 2: 兴趣图谱」(:75)、「Layer 3: 交互协议」(:80)、「Layer 4: 认知内核」(:84);MemoryCore/src/core/persona/persona-generator.ts:2-3 的类注释也称之为「the four-layer deep scan model」。

这套 Layer 1-4 和 README 的 L0-L3 是两套不同的编号,都写作「层」。我们只陈述二者同时存在,不推断它们之间的关系。真正干活时能用上的是同文件 :166 那条约束:「禁止低层事实堆积:项目名、版本号、任务名、PR、Issue、文档名通常不要进入 L3,除非它们代表可复用范式。」

召回那一头:哪些层自动进上下文,哪些要自己去查

分层不只影响写入,也影响读出。MemoryCore/src/core/hooks/auto-recall.ts:1-10 的文件头注释列了自动召回都做什么:按可配置策略(keyword / embedding / hybrid)搜 L1;keyword 走 FTS5 BM25(注释写「requires FTS5; returns empty if unavailable」);embedding 走 VectorStore 余弦相似度;hybrid 是两者用 RRF 合并。另外两条是「L3 persona injection」和「L2 scene navigation (full injection, LLM decides relevance)」。

RRF 的常量就写在同文件:const RRF_K = 60;:727),注释解释「RRF score for a record at rank r = 1 / (k + r)」(:632),并注明 k=60 来自 RRF 论文(:726)。

四层里唯独 L0 不在自动注入之列。注入的工具指南里写「tdai_conversation_search:搜索原始对话(L0)」(:47),也就是要 Agent 自己发起检索;同处还有一条硬限制:「每轮对话中,tdai_memory_search 和 tdai_conversation_search 合计最多调用 3 次」(:51)。这和路由层「L0 不参与审计(不可变流水)」是同一种定位。

召回预算在 MemoryCore/src/config.tsRecallConfig:84-100,实际默认值 :571-578):maxResults 5、scoreThreshold 0.3、strategy "hybrid"timeoutMs 5000。有意思的是 maxCharsPerMemorymaxTotalRecallChars 的实际默认值都是 0,而它们的 JSDoc 明写「0 disables the per-memory limit」/「0 disables the total limit」(:90-93)——即默认状态下这两道字符预算是关着的。README :257 提到的「条数、字符预算和超时限制」里,条数与超时有非零默认值,字符预算这两项默认为 0,两处口径不同,照实并列在这里。

分层里还没做完的部分

按红线,标了计划中的地方必须照实标出来:

  • ROADMAP_CN.md:42-53「记忆可编辑:L1 - L3 支持修改」是计划项,原文写「目前面板只能查看和删除,无法修正」。
  • ROADMAP_CN.md:55-63「L0 / L1 记忆搜索」列在下个版本 v2.0.1 的计划项里。
  • ROADMAP_CN.md:7 的免责声明原文:「路线图列出的是团队正在推进的工作,不是承诺,范围与时间可能调整。」
  • MemoryCore/src/core/seed/seed-runtime.ts:11:206 有两条 FIXME,原文提到当前只等 L1 变为 idle 就销毁、pipeline 在 L1 idle 后销毁因此 L2/L3 可能受影响。这是源码里的原样标注,我们没有运行验证过它的实际表现。

这一趟走下来

如果只记一件事:README 的四行表是一个概念模型,代码里对应的是三种存储形态、四组路径、一套调度默认值。L0 是不可变流水、不参与审计也不自动注入;L1 是唯一有完整类型族和检索策略的结构化层;L2 与 L3 共用 ProfileRecord,靠 type: "l2" | "l3" 区分,落盘是 Markdown。想在这个项目上定位问题,从 v2-router.ts 的 18 条子路径、core/store/types.ts 的三类记录、core/storage/types.ts 的四个路径常量、config.ts 的两组默认值这四个地方进去,比从 README 那张表进去更快找得到东西。

最后再提醒一次分支:以上路径都在 feat/server_team 上。写 raw 链接或在别的分支上找同名文件,很可能对不上。

延伸阅读


本文依据 TencentDB Agent Memory 官方仓库(github.com/TencentCloud/TencentDB-Agent-Memoryfeat/server_team 分支上的 README、INSTALL、CHANGELOG、ROADMAP 与四个模块的源码整理, 核对日 2026-08-16,对应仓库快照 97f9465。该仓库的默认分支即为 feat/server_team。 本文内容为仓库源码与文档口径,我们没有部署、也没有运行过该项目的任何一个模块, 因此不涉及运行效果、检索质量与性能的任何描述。 该项目主模块处于 beta 阶段、其余模块版本号仍为 0.1.0,参数与接口随版本变动,请以仓库最新内容为准。 该项目会采集并存储团队的对话、文档与代码,属于敏感数据,是否使用请结合自身合规要求评估。 安全与合规相关做法请结合自身环境评估,本文不构成安全方案建议。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。