Agent 记忆

异步提炼管线与整体系统架构

终期拆解"幕后":L1→L3 的提炼为什么必须异步,任务如何被调度与容错,以及 MemoryCore / Knowledge / Proxy / Panel 四模块如何协作。

记忆沉淀管线:对话 → L0 → L1 → L2 → L3 全屏查看 ↗
异步提炼管线与存储架构 全屏查看 ↗

一、为什么必须异步

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 个分片 ZSETtdai_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.tsIMemoryStore(sync-first 接口,全部方法返回 MaybePromise,上层只依赖接口不依赖实现);装配点:src/core/store/factory.tscreateStoreBundle,按 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/资产审核/配装/权限
└─────────────────────┘
模块语言/框架端口职责
MemoryCoreNode22 + TS,node:sqlite + sqlite-vec + FTS5 + jieba8420记忆内核:L0-L3 存储 / 提炼 / 召回、Skill、资产元数据(meta)
MemoryKnowledgeNode22 + TS,Hono + Drizzle + better-sqlite3 + Vercel AI SDK8424Wiki / CodeGraph 知识构建与检索
MemoryProxyNode22 + TS,Hono,Redis/ClickHouse8096LLM 网关:协议适配、记忆注入、鉴权、多 Agent 路由
MemoryPanelReact 18 + Vite + Tailwind + tea-component + zustand8125管控台 UI(Team/Agent/资产审核/配装/权限)
sdkTS + PythonMemoryClient(L0-L3 读写接口,v3 三元组隔离)
deploydocker run 脚本三容器一键拉起(core / hub / proxy)

关键设计:数据面与管控面分离

资产绑定关系只落管控面,不污染数据面。

  • 数据面vectors.db):记忆内容本身——L0/L1 表、向量、FTS;
  • 管控面metadata.dbmeta_* 表):User / Team / Agent / Task / Asset 以及绑定、权限、版本。

新增资产类型需要两侧都动(官方二次开发指南第 2 条)。

端到端主流程串讲

流程 A:对话沉淀(写)

  1. Agent 把 base URL 指向 Proxy(:8096),请求协议不变;
  2. Proxy 拦截 LLM 请求 → 解析 AgentContext → 调 MemoryCore /v3/conversation/add 记 L0;
  3. timer-scanner 扫描 + pipeline-worker 调度,LLM 逐层提炼 L1 原子事实 → L2 场景块 → L3 画像;满足阈值时提炼 Skill;
  4. 资产登记进 meta_assets(Owner、版本、可见性)。

流程 B:记忆注入(读,每次请求发生)

  1. Proxy 收到新的 LLM 请求,按 /:agent/:spaceId/ 识别身份;
  2. InjectionPipeline:召回 L2/L3 画像(快)+ 需要时 RRF 检索 L1(准),经预算截断后写入 system prompt 注入点;
  3. Skill / Wiki / CodeGraph 以"工具清单"形式注入,Agent 按需 /v3/tools/call
  4. 改写后的请求转发上游 LLM,响应原样返回 Agent。

流程 C:冷启动读档

  1. Panel 导入文档 → Wiki ingest 异步构建(sha256 增量 + LLM 抽取合并);
  2. Panel 导入代码库 → CodeGraph 索引(增量 sync);
  3. 导入历史对话 Session → 提炼 Skill 与 Chat Memory;
  4. 在 Hub 给 Agent 配装(Fixed Binding + ACL),新 Agent 上线即读档。

六、Benchmark

官方 README 给出的评测结果:

Benchmark无 TencentDB Agent Memory启用后相对提升
PersonaMem48%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 的记忆不是聊天记录仓库,而是一套有分层、有治理、可溯源的基础设施。


图示索引

源码索引

内容文件
定时扫描MemoryCore/src/services/timer-scanner.ts
竞争消费 WorkerMemoryCore/src/services/pipeline-worker.ts
并发信号量MemoryCore/src/services/worker-permit-pool.ts
存储抽象MemoryCore/src/core/store/types.tsfactory.tssqlite.tstcvdb.ts
状态后端MemoryCore/src/core/state/
系统架构(官方)docs/项目架构与定制开发指南.md

异步管线系统架构任务调度容错

← 返回AI 笔记