feat(memory): add structured on-demand recall - #10178
Conversation
fe89220 to
2bc4533
Compare
2bc4533 to
cf48eb7
Compare
|
Superseded by #10183, which uses an in-repository head branch so the standard pull_request CI can run without fork-workflow approval. |
|
Thanks for the PR, @ZijianZhang989!
Escalating to maintainer awareness per the core-module two-tier rule (feat-type, 500+ production lines touching 中文说明感谢贡献,@ZijianZhang989!
依据核心模块两档规则(feat 类型、触及 — Qwen Code · qwen3.8-max Reviewed at |
|
Please do not rebase or force-push to an active PR as it invalidates existing review comments. Note for future reference, the bots always squash all changes into a single commit automatically as part of the integration. 中文请勿对活跃的 PR 执行 rebase 或 force-push,因为这会使已有的评审评论失效。另外,供日后参考:作为集成流程的一部分,机器人始终会自动将所有改动压缩(squash)为单个提交。 |
Code reviewRead-only static review of the diff at the commit below — no PR code was built or executed, per triage rules. Test evidence in this comment comes from the PR's own CI via the API (see the CI section: the PR's CI has not run on this commit at all), plus the author's self-reported local results, which are the author's claim and not independently verified here. First, what's good: this is careful engineering. The cursor machinery is HMAC-signed with timing-safe comparison and invalidated on file mtime change; body windows and aggregate budgets are bounded; migration writes go through Concerns, in rough order of importance — these are for discussion, not blockers:
Reuse check: the new engine builds on the existing sequenceDiagram
participant P1 as User
participant P2 as GeminiClient turn loop
participant P3 as MemoryManager recall
participant P4 as Tree renderer
participant P5 as search_memory tool
participant P6 as History residency map
P1->>P2: sends a query
P2->>P3: begin recall prefetch
P3-->>P2: selected docs plus focused subtree
P2->>P4: render focused subtree
P4-->>P2: metadata prompt
Note over P2: full tree added only when the revision changed
P2-->>P1: answer streams back
P2->>P5: model calls fetch or search when metadata is not enough
P5->>P6: check body version already resident
P6-->>P5: resident, evicted, or changed
P5-->>P2: bounded body window with signed cursor
Files changed (28 of 88 shown)
TestingEvidence carried by this run: API-fetched CI signal only. No PR code was built, run, or tested by triage (unattended CI runs never execute PR code). The PR's CI has not run on the reviewed commit: the API shows zero check-runs and zero
Sandboxed verification would settle the behavioural claims: 中文说明代码审查仅对下列 commit 的 diff 做静态只读审查——按审查规则,未构建或执行任何 PR 代码。本评论中的测试证据来自 PR 自身的 CI(经 API 获取,见 CI 部分:该 PR 的 CI 尚未在此 commit 上运行过);作者自报的本地结果属于作者声明,未经独立验证。 先说优点:这是细致的工程。游标机制使用 HMAC 签名、时序安全比较,并在文件 mtime 变化时失效;正文窗口与总预算均有界;迁移写入走 顾虑按重要性大致排序——供讨论,不构成阻塞:
复用检查:新引擎基于现有 序列图:用户提问 → GeminiClient 发起召回预取 → 交付 focused subtree(仅当树 revision 变化时附完整树)→ 模型在 metadata 不足时调用 search_memory → 工具核对历史驻留状态后返回带签名游标的有界正文窗口。 测试本次运行携带的证据:仅 API 获取的 CI 信号。 审查未构建、运行或测试任何 PR 代码(无人值守 CI 运行从不执行 PR 代码)。 该 PR 的 CI 尚未在被审 commit 上运行:API 显示该 commit 上没有任何 check-run,也没有任何
沙箱验证可以落地行为性声明: — Qwen Code · qwen3.8-max Reviewed at |
|
Confidence: 3/5 — the implementation quality is genuinely high, but the problem is not grounded in measured evidence, the direction pre-empts this repo's own measure-first plan, and it arrives at roughly ten times the size of the incremental alternative; a maintainer needs to judge the vehicle, and the 3/5 cap here is Stage 0 policy (core paths, 500+ production lines, escalated for maintainer awareness), not a code-quality verdict. Going back to my independent proposal: for "flat memory indexes spend tokens on irrelevant entries," I would have added one bounded fetch/search tool on top of the existing recall path and trimmed the per-turn injection to metadata — roughly 500–800 lines. This PR is 6,860 production lines. Each piece of added machinery is individually defensible — background migration with rollback, body-residency tracking across compaction, HMAC-signed cursors, a whole-tool lockdown surface, a telemetry split — but together they multiply the review, revert, and maintenance surface, and none of it is justified by measurement yet. The question I keep coming back to is whether the problem is demonstrated at all. The repo's own plan for this exact area was to measure first: #8998 exists to decide from production delivery telemetry whether a recall change is warranted. The pointed detail is that the author wrote #7393 — the delivery telemetry #8998 is meant to read — so this PR leapfrogs the very instrument they built. That doesn't make the change wrong; it makes the direction a maintainer call, not a gate call. On execution, to be clear where my reservations are not: the code is built with real care. Signed cursors with timing-safe comparison and mtime invalidation, atomic migration writes, an explicit prepare/confirm/commit transition with rollback and telemetry on every branch, deferred delivery logging, symlink-aware path matching, and 6,288 lines of tests. If a maintainer accepts the direction, the quality bar to maintain is there. My reservations are about necessity and sequencing: no opt-out for the automatic file-access denial, a lockdown that steers rather than enforces, an LLM call per legacy file during migration, and an incremental path that was plausibly available. On evidence: there is none at runtime. CI has not run on the reviewed commit (the fork's CI workflows have not been approved/triggered), Windows and Linux are untested, and the token-savings and recall-quality claims — the entire "why" of the PR — are substantiated by nothing in the PR or its CI. Approving on that record would be approving a description. If I had to maintain this in six months: the dual legacy/structured paths plus the migration state machine are real ongoing cost, mitigated by the transition being designed to go one-way once the corpus is migrated. I'm not approving because I ran out of objections — I'm deferring because the central question ("should we do this now, this way, at this size?") is a product judgment the gate cannot make. 中文说明置信度:3/5 —— 实现质量确实高,但问题缺乏实测证据支撑,方向抢先于本仓库自己的"先测量"计划,且规模约为增量替代方案的十倍;应由维护者判断载体是否合适。此处的 3/5 上限来自 Stage 0 政策(核心路径、生产代码 500+ 行、已升级至维护者知悉),而非代码质量结论。 回到我的独立方案:针对"平铺记忆索引在不相关条目上消耗 Token",我会新增一个有界 fetch/search 工具置于现有召回链路之上,并把每轮注入精简为 metadata——约 500–800 行。本 PR 是 6,860 行生产代码。新增机制的每一部分单独看都有道理——带回滚的后台迁移、跨压缩的正文驻留跟踪、HMAC 签名游标、整工具面的封锁、遥测拆分——但合在一起放大了评审、回滚与维护面,且目前没有任何一项由测量证明必要。 我反复回到的问题是:问题本身是否被证实。本仓库对该领域的既定计划是先测量:#8998 的存在就是为了依据生产交付遥测判定是否有理由做召回变更。耐人寻味的是,作者写了 #7393——即 #8998 要读取的那套交付遥测——因此本 PR 跨越了他们自己建造的测量仪器。这不意味着变更是错的;它意味着方向问题是维护者的判断,而不是 gate 的判断。 关于执行,先说清楚我的顾虑不在哪里:代码构建得相当用心。时序安全比较与 mtime 失效的签名游标、原子迁移写入、带遥测且每个分支都有回滚的明确 prepare/confirm/commit 切换、推迟的交付日志、符号链接感知的路径匹配,以及 6,288 行测试。如果维护者接受该方向,可维护的质量标准是在的。我的顾虑在于必要性与顺序:自动文件访问拒绝没有退出开关、封锁是引导而非强制、迁移期间每个旧文件一次 LLM 调用,以及一条大概率可行的增量路径。 关于证据:运行时证据为零。CI 尚未在被审 commit 上运行(fork 的 CI 工作流未被批准/触发),Windows 与 Linux 未测试,而 Token 节省与召回质量这两项声明——整个 PR 的"为什么"——在 PR 与其 CI 中都没有任何支撑。在这种记录下批准,等于批准一份描述。 如果六个月后由我维护:legacy/structured 双路径加迁移状态机是真实的持续成本,缓解因素是切换被设计为语料迁移完成后单向进行。我不批准,不是因为我说不出反对理由才放行——而是因为核心问题("现在该不该以这种方式、这种规模做?")是 gate 无法替代的产品判断,所以我选择转交。 — Qwen Code · qwen3.8-max Reviewed at |
|
⏸️ Deferring to @yiliang114 — needs a human call on this one. This PR is not approved and not rejected. Three things put it in the maintainer's court:
The implementation itself is high quality — full notes in the review comments above. @ZijianZhang989, nothing here is a rejection of the work; this is the gate saying the decision is bigger than the gate. 中文说明⏸️ 转交 @yiliang114 —— 需要人工判断。 本 PR 既未批准也未拒绝。三点原因使其交由维护者决定:
实现本身质量很高——完整笔记见上方审查评论。@ZijianZhang989,这里没有任何否定工作的意思;这只是 gate 在说:这个决定比 gate 的职权更大。 — Qwen Code · qwen3.8-max |
What this PR does
This PR evolves managed auto-memory from a flat, body-heavy prompt into a structured push/pull recall protocol. The model receives a complete two-level ref/title tree when the memory corpus changes, a query-focused metadata subtree on relevant turns, and a dedicated tool for exact fetch, keyword or phrase search, and category exploration. Memory bodies are loaded in bounded windows only when metadata is insufficient, with signed cursor continuation and duplicate-window suppression.
Existing memory remains usable throughout migration. Legacy files continue through the existing recall path while a best-effort background migration adds validated frontmatter atomically; the runtime switches to structured recall only after every visible enabled scope is ready, and rolls back the protocol if activation cannot complete safely. Remember, extraction, project dream, and user dream reuse a dynamic canonical keyword vocabulary while preserving the original memory body.
The change also tracks whether fetched body versions are still resident in conversation history. Unchanged resident bodies are not injected again, compacted bodies can be fetched again, and modified files are treated as new versions. Telemetry separates scan, fast selection, model selection, delivery, search, migration, and protocol-transition timing.
Direct file and shell access to managed memory is restricted while structured recall is active so the tool remains the single audited body-loading path. Glob protection covers managed-memory descendants, ancestor dot-globs, brace expansion, and early Node 22 releases without blocking ordinary non-dot globs.
Why it's needed
Flat memory indexes consume prompt tokens even when most entries are irrelevant, provide little hierarchy over a growing corpus, and make precise per-turn recall difficult. Model-only asynchronous selection can also miss the first request or fail independently, while injecting complete bodies eagerly increases latency and context cost.
The structured protocol keeps a stable global map, adds a small query-specific focus layer, and lets the model pull only the body windows needed for the task. The migration gate preserves existing users' memories and recall behavior until the new metadata contract is complete rather than requiring a destructive one-time conversion.
Reviewer Test Plan
How to verify
./*,~/*, and{src,docs}/README.mdremain usable.cd packages/core && npx vitest run src/memory/tree.test.ts src/memory/recall.test.ts src/memory/search-memory.test.ts src/memory/manager.test.ts src/tools/search-memory.test.ts src/core/client.test.ts src/config/config.test.ts src/memory/metadata-migration.test.ts src/memory/user-dream.test.ts src/memory/writer-keyword-vocabulary.test.ts.cd packages/core && npx vitest run src/tools/shell.test.tsandnpx -y node@22.4.1 ../../node_modules/vitest/vitest.mjs run src/tools/shell.test.ts.Evidence (Before & After)
Before: managed memory is exposed primarily through a flat index and selector-selected prompt content, with no audited on-demand body protocol or automatic compatibility transition for legacy metadata.
After: the model receives a revisioned complete tree plus per-turn focused subtrees, reads bounded bodies through the memory tool, avoids duplicate resident content, and transitions from legacy recall only after atomic background metadata migration is complete.
Local verification completed: 1,125 focused Core tests passed; the shell suite passed 331/331 on the development runtime and 331/331 on Node 22.4.1; Core TypeScript and ESLint checks passed.
Tested on
Environment (optional)
macOS, local TypeScript workspace, Node 26.2.0 and Node 22.4.1 compatibility smoke run.
Risk & Scope
Linked Issues
Related to #8998.
中文说明
本 PR 做了什么
本 PR 将托管自动记忆从平铺、正文占比较高的提示上下文升级为结构化的 push/pull 召回协议。记忆语料发生变化时,模型会收到完整的两层 ref/title 主题树;相关轮次会收到面向当前问题的 metadata 子树;同时提供专用工具执行精确 fetch、关键词或短语 search 以及分类 explore。只有 metadata 不足时才会按有界窗口读取记忆正文,并支持签名 cursor 续读和重复窗口抑制。
迁移期间已有记忆始终可用。旧格式文件在 best-effort 后台迁移以原子方式补充经过校验的 frontmatter 时,继续使用原有召回链路;只有所有可见且已启用的 scope 全部就绪后,runtime 才切换到结构化召回;如果协议激活无法安全完成,则回滚到原协议。Remember、Extraction、项目 Dream 和用户 Dream 会复用动态 canonical keyword vocabulary,同时保留原始记忆正文。
本改动还会跟踪已 fetch 的正文版本是否仍存在于对话历史中。未变化且仍驻留的正文不会重复注入;被压缩移除的正文可以重新 fetch;记忆文件发生修改后会被视为新版本。Telemetry 分别记录 scan、fast selection、model selection、delivery、search、migration 和协议切换的耗时。
结构化召回启用时,系统会限制通过普通文件工具和 shell 直接访问托管记忆,使专用工具成为唯一可审计的正文加载路径。Glob 防护覆盖记忆目录内部路径、能够命中祖先目录的 dot-glob、brace expansion 和早期 Node 22 版本,同时不会阻止普通非 dot glob。
为什么需要
平铺记忆索引会在大部分条目与当前问题无关时仍消耗提示 Token,难以表达不断增长语料的层次,也不利于每轮精确召回。仅依赖异步模型 selector 还可能错过首个请求或独立失败,而提前注入完整正文会增加延迟和上下文成本。
结构化协议保留稳定的全局地图,增加较小的当前问题聚焦层,并允许模型只拉取完成任务所需的正文窗口。迁移门禁会在新 metadata contract 完整之前保留已有用户的记忆和召回行为,不要求进行破坏性的一次性转换。
审阅者测试计划
如何验证
./*、~/*和{src,docs}/README.md等普通模式仍可使用。cd packages/core && npx vitest run src/memory/tree.test.ts src/memory/recall.test.ts src/memory/search-memory.test.ts src/memory/manager.test.ts src/tools/search-memory.test.ts src/core/client.test.ts src/config/config.test.ts src/memory/metadata-migration.test.ts src/memory/user-dream.test.ts src/memory/writer-keyword-vocabulary.test.ts。cd packages/core && npx vitest run src/tools/shell.test.ts,以及npx -y node@22.4.1 ../../node_modules/vitest/vitest.mjs run src/tools/shell.test.ts。证据(修改前与修改后)
修改前:托管记忆主要通过平铺索引和 selector 选中的提示内容暴露,没有经过审计的按需正文协议,也没有兼容旧 metadata 的自动切换过程。
修改后:模型会收到带 revision 的完整树和每轮 focused subtree,通过记忆工具读取有界正文,避免重复驻留内容,并且只有原子后台 metadata 迁移完成后才从旧召回切换。
本地验证结果:1,125 条聚焦 Core 测试通过;shell 测试在开发运行时下 331/331 通过,在 Node 22.4.1 下同样 331/331 通过;Core TypeScript 和 ESLint 检查通过。
测试平台
环境(可选)
macOS,本地 TypeScript workspace,Node 26.2.0,并使用 Node 22.4.1 做兼容性冒烟测试。
风险与范围
关联 Issue
关联 #8998。