环境
- Memmy.app 1.1.8,memory runtime 2.1.3(
~/.memmy/memory-service/runtime/2.1.3/darwin-arm64),sqlite + fts5
- Codex Desktop,
cli_version 0.155.0-alpha.9.2
- Codex hook:
~/.codex/hooks/memmy-resume-hook.mjs(SOURCE = "codex",2026-09-23 11:58 由 app 刷新)
现象
2026-09-23 上午的 Codex 会话(session_d7cac27e842b5f92f69f → episode_8a5be23979f6927cae7c,17 turns)首轮真实问题是:
read this, write a reference note in the upper folder to provide a look-up table of flops and rw for non-linear ops like rope and softmax.
之后在 Claude Code 中执行 /memmy-resume non-linear ops,候选列表中不包含该 episode;只返回了一个 Claude Code episode。用首轮原句整句检索同样召回不到;检索 recommended_plugins 反而能召回该轮。
根因
Codex 在每个会话首轮的用户消息前注入约 22.7k 字符的前言(<recommended_plugins> 插件列表、AGENTS.md、<INSTRUCTIONS>、<environment_context>),真实问题位于末尾。Memmy 将整段文本作为 userText 入库,未剥离注入块。首轮 trace trace_68eae4ad4cf5b250fe54 的实际字段:
| 字段 |
值 |
title |
<recommended_plugins> |
summary / internal_info.trace.summary |
<recommended_plugins>(summary_at 2026-09-23T02:27:09Z) |
internal_info.trace.userText |
以 <recommended_plugins> Here is a list of plugins... 开头,截断为 4000 字符 |
refs.rawTurn.userText |
22,739 字符,真实问题从第 22,603 字符开始 |
所属 episode 的 title / summary |
同为 <recommended_plugins> |
标题取自首个非空行(storage/repositories.js 的 titleFromValue → firstLine),因此首行必然是注入标签。摘要也退化成同一个标签;我无法确认这是摘要 LLM 的输出还是 fallback。
在检索链路中的失效位置
同一条 trace 在 api_logs 中出现了两种失败,时间上前后相接。
失败一:进入 ranked 第 1 位,但被 LLM filter 丢弃(llm_dropped_all)
03:31–03:44 期间,non-linear reference、non-linear ops reference、linear reference 共 14 次检索(id 168–212)结果相同:trace_68eae4ad… 进入 ranked 候选,其中 id 185 与 212 排在第 1 位,score 1.007,其余候选均 ≤ 0.24。LLM filter 随后返回空选择,结果为 llm_filter:llm_dropped_all,最终返回 0 条。
原因在于 filter 给 LLM 的候选描述。retrieval-service.js 的 describeRetrievalFilterCandidate 对 L1 trace 只输出 [TRACE] clip(hit.snippet, llmFilterCandidateBodyChars),默认 500 字符(config/index.js:226)。该 trace 的 snippet 共 639 字符,前 500 字符全部是 <recommended_plugins> 插件列表,不含 "non-linear" 一词(content.find("non-linear") == -1)。因此 LLM 实际看到的是一份 Dropbox / Box / Figma 插件清单,判定与查询无关是合理的。排序阶段已经正确命中,是 LLM 看到的内容错了。
失败二:被讨论该问题的 trace 挤出 ranked 候选
从 03:48(id 217)开始,同样的查询下 trace_68eae4ad… 不再出现在 ranked 20 中。第 1 位换成了 Claude Code 中讨论“为什么检索不到 non-linear ops reference”的那一轮(trace_645fd16d…,score 1.013)。12:07 的 /memmy-resume non-linear ops(id 238)以及 id 197、209、253、254 同样如此:该 episode 的 17 条 trace 均不在 ranked 候选中。第 1 位的 score 约为 1.0,远高于第 2 位(约 0.2–0.3),看起来像是只授予一条的强匹配加分;原 trace 被整条移出候选,而不是降到第 2 位。其中的机制(加分规则、去重或近似内容合并)我没有读到代码,未核实。
标题来源与按标题查找
我原本想用 Codex 侧边栏中的会话标题找回这段对话。Codex 已为该会话生成干净的标题,但 Memmy 没有使用它,也没有提供按标题查找的路径。
同一会话存在三种标题
| 来源 |
值 |
状态 |
依据 |
Codex thread_name |
Add nonlinear ops reference note |
已存在,干净 |
~/.codex/session_index.jsonl,id = 01a0cc0c-1216-78b2-97c2-b1dbf7ed16b6 |
Memmy LLM 标题(episode-title-service.js) |
空 |
未生成 |
evolution_jobs 中 episode_title 任务共 5 个,均创建于 2026-09-23 04:07Z 之后(runtime 升级后),只覆盖 3 个 Claude Code episode;episodes.title 为空 |
首行兜底(titleFromValue → firstLine) |
<recommended_plugins> |
当前显示 |
见“根因” |
- runtime 代码中没有任何位置读取
session_index.jsonl 或 thread_name(在 runtime/2.1.3/darwin-arm64/dist/src 中 grep 结果为 0)。
- 即使补跑 LLM 标题任务,推测结果仍会被污染:
EpisodeTitleService.buildInput 从 raw_turns 取每轮 userText 的前 800 字符(USER_TEXT_MAX_CHARS = 800),而 Codex 首轮的前 800 字符全是插件前言。未实测。
turn 模型与 session 需求的错位
Memmy 按 turn 存储和检索,按 episode 展示。episode 由服务端在 session 内逐轮判断 continue / new_task / end_topic 后切分(session-turn-service.js 的 commitTurnRouteProposal、plugin-algorithms.js 的 classifyTurnRelation)。
| 场景 |
合适的粒度 |
当前实现 |
| 每轮开头自动注入相关记忆 |
turn |
turn,匹配 |
/memmy-resume 接续一个任务 |
session / episode |
先检索 turn,再按 episode 聚合 |
| 按标题找回某个会话 |
session |
没有这条路径,只能经过语义检索、排序和 LLM filter |
按名称查找是一种确定性操作,不应经过相关性过滤。当前实现把它交给了 turn 级语义检索,因此前文的两种失败都会直接体现为“按标题找不到”。
影响范围
- Memmy 按 turn 入库:一个 turn 生成一条 L1 trace,检索、排序与 LLM filter 都在 trace 粒度进行。因此只有带注入前言的那一轮失效,后续各轮的摘要正常,检索
MoE 参数加载 rw compute 建模 等话题可以召回该 episode。
- Codex 会话按 episode 聚合,episode 标题取自首轮。因此
/memmy-resume 候选列表中所有 Codex episode 的标题都会是 <recommended_plugins>,无法辨认。
- 首轮通常承载整段会话的任务目标,这是最需要被召回的一轮。
- 预期所有 Codex 会话都会受影响,因为插件前言在每个会话首轮都会注入;此处只核实了本例。
复现
- 在 Codex Desktop 新开会话,首轮提问一个带明确关键词的问题(例如 "non-linear ops")。
- 等待该轮摘要生成完成。
GET /api/v1/memory/<首轮 trace id>:title 与 summary 为 <recommended_plugins>。
POST /api/v1/memory/search {"query":"non-linear ops reference","layers":["L1"],"limit":20,"verbose":true},此时库中还没有其他含该短语的 trace。结果为 llm_filter:llm_dropped_all;api_logs.output_json.candidates 中首轮 trace 排在第 1 位,其 content 前 500 字符为插件列表。
- 另开一轮提到同一短语的对话后,重复第 4 步:首轮 trace 不再出现在
candidates 中。
建议修复
- 在 Codex 入库路径上剥离 harness 注入块,再生成
userText、标题、摘要与向量。涉及的块包括 <recommended_plugins>、AGENTS.md 与 <INSTRUCTIONS> 段、<environment_context>、<user_instructions>。用户粘贴的内容(例如 <pasted_content>)必须保留。
- 标题生成跳过以注入标签开头的行,或直接取剥离后的最后一段用户文本。
- LLM filter 的候选描述不应只取 snippet 前 500 字符:优先使用已生成的摘要;snippet 过长时截取与查询匹配的窗口,而不是固定取开头(仓库中已有
snippetForQuery,可考虑复用)。即使第 1 条修复不完整,这一条也能防止排序第 1 的候选被 filter 误杀。
- 为已入库的受影响 trace 与 episode 重新生成标题、摘要与向量。
- Codex episode 直接采用 Codex 的
thread_name(~/.codex/session_index.jsonl,按 thread id 关联)作为标题;只有缺失时才由 LLM 生成,最后才退回首行。
/memmy-resume 增加按标题查找:先对 episode 标题做精确或模糊匹配,并且不经过 LLM filter;匹配不到再走语义检索。
- LLM 标题任务的输入同样需要剥离注入前言,并为 runtime 升级前已存在的 episode 补跑标题任务。
- (独立问题)
memmy-resume-hook.mjs 的 buildEpisodeCandidates 会丢弃没有 refs.episode 的 agent-source 扁平 trace。另外,fallback detail.episodeId 在 API 响应中不存在,实际字段是 detail.trace.episodeId。本例中这一点不是主因,但它会降低召回。
相关 issue
🤖 Generated with Claude Code
环境
~/.memmy/memory-service/runtime/2.1.3/darwin-arm64),sqlite + fts5cli_version0.155.0-alpha.9.2~/.codex/hooks/memmy-resume-hook.mjs(SOURCE = "codex",2026-09-23 11:58 由 app 刷新)现象
2026-09-23 上午的 Codex 会话(
session_d7cac27e842b5f92f69f→episode_8a5be23979f6927cae7c,17 turns)首轮真实问题是:之后在 Claude Code 中执行
/memmy-resume non-linear ops,候选列表中不包含该 episode;只返回了一个 Claude Code episode。用首轮原句整句检索同样召回不到;检索recommended_plugins反而能召回该轮。根因
Codex 在每个会话首轮的用户消息前注入约 22.7k 字符的前言(
<recommended_plugins>插件列表、AGENTS.md、<INSTRUCTIONS>、<environment_context>),真实问题位于末尾。Memmy 将整段文本作为userText入库,未剥离注入块。首轮 tracetrace_68eae4ad4cf5b250fe54的实际字段:title<recommended_plugins>summary/internal_info.trace.summary<recommended_plugins>(summary_at2026-09-23T02:27:09Z)internal_info.trace.userText<recommended_plugins> Here is a list of plugins...开头,截断为 4000 字符refs.rawTurn.userTexttitle/summary<recommended_plugins>标题取自首个非空行(
storage/repositories.js的titleFromValue→firstLine),因此首行必然是注入标签。摘要也退化成同一个标签;我无法确认这是摘要 LLM 的输出还是 fallback。在检索链路中的失效位置
同一条 trace 在
api_logs中出现了两种失败,时间上前后相接。失败一:进入 ranked 第 1 位,但被 LLM filter 丢弃(
llm_dropped_all)03:31–03:44 期间,
non-linear reference、non-linear ops reference、linear reference共 14 次检索(id 168–212)结果相同:trace_68eae4ad…进入 ranked 候选,其中 id 185 与 212 排在第 1 位,score 1.007,其余候选均 ≤ 0.24。LLM filter 随后返回空选择,结果为llm_filter:llm_dropped_all,最终返回 0 条。原因在于 filter 给 LLM 的候选描述。
retrieval-service.js的describeRetrievalFilterCandidate对 L1 trace 只输出[TRACE] clip(hit.snippet, llmFilterCandidateBodyChars),默认 500 字符(config/index.js:226)。该 trace 的 snippet 共 639 字符,前 500 字符全部是<recommended_plugins>插件列表,不含 "non-linear" 一词(content.find("non-linear") == -1)。因此 LLM 实际看到的是一份 Dropbox / Box / Figma 插件清单,判定与查询无关是合理的。排序阶段已经正确命中,是 LLM 看到的内容错了。失败二:被讨论该问题的 trace 挤出 ranked 候选
从 03:48(id 217)开始,同样的查询下
trace_68eae4ad…不再出现在 ranked 20 中。第 1 位换成了 Claude Code 中讨论“为什么检索不到 non-linear ops reference”的那一轮(trace_645fd16d…,score 1.013)。12:07 的/memmy-resume non-linear ops(id 238)以及 id 197、209、253、254 同样如此:该 episode 的 17 条 trace 均不在 ranked 候选中。第 1 位的 score 约为 1.0,远高于第 2 位(约 0.2–0.3),看起来像是只授予一条的强匹配加分;原 trace 被整条移出候选,而不是降到第 2 位。其中的机制(加分规则、去重或近似内容合并)我没有读到代码,未核实。标题来源与按标题查找
我原本想用 Codex 侧边栏中的会话标题找回这段对话。Codex 已为该会话生成干净的标题,但 Memmy 没有使用它,也没有提供按标题查找的路径。
同一会话存在三种标题
thread_nameAdd nonlinear ops reference note~/.codex/session_index.jsonl,id=01a0cc0c-1216-78b2-97c2-b1dbf7ed16b6episode-title-service.js)evolution_jobs中episode_title任务共 5 个,均创建于 2026-09-23 04:07Z 之后(runtime 升级后),只覆盖 3 个 Claude Code episode;episodes.title为空titleFromValue→firstLine)<recommended_plugins>session_index.jsonl或thread_name(在runtime/2.1.3/darwin-arm64/dist/src中 grep 结果为 0)。EpisodeTitleService.buildInput从raw_turns取每轮userText的前 800 字符(USER_TEXT_MAX_CHARS = 800),而 Codex 首轮的前 800 字符全是插件前言。未实测。turn 模型与 session 需求的错位
Memmy 按 turn 存储和检索,按 episode 展示。episode 由服务端在 session 内逐轮判断 continue / new_task / end_topic 后切分(
session-turn-service.js的commitTurnRouteProposal、plugin-algorithms.js的classifyTurnRelation)。/memmy-resume接续一个任务按名称查找是一种确定性操作,不应经过相关性过滤。当前实现把它交给了 turn 级语义检索,因此前文的两种失败都会直接体现为“按标题找不到”。
影响范围
MoE 参数加载 rw compute 建模等话题可以召回该 episode。/memmy-resume候选列表中所有 Codex episode 的标题都会是<recommended_plugins>,无法辨认。复现
GET /api/v1/memory/<首轮 trace id>:title与summary为<recommended_plugins>。POST /api/v1/memory/search {"query":"non-linear ops reference","layers":["L1"],"limit":20,"verbose":true},此时库中还没有其他含该短语的 trace。结果为llm_filter:llm_dropped_all;api_logs.output_json.candidates中首轮 trace 排在第 1 位,其content前 500 字符为插件列表。candidates中。建议修复
userText、标题、摘要与向量。涉及的块包括<recommended_plugins>、AGENTS.md 与<INSTRUCTIONS>段、<environment_context>、<user_instructions>。用户粘贴的内容(例如<pasted_content>)必须保留。snippetForQuery,可考虑复用)。即使第 1 条修复不完整,这一条也能防止排序第 1 的候选被 filter 误杀。thread_name(~/.codex/session_index.jsonl,按 thread id 关联)作为标题;只有缺失时才由 LLM 生成,最后才退回首行。/memmy-resume增加按标题查找:先对 episode 标题做精确或模糊匹配,并且不经过 LLM filter;匹配不到再走语义检索。memmy-resume-hook.mjs的buildEpisodeCandidates会丢弃没有refs.episode的 agent-source 扁平 trace。另外,fallbackdetail.episodeId在 API 响应中不存在,实际字段是detail.trace.episodeId。本例中这一点不是主因,但它会降低召回。相关 issue
🤖 Generated with Claude Code