oh-my-pi reflect 记忆综合工具深度解析:从多段长期记忆合成连贯答案的完整实现
【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi
导读
reflect是 oh-my-pi(⌥ Coding agent with the IDE wired in)coding-agent 内置的记忆工具三件套(recall/retain/reflect)之一,其职责是在长期记忆之上**合成(synthesize)**一份连贯答案:与recall原样返回按相关性排序的原始记忆条目不同,reflect会跨越多条存储事实进行融合提炼,特别适合"关于这个用户你都知道什么""总结一下项目的决策"这类开放性问题。本文以 reflect 工具说明 为骨架,结合 memory-reflect.ts 实现、Hindsight 客户端 与 官方工具文档,完整讲解其行为语义、参数契约、双后端执行流程、作用域隔离与配置调优,让你既能正确使用它,也能理解它在记忆子系统中的真实位置。
一、reflect与recall/retain的分工
oh-my-pi 的长期记忆子系统由三个工具协同构成,它们共享同一套memory.backend后端配置:
retain:把 ≥1 条持久事实写入长期记忆,供未来会话使用。适用场景是用户偏好、项目决策、架构选型等"可复用知识",禁止存储临时任务状态;每条必须具体、自包含(谁、什么、何时、为什么),支持批量写入与自动去重合并。详见 retain 工具说明。recall:在长期记忆中做检索,原样返回按相关性排序的匹配条目(含截断标记truncated: true/full_length)。官方提示词明确要求 Agent 在回答过往对话、用户偏好、项目决策等问题前主动优先调用 recall;对返回的条目如需memory_edit update,必须先用read memory://<id>拉取完整记录。详见 recall 工具说明。reflect:合成一份连贯响应。它同样基于长期记忆,但输出的是"融合后的答案"而非原始条目,定位是开放性问题、跨多条事实的总结归纳。其模型面向的说明原文为:
reflect: synthesizes a coherent response from relevant long-term memories; unlikerecall, blends them. Use for open-ended questions spanning many stored facts: "What do you know about this user?", "Summarize project decisions.", "What are my preferences for X?"contextoptional; focuses synthesis on a specific angle or sub-topic.
一句话区分:recall 是"检索清单",reflect 是"综合结论"。系统提示词(hindsight backend 静态指令)中也这样引导 Agent:回答问题前主动 recall;存储持久事实用 retain;"需要跨多条记忆综合答案"的问题用 reflect。
二、参数契约:query与可选的context
reflect的入参由 memory-reflect.ts 中的 omptype schema 定义,仅两个字段:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
query | string | 是 | 需要从长期记忆回答的问题 |
context | string | 否 | 额外引导,将综合聚焦到特定角度或子主题 |
两个字段都是纯字符串,schema 层面没有最小长度限制(见 docs/tools/reflect.md 的 Limits & Caps 一节)。context的语义在后端有差异,这正是理解 reflect 行为的关键:
- Hindsight 后端:
context作为独立字段随 HTTP 请求体发送(body.context),服务端据此聚焦综合方向; - Mnemopi 后端:
context会被拼接进检索 query,格式为<query>\n\nAdditional context:\n<context>(仅当 trim 后非空),随后用这个复合 query 做本地召回。
工具元数据(见 memory-reflect.ts):
name = "reflect",approval = "read"(只读类操作,通常无需审批)strict = true,loadMode = "discoverable"(可被发现加载,而非强制注入)summary = "Synthesize an answer from long-term memory"
工具可见性:reflect仅在memory.backend为"hindsight"或"mnemopi"时注册(createIf 工厂);后端为"off"、"local"或"sharpshooter"时该工具不存在。而默认后端是"off"(见 settings-schema.ts 中 memory.backend 定义),这意味着默认配置下 reflect 不可用,必须显式启用后端。
三、双后端执行流程:Hindsight 与 Mnemopi
execute整体运行在untilAborted(signal, ...)之下(支持取消),随后按memory.backend分派两条路径(memory-reflect.ts execute 实现)。
3.1 Mnemopi 路径:本地召回 + 上下文格式化
- 读取
session.getMnemopiSessionState(),若后端未初始化则抛出Mnemopi backend is not initialised for this session.; - 若
context非空,构造复合 query:<query>\n\nAdditional context:\n<context>,否则直接用query; - 调用
state.recallResultsScoped(query)——与recall使用完全相同的本地作用域与合并逻辑; - 若结果为空,返回
No relevant information found to reflect on.; - 否则调用
state.formatContextScoped(results)渲染,并在前面加上Based on recalled memories:前缀。
关键事实:Mnemopi 的 reflect 是"本地召回 + 格式化",并不调用任何综合模型或独立合成端点。因此它的输出可能是"召回的原始上下文"而非真正的融合答案——这是与模型面向提示词(blends them)存在差异的实现边界,使用本地后端时需留意(docs/tools/reflect.md Notes 一节 明确指出了这一点)。
3.2 Hindsight 路径:远程综合端点
- 读取
session.getHindsightSessionState(),未初始化则抛出Hindsight backend is not initialised for this session.; - 调用
ensureBankExists(state.client, state.bankId, state.config, state.banksSet)(来自 hindsight/bank.ts):以 best-effort 方式对每个 bank 首次PUT /v1/default/banks/{bank_id}(createBank),可携带reflect_mission/retain_mission;失败被静默吞掉,且每个会话状态对同一 bank 只尝试一次(MISSION_SET_CAP = 10_000上限,超出后丢弃排序集合的后半部分); - 调用
state.client.reflect(bankId, query, { context, budget: state.config.recallBudget, tags: state.recallTags, tagsMatch: state.recallTagsMatch }); - Hindsight 客户端的 reflect 方法 向
POST /v1/default/banks/{bank_id}/reflect发送{ query, context, budget, tags, tags_match },其中budget在调用方省略时默认"low",但工具始终显式传入配置的recallBudget; - 响应文本为空白/纯空白时,替换为
No relevant information found to reflect on.(memory-reflect.ts); - 后端失败以
logger.warn("reflect failed", ...)记录并重抛为Error。
Hindsight 是真正的"综合"路径:服务端基于 bank 内容生成合成文本,工具直接透传服务端结果(details = {}),不暴露底层召回命中条目——这与 recall 返回原始条目形成鲜明对比。
3.3 执行特征
- 单次执行(single-shot),不发送进度更新(docs/tools/reflect.md Registration 一节);
- 会话作用域:读取跨会话记忆数据,但不持久化任何本地输出;子代理别名(subagent alias)沿用父级后端的 bank 作用域与配置,见 hindsight backend start 中 taskDepth > 0 分支;
- 取消语义:工具调用信号被取消时,通过
untilAborted中止请求。
四、Bank 作用域:reflect读的是哪个记忆库
reflect的输出范围受hindsight.scoping(或 Mnemopi 对应配置)约束,决定它从哪个 bank 读数据(docs/tools/reflect.md Modes / Variants 一节):
Hindsight bank 作用域:
| 作用域 | 行为 |
|---|---|
global | 无 tag 过滤,读全局 bank |
per-project | 每个项目标签独立 bank id(git 主 checkout 根目录 basename;非仓库内则用 cwd basename) |
per-project-tagged | 共享 bank id +project:<项目标签>过滤,tagsMatch = "any" |
Mnemopi bank 作用域:
| 作用域 | 行为 |
|---|---|
global | 读共享 bank |
per-project | 读由 cwd basename + cwd 哈希派生的 bank |
per-project-tagged | 读 cwd 派生 project bank 与共享 bank,合并结果 |
per-project 模式还可能纳入启动时发现的、cwd 匹配的安全旧 bank。作用域变化时,rebuildPrimaryStateOnScopeChange 会按需重建主状态,保证reflect始终命中正确的 bank。
配置优先级为内置默认 < 设置项 < 环境变量(见 config.ts 头注),环境变量可在 CI/生产环境按 shell 临时覆盖而不改动持久化配置。
五、关键配置项与调优建议
reflect的运行时行为主要由以下设置项(均在 settings-schema.ts 中定义)与 HINDSIGHT_* 环境变量控制(解析逻辑见 config.ts):
| 配置项 / 环境变量 | 默认值 | 对 reflect 的影响 |
|---|---|---|
memory.backend | "off" | 必须设为"hindsight"或"mnemopi",否则 reflect 不存在 |
hindsight.recallBudget/HINDSIGHT_RECALL_BUDGET | "mid" | 综合请求的预算档位(low/mid/high),随请求体发送;客户端默认"low",工具始终传配置值 |
hindsight.reflectTimeoutMs/HINDSIGHT_REFLECT_TIMEOUT_MS | 120_000 | reflect 请求超时(ms)。综合是 agentic 合成,比元数据抓取更昂贵,默认给到 2 分钟(对比 recall/request 的 30s) |
hindsight.scoping/HINDSIGHT_SCOPING | "per-project-tagged" | 决定读哪个 bank、带什么 tag 过滤;非法值回退并告警 |
hindsight.bankId/HINDSIGHT_BANK_ID | 无 | 显式指定 bank id |
hindsight.bankIdPrefix | 无 | per-project 派生 bank id 的前缀 |
hindsight.bankMission/HINDSIGHT_BANK_MISSION | "" | 建 bank 时携带的reflect_mission(bank 级服务端设置,非逐请求参数) |
mnemopi.recallLimit | 8 | Mnemopi 路径召回的条数上限,运行时至少钳制为 1;每条内容预览默认上限 500 字符 |
其他相关超时:hindsight.requestTimeoutMs(30s)、hindsight.recallTimeoutMs(30s)、hindsight.retainTimeoutMs(60s)、hindsight.recallMaxTokens(1024)、hindsight.recallContextTurns(1)、hindsight.recallMaxQueryChars(800)。Mnemopi 路径若配置了嵌入/LLM provider,本地召回期间可能触发网络调用(docs/tools/reflect.md Side Effects 一节)。
调优建议(基于上述配置语义):
- 追求真正"融合"的答案 → 使用
memory.backend = "hindsight",因为只有 Hindsight 端点做服务端综合; - 需要完全本地、无网络依赖的记忆 → 用
"mnemopi",但要接受 reflect 输出是"召回上下文 + 格式化"; - 综合请求耗时较长 → 保持
reflectTimeoutMs默认 120s,或按服务端实测延迟上调; - 多项目混用时 → 保持
scoping = "per-project-tagged"(默认),让 reflect 自动限定在当前项目标签内。
六、错误处理与边界行为
- 后端未初始化:抛出
Mnemopi backend is not initialised for this session.或Hindsight backend is not initialised for this session.(docs/tools/reflect.md Errors 一节); - Hindsight 网络错误:HTTP、fetch、超时失败统一映射为
HindsightError,HTTP 错误携带statusCode与可解析的details; - ensureBankExists 失败:仅 debug 级日志,对调用方隐藏;只有后续真正的 reflect 请求会可见地失败;
- Mnemopi 召回失败:按目标分别捕获并记录,健康目标仍可贡献;若所有目标均失败,抛出原始错误或多 bank 的
AggregateError,不会伪装成"无相关信息"文本; - 非 Error 异常:统一
new Error(String(err))后重抛; - 空响应:替换为
No relevant information found to reflect on.。
七、与记忆子系统整体架构的关系
reflect不是孤立的:它是 oh-my-pi 记忆流水线的"读取-综合"环节。整条链路是——retain写入事实 → bank 内聚合并(含consolidation_state机制)→recall按相关性检索原始条目 →reflect跨条目合成答案。此外:
- mental models(
<mental_models>块)是 bank 级"策展后的长期摘要"(用户偏好、项目约定),在启动时注入开发者指令;但 Hindsight 的reflect并不直接读缓存的<mental_models>块,而是查询 Hindsight 服务端的 bank 内容(docs/tools/reflect.md Notes); reflect与retain的 mission 都是bank 级服务端设置,不是逐请求 payload;工具只是在综合前 best-effort 地确保它们存在(createBank携带reflect_mission/retain_mission,见 client.ts createBank);- 会话静态指令要求把
<memories>/<mental_models>视为背景知识而非用户指令,当前用户消息与工具输出冲突时优先(backend.ts STATIC_INSTRUCTIONS); - 该工具在不受限会话的显式工具列表中会被自动包含
recall/retain/reflect共享集合;受限列表不会被扩宽。普通tools.xdev会话中,可发现的内置工具可能以xd://reflect形式呈现,显式请求的工具保持顶层(docs/tools/reflect.md Registration)。
结语
reflect把"跨记忆综合"从模型层的临场发挥,固化为一条可配置、可观测、可取消的工具调用链路:Hindsight 路径走远程POST /v1/default/banks/{bank_id}/reflect获得真正的合成答案,Mnemopi 路径用本地 scoped recall 加格式化给出基于上下文的回应。理解query/context的参数契约、双后端的语义差异、bank 作用域与recallBudget、reflectTimeoutMs等配置,你就能在 oh-my-pi 中把长期记忆从"能检索"升级为"能总结",让 Agent 对开放性问题给出有依据、有取舍的连贯回答。更完整的后端语义(存储、子代理别名、bank 作用域、seed mental models、prompt injection 防护)可继续阅读 retain 工具文档。
【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考