终期拆解"幕后":L1→L3 的提炼为什么必须异步,任务如何被调度与容错,以及 MemoryCore / Knowledge / Proxy / Panel 四模块如何协作。
一、为什么必须异步
L1→L3 的提炼全部要调 LLM,而 LLM 调用是慢的(秒级)、贵的(Token)、不可靠的(超时 / 限流)。如果放在 Agent 请求的同步链路上,会直接拖垮每一轮对话的响应。
所以官方把提炼从读路径里拆出去,做成独立的异步管线:
- Agent 对话的同步链路上只做:L0 记录(无 LLM,毫秒级)+ 召回注入(有预算与超时保护);
- L1/L2/L3 的提炼:定时扫描 → 任务入队 → Worker 竞争消费 → LLM 执行 → 写回,全部在后台完成。
官方架构文档的写入主流程:
Agent 对话 → Proxy 转发时旁路记录(或 SDK addConversation)
→ l0-recorder.ts 写 JSONL + l0_conversations
→ timer-scanner.ts 定时扫描 + pipeline-worker.ts(分布式锁/重试/死信)
→ L1: core/record/l1-extractor.ts 单次 LLM JSON 抽取 + 场景分段 + 批量去重
→ L2: core/scene/scene-extractor.ts 场景知识块整理(LLM 用工具读写 md)
→ L3: core/persona/persona-generator.ts 长期画像生成
二、调度层:Timer Scanner(无主分片扫描)
timer-scanner.ts 采用 Scheme D 无主模式,核心设计:
- 16 个分片 ZSET:
tdai_memory:timers:shard_{0..15},成员格式{instanceId}\x00{sessionId}:{timerType},score = fireAtMs(到期时间); - 所有 Pod 都扫描,不需要选主(leaderless);
- Lua 原子认领:
ZRANGEBYSCORE + ZREM在一个 Lua 脚本里完成,天然避免多 Pod 重复消费; - 性能:每次扫描固定 16 次 shard 认领调用,O(1) 与实例数量无关,分片避免热 key;
- 默认扫描间隔 2s,每 shard 每次最多 1000 个到期 Timer。
到期 Timer 被转换为任务入队(enqueueTask),任务类型如 L1_idle / L2_schedule 等。
三、执行层:Pipeline Worker(竞争消费 + 锁 + 重试 + 死信)
pipeline-worker.ts 头注释概括了全部机制:
需求 #12 Worker 竞争消费 + #13 死信队列与失败处理
- XREADGROUP Consumer Group 竞争消费 task(单一队列)
- 分布式锁保护:per-session (L1/L2), per-instance (L3)
- 锁续约:每 30s 续约,续约失败 abort
- LLM 执行 + 写入:取 buffer → 调 LLM → 写 VDB/COS
- 级联调度:L1→L2 (via onL1Complete timer推进), L2→L3 (直接入队)
- 死信队列:超过重试上限 → 写入死信
- 重试策略:抢锁失败 5s 重投, LLM 超时指数退避 5s/15s/45s
- 幂等:VDB upsert by record_id, COS 覆盖写
拆开看:
| 机制 | 说明 |
|---|---|
| 竞争消费 | 默认 60 并发协程,不同 session 并行执行;poll 200ms |
| 分布式锁 | 粒度:L1/L2 按 session、L3 按 instance;TTL 10min,每 30s 续约,续约失败 abort(配合 AbortSignal 中断在途 LLM 调用) |
| 重试 | 抢锁失败 5s 重投(最多 15 次重排 MAX_LOCK_REQUEUE);LLM 超时指数退避 5s / 15s / 45s,最多 3 次 |
| 死信 | 超过重试上限 → 持久化死信条目(任务 + 错误 + 重试次数 + 时间),供人工排查 |
| 幂等 | VDB upsert by record_id;COS 覆盖写;pending 消息超时回收(5min) |
| 级联 | L1 完成 → onL1Complete 推进 L2 Timer;L2 完成 → onL2Complete 设置 L2 maxInterval,或直接入队 L3 |
这套机制与 Knowledge 的 BuildQueue 是两套成熟异步范式,官方在二次开发指南里明确建议复用:
异步任务:Core 的 pipeline-worker(锁/重试/死信)与 Knowledge 的 BuildQueue(SerialQueue + 409 busy + 重启恢复)是两套成熟机制,扩展异步功能优先复用。
四、存储抽象:IMemoryStore
L0/L1 的读写不直接依赖 SQLite,而是通过抽象接口:
抽象契约:
src/core/store/types.ts的IMemoryStore(sync-first 接口,全部方法返回 MaybePromise,上层只依赖接口不依赖实现);装配点:src/core/store/factory.ts的createStoreBundle,按config.storeBackend分发(当前sqlite/tcvdb两个实现)。
这意味着换存储后端(SQLite → TCVDB)只新增一个实现类,上层逻辑(抽取 / 召回 / 去重)完全不动。
五、整体系统架构
架构四模块协作图:
Agent (Claude Code / Codex / CodeBuddy / WorkBuddy / ...)
│ base URL 指向 Proxy,协议不变、零代码接入
▼
┌─────────────────────┐
│ MemoryProxy :8096 │ LLM API 网关:拦截 Anthropic/OpenAI/Responses
│ (请求改写/注入) │ 请求 → 解析 AgentContext → 注入记忆 → 转发上游 LLM
└──────────┬──────────┘
│ HTTP /v3
┌───────┴────────────────┐
▼ ▼
┌──────────────────┐ ┌──────────────────────┐
│ MemoryCore :8420 │ │ MemoryKnowledge :8424│
│ 记忆数据面+管控面 │ │ Wiki + CodeGraph 构建 │
│ L0-L3 / Skill / │ │ 异步 BuildQueue Worker│
│ meta 资产元数据 │ │ /v3/tools/list|call │
└──────────────────┘ └──────────────────────┘
▲
│ /api/v1/meta/* + /v3/*
┌──────────┴──────────┐
│ MemoryHub Panel │ 管控台 UI :8125
│ (React 18 + Vite) │ Team/Agent/资产审核/配装/权限
└─────────────────────┘
| 模块 | 语言/框架 | 端口 | 职责 |
|---|---|---|---|
| MemoryCore | Node22 + TS,node:sqlite + sqlite-vec + FTS5 + jieba | 8420 | 记忆内核:L0-L3 存储 / 提炼 / 召回、Skill、资产元数据(meta) |
| MemoryKnowledge | Node22 + TS,Hono + Drizzle + better-sqlite3 + Vercel AI SDK | 8424 | Wiki / CodeGraph 知识构建与检索 |
| MemoryProxy | Node22 + TS,Hono,Redis/ClickHouse | 8096 | LLM 网关:协议适配、记忆注入、鉴权、多 Agent 路由 |
| MemoryPanel | React 18 + Vite + Tailwind + tea-component + zustand | 8125 | 管控台 UI(Team/Agent/资产审核/配装/权限) |
| sdk | TS + Python | — | MemoryClient(L0-L3 读写接口,v3 三元组隔离) |
| deploy | docker run 脚本 | — | 三容器一键拉起(core / hub / proxy) |
关键设计:数据面与管控面分离
资产绑定关系只落管控面,不污染数据面。
- 数据面(
vectors.db):记忆内容本身——L0/L1 表、向量、FTS; - 管控面(
metadata.db的meta_*表):User / Team / Agent / Task / Asset 以及绑定、权限、版本。
新增资产类型需要两侧都动(官方二次开发指南第 2 条)。
端到端主流程串讲
流程 A:对话沉淀(写)
- Agent 把 base URL 指向 Proxy(:8096),请求协议不变;
- Proxy 拦截 LLM 请求 → 解析 AgentContext → 调 MemoryCore
/v3/conversation/add记 L0; - timer-scanner 扫描 + pipeline-worker 调度,LLM 逐层提炼 L1 原子事实 → L2 场景块 → L3 画像;满足阈值时提炼 Skill;
- 资产登记进 meta_assets(Owner、版本、可见性)。
流程 B:记忆注入(读,每次请求发生)
- Proxy 收到新的 LLM 请求,按
/:agent/:spaceId/识别身份; - InjectionPipeline:召回 L2/L3 画像(快)+ 需要时 RRF 检索 L1(准),经预算截断后写入 system prompt 注入点;
- Skill / Wiki / CodeGraph 以"工具清单"形式注入,Agent 按需
/v3/tools/call; - 改写后的请求转发上游 LLM,响应原样返回 Agent。
流程 C:冷启动读档
- Panel 导入文档 → Wiki ingest 异步构建(sha256 增量 + LLM 抽取合并);
- Panel 导入代码库 → CodeGraph 索引(增量 sync);
- 导入历史对话 Session → 提炼 Skill 与 Chat Memory;
- 在 Hub 给 Agent 配装(Fixed Binding + ACL),新 Agent 上线即读档。
六、Benchmark
官方 README 给出的评测结果:
| Benchmark | 无 TencentDB Agent Memory | 启用后 | 相对提升 |
|---|---|---|---|
| PersonaMem | 48% | 76% | +59% |
PersonaMem 检验 Agent 能否在长期交互后正确理解和运用用户信息。官方社区解读中还有更细的指标:用户事实召回从原生不足 30% 提升至 79% 以上,Token 开销下降约 61%(短期记忆压缩与分层归纳)。
七、终期小结
- 异步是必须的:LLM 提炼慢且贵,从同步读路径剥离,走"定时扫描 → 队列 → 竞争消费"。
- 调度:16 分片 ZSET + Lua 原子认领 + 无主模式,O(1) 扫描、无重复消费。
- 执行:60 并发 + 分布式锁(30s 续约)+ 指数退避重试 + 死信 + 幂等写 + 级联调度 L1→L2→L3。
- 存储抽象:IMemoryStore 让 SQLite / TCVDB / pgvector 可替换,上层逻辑不动。
- 四模块:Core(记忆内核)+ Knowledge(知识构建)+ Proxy(注入网关)+ Panel(管控台),数据面与管控面分离。
- 三条主流程:沉淀(写)、注入(读)、冷启动读档,串起整个系统。
至此,本系列七期全部结束。从"为什么要四层"到"每层怎么实现"再到"怎么调度与注入",希望这套拆解能帮助你理解:Agent 的记忆不是聊天记录仓库,而是一套有分层、有治理、可溯源的基础设施。
图示索引
- 图示:记忆沉淀管线:对话 → L0 → L1 → L2 → L3(在新窗口打开完整图示)
- 图示:异步提炼管线与存储架构(在新窗口打开完整图示)
源码索引
| 内容 | 文件 |
|---|---|
| 定时扫描 | MemoryCore/src/services/timer-scanner.ts |
| 竞争消费 Worker | MemoryCore/src/services/pipeline-worker.ts |
| 并发信号量 | MemoryCore/src/services/worker-permit-pool.ts |
| 存储抽象 | MemoryCore/src/core/store/types.ts、factory.ts、sqlite.ts、tcvdb.ts |
| 状态后端 | MemoryCore/src/core/state/ |
| 系统架构(官方) | docs/项目架构与定制开发指南.md |