本期拆解"读路径":每次 LLM 请求发生时,L1/L2/L3 如何被召回、截断、并注入到提示词的不同位置。
一、读路径全景
官方对读路径的定位是"按需取用":
记忆不是全局 Prompt,而是 Agent 的 Loadout。Chat Memory、Skill、Wiki 和 CodeGraph 都被统一登记为 Memory Asset。Memory Hub 通过 Fixed Binding + ACL 决定某个 Agent 能带走哪些资产:先按 Team、User、Agent 和可见性缩小权限范围,再按当前问题召回。
落到每次请求上,链路是:
LLM 请求(Anthropic / OpenAI / Codex Responses)
│ Proxy 拦截,按 /:agent/:spaceId/ 识别身份
▼
① 召回(MemoryCore · auto-recall.ts · 超时 5s)
├── L1 搜索:keyword / embedding / hybrid(RRF)
├── L3 Persona:无条件读取
└── L2 场景导航:全量读取
▼
② 预算截断(applyRecallBudget)
▼
③ 稳定/动态分离
├── appendSystemContext(system 尾部,可缓存):persona + 场景导航 + 工具指南
└── prependContext(user 前缀,每轮变):L1 相关记忆
▼
④ Proxy 注入管线(InjectionPipeline)按注入点改写 system prompt
▼
⑤ 转发上游 LLM,响应原样返回
二、召回:auto-recall.ts
官方架构文档的召回策略表:
| 策略 | 实现 |
|---|---|
| keyword | FTS5 BM25,buildFtsQuery + jieba 分词 |
| embedding | sqlite-vec cosine |
| hybrid | 客户端 RRF 融合(K=60),src/core/store/search-utils.ts |
| TCVDB 后端 | 原生 hybridSearch(src/core/store/tcvdb.ts) |
三通道并行
- L1 原子记忆搜索(动态,随问题变化):
keyword:FTS5 BM25,查询先经 jieba 分词(buildFtsQuery);embedding:embeddingService.embed(userText)→ vec0 cosine 检索;hybrid:两路并行,客户端 RRF 融合——score = Σ 1/(K + rank),K=60;- 默认阈值
scoreThreshold = 0.3、默认maxResults = 5; - TCVDB 后端有原生 hybridSearch 时直接一次调用(省一次 embed + 一次 HTTP)。
- L3 Persona(稳定,无条件):读
persona.md剥离导航。 - L2 场景导航(稳定,全量):读
scene_index生成导航块,全量注入,由 LLM 自行判断相关性;需要深挖时用read_file沿导航下钻。
工程细节:查询文本先经
sanitizeText清洗(剥离 Sender、时间戳、媒体标记、base64 图片等注入元数据),保证检索的是纯用户意图。
超时与失败语义
召回包在 Promise.race 里,默认超时 5s(recall.timeoutMs)。超时不返回空,而是返回带结构化错误的 RecallResult.error(如 dependencyTimeout),让上层能区分"无结果"和"召回失败"——这是官方 H-15 的设计:结构化失败信号,永不静默。
预算控制:applyRecallBudget
防止记忆反过来占满上下文,官方 README 的原话:
结果还会经过条数、字符预算和超时限制,避免记忆反过来占满上下文。
具体规则:
maxCharsPerMemory:单条记忆按码点截断(不是 UTF-16 单元,防止切坏代理对导致乱码),超出部分追加"…(已截断)";maxTotalRecallChars:总量预算,超出就丢弃后面的记忆。
注入格式
召回结果被格式化成自然语言行,例如(源码注释示例):
- [persona] 用户叫王小明,30岁,是一名软件工程师。
- [episodic|旅行计划] 用户计划五月去日本旅行。(活动时间: 2025-05-01 ~ 2025-05-10)
- [instruction] 用户要求回答时使用中文,保持简洁。
[type|scene] 标签 + 活动时间,让 LLM 一眼看懂记忆类型与时效。
三、稳定 / 动态分离(提示词缓存优化)
这是读路径里非常精妙的设计。官方实现把召回内容拆成两份:
| 部分 | 内容 | 位置 | 特性 |
|---|---|---|---|
appendSystemContext | persona + 场景导航 + 记忆工具指南 | system prompt 尾部 | 稳定、变化少,可命中提示词缓存 |
prependContext | L1 相关记忆(<relevant-memories>) | user prompt 前缀 | 每轮变化,放 user 侧不打爆系统缓存 |
源码注释原文:providers with prompt caching (Anthropic/OpenAI) can cache this region……prependContext —— different every turn, moved out of system prompt so it doesn’t bust the system prompt cache.
L1 记忆放 user 前缀还有一个好处:<relevant-memories> 里明确写着"以下是当前对话召回的相关记忆,不代表当前任务进程,仅作为参考"——记忆只是参考,不覆盖用户当下意图。
记忆工具指南
系统还会注入一份 memory-tools-guide,教会主 Agent 主动深挖:
tdai_memory_search:搜 L1 结构化记忆;tdai_conversation_search:搜 L0 原始对话(找原话、时间线);read_file:沿场景导航读完整场景档案;- 调用约束:两个搜索工具每轮合计最多 3 次,3 次无果即停止。
四、注入:MemoryProxy 的 InjectionPipeline
官方架构文档对注入管线的描述:
请求 → adapters/openai.ts | adapters/anthropic.ts 解析为统一 AgentContext → pipeline.ts InjectionPipeline 依序执行 injector: TdaiProfileMemoryInjector(L2/L3 画像) TdaiToolsInjector、SkillInjector、KnowledgeToolsInjector … → 各 injector 在注入点改写 system prompt: system.prefix / before_tools / after_tools / system.suffix → 重建请求转发上游 LLM
四个注入点
| 注入点 | 用途 | 典型注入器 |
|---|---|---|
system.prefix | 稳定画像,提示词最前 | TdaiProfileMemoryInjector(L2/L3) |
before_tools | 动态记忆,工具列表前 | TdaiL1RecallInjector |
after_tools | 工具清单 | TdaiToolsInjector / SkillInjector |
system.suffix | 知识工具(按需调用) | KnowledgeToolsInjector(Wiki / CodeGraph) |
不同 Agent(CodeBuddy / Claude Code / WorkBuddy / Pi)各自有 AgentProfile 负责解析 / 重建其 XML 标签式 system prompt——协议不同,注入点语义相同。
知识不进上下文(按需调用)
官方 README 强调:
文档被整理为可搜索、可沿链接下钻的 Wiki;代码库被索引为包含文件、符号和调用关系的 CodeGraph。Agent 先通过
/v3/tools/list发现能力,再用/v3/tools/call读取相关页面、源码或影响路径。这让文档和代码也成为记忆,但它们平时只是可用的工具,只有真正需要时才进入上下文。
——即 Wiki / CodeGraph 不整库注入,而是以"工具清单"形式出现,Agent 需要时才调用。
五、本期小结
- 召回三通道:L1 搜索(keyword / embedding / hybrid-RRF)+ L3 无条件 Persona + L2 全量场景导航。
- 三重约束:scoreThreshold / maxResults 过滤、字符预算截断、5s 超时(结构化失败信号)。
- 稳定 / 动态分离:persona + 导航进 system 尾部(可缓存),L1 记忆进 user 前缀(不污染缓存)。
- 注入四点位:prefix / before_tools / after_tools / suffix,按 AgentProfile 重建提示词。
- 知识按需调用:Wiki / CodeGraph 以工具形式存在,需要时才进上下文。
下一期(终期)看支撑这一切的"幕后":异步提炼管线与整体系统架构。
图示索引
- 图示:召回与注入:记忆如何进入 Agent 上下文(在新窗口打开完整图示)
源码索引
| 内容 | 文件 |
|---|---|
| 召回主流程 | MemoryCore/src/core/hooks/auto-recall.ts |
| RRF 融合 | MemoryCore/src/core/store/search-utils.ts |
| FTS 查询构建 | MemoryCore/src/core/store/sqlite.ts(buildFtsQuery) |
| 注入管线 | MemoryProxy/src/injection/pipeline.ts、index.ts |
| 注入器 | MemoryProxy/src/injection/injectors/(profile / l1-recall / tools / skill / knowledge) |
| 知识工具 | MemoryKnowledge/src/routes/tools.ts(/v3/tools/list · call) |