Skip to content

[Bug] Codex 会话首轮的 harness 注入前言被当作用户问题入库,导致 trace 与 episode 标题为 <recommended_plugins>、该轮无法召回,且无法按 Codex 标题找回会话 #530

Description

@yanggthomas

环境

  • 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.mjsSOURCE = "codex",2026-09-23 11:58 由 app 刷新)

现象

2026-09-23 上午的 Codex 会话(session_d7cac27e842b5f92f69fepisode_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.jstitleFromValuefirstLine),因此首行必然是注入标签。摘要也退化成同一个标签;我无法确认这是摘要 LLM 的输出还是 fallback。

在检索链路中的失效位置

同一条 trace 在 api_logs 中出现了两种失败,时间上前后相接。

失败一:进入 ranked 第 1 位,但被 LLM filter 丢弃(llm_dropped_all

03:31–03:44 期间,non-linear referencenon-linear ops referencelinear 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.jsdescribeRetrievalFilterCandidate 对 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.jsonlid = 01a0cc0c-1216-78b2-97c2-b1dbf7ed16b6
Memmy LLM 标题(episode-title-service.js 未生成 evolution_jobsepisode_title 任务共 5 个,均创建于 2026-09-23 04:07Z 之后(runtime 升级后),只覆盖 3 个 Claude Code episode;episodes.title 为空
首行兜底(titleFromValuefirstLine <recommended_plugins> 当前显示 见“根因”
  • runtime 代码中没有任何位置读取 session_index.jsonlthread_name(在 runtime/2.1.3/darwin-arm64/dist/src 中 grep 结果为 0)。
  • 即使补跑 LLM 标题任务,推测结果仍会被污染:EpisodeTitleService.buildInputraw_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.jscommitTurnRouteProposalplugin-algorithms.jsclassifyTurnRelation)。

场景 合适的粒度 当前实现
每轮开头自动注入相关记忆 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 会话都会受影响,因为插件前言在每个会话首轮都会注入;此处只核实了本例。

复现

  1. 在 Codex Desktop 新开会话,首轮提问一个带明确关键词的问题(例如 "non-linear ops")。
  2. 等待该轮摘要生成完成。
  3. GET /api/v1/memory/<首轮 trace id>titlesummary<recommended_plugins>
  4. POST /api/v1/memory/search {"query":"non-linear ops reference","layers":["L1"],"limit":20,"verbose":true},此时库中还没有其他含该短语的 trace。结果为 llm_filter:llm_dropped_allapi_logs.output_json.candidates 中首轮 trace 排在第 1 位,其 content 前 500 字符为插件列表。
  5. 另开一轮提到同一短语的对话后,重复第 4 步:首轮 trace 不再出现在 candidates 中。

建议修复

  1. 在 Codex 入库路径上剥离 harness 注入块,再生成 userText、标题、摘要与向量。涉及的块包括 <recommended_plugins>、AGENTS.md 与 <INSTRUCTIONS> 段、<environment_context><user_instructions>。用户粘贴的内容(例如 <pasted_content>)必须保留。
  2. 标题生成跳过以注入标签开头的行,或直接取剥离后的最后一段用户文本。
  3. LLM filter 的候选描述不应只取 snippet 前 500 字符:优先使用已生成的摘要;snippet 过长时截取与查询匹配的窗口,而不是固定取开头(仓库中已有 snippetForQuery,可考虑复用)。即使第 1 条修复不完整,这一条也能防止排序第 1 的候选被 filter 误杀。
  4. 为已入库的受影响 trace 与 episode 重新生成标题、摘要与向量。
  5. Codex episode 直接采用 Codex 的 thread_name~/.codex/session_index.jsonl,按 thread id 关联)作为标题;只有缺失时才由 LLM 生成,最后才退回首行。
  6. /memmy-resume 增加按标题查找:先对 episode 标题做精确或模糊匹配,并且不经过 LLM filter;匹配不到再走语义检索。
  7. LLM 标题任务的输入同样需要剥离注入前言,并为 runtime 升级前已存在的 episode 补跑标题任务。
  8. (独立问题)memmy-resume-hook.mjsbuildEpisodeCandidates 会丢弃没有 refs.episode 的 agent-source 扁平 trace。另外,fallback detail.episodeId 在 API 响应中不存在,实际字段是 detail.trace.episodeId。本例中这一点不是主因,但它会降低召回。

相关 issue

🤖 Generated with Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions