Skip to content

feat(memory): add scoped external import jobs for CLI and HTTP - #14281

Open
callmeYe wants to merge 36 commits into
mainfrom
codex/external-memory-import
Open

callmeYe wants to merge 36 commits into
mainfrom
codex/external-memory-import

Conversation

@callmeYe

@callmeYe callmeYe commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

What this PR does

Adds a Qwen-owned external memory import service for CLI and cloud integrations, starting with Codex. Clients submit relative Markdown paths and contents, inspect a deterministic preview, explicitly select sections and destination scope, then start durable background consolidation using the selected workspace's configured model. Results enter native memory search, fetch, recall and forget. Main checkouts and registered linked worktrees share the imported project layer while ordinary memory isolation remains unchanged.

The service exposes memory_import_v1, workspace-qualified HTTP preview/submit/status/source/cancel/retry endpoints, and offline memory import codex --dry-run --json. Unknown source scope stays unassigned. The server owns destination paths and validates active workspace trust; it never falls back to the primary runtime. Only selected source snapshots are retained. Journaled publication, idempotency, cancellation and retry protect existing, edited and forgotten memories.

Consolidation uses bounded batches per source project and confirmed scope, a compact same-scope metadata index with relevant evidence, and durable aggregate call/token reservations. Preview exposes usage estimates without reading native memories or invoking a model. Failure, cancellation and retry do not replenish the budget. Saved batch proposals preserve per-source recovery and edit/forget protection; imported repository identity lookups are cached and skipped entirely when no imported layer exists.

Why it's needed

A cloud runtime cannot read a user's laptop filesystem. Direct per-bullet conversion also loses historical context and does not reconcile old knowledge with existing native memory. This replaces #14149 with shared backend capabilities and model-backed consolidation. No external product UI, independent collector, dependency installation requirement or unrelated CI repair is included.

Reviewer Test Plan

How to verify

  • Submit a synthetic Codex source set containing two projects and a referenced distilled Markdown resource. Preview should return grouped sections, references and assignment reasons without model calls or memory/task writes. An ambiguous section should stay unassigned.

  • Select only one project and submit to a trusted non-primary workspace. Observe queued/running/completed results, then fetch its retained source and search the native result. The other project's text must never reach the model or retained snapshot; unknown/untrusted/draining destinations must not fall back to primary.

  • Repeat the same request/key and then try another key with the same selected content. Native documents should not multiply. Edit or forget an imported document; another import must return conflict rather than restore it.

  • Cancel during consolidation, retry a failed item, and restart after a forced process kill before/after publication. Recovery should wait out a dead worker's lease, preserve accurate counts/refs and publish each document only once.

  • Verify the imported project result through a linked worktree, and verify an independent clone remains isolated. Ordinary per-worktree memory must retain its existing behavior.

  • Verify a 12-section selected project is processed in two bounded calls, while excluded groups never reach the model. Preview should show group and aggregate usage estimates; retrying a budget-exhausted task must not reset the allowance. Interrupt between publications and verify saved proposals resume without another paid call or restoring edited/forgotten knowledge.

Evidence (Before & After)

Before: the installed baseline CLI rejected memory import codex, and the base daemon did not advertise the capability. After: synthetic shipped-CLI dry-runs (bare, leading debug and safe mode) returned versioned pure JSON with zero network attempts and zero filesystem changes. Confirmed CLI import and the real bundled daemon → real ACP child → loopback OpenAI-compatible model produced native searchable memory without unselected input. Bare/safe confirmed import rejected before source access or authentication.

Initial implementation tests passed: 559 CLI cases, 31 core cases, and the focused cold-workspace ACP bridge case. Full workspace build and typecheck passed; affected packages were rebuilt after final edits, with a final bundle and CLI typecheck. ESLint and formatting passed. Real SIGKILL probes left stale lock directories and recovered automatically after approximately 120 seconds, without manual lock deletion; both interruption points produced one document and correct original-batch counts. Two self-audit passes and a local medium review found no remaining issue; these are not formal approval.

Latest cost update after merging main: 55 focused CLI and 126 core tests passed, along with full build, bundle, typecheck, lint and format. Real bundled CLI/HTTP each processed 12 selected sources as [8, 4], with two requests and no extra request on repetition. Synthetic existing-context payload fell from 199,080 to 2,170 bytes. Interrupted-batch, budget-recovery and cross-group ordering probes passed. Production billing savings are not asserted.

Tested on

OS Status
🍏 macOS ✅ Synthetic CLI, real daemon/ACP/model transport, native memory and process recovery
🪟 Windows ⚠️ Awaiting CI
🐧 Linux ⚠️ Awaiting CI

Environment (optional)

Isolated synthetic HOME, runtime, source and repository directories; deterministic loopback provider only. The full build used an 8 GiB Node heap. No actual user memory or production provider was accessed.

Risk & Scope

  • Budget limits: 32 logical invocations and 512,000 conservative reserved token units per task, with 8,192 output tokens per request. These are not guaranteed provider-billed-token or currency caps; oversized metadata/relevant evidence fails closed.
  • Main risk or tradeoff: this is a cross-package feature with native memory reader and ACP integration; maintainer architectural review is requested. Model output is schema/native-format checked and cannot overwrite existing documents, but production-model semantic quality still requires evaluation.
  • Not validated / out of scope: external product UI, standalone collectors, continuous sync, cross-instance sync and production deployment. SSH workspaces and legacy memory base/local path overrides are rejected in v1. Resource/context limits are documented; oversized context fails instead of silently truncating.
  • Breaking changes / migration notes: additive capability and endpoints; ordinary writable memory roots remain unchanged. This does not migrate experimental state from feat(cli): add selective offline Codex memory import #14149.

Design and API contracts are complete and synchronized in both languages: Design (English), 设计(中文), API (English), 接口(中文).

Linked Issues

Supersedes #14149 following the requested backend redesign.

中文说明

本 PR 的变更

提供由 Qwen 自身负责的外部记忆导入服务,供 CLI 和云端系统接入,首版支持 Codex。客户端提交相对 Markdown 路径和内容,查看确定性预览,明确选择章节和目标范围后,启动使用目标工作区模型的持久化后台整理任务。结果进入原生记忆搜索、全文、召回和遗忘流程。主检出与已注册 linked worktree 共享导入项目层,普通记忆的既有隔离语义保持不变。

提供 memory_import_v1、限定工作区的 HTTP 预览/提交/状态/来源/取消/重试接口,以及离线 memory import codex --dry-run --json。未知归属保持待分配。目标路径由服务端管理,检查活动工作区信任状态,不回退主运行时。只保留选中来源快照,通过提交日志、幂等、取消和重试保护已有、已编辑和已遗忘的记忆。

整理按来源项目和确认范围分成有界批次,发送紧凑的同范围元数据索引及相关证据,并持久化总调用/token预留预算。预览展示估算,不读取原生记忆、不调用模型;失败、取消、重试不会重置预算。整批提案持久化后支持逐来源恢复,继续保护编辑和遗忘。仓库身份采用有界缓存,没有导入层时跳过遍历。

为什么需要

云端运行时无法直接读取用户电脑的文件系统;逐条转换 Markdown 列表还会丢失历史上下文,无法协调历史知识和已有原生记忆。本 PR 以共享底层能力和模型整理替代 #14149。不包含外部产品界面、独立收集器、额外安装要求或无关 CI 修补。

评审测试计划

使用含两个项目和一个引用 Markdown 资源的合成来源,验证预览返回分组、引用和归属原因,不调用模型,不写记忆或任务;歧义章节保持待分配。只选择一个项目并提交到可信次工作区,观察任务状态、查看保留来源并搜索原生结果;未选内容不得进入模型或快照,未知、不可信和退出中的目标不得回退主工作区。

重复同一请求/幂等键,再用新键提交相同内容,文档数量不应增加。编辑或遗忘导入文档后再次导入应返回冲突,不恢复内容。验证取消、失败重试,以及发布前后强制结束进程后的重启恢复:等待旧锁租约后自动继续,计数和引用正确且只发布一次。验证 linked worktree 共享结果、独立 clone 隔离及普通记忆原有行为。

前后证据

基线已安装 CLI 不支持该命令,基线 daemon 无此能力标识。实现后,成品 CLI 的普通、前置 debug 和 safe 模式预览均输出版本化纯 JSON,零网络尝试和零文件变化。确认后的 CLI 导入,以及真实 daemon → 真实 ACP 子进程 → 本地 OpenAI 兼容模拟模型的链路,均生成可搜索原生记忆且不包含未选来源。bare/safe 模式确认导入在读取来源或认证前被拒绝。

本地通过 559 项 CLI、31 项 core 测试及冷启动工作区 ACP 专项测试。全工作区构建和类型检查通过;最后修改后重新构建受影响包,完成最终 bundle 和 CLI 类型检查。ESLint、格式检查通过。真实 SIGKILL 遗留锁目录,约 120 秒后自动恢复,无需手工删锁;两个中断点均只生成一个文档,原批次计数正确。两轮自审和本地 medium 审查无剩余问题,不等同正式批准。

最新成本更新同步主线后,55 项 CLI、126 项 core 定向测试及全构建、bundle、typecheck、lint/format 通过。真实 CLI/HTTP 均将 12 条选中来源分成 8+4,只调用两次,重复提交不增加调用;预览展示每组和总用量估算。合成已有上下文从 199,080 降至 2,170 字节,中断恢复、预算不重置与交错项目顺序验证通过。这不是生产账单节省率。

平台与环境

macOS 已完成合成 CLI、真实 daemon/ACP/模型传输、原生记忆和进程恢复验证;Windows、Linux 等待 CI。使用隔离的合成 HOME、运行目录、来源和仓库,以及确定性本地模拟模型;全量构建使用 8 GiB Node 堆。未访问真实用户记忆或生产模型。

每任务上限为 32 次逻辑调用及 512,000 个保守预留 token 单位,每次输出最多 8,192 token;不保证供应商实际计费 token 或金额硬上限。元数据或相关证据超限时明确失败。

风险、范围与迁移

本功能跨包并接入原生记忆读取和 ACP,需要维护者架构评审。模型输出经过结构化和原生格式校验且不能覆盖现有文档,但生产模型语义质量仍需评估。不包含外部产品界面、独立收集器、持续同步、跨实例同步或生产部署。v1 拒绝 SSH 工作区及旧式记忆 base/local 路径覆盖;资源和上下文限制已说明,超限失败而不静默截断。

新增能力和接口,普通记忆写入根目录不变。不迁移 #14149 的实验状态。双语设计与接口文档内容完整同步,链接见上方。本 PR 按重新设计要求替代 #14149。

@qwen-code-review-bot

qwen-code-review-bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

✅ Qwen Triage finished — view run. See the stage comments in this thread for the result.

✅ Qwen Triage 已完成 —— 查看运行。结果见本线程中的各阶段评论。

@callmeYe

callmeYe commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator Author

2026-10-20: preserve native user-memory capacity; repair Java Checkstyle

Published memory fix 46b716e. Capped user scans and index generation prioritize native entries over the reserved imported/ subtree, including long native links under the character budget; imports use remaining catalogue space. Existing caps and project/team ordering remain unchanged. Exact-parent regressions were red; real-service synthetic probes now preserve the original catalogue with (a) 1 native + 201 imports, (b) 200 native + 1 import, and (c) a nearly full character budget containing a long native link + 20 imports. Omitted imported entries remain searchable and explicitly fetchable. R2-3 is addressed.

Preserved and independently verified bot commit 29fb438 for R1-37: the old service fails the mature-store regression, while the bounded index reaches one model call. A synthetic 55,581-byte metadata index is reduced to 32,541 bytes; relevant full evidence is admitted first, oversized relevant evidence rejects with zero model calls/budget, and an omitted exact-body duplicate remains protected by the full snapshot. Reservation equals the actual serialized model input. This verifies deterministic admission/exact-body protection, not semantic completeness of the existing relevance heuristic or a production billing limit.

A separate real CI failure occurred in Java fast job118520265720 (run39390337018), actual merge900c71b of head29fb and main9345c4: 5000 tests / 0 failures / 21 skipped, followed by six Checkstyle violations in an upstream test source. Current main had the identical blob. Normal main merge cf9f04c preserves the functional patch exactly (stable patch ID58ad1e5d8d6e1960fcd4fc442ff86e985cf17042); final fix3db4352af6448f38b1b01cd387420e9771f2421f removes only one unused import and seven surplus blank lines. No Java executable logic/assertions or CI gates changed. The same Checkstyle9.3 reproduces six violations before and zero after; both configured source groups pass (608 managed + 110 broker files). This local checker verification is not a full Maven/database integration rerun.

Final merged-tree verification: core214/214, CLI87/87, full workspace build, bundle, sequential typecheck, focused lint/format and normal hooks pass. Final real bundled CLI and daemon→ACP HTTP each pass preview (zero model/native writes), selected1/2, added1, one loopback model call, and idempotent repeat with no extra call. Two clean memory-delta self-audits and a local medium quick review found no additional issue; the trivial Java delta also received a clean audit/quick pass. These are not formal approval. Only synthetic isolated data was used; no real memories or production providers were accessed. Final PR scope is46 code/docs/synthetic-test files (the native-capacity repair adds four core paths to the earlier41, and the Java style fix adds one). New-head CI is starting, not confirmed green.

All 67 review threads are now closed after evidence-backed fixes or the explicit dispositions below. The six obsolete bot blocking reviews were dismissed through the normal GitHub action; live reviewDecision is REVIEW_REQUIRED, with no APPROVED review. Maintainer direction/approval and current-head CI remain outstanding. This is not a merge or approval.

Explicit non-blocking dispositions after more than five review rounds

Per the repository's Critical-only rule, the following remaining Suggestion threads are recorded individually here and closed as addressed/deferred rather than silently expanded into this PR. Deferred means future work, not implemented behavior; the existing scope, preview-estimate, relevance, diagnostics, cleanup and coverage limitations remain visible. No human request or approval is being dismissed.


2026-10-20: invalidate stale repository identities (03663c6)

R3-6 is repaired without requiring a daemon restart or a cache clear from the import process. Cached recall now validates the recorded Git/path dependencies; changed topology is resolved again and invalid registrations fail closed. The original pointer syntax, linked-worktree checks, runtime-root selection, ordinary-memory isolation and 256-entry cache cap remain unchanged. The read-race check rejects changed dependencies before a resolved identity is cached; this is not an atomic filesystem snapshot.

Purely synthetic red/green evidence: new tracked regression tests failed 3/6 on exact bbb32f4 and pass 6/6 after the fix. Seven independent topology mutations reproduced stale native search results before the fix; all seven stop old-repository recall in both the fixed source and final built core. Core scan/search/fetch/recall/forget tests pass 236/236; CLI import service/source/HTTP tests pass 86/86. Actual bundled CLI and daemon→ACP→loopback HTTP each preview without model/native-memory writes, import one explicitly selected source with one model call, and reuse the same task on repeat with no extra model call. Pointer/commondir read-race probes reject the raced identity and recover on the next lookup.

Unchanged cache hits use dependency stats rather than repeating discovery, realpath or metadata reads (23 lstat calls per hit for the linked-worktree fixture). No imported runtime directory still means no identity lookup. Full workspace build using the current upstream 6144 MiB script, bundle, sequential typecheck, focused lint/format and normal commit hooks passed. This commit changes four files only: implementation, synthetic regression tests and synchronized design docs; no real memories or production model calls were used.

The preceding bbb32f4 CI now confirms the upstream heap repair across Ubuntu, lint/static, no-key integration, Java fast/real daemon, Serve A/B, TUI and native helpers. Its Web Shell job checked out exact bbb32f4 and finished with 306 passed plus 2 flaky retries (approval-mode settings and browser notifications), not 308 first-pass passes. These are previous-head results; new 03663c6 checks must run independently. Reference publishing and full database lanes that were skipped are not claimed as passed.

The two Critical findings concerning the complete metadata-index ceiling (R1-37) and user-memory index capacity (R2-3) remain open. Formal approval is still absent and this PR remains blocked. Earlier entries below are historical evidence, not current-head CI results.

2026-10-20: adopt the upstream build-heap repair (bbb32f4)

The latest failing head a0c430 and its tested merge a6189a5 hit the fixed 4096 MiB V8 heap cap while compiling the CLI. All 13 failed checks were inspected: 12 show that compilation OOM and the Managed Agent fast-tier check aggregates it. These logs do not show a separate test assertion failure. The Serve A/B failure occurred in the head installation, not proof that its base failed.

After reproducing this with the normal frozen-lockfile installation, an initial one-line 8 GiB repair was published as 2712f46. Main was then found to already contain #14822 (eeec911), which sets the cap to 6144 MiB. The final commit merges exact main ca06036 and takes its package.json unchanged, resolving the conflicting cap. The final build cap is 6 GiB, not 8 GiB. All intervening bot merges are preserved. The PR remains 41 files, with no package.json, lockfile, or workflow diff against that main; the feature patch's stable patch ID is identical before and after this integration.

Final merged-tree validation used the normal frozen-lockfile installation with prepare enabled: full workspace build and bundle passed at 6 GiB. Full typecheck and the targeted ACP import-channel test passed. The 86 synthetic CLI import tests first returned 85 passes and one 5-second HTTP task-poll timeout; the unchanged exact case and then the unchanged full 86-test combination passed. That first timeout's cause remains undetermined, so this is not claimed as a clean first run. Normal commit hooks and the eight-file gate-freshness check passed. Local macOS/Node 22.17 verification does not replace the new Linux/Windows/Node 22.23.2 CI run.

The three Critical concerns about large metadata context, native user-index capacity, and stale repository-identity cache remain open. No real memories or production model calls were used. Remote CI for the final merge still needs verification.


2026-10-19: reject unsupported symlink roots before paid consolidation

Published 3151dc7 on 58b8194 without rewriting history. R1-6 now uses its stated fail-closed acceptance alternative: a symlinked publication root produces an actionable per-item failure before scanning existing content or reserving model budget. This is not support for publishing through a symlink or across filesystems; that capability is explicitly deferred in both design translations. Ordinary native scanning, private-state symlink guards, budget retention and existing-memory protection remain unchanged. The delta is four files: four production lines, one synthetic regression and synchronized design notes.

Exact-parent reproduction consumed one model call before failing generically; the fixed service/source/HTTP suite passes 86/86. Actual bundled CLI and daemon→ACP→HTTP both reject the symlink layout on first attempt and retry with zero wire calls / zero reserved units and an unchanged external synthetic target. Changing the synthetic layout to a regular directory allows the same task/key to complete on retry with one added document and exactly one loopback call. Full workspace build, final bundle, sequential typecheck, focused lint/format and normal hooks pass. Two clean self-audit passes and a local medium quick review found no additional issue in this delta; neither is formal approval. No real memories or production models were accessed.

Detailed R1-6 evidence and explicit capability limit is replied and resolved. Three existing Criticals remain (large metadata context admission, user index capacity and long-lived Git identity caching); blocking reviews remain valid. New-head CI is starting/queued, not yet confirmed green. The prior 58b8194 Ubuntu job 118031599389 did succeed with verified exact checkout: server 1383/1383, CLI 43193 passed / 104 skipped, core 37469 passed / 19 skipped. That closes the earlier four fixture failures but is not new-head CI evidence. The PR remains unapproved and unmerged, with 41 code/documentation/synthetic-test files and no private artifacts.


2026-10-19: repair the four Ubuntu CI runtime-fixture failures

Published 58b8194 on exact parent 2f94 without rewriting history. Ubuntu job 118005434962 (run 39236011054) checked out 2f94 and failed four injected-registry tests because their synthetic runtime omitted its required sessionRuntimeBaseDir. Startup memory-import recovery exposed that incomplete fixture through path.resolve(undefined). This repair adds only the suite-private temporary runtime directory: one test file, four lines. Production runtime ownership, error handling, assertions, timeouts and CI gates are unchanged.

The exact parent reproduces the same four failures (4 failed / 10 passed); the repaired group passes 14/14. HTTP import regression passes 21/21. Full workspace build, sequential typecheck, focused ESLint/Prettier and normal commit hooks pass. The broader server-file run was 1379 passed / 4 different failures (two status-code mismatches and two timeouts); those groups' eight cases pass in focused runs on both the repaired file and exact parent. Their broader-run causes remain unclassified, so this is not an all-green full-file claim. No Unhandled Errors occurred. All verification used synthetic isolated data, with no real memories or production models.

The published SHA is verified remotely and the worktree is clean. New-head workflows are starting/queued; the old failed run is superseded, not rerun. The PR remains blocked by existing reviews, with four Criticals still open and no approval. This CI repair does not resolve or dismiss those findings. The PR diff remains 41 code/documentation/synthetic-test files with no private artifacts.


2026-10-19: preserve durable HTTP receipts and distinguish server failures

Published 2f94b5a while preserving the concurrent main merge 1bf32ae. R3-5 now distinguishes input errors (400), explicitly missing batches (404), unavailable runtime generations (503 with Retry-After), and unexpected server/storage failures (500). Successful durable submit/retry admissions retain their 202 task ID if the runtime closes afterward. Publication guards and post-read access checks remain; a 202 is admission, not completion. Eight changed files contain only the shared import errors/service, route, synthetic tests and synchronized English/Chinese design/API documentation.

The eight new regressions produce seven failures and one control pass against exact parent 1bf; the fixed six-file suite passes 98/98, with the final HTTP revision independently passing 21/21. Real router/service timing probes cover pre-admission drain, durable receipts, and no source-content disclosure after closure or trust revocation. With the final bundled daemon, a synthetic task file made unreadable by real filesystem permissions returns 500, and restoring permissions returns 200/completed; invalid requests remain 400, missing batches 404, and missing bearer authentication 401. Bundled CLI/HTTP import, same-key idempotency, no-write/no-model preview, and transient recovery also pass. Full workspace build, bundle, typecheck, focused lint/format and normal hooks pass. Local audit and medium quick review found no additional issue in this delta; neither is formal approval. Lifecycle timing injection is not a real process-crash test. One earlier mixed test run had an unclassified old drain assertion failure; final serial suites passed. No real memories or production models were used.

Detailed R3-5 evidence is replied and resolved. Four Criticals remain (large metadata context admission, user index capacity, native symlink-root handling, and long-lived Git identity caching); blocking reviews remain valid. New-head CI run 39236011054 and related workflows are running or queued, so previous green heads are not presented as this head's result. The PR is not approved or merged. Current PR diff remains 41 code/documentation/synthetic-test files, with no private artifacts.


2026-10-19: retry failed HTTP startup recovery

Published 5e40886 on 0957945 without rewriting history. R2-17 now retains the service and worker map while tracking recovery separately: task requests await a shared recovery attempt, failures are logged and return 503 memory_import_recovery_unavailable with Retry-After: 1, and the next request retries. Successful recovery remains cached; preview does not trigger recovery. Six changed files contain only the route, synthetic tests and synchronized English/Chinese design/API documentation.

All three new regressions fail against the exact prior router; the final HTTP suite passes 13/13 and command/local/environment coverage adds 13/13. Actual-service probes verify queued work resumes after transient startup failure, concurrent callers share one attempt, and partial recovery does not duplicate model work. The final bundled daemon was tested with a real filesystem obstruction in an isolated synthetic runtime: GET returned 503 while preview remained 200 with zero model/native-memory writes; restoring the directory allowed the next poll to complete the queued task with one loopback model call, retaining the previously charged usage. Basic bundled CLI/HTTP import and idempotency also pass. Full workspace build, bundle, typecheck, lint/format, normal hooks and two clean local audit passes completed. No real memory or production model was used.

Recovery evidence closes R2-17 only. Five Criticals remain, including R3-5: general runtime/storage error classification and durable-response handling were separately reproduced but are not fixed by this change. Blocking reviews remain valid and there is no approval or merge. New-head CI has started and is not yet verified. For the preceding 095 head, actual Ubuntu/Web Shell checkout logs confirm 43,123 CLI tests, 37,445 core tests and 298 Web Shell E2E passes; those results are not claimed for this new commit.


2026-10-19: cross-group replay and unpublished-intent recovery

Published 0957945 on top of the concurrent bot merge 4cd33e9 without rewriting history. This change is limited to the import service, synthetic regressions and the linked English/Chinese design documents (four files). R1-35 now recognizes committed outputs across groups of the same task and locked scope while preserving recorded baseline hashes. R3-3 durably rolls back a changed-memory intent only before native publication; a fresh confirmed task can reconsolidate against current memory, while published edits and forgotten outputs remain protected.

On the exact prior implementation, six regression cases produced four failures and two passing controls. Final related coverage is 87/87; ten recovery/protection probes and two extra cleanup/baseline controls passed. A cleanup failure after ledger rollback leaves an orphan stage rather than a poisoned source ID. A three-group retry preserves edits already included in a later baseline. Final bundled CLI and real daemon→ACP→loopback HTTP each completed preview, selected import and idempotent repetition: preview made no model/native-memory writes and repetition made no extra model call. Full workspace build, final bundle, full typecheck, focused lint/format and normal hooks passed. All data and targets were synthetic and isolated; no real memories or production model were used. Fault injection is not a claim of real SIGKILL testing or atomic filesystem fencing.

Cross-group evidence and unpublished-intent evidence close those two threads. Six Critical findings remain: large-context admission, native index capacity, root-recovery retry, symlink native roots, HTTP lifecycle/error semantics and stale Git-identity cache. Existing blocking reviews remain valid; no approval or merge is claimed. New-head CI has started and still requires remote verification; prior-head success does not establish this head's result.


2026-10-19: native metadata, retry recovery and pipe fixes; browser CI follow-up

Published d202ca0 (memory repair deda75c) on d75eab0 without rewriting history. R1-5 now builds discovery metadata from the native parser's recognized fields instead of a second frontmatter regex. R1-40 validates scope before retaining proposals; retries can discard only the invalid item from older journals, preserving valid siblings and charged usage. R3-2 uses the existing broken-pipe handler for asynchronous stdout errors. R1-1 and R1-2 were already fixed and are now closed with current-code recovery/race evidence.

Five new red regressions failed on the prior implementation; final import coverage is 82/82. Synthetic mixed legacy journals verified that retry requests only the invalid item, preserves edited siblings, does not restore forgotten siblings and does not reset usage. Final bundled CLI and daemon→ACP→loopback HTTP each completed a selected import; repeating added no model calls. Offline preview had no model/native-memory writes. Real OS pipes closed before output or after one byte now exit 0 without EPIPE, while full readers still receive complete versioned JSON. Full workspace build, final bundle, full typecheck, focused lint/format and normal commit hooks passed. All inputs/targets were synthetic and isolated; no real memories or production model were used.

Observed a new failure on d75's Ubuntu Web Shell E2E job 117877119954 (run 39192410625): the remote-computer return URL still contained its one-shot Connections marker, or was still in the verification handoff. A real Chromium probe delaying the returned App module by 12.5 seconds reproduced the retained-marker failure under the original 10-second wait; the marker then cleared normally. The narrow test-only fix waits up to 30 seconds for the same exact clean URL, retaining the 60-second whole-test deadline and all category/catalog assertions. Delayed boot now passes; permanently retaining the marker still fails after 30 seconds. The actual failed case and two previously flaky cases passed 3/3 without retries locally. This macOS evidence does not establish the cause of every CI attempt; the new Ubuntu run still must pass. No navigation production code or CI gate was changed.

This closes five threads, not the whole review. Cross-batch journal recovery, large-context indexing, native index capacity, recovery-root retry, symlink native roots, poisoned prepublication intents, HTTP lifecycle/error semantics and stale identity caches remain under review. No approval or merge is claimed.


2026-10-19: lock ownership and preview admission fixes

Published d75eab0 on top of the latest concurrent bot merge e26a391, preserving its upstream changes. This eight-file change addresses R1-3, R2-2, R5-1, R5-2 and R5-3: unknown retries no longer allocate directories, lock loss is isolated to dependent work, detected old holders cannot replace task/proposal/ledger records or publish staged documents, and Markdown parser/expanded-preview admission is bounded. The existing global budgets and ordinary user cancellation remain intact.

Reproduced each failure before fixing it. Final targeted CLI coverage: 78/78; complete workspace build, final bundle, full typecheck, focused lint/format and normal commit hooks passed. Actual bundled CLI and daemon/ACP HTTP with isolated synthetic sources and a loopback model each imported one document with one call; repeating the submission added no model calls. Ordinary preview performed zero model calls, native writes or task writes. Three excessive-input shapes were rejected by CLI and HTTP while the daemon remained healthy.

Real file-lock probes injected compromise notifications during model work, scope work and temporary task-state writes: replacement state/usage and ledger bytes remained identical, unrelated work completed, explicit cancellation still persisted cancelled, and scope loss published no project document. This is notification-injection evidence, not a real 120-second stale-lease experiment or an atomic filesystem fencing-token guarantee. Synthetic 64 KiB delimiter-heavy input changed from ~5.85 seconds parsing to ~0.95 ms rejection; the setext case changed from ~2.23 seconds / ~1.38 GB heap growth to ~2.05 ms / <0.7 MB. These local measurements are not universal parser guarantees. Small controls retain their ancestor context. HTTP rejection remains generic memory_import_rejected; richer diagnostics remain an open item.

Only code, bilingual docs and synthetic regression tests were committed. No user memory or production model was accessed. Local reports, probes and raw outputs remain untracked/ignored. New-head CI is still pending verification. Other blocking review findings remain open: this fixes five findings and does not clear the formal CHANGES_REQUESTED reviews or imply maintainer approval.


Review follow-up and CI — updated 2026-10-16 06:18 UTC

Current head remains 840442b67146bac09851395f52540ccfa2a8abb3. Ubuntu and lint logs both confirm this exact checkout. Ubuntu reports 42,235 CLI tests passed (102 skipped) and 37,138 core tests passed (17 skipped), including all 24 import-service cases and the new model/path tests. Lint's eight-file freshness check passed. No-AK, Serve A/B, TUI/no-flicker, native checks and reference build/verification have also succeeded. Reference publication was skipped. Web Shell E2E has now succeeded on the exact same checkout: 276 passed and one Chromium trajectory search/folding test marked flaky after retry (not 277 first-attempt passes). Qwen Code CI run 38845368560 is completed SUCCESS. Automatic review is queued; no formal approval has been submitted.

Response to doudouOUC's COMMENTED review: the review body discusses d2acc053f4, while the submitted review is attached to 840442b671. The requested pre-merge traversal fix is present in the latter: a bounded repository-identity cache is cleared with the existing memory-root cache, scans with no imported layer skip resolution, runtime paths remain separate, and writes force fresh identity validation. Independent probes changed five no-import lookups from 30 ancestor checks to zero. The cache, runtime, invalidation and worktree/clone regression tests pass locally and on the exact-head Ubuntu job. Cost/cache change.

The main synchronization request was handled by normal merge 8691b057 of main 17d5f774 before the cost commit; the affected CLI/HTTP and core suites, full build/bundle/typecheck and actual CLI/HTTP transport were rerun. Main has since advanced five commits to 05714e96; those changes contain no lint-gate files. This is evidence for the checked head, not a claim that every later merge tree has been tested.

Non-blocking items are tracked explicitly here:

  • Imported project knowledge remains repository-keyed and is visible to trusted workspaces sharing that actual repository, including nested workspaces; the ordinary project-scope setting does not partition this imported layer. Do not change that semantic without maintainer direction. A more explicit paired design/API sentence is a documentation follow-up.
  • Source path/line provenance lives in retained snapshots and the controlled source endpoint. Published native documents do not carry a special provenance marker. Clarifying that distinction in both language documents is a documentation follow-up; it is not a claim that native recall displays provenance today.
  • Runtime state inside a Git checkout fails closed. The service names this restriction, but the HTTP error mapping can hide its actionable cause. Improving the public diagnostic and listing it more prominently among v1 limitations remains a follow-up, as requested non-blocking in the review.
  • Cost signals are now implemented: per-group/aggregate preview estimates, bounded batches, compact existing evidence, and durable task-wide reservations. Runtime exhaustion returns a partial result with a specific reason, preserving already completed selections. The implementation does not reject the entire task solely because the conservative all-selectable preview estimate exceeds the budget.
  • The launcher's in-process memory route avoids the interactive supervisor/bootstrap path for offline previews. Confirmed import still loads normal configuration/authentication and uses the same bounded service; it bypasses no trust or scope checks. Daemon legacy-path overrides are rejected at admission. CLI override handling is not equivalent and can fail later per item; early actionable rejection and corresponding documentation are tracked as a non-blocking follow-up, not claimed fixed here.

No new code or data was published in this follow-up. No approval has been submitted; normal maintainer approval is still needed. No review rerun or repeated reviewer request was dispatched.


Cost and traversal update — 840442b

Implemented the requested cost controls after reproducing the concerns. Confirmed sources are grouped by original project and selected scope, with at most eight sections / 64 KiB per model request. The model receives the complete bounded metadata index plus relevant full evidence instead of every existing body. Index-only uncertainty is explicitly a conflict; oversized metadata or relevant evidence fails before a paid call. Offline previews expose aggregate and per-group call/token estimates. Unassigned groups remain provisional and estimates cover all selectable sections, not an unsubmitted selection.

Tasks permit at most 32 logical calls and 512,000 reserved token units, with an 8,192-token output allowance per request. Reservations are persisted before invocation and are not refunded on failure, cancellation, restart or retry. Import disables unattended outer retries. These are logical-call/conservative-reservation limits, not guaranteed provider-billed-token or currency caps; provider-internal retries and tokenizer accounting can differ. Native semantic quality still requires production-model evaluation.

Complete validated batch responses and baseline hashes are retained before the first publication. Recovery reuses them while preserving per-source ledgers, native validation, selection isolation and edit/forget checks. A reproduced interleaving bug was fixed by completing a project batch before moving to another group. Git identity lookups now have a bounded cache tied to the existing root-cache reset; scans with no imported layer skip the walk entirely. Writes refresh identity. Ordinary memory isolation and workspace authorization are unchanged.

Synthetic measurements and final validation:

  • 1 / 4 / 12 sections: 1 / 4 / 12 logical calls before, 1 / 1 / 2 after. For 12 sections, repeated existing-memory payload fell from 199,080 to 2,170 bytes; relevant full bodies remain available. This measures synthetic input reduction, not production billing savings.
  • Five repeated root lookups with no imports: 30 Git-ancestor checks before, zero after. Same-repository linked worktree sharing, independent clone isolation, runtime separation and cache invalidation tests pass.
  • Forty failing separate-group requests stopped after 23 calls at 497,271 / 512,000 reservation units. A fresh service and explicit retry added no calls and did not reset the budget. Legacy unaccounted tasks get zero remaining budget, not fabricated historical usage.
  • Two-source interrupted publication reused the saved proposal with one total model call. Editing or forgetting the published source before retry produced conflict without overwrite/restoration. Sixteen interleaved-source groups reproduced 9 additions / 7 erroneous conflicts before the ordering fix and 16 additions / zero conflicts / two calls afterward.
  • Final bundled CLI and real daemon → ACP → loopback provider HTTP each submitted 12 selected sections plus an excluded project: request sizes were [8, 4], two actual requests with wire max_tokens=8192, task usage was two calls / 45,761 reserved units, and repeat submission added zero requests. Identical synthetic output correctly yielded one addition and 11 duplicates. Both previews made zero model calls and no native memory writes; excluded content never reached the model. Single-selection CLI and HTTP examples also passed.
  • After normal merge of main 17d5f774: 55 CLI tests and 126 core tests pass; full workspace build (8 GiB heap), final bundle, sequential full typecheck, lint/format and commit hooks pass. Two clean self-audits and local medium review found no additional issue; this is not formal approval. The prior isolated HTTP status transient remains unexplained; final equivalent tests and real HTTP flows passed.

Only code, bilingual contracts and synthetic unit tests are committed. No real memory, local preview, test-run output, reports, personal paths or external-product changes are included. The normal main merge updates existing gate definitions; this PR does not modify CI gates relative to main. Fresh remote CI is pending verification. Existing maintainer review requests remain; the previously manually cancelled automatic review was not restarted.


Synthetic verification report

Validated commit 1b387a3c529b58f50bb377399184e659aedfae07. All sources, homes, runtimes, targets and provider responses used for testing were synthetic and isolated. No real user memories were imported or included in this PR.

  • 559 focused CLI tests, 31 core tests and the targeted ACP cold-workspace bridge regression passed. Full workspace build and typecheck, final affected-package rebuild and CLI bundle, ESLint and Prettier passed.
  • The shipped CLI produced versioned JSON for bare, debug-prefixed and safe-mode dry-run invocations with zero network attempts and no source, runtime or memory writes. Confirmed imports in bare/safe mode were rejected before source access or authentication.
  • A real bundled daemon, real ACP child and loopback OpenAI-compatible provider completed the HTTP preview → submit → background job → native search flow. Preview returned 200, admission returned 202, and the completed task reported one added document with references. The model received only selected content and structured output tooling; unselected content and operational tools were absent. An idempotent CLI repeat caused no additional model call.
  • HTTP partial failure survived a process restart and completed on explicit retry. Selected source retrieval succeeded while unselected retrieval was rejected. Router tests covered secondary, missing, untrusted and draining runtimes and nested project scope.
  • SIGKILL immediately before and after atomic publication left real stale locks. Fresh processes recovered after the unmodified 120-second lease without deleting locks or changing timestamps. Each case retained one native document and one total model call, with correct added counts and references. Subsequent user edits and forgotten documents remained protected as conflicts.
  • Native search, fetch and forget exercised shared imported knowledge across a main project and registered linked worktree. Independent clones remained separate and ordinary project memory roots remained isolated. Synthetic tests also covered selection boundaries, missing references, invalid model output, cancellation, duplicate/conflict handling and concurrent submission.

Limits: local end-to-end model behavior used a deterministic loopback provider, not a production model; this verifies the integration contract rather than production model quality. Local validation was on macOS. Remote CI and normal maintainer review are still required. The full build used an 8 GiB Node heap after the default 4 GiB invocation exhausted memory.

This replaces #14149 with the shared CLI/HTTP service design. It contains no unrelated fixes carried from that PR and no DataAgent changes.

CI follow-up — d2acc05

The first Ubuntu run failed six contract assertions, and the no-AK run failed the baseline capability assertion. Reproduced all seven locally. The fix registers the existing memory command/capability in the exact expected lists and documents the two narrowly scoped environment reads without weakening the process-environment guard.

The environment audit also reproduced a real ownership issue: an existing runtime snapshot with an absent project-scope key inherited the process scope. Import now honors the complete selected-runtime snapshot; an overlay runtime without a snapshot does not inherit process scope either. Only parent-process runtimes lacking a snapshot retain compatibility fallback. Legacy process-global memory path overrides remain rejected. Both absence cases failed under the original expression with wrong-scope existing memory in the model input and pass after the fix.

Validation: the original seven CI failures now pass, scope/environment coverage passes 13/13, the actual bundled no-AK capability check passes, and synthetic HTTP preview/import/idempotence passes with no production model. Full workspace build, final CLI build/bundle, full typecheck, focused lint/format and normal commit hooks passed. Only six source/test files changed; no private data or unrelated CI changes.

Expanded-suite limitation: one four-file run passed 1470 tests but had transient HTTP parsing/transcript failures; an entire server-file rerun passed 1371 but timed out in a different attachment-upload test. Each affected case passed focused comparison with both original and fixed expressions. Their root cause remains undetermined; this is not a claim that the complete server suite passed in one run. The new remote CI must still be checked.

Historical consolidation-cost assessment — d2acc05 (superseded by the update above)

Rechecked the current implementation against upstream openai/codex commit 7c152d468447244583f5013887a4bc1371cf1f4c. The cost concern is valid and is not fixed by the CI follow-up. Each previously unseen selected section currently invokes a logical model consolidation with all existing documents in its scope. Earlier additions can increase subsequent inputs. The 512 KiB serialized-input limit is a request guard, not an aggregate token budget; up to 2048 selections are admitted without a call/token estimate. Completed-source ledger entries avoid repeat calls, but this does not bound first-import cost.

Codex external import preserves selected project resources and scope metadata, then enqueues global consolidation rather than extracting every imported section separately (import implementation). The pipeline skips model work when the synchronized workspace has no changes and its artifacts validate, otherwise starts a consolidation agent; its incremental instructions prioritize changed evidence and retain detailed sources behind references (pipeline, instructions). A consolidation agent may make multiple model turns: one queued job is not one model request. Large first imports can still be expensive. Native rollout extraction is a separate pipeline; its rollout-count defaults must not be represented as external-import file limits.

The useful direction is bounded batching by confirmed target scope, compact/relevant existing-memory context with progressive evidence access, and admission-time estimates plus explicit aggregate limits and actual usage reporting. This must retain selected-source isolation, native validation, durable retries, and commit-time edit/forget protection. Codex's global consolidation ownership must not be copied across Qwen workspace authorization boundaries. These are assessment recommendations, not claims of implemented behavior or measured production savings. No production model or user memory was used for this assessment.

Remote CI is now successful on d2acc05, including Ubuntu, lint/static, no-AK, Serve A/B, TUI, native, Web Shell E2E and reference distribution build/verification. Web Shell checkout was verified as the exact head; its browser summary is 254 passed and one mobile-WebKit focus test marked flaky after retry, not 255 first-attempt passes. Reference publication was skipped, not published. That automatic review was subsequently cancelled manually by a maintainer; no formal approval has been submitted. Normal maintainer review is still required. For triage accuracy, #14149 is already closed and replaced by this PR. The CLI/HTTP redesign was explicitly requested by the end user; that is user demand, not maintainer approval of the product direction.

@qwen-code-review-bot

Copy link
Copy Markdown
Collaborator

Thanks — this is a serious piece of work, and a much better shape than #14149.

Template looks good ✓ — every required section present, Tested on filled in, and I checked the EN/ZH design and API pairs rather than assuming: same five sections in the same order in both, same endpoint table, same limits. They are genuinely synchronized, not a stub translation. (The line-count gap is just EN hard-wrapping vs. ZH single-line paragraphs.)

Problem: this is a feat, not a fix, so a before/after reproduction is the wrong bar. The gap is concrete and real — a cloud runtime cannot read a user's laptop filesystem, so Codex knowledge currently has no path into scoped native memory. What is still thin is the demand signal, and this is the same gap the triage on #14149 named and nobody has answered since: there is no linked issue and no user request behind either PR. closingIssuesReferences is empty here — "Supersedes #14149" carries no closing keyword, so nothing links. Worth saying plainly because it is a product-direction question, not a code question.

Direction: memory is a first-class subsystem here and this reuses it instead of standing up a parallel store — imported documents land in the native scan/search/fetch/recall/forget path, consolidation goes through the existing runSideQuery, and the routes hang off the existing workspace-runtime resolution and mutation gate. Architecturally that fits. The scope question is sharper than it was on #14149, though: this adds a durable background job engine (leases, journals, idempotency, cancel, retry, crash recovery) plus six new HTTP routes and a new ACP control method, where the predecessor was a CLI-only flow. CHANGELOG: no precedent for cross-agent memory import.

Two process notes I would not want buried:

Size: core paths are touched — packages/core/src/memory/** plus cross-package changes spanning acp-bridge, cli, and core. Breakdown of the +3,168 / −5:

Bucket Lines Files
Production logic 1,826 17
Tests 1,071 8
Docs (design EN+ZH) 276 4
Generated/schema 0 0

Of the production lines, 228 are in packages/core/src/memory/** (import-model.ts 116, import-paths.ts 73, paths.ts 32, forget.ts 6); the rest is CLI/serve/bridge.

You are a listed core owner with write access, so the external two-tier core gate does not apply to you and nothing here is blocked on size. Two things are still worth flagging, both informational: 1,826 production lines crosses the 1,000-line large-PR advisory, and the core-memory area owner in .github/issue-owners.json is @wenshao, not you — your own description asks for "maintainer architectural review," so I have routed it there rather than treating your write access as the sign-off.

Approach: my independent baseline before reading the diff was much smaller — a parser plus a memory import codex subcommand that previews, takes an explicit selection, and writes through the existing memory APIs, with at most one thin daemon endpoint for the cloud case. What landed is roughly four times that. Having read it, I think most of the delta is defensible: the crash-recovery ledger exists because a half-published import that resurrects something the user deliberately forgot is a genuinely bad failure mode, and the workspace-qualified routing is what keeps this from falling back to the primary runtime. The part I would still push on is the model-backed consolidation. Deterministic per-section conversion was the thing #14149 did; this replaces it with a paid model call per selected section (up to 2,048 selections, serially), and the argument for it — "direct per-bullet conversion loses historical context" — is asserted rather than demonstrated. More on the concrete consequences in Stage 2.

Diff hygiene is clean: 5 deletions total, no drive-by refactors, no formatting churn, no unrelated edits. Test-to-production ratio is roughly 1:1.6 and the tests assert the invariants they name rather than restating the implementation.

Risk: Stage 1e matched one high-risk path — packages/cli/src/acp-integration/acpAgent.ts (acp-integration), which is the revert-correlated set. That does not block anything, but it means no enrichment gets skipped and CI evidence is required before approval. Separately, scripts/cli-entry.js adds memory to the in-process fast path next to serve/mcp — a one-line change that alters startup for every qwen memory invocation and is not explained in the description. I have a question on it in Stage 2.

Moving on to code review. 🔍

中文说明

感谢贡献——这是一份很扎实的工作,形态也比 #14149 好得多。

模板完整 ✓——所有必填章节齐全,Tested on 已填写。中英双语的设计文档和接口文档我实际比对了而非想当然:两版都是相同的五个章节、相同顺序、相同的接口表格和相同的限制值,确实是同步的,不是占位翻译。(行数差异只是英文硬换行、中文单行长段落。)

**问题:**这是 feat 而非 fix,所以 before/after 复现不是合适的门槛。缺口本身具体且真实——云端运行时无法读取用户本机文件系统,因此 Codex 知识目前没有进入按范围原生记忆的通路。仍然薄弱的是需求信号,而且这正是 #14149 那次 triage 提出、至今无人回答的同一个问题:两个 PR 都没有关联 issue,也没有用户诉求。本 PR 的 closingIssuesReferences 为空——"Supersedes #14149" 不含关闭关键字,因此不构成关联。这一点值得直说,因为它属于产品方向问题,而不是代码问题。

方向:记忆在这里是一等子系统,本 PR 复用它而不是另建并行存储——导入的文档进入原生扫描/搜索/获取/召回/遗忘链路,整理走既有的 runSideQuery,路由挂在既有的工作区运行时解析和变更门禁上。架构上是契合的。但范围问题比 #14149 更突出:本 PR 新增了持久化后台任务引擎(租约、日志、幂等、取消、重试、崩溃恢复),外加六个新 HTTP 路由和一个新 ACP 控制方法,而前身只是 CLI 流程。CHANGELOG:没有跨 agent 记忆导入的先例。

有两个流程问题我不希望被埋没:

**规模:**确实触及核心路径——packages/core/src/memory/**,以及跨 acp-bridge、cli、core 的跨包改动。+3,168 / −5 的构成:生产逻辑 1,826 行(17 个文件)、测试 1,071 行(8 个文件)、文档 276 行(4 个文件)、生成/schema 0 行。

其中 228 行生产代码位于 packages/core/src/memory/**(import-model.ts 116、import-paths.ts 73、paths.ts 32、forget.ts 6),其余在 CLI/serve/bridge。

你是有写权限的既定 core owner,因此外部两级核心门禁不适用于你,这里没有任何基于体量的阻塞。但有两点仍值得提示,均为信息性:1,826 行生产代码越过了 1,000 行大 PR 提示线;.github/issue-owners.json 中 core-memory 领域的 owner 是 @wenshao 而不是你——你自己的描述请求"维护者架构评审",所以我把它转给了该领域 owner,而不是把你的写权限当作签核。

方案:在读 diff 之前,我自己的基线要小得多——一个解析器加一个 memory import codex 子命令,先预览、接受显式选择、再通过既有记忆 API 写入,云端场景至多一个很薄的 daemon 接口。最终落地大约是它的四倍。读完之后我认为大部分增量是站得住的:崩溃恢复台账的存在是必要的,因为"半发布的导入复活了用户刻意遗忘的内容"是很糟的失败模式;限定工作区的路由也正是防止回退到主运行时的关键。我仍然想追问的是模型整理这一层。确定性的逐章节转换正是 #14149 的做法,本 PR 把它换成了每个选中章节一次付费模型调用(最多 2,048 个选择,串行执行),而支持它的论据——"直接逐条转换会丢失历史上下文"——是断言而非论证。具体后果见 Stage 2。

Diff 卫生良好:总共只有 5 行删除,没有顺手重构、没有格式化噪声、没有无关改动。测试与生产代码比例约 1:1.6,测试断言的是它声称的不变量,而不是复述实现。

**风险:**Stage 1e 命中一个高风险路径——packages/cli/src/acp-integration/acpAgent.ts(acp-integration),属于与回滚相关的路径集合。这不阻塞任何事,但意味着不跳过任何 Stage 2 增强项,且批准前必须有 CI 证据。另外,scripts/cli-entry.js 把 memory 加进了与 serve/mcp 并列的进程内快速路径——一行改动就改变了每次 qwen memory 调用的启动行为,而描述里没有解释。我在 Stage 2 对此有一个问题。

进入代码审查 🔍

— Qwen Code · qwen3.8-max-2026-09-02

Reviewed at 1b387a3c529b58f50bb377399184e659aedfae07 · re-run with @qwen-code /triage

@qwen-code-review-bot

qwen-code-review-bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

Independent proposal first (written before reading the diff, from the title and "Why it's needed" only): a Codex Markdown parser, a memory import codex subcommand that previews then writes an explicit selection through the existing memory APIs, and for the cloud case one daemon endpoint that accepts uploaded content. No job engine — a synchronous import per selection, with the filesystem as the only state. That is my baseline; below I judge the PR against it.

The PR exceeds my baseline on durability and I have moved off it there: a half-published import that resurrects something the user deliberately forgot is a real failure mode, and the intent→commit ledger keyed on content digest is a sound answer. It exceeds my baseline on routing too — refusing unknown/untrusted/draining/SSH/legacy-override runtimes rather than falling back to primary is the correct call and matches how the rest of the workspace routes behave.

Findings

1. getProjectAutoMemoryRoots now does an uncached synchronous filesystem walk on every project-scope memory scan — for every user, including the ~all who never import. This is the one I would want fixed before merge.

paths.ts adds, inside the trustedProject branch:

const imported = getImportedProjectMemoryRoot(projectRoot);
if (fs.existsSync(imported)) roots.push(imported);

The existsSync guard is the right instinct, but it runs after the expensive part. getImportedProjectMemoryRoot → memoryImportRepositoryIdentity does fs.realpathSync(projectRoot), then walks every ancestor directory to the filesystem root calling existsSync at each level, then lstatSync + realpathSync on .git, then a sha256 — all synchronous, none memoized. On a typical checkout that is 20–30 blocking syscalls to discover that imported-project-memory/<hash> does not exist.

This lands on a hot path, and it is worse than one call: getProjectAutoMemoryRoots is invoked from structured-scan.ts at lines 645, 705 and 791 plus metadata-migration.ts:226, and scanAutoMemorySnapshot (705) reaches scanProjectAutoMemoryWithStatus (645) — so a single snapshot pays it at least twice. MemoryImportService.snapshot() then calls scanAutoMemorySnapshot three times per section, so an N-section import performs ~6N of these walks on top of reading every same-scope memory document three times over.

A module-level Map<projectRoot, string> memo for the identity would remove essentially all of it; the git common directory does not change under a running process. This repo cares about exactly this kind of cost — scripts/cli-entry.js exists to avoid a process spawn — so adding an uncached recursive walk to core memory resolution reads against the grain.

2. The imported layer silently ignores QWEN_CODE_MEMORY_PROJECT_SCOPE. getAutoMemoryRoot consults resolveWorkspaceProjectScope() (paths.ts:116-118) to choose workspace- vs git-root-scoped ordinary memory. getImportedProjectMemoryRoot takes no scope argument and always keys on the git common directory. So a user who explicitly configured workspace scoping gets workspace-scoped ordinary memory but repository-scoped imported memory in the same recall — two different isolation rules in one result set, with nothing documenting it.

The consequence is concrete in a monorepo: import while working in packages/a, and the content is recalled from packages/b, because both resolve to the same .git. The design doc covers "main checkouts and linked worktrees share it; clones/forks do not" — that is a defensible choice for worktrees, but subdirectory workspaces inside one repository are a much more common arrangement than linked worktrees, and they are not addressed. Note the PR already builds the machinery to express this: runWithMemoryProjectScope exists and the serve layer threads it, yet the imported root never reads it. Either honour the setting, or state the divergence explicitly in the design doc and the API doc.

3. prepare() permanently rejects a runtime directory inside a Git checkout — which breaks users whose $HOME is a dotfiles repo. The walk from Storage.getRuntimeBaseDir() (default ~/.qwen) upward throws Memory import requires a runtime directory outside Git checkouts. on the first ancestor containing .git. Keeping $HOME under Git is an uncommon but entirely legitimate setup, and for those users every confirmed import fails with no workaround and an error message that does not name the actual cause. The direction is fail-closed, which is right for private source material; the message should say "your Qwen runtime directory is inside a Git repository at " so it is actionable, and the design doc should list it as a v1 limitation alongside the SSH and legacy-override rejections it already lists.

4. Up to 2,048 serial paid model calls are accepted with a 202 and no cost signal. submit caps selections.length at 2,048 and the worker processes them one at a time, each a runSideQuery with a 130 s ACP timeout. Worst case that is hours of billed model work kicked off by a single request that returns immediately. There is no batch-level cap, no estimate in the preview, and nothing in the API doc warning a host that a large selection is expensive. At minimum the preview should surface the count of model calls a full selection would cause so a host can warn the user; ideally there is a default cap well below 2,048.

5. Consolidation input is capped at 512 KiB including all existing same-scope memory, so the feature degrades to unusable as a user's memory grows. snapshot() passes every existing document (uncapped: true, full file reads) into proposeMemoryImport, which throws once JSON.stringify(input) exceeds 524,288 bytes. A user with more than ~512 KiB of project memory can never import anything, and the failure surfaces per-section as a generic failed item with the retry boilerplate reason — not as an up-front, actionable error. The doc is honest that oversized context fails rather than truncates, which I agree with; but the ceiling should be checked once in submit and reported as a batch-level rejection, and the number belongs in the API doc's limits paragraph, since it is the one limit a host cannot compute from the source alone.

6. scripts/cli-entry.js: why does a one-shot command need the in-process fast path? memory is added beside serve and mcp, which are there because they are long-running daemons for whom the spawn/relaunch machinery is disruptive. qwen memory import codex is a one-shot command — and with finding 4 it can be a long one-shot. Taking the fast path skips the else branch's spawn, QWEN_CODE_LAUNCHER_PID handling and standalone-update/relaunch flow for every qwen memory invocation. If this is a startup-latency win, say so in the description and note what is intentionally skipped; if it is not needed, dropping the line shrinks the blast radius of the PR's only change to a shipped launcher script.

7. Minor, non-blocking. POST /batches can block ~120 s inside the request handler: submit takes the admission lock via locked(), whose ELOCKED retry loop passes no abort signal, so a lock left by a SIGKILLed worker is waited out to the stale: 120_000 threshold rather than failing fast. StoredTask.result is declared but never persisted — publicTask() always recomputes it from items, so the stored field is inert. And recover() only scans batches/<owner> where owner is keyed on projectRoot, while the memory it publishes is keyed on the repository — a batch submitted from a main checkout is not recovered by a linked worktree's daemon. That is arguably correct ownership, but it is worth one line in the design doc.

What I checked and found sound

I verified every cross-package symbol this PR imports actually exists on main rather than trusting the author's typecheck claim: rebuildAutoMemoryIndexAtRoot(root, scope) (indexer.ts:342, and 'project'/'user' are both valid AutoMemoryScope members), scanAutoMemorySnapshot with uncapped (structured-scan.ts:683, 477), validateStructuredAutoMemoryDocument and parseAutoMemoryTopicDocument, all four prompt.ts section exports, resolveTrustedMemoryFile(root, anchor, relativePath) returning string | undefined as the null-check assumes, runSideQuery's purpose/schema/maxAttempts/skipOutputLanguagePreference options, WorkspaceRegistry.list(), getEntryByWorkspaceId, entry.current.guard, createMutationGate returning (opts?: MutationGateOptions) => RequestHandler as the mutate dep type declares, and proper-lockfile/unified/remark-parse all already in packages/cli/package.json. One thing I initially suspected and then disproved: runtime.routeFileSystemFactory.sshWorkspace is real — WorkspaceFileSystemFactory declares it as an optional readonly property (fs/workspace-file-system.ts:377), so the SSH rejection works.

The security posture is genuinely strong and better than #14149's: O_NOFOLLOW on every private read, symlink rejection at each path component in privateDirectory, Git-checkout containment checked both before and after realpath in prepare(), wx exclusive-create for staged files and temp writes, a source-path allowlist regex that rejects absolute paths, drive letters, backslashes, control characters, empty/./.. segments and case-duplicate paths, model input framed as untrusted DATA with tools undefined and maxAttempts: 1, every model-proposed document run through validateStructuredAutoMemoryDocument and parseAutoMemoryTopicDocument before write, project-typed knowledge refused into user scope, and the memory-version rechecked after model work and again immediately before publication so a concurrent edit cannot be clobbered. The active() guard in the serve layer matches its documented failure semantics exactly, including the effectiveEnv ?? process.env precedence on the legacy-override check. Route mount order is correct too — the more specific import literals are registered before mountWorkspaceMemoryRoutes, which matters because Express is order-dependent.

I looked hard for the class of bug that sank #14149 (the /review there found nine Criticals, mostly around index rebuilds, lock release and containment) and did not find a repeat: the lock is released in a finally, onCompromised aborts every controller, and the ledger verifies staged digests before renaming.

Sequence: confirmed import through the daemon
sequenceDiagram
    participant P1 as Host client
    participant P2 as Daemon route
    participant P3 as MemoryImportService
    participant P4 as ACP bridge
    participant P5 as Agent child
    participant P6 as Model
    participant P7 as Imported memory root
    P1->>P2: POST preview with source envelope
    P2-->>P1: sections, groups, warnings (pure parse, no writes)
    P1->>P2: POST batches with selection and idempotency key
    P2->>P3: submit
    P3->>P3: validate selection, persist source and task
    P3-->>P1: 202 task queued
    P3->>P3: worker acquires per-scope lock
    P3->>P3: snapshot existing same-scope memory
    P3->>P4: invokeWorkspaceCommand import plan
    P4->>P5: control ext method
    P5->>P6: runSideQuery, no tools, maxAttempts 1
    P6-->>P5: decision plus proposed documents
    P5->>P5: validate schema and native document format
    P5-->>P3: validated proposal
    P3->>P3: recheck memory version, stage files, write ledger intent
    P3->>P7: rename staged directory into imported root
    P3->>P7: rebuild index, mark ledger committed
    P3-->>P1: status reports added refs
Loading
Files changed (29 of 29)
File What changed
docs/design/external-memory-import.md Design rationale, English: problem, layering, durability, validation plan
docs/design/external-memory-import.zh-CN.md Same design in Chinese; same five sections, genuinely synchronized
docs/design/external-memory-import-api.md HTTP and CLI contract, English: limits, endpoints, task shape, host example
docs/design/external-memory-import-api.zh-CN.md Same contract in Chinese, including the identical endpoint table
packages/core/src/memory/import-paths.ts New. Git identity resolution with symlink and forged-gitdir rejection, imported root path
packages/core/src/memory/import-paths.test.ts New. Worktree sharing, clone isolation, forged .git pointer, real search and forget
packages/core/src/memory/import-model.ts New. Tool-free consolidation side query, input limits, proposal validator
packages/core/src/memory/import-model.test.ts New. Pins model options and rejects malformed proposals
packages/core/src/memory/paths.ts Adds the imported root to project recall (finding 1) and an ALS project-scope override
packages/core/src/memory/forget.ts Rebuilds the imported index after a managed forget, so removals stay consistent
packages/cli/src/memory-import/source.ts New. Codex Markdown parser, path allowlist, limits, section and group derivation
packages/cli/src/memory-import/source.test.ts New. Parser coverage for paths, references, scope hints, oversize sections
packages/cli/src/memory-import/service.ts New. The durable job engine: locks, ledger, staging, publication, cancel, retry, recover
packages/cli/src/memory-import/service.test.ts New. Largest test file; idempotency, conflicts, crash recovery, cancellation
packages/cli/src/memory-import/local.ts New. Builds a local service from CLI config, trust and auth
packages/cli/src/commands/memory.ts New. The memory import codex subcommand, preview and apply paths
packages/cli/src/commands/memory.test.ts New. Pins bare and safe mode rejection of confirmed import
packages/cli/src/serve/memory-import.ts New. Six workspace-qualified routes plus the fail-closed active-runtime guard
packages/cli/src/serve/memory-import.test.ts New. Route coverage for trust, scope, idempotency and error mapping
packages/cli/src/serve/server.ts Mounts the import routes ahead of the existing workspace memory routes
packages/cli/src/serve/capabilities.ts Advertises the memory_import_v1 capability
packages/cli/src/cli.ts Adds the memory bootstrap route, help entry and a minimal yargs host
packages/cli/src/config/config.ts Registers the command and adds memory to the exit-after-run token list
packages/cli/src/config/config.test.ts Pins that a prefixed debug or safe-mode flag still reaches the preview
packages/cli/src/acp-integration/acpAgent.ts Handles the plan control method in the agent child, gated on trust and managed memory
packages/acp-bridge/src/status.ts Declares the new control ext method name
packages/acp-bridge/src/session-control-plane.ts Treats the plan method as channel-starting, like the MCP restart method
packages/acp-bridge/src/bridge.test.ts Covers channel startup for the new method
scripts/cli-entry.js Adds memory to the in-process fast path (finding 6)

Testing evidence

This is an unattended CI run (GITHUB_EVENT_NAME=pull_request_target), so per the gate's rules I did not build, run, or execute anything from this PR — no npm, no vitest, no gh pr checkout. All evidence below is the PR's own CI read through the API for commit 1b387a3c, plus my static reading. 85 check-runs exist on this commit; the non-skipped ones are in the table.

CI had not settled when I fetched it, and I did not poll. Four pull_request workflow runs are still incomplete, including Qwen Code CI (in_progress). So I cannot attest to a green build, and neither Lint nor the unit suite has produced a conclusion against these 1,826 production lines on this head. The macOS and Windows Test legs are not reporting yet — they are gated on the ubuntu leg, same as on #14149. The finalize job rewrites the table below in place once CI lands.

Not verified, and why: the author's "559 CLI cases, 31 core cases", the full-workspace build/typecheck, and the SIGKILL recovery probes are the author's own claims on macOS only — I did not and could not re-run them here. Nothing in this run independently confirms them.

CI results for 1b387a3 — this table auto-updates as CI workflows complete:

Check Conclusion
Build and verify reference distribution ⏳ running
Integration Tests (no-AK, No Sandbox) ❌ failure
Test (ubuntu-latest, Node 22.x) ❌ failure
web-shell E2E Smoke (ubuntu-latest, Node 22.x) 🚫 cancelled
arm64 ✅ success
Classify PR ✅ success
Lint & Static (ubuntu-latest, Node 22.x) ✅ success
Native Linux CLI boundary ✅ success
Native Windows settings and streams ✅ success
OpenTUI no-flicker gate ✅ success
Prepare reference distribution ✅ success
Serve A/B (ubuntu-latest, Node 22.x) ✅ success
TUI parity snapshots (ink vs opentui) ✅ success
x64 ✅ success

One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。

Sandboxed verification would settle most of what CI cannot: @qwen-code /verify — the central claims here are durable-job behaviours that a green unit suite does not pin, because the suite can pass identically with the invariants removed. Specifically unsubstantiated by anything I could read: that a SIGKILL between ledger-intent and publication recovers to exactly one document after the 120 s lease (the author reports this on macOS only), that a repeated idempotency key with a changed selection is rejected while an identical one returns the original batch without multiplying native documents, that cancel during an in-flight model call discards the result rather than publishing it, and that a linked worktree recalls the imported layer while an independent clone does not. @qwen-code /tmux would cover the CLI surface — qwen memory import codex --dry-run --json output shape, and that bare/safe mode rejects --apply before touching sources or auth. You have write access, so both lanes are available to you directly rather than needing a sponsored run.

中文说明

先说独立方案(在读 diff 之前,仅依据标题和"为什么需要"写下):一个 Codex Markdown 解析器、一个先预览再通过既有记忆 API 写入显式选择的 memory import codex 子命令,云端场景一个接受上传内容的 daemon 接口。不要任务引擎——每次选择同步导入,文件系统就是唯一状态。这是我的基线,下面据此评判本 PR。

在持久性上本 PR 超出了我的基线,我也因此改变了看法:半发布的导入复活用户刻意遗忘的内容是真实的失败模式,以内容摘要为键的 intent→commit 台账是恰当的解法。在路由上也超出我的基线——对未知/不可信/退出中/SSH/旧式覆盖配置的运行时一律拒绝而不回退主运行时,这是正确的做法,与其余工作区路由的行为一致。

发现 1(唯一我希望合入前修掉的):paths.ts 在 trustedProject 分支里新增了对 getImportedProjectMemoryRoot 的调用,而 existsSync 守卫发生在昂贵部分之后。该函数会 realpathSync(projectRoot),然后逐级向上遍历到文件系统根、每级一次 existsSync,再对 .git 做 lstatSync + realpathSync,最后算 sha256——全部同步、全部无缓存。典型检出下这是 20–30 次阻塞系统调用,只为了发现 imported-project-memory/<hash> 并不存在。而且它落在热路径上:getProjectAutoMemoryRoots 被 structured-scan.ts 的 645、705、791 行和 metadata-migration.ts:226 调用,其中 scanAutoMemorySnapshot(705)又会走到 scanProjectAutoMemoryWithStatus(645),因此单次快照至少付两次;MemoryImportService.snapshot() 每章节再调用 scanAutoMemorySnapshot 三次,所以 N 个章节的导入要额外做约 6N 次这种遍历。用模块级 Map<projectRoot, string> 缓存 git 身份即可基本消除——git common directory 在进程运行期间不会变。本仓库对这类开销是很在意的(scripts/cli-entry.js 的存在就是为了省掉一次进程 spawn),所以在核心记忆解析里加入无缓存的递归遍历与其风格相悖。

**发现 2:**导入层完全忽略 QWEN_CODE_MEMORY_PROJECT_SCOPE。getAutoMemoryRoot 会通过 resolveWorkspaceProjectScope() 决定 workspace 还是 git-root,而 getImportedProjectMemoryRoot 不接受 scope 参数、永远以 git common directory 为键。于是显式配置了 workspace 的用户,同一次召回里普通记忆按工作区隔离、导入记忆却按仓库隔离,两套规则混在一个结果集里且无任何文档说明。在 monorepo 中后果很具体:在 packages/a 下导入的内容,会在 packages/b 下被召回。设计文档覆盖了"主检出与 linked worktree 共享、clone/fork 不共享",对 worktree 而言这是可辩护的选择,但同一仓库内的子目录工作区比 linked worktree 常见得多,文档并未涉及。而且本 PR 已经造好了表达这件事的机制——runWithMemoryProjectScope 存在且 serve 层已串接——导入根目录却从不读取它。要么遵循该配置,要么在设计文档和接口文档中明确写出这一差异。

发现 3:prepare() 会永久拒绝位于 Git 检出内的运行时目录——这会让 $HOME 是 dotfiles 仓库的用户彻底无法使用。从 Storage.getRuntimeBaseDir()(默认 ~/.qwen)向上遍历,遇到第一个含 .git 的祖先就抛错。把 $HOME 纳入 Git 虽不常见但完全正当,这些用户的每次确认导入都会失败、没有变通办法,且错误信息没有说明真正原因。拒绝方向是 fail-closed,对私有来源材料而言是对的;但信息应改成"你的 Qwen 运行时目录位于 这个 Git 仓库内"才可操作,设计文档也应把它与已经列出的 SSH、旧式覆盖拒绝并列为 v1 限制。

**发现 4:**最多 2,048 次串行付费模型调用被一个 202 接受,且没有任何成本提示。submit 把 selections.length 上限设为 2,048,worker 逐个处理,每个都是 130 秒超时的 runSideQuery。最坏情况是一个立即返回的请求触发数小时的计费模型工作。没有批次级上限,预览里没有估算,接口文档也没有提醒宿主大选择集代价高昂。至少预览应给出"全选将产生多少次模型调用"以便宿主提示用户;理想情况下默认上限应远低于 2,048。

**发现 5:**整理输入上限 512 KiB,且包含同范围全部已有记忆,因此用户记忆越多、该功能越不可用。snapshot() 以 uncapped: true 传入每一份已有文档(完整读文件),proposeMemoryImport 在 JSON.stringify(input) 超过 524,288 字节时抛错。项目记忆超过约 512 KiB 的用户将永远无法导入任何内容,而失败是以每章节一条泛化的 failed(附重试套话原因)呈现,而不是一个前置、可操作的错误。文档诚实说明了超限失败而非静默截断,这点我同意;但这个上限应在 submit 中一次性检查并作为批次级拒绝返回,且该数值应写进接口文档的限制段落——它是唯一一个宿主无法仅凭来源自行推算的限制。

发现 6:scripts/cli-entry.js——为什么一次性命令需要进程内快速路径?memory 被加在 serve 和 mcp 旁边,而后两者之所以在那里,是因为它们是长驻 daemon、spawn/relaunch 机制会干扰它们。qwen memory import codex 是一次性命令——而且按发现 4,可能是个很长的一次性命令。走快速路径会让每次 qwen memory 调用都跳过 else 分支的 spawn、QWEN_CODE_LAUNCHER_PID 处理和独立更新/relaunch 流程。如果这是启动延迟收益,请在描述里说明并指出有意跳过了什么;如果并不需要,去掉这一行可以缩小本 PR 对已发布启动脚本的唯一改动的爆炸半径。

发现 7(次要,不阻塞):POST /batches 可能在请求处理内阻塞约 120 秒——submit 通过 locked() 获取准入锁,其 ELOCKED 重试循环没有传入 abort signal,因此被 SIGKILL 的 worker 留下的锁会被等到 stale: 120_000 阈值才放行,而不是快速失败。StoredTask.result 已声明但从未持久化——publicTask() 总是从 items 重算,该存储字段是惰性的。另外 recover() 只扫描 batches/<owner>,而 owner 以 projectRoot 为键,发布的记忆却以仓库为键——从主检出提交的批次不会被 linked worktree 的 daemon 恢复。这可以说是正确的归属划分,但值得在设计文档里写一行。

**已核实且认为可靠的部分:**我逐个核对了本 PR 跨包导入的符号在 main 上确实存在,而不是采信作者的 typecheck 声明:rebuildAutoMemoryIndexAtRoot(root, scope)(indexer.ts:342,且 'project'/'user' 都是合法的 AutoMemoryScope 成员)、带 uncapped 的 scanAutoMemorySnapshot(structured-scan.ts:683、477)、validateStructuredAutoMemoryDocument 与 parseAutoMemoryTopicDocument、prompt.ts 的四个 section 导出、返回 string | undefined(与空值判断一致)的 resolveTrustedMemoryFile(root, anchor, relativePath)、runSideQuery 的 purpose/schema/maxAttempts/skipOutputLanguagePreference 选项、WorkspaceRegistry.list()、getEntryByWorkspaceId、entry.current.guard、返回 (opts?: MutationGateOptions) => RequestHandler(与 mutate 依赖类型声明一致)的 createMutationGate,以及 proper-lockfile/unified/remark-parse 均已在 packages/cli/package.json 中。有一处我最初怀疑、随后证伪:runtime.routeFileSystemFactory.sshWorkspace 是真实存在的——WorkspaceFileSystemFactory 将其声明为可选只读属性(fs/workspace-file-system.ts:377),因此 SSH 拒绝是有效的。

安全处理确实扎实,且优于 #14149:每次私有读取都带 O_NOFOLLOW,privateDirectory 对每一级路径分量做符号链接拒绝,prepare() 在 realpath 前后各做一次 Git 检出包含检查,暂存文件与临时写入用 wx 独占创建,来源路径白名单正则拒绝绝对路径、盘符、反斜杠、控制字符、空/./.. 分段和大小写重复路径,模型输入被框定为不可信 DATA 且 tools 为 undefined、maxAttempts: 1,每份模型提议文档在写入前都经过 validateStructuredAutoMemoryDocument 和 parseAutoMemoryTopicDocument,项目类型知识被拒绝进入 user 范围,记忆版本在模型工作后复查、发布前再复查一次,因此并发编辑不会被覆盖。serve 层的 active() 守卫与其声明的失败语义完全一致,包括旧式覆盖检查中 effectiveEnv ?? process.env 的优先级。路由挂载顺序也是对的——更具体的 import 字面路径先于 mountWorkspaceMemoryRoutes 注册,而 Express 依赖注册顺序,这一点很重要。

我重点排查了拖垮 #14149 的那类缺陷(那里的 /review 找到九个 Critical,多与索引重建、锁释放和包含检查有关),没有发现重演:锁在 finally 中释放,onCompromised 会中止所有 controller,台账在 rename 前校验暂存摘要。

测试证据:这是无人值守的 CI 运行(GITHUB_EVENT_NAME=pull_request_target),因此按门禁规则我没有构建、运行或执行本 PR 的任何内容——没有 npm、没有 vitest、没有 gh pr checkout。上述全部证据来自通过 API 读取的本 PR 自身 CI(commit 1b387a3c),以及我的静态阅读。

**我抓取时 CI 尚未结束,且我没有轮询。**有 4 个 pull_request 工作流运行未完成,包括 Qwen Code CI(in_progress)。所以我无法证明构建是绿的,Lint 和单测套件在这个 head 上都还没有对这 1,826 行生产代码给出结论。macOS 和 Windows 的 Test 分支尚未上报——它们与 #14149 一样以 ubuntu 分支为前置。CI 落地后 finalize 任务会就地重写上面的表格。

未验证及其原因:作者的"559 项 CLI、31 项 core 用例"、全工作区构建/类型检查、以及 SIGKILL 恢复探测,都是作者本人在 macOS 上的声明——我在本次运行中没有也无法重跑。没有任何东西独立确认了这些结果。

沙箱化验证可以解决大部分 CI 无法解决的问题:@qwen-code /verify——这里的核心主张是持久化任务行为,而绿色单测套件钉不住它们,因为把这些不变量移除后套件可能同样通过。具体而言,我无法从任何可读材料中得到证实的有:在 ledger-intent 与发布之间 SIGKILL 后,是否确实在 120 秒租约到期后恢复为恰好一份文档(作者仅在 macOS 上报告过);重复使用幂等键但改变选择时是否被拒绝、而相同选择是否返回原批次且不增加原生文档;在模型调用进行中取消是否会丢弃结果而非发布;linked worktree 是否能召回导入层而独立 clone 不能。@qwen-code /tmux 可覆盖 CLI 表面——qwen memory import codex --dry-run --json 的输出形态,以及 bare/safe 模式是否在接触来源或认证之前就拒绝 --apply。你有写权限,因此这两条通道可由你直接触发,无需 sponsored run。

— Qwen Code · qwen3.8-max-2026-09-02

Reviewed at 1b387a3c529b58f50bb377399184e659aedfae07 · re-run with @qwen-code /triage

@qwen-code-review-bot

qwen-code-review-bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

🩺 serve daemon A/B

Built the PR base vs this PR head d95f523, drove a fixed endpoint set against each, and diffed the JSON responses. Only fields that changed are shown.

capabilities

field PR base (before) this PR (after)
features[] — "memory_import_v1"

— Qwen Code · serve A/B

@qwen-code-review-bot

Copy link
Copy Markdown
Collaborator

Confidence: 3/5 — the engineering is good and I found no merge-blocker, but there are two things I would want fixed, a direction question that has now gone unanswered across two PRs, and CI that has not landed. Deferring, not approving.

Going back to my independent proposal: I sketched a parser, a subcommand, and one endpoint. This is 17 production files. Having read all of it, I have genuinely moved — the ledger and the fail-closed workspace routing are not decoration, they are the difference between "import that mostly works" and "import that cannot resurrect something you deliberately forgot." That is the right instinct and it is executed carefully. If I picked this up in six months I would thank the author for the durability model.

But I would curse them for finding 1. Adding an uncached, synchronous, recursive-up-to-the-filesystem-root walk to getProjectAutoMemoryRoots puts new blocking I/O on a path every project-scope memory scan already takes at least twice, to compute a directory that does not exist for essentially every user. It is a small regression in isolation and it is in core, where small regressions get called from everywhere. The fix is a memo map; there is no reason to ship without it. Finding 2 sits next to it: the imported layer is always repository-keyed while ordinary memory honours QWEN_CODE_MEMORY_PROJECT_SCOPE, so a monorepo user who chose workspace scoping gets two isolation rules in one recall result, undocumented. The PR builds runWithMemoryProjectScope and then does not consult it here — that asymmetry looks unintentional.

On the honest 80/20 question, my answer is different from #14149's and I want to be explicit that I am not just repeating the prior triage. There, the questionable third was an interactive TUI; here it is the model-backed consolidation, and I think that one deserves a harder look. It converts a deterministic transformation into one paid model call per selected section — up to 2,048 of them, serially, accepted with a 202 and no cost signal — and it introduces the entire validation surface that findings 4 and 5 are about. The stated justification is that direct per-bullet conversion "loses historical context and does not reconcile old knowledge with existing native memory." That may well be true, but it is asserted, and it is the load-bearing assumption of the whole design. A before/after on one real Codex memory set — deterministic conversion vs. consolidated, side by side — would settle whether the model layer earns its cost and its complexity. Nobody has shown that, on either PR.

The pattern question, since Stage 3 asks it of me directly: this is the same author's second large PR for the same feature in two days, both self-reviewed, neither with a linked issue or an independent human review, the first carrying nine Critical findings from /review and still open. I evaluated this one on its own merits and it is materially better than its predecessor — I looked specifically for a repeat of the index-rebuild, lock-release and containment bugs that sank #14149 and did not find one. But "better than the last one" is not the same as "wanted," and I am not going to let volume wear the direction question down into an approval.

Two concrete process asks, neither of which needs me:

Why I am deferring rather than approving, concretely: CI had not settled when I fetched it (four pull_request runs incomplete, Qwen Code CI still in_progress, no Lint or unit conclusion against these 1,826 production lines on this head), and I did not poll. So there is nothing green to approve against, and your "559 CLI cases / 31 core cases" and the SIGKILL recovery results remain your own macOS-only claims that I could not re-run — this is an unattended CI run and the gate forbids me from executing PR-derived code. I am also deliberately not leaving a deferred-approval instruction on this comment: that mechanism exists only for an approve verdict, and my verdict is defer. So nothing will auto-approve this when CI goes green — approval here needs a human, not a CI signal.

And it needs a human for a reason the size policy does not capture. You have write access and are a listed core owner, so the external two-tier core gate is exempt and nothing in this review is capped by policy — my 3/5 reflects real reservations, not a rule. But you are not the core-memory owner: .github/issue-owners.json routes packages/core/src/memory/ to the core-memory area, whose sole owner is @wenshao. The label-based resolver returns nothing here (the PR has no labels and no reviews), so I resolved by the policy file's own path-prefix routing — longest prefix wins, packages/core/src/memory/ over packages/core/ — and confirmed the permission. Your description asks for maintainer architectural review; this is me routing it to the maintainer who owns that architecture.

⏸️ Deferring to @wenshao. Four things need your call, and none of them are resolvable from the diff, the tests, or the description:

  1. Is this a surface we want to own? Importing one specific other agent's memory format is a standing maintenance commitment that outlives the PR. There is no linked issue and no user request behind either attempt. The cloud-runtime gap is real, but "real gap" and "we should maintain this" are different questions, and this is now the second PR asking it.
  2. Does the imported layer belong at repository scope? Finding 2: it ignores QWEN_CODE_MEMORY_PROJECT_SCOPE and shares across subdirectory workspaces in a monorepo. That is a memory-isolation semantics decision for the area owner, not for the gate.
  3. Is the model-backed consolidation worth its cost and surface? Up to 2,048 serial paid calls per batch, and a 512 KiB ceiling on section-plus-existing-memory that makes the feature stop working as a user's memory grows (finding 5). Deterministic conversion was the predecessor's approach; the argument for replacing it is asserted, not demonstrated.
  4. Finding 1 should be fixed before merge regardless of the above — the uncached synchronous walk in getProjectAutoMemoryRoots is a core-path regression affecting every user, including those who never import.

Findings 3, 4, 6 and 7 in Stage 2 are smaller and can be follow-ups. I found no security defect: the containment, symlink, exclusive-create, allowlist and untrusted-data handling are all sound, and I verified every cross-package symbol this PR imports actually exists on main rather than trusting the typecheck claim.

中文说明

置信度:3/5 —— 工程质量不错,我没有发现合入阻塞项,但有两处我希望修掉、一个已经跨两个 PR 无人回答的方向问题,以及尚未结束的 CI。因此是转交,不是批准。

回到我自己的独立方案:我当初设想的是一个解析器、一个子命令、一个接口,而这里是 17 个生产文件。全部读完之后我确实改变了看法——台账和 fail-closed 的工作区路由不是装饰,它们是"基本能用的导入"与"不可能复活用户刻意遗忘内容的导入"之间的差别。这个直觉是对的,实现也很细致。六个月后接手这份代码,我会感谢作者设计了这套持久性模型。

但发现 1 会让我骂人。在 getProjectAutoMemoryRoots 里加入无缓存、同步、逐级向上直到文件系统根的遍历,等于给一条每次项目范围记忆扫描至少走两次的路径增加了新的阻塞 I/O,而它算出来的目录对几乎所有用户都不存在。孤立地看这是个很小的回退,但它在核心里——核心的小回退会被到处调用。修法就是一个 memo map,没有理由不修就合入。发现 2 紧挨着它:导入层永远以仓库为键,而普通记忆遵循 QWEN_CODE_MEMORY_PROJECT_SCOPE,于是选择了 workspace 范围的 monorepo 用户会在同一次召回结果里得到两套隔离规则,且无文档说明。本 PR 造出了 runWithMemoryProjectScope,却在这里不读取它——这个不对称看起来不是有意的。

关于诚实的 80/20 问题,我的答案与 #14149 那次不同,我想明确说明我不是在重复上一轮 triage。那次可疑的三分之一是交互式 TUI;这次是模型整理,而我认为这一项值得更严格地审视。它把一个确定性转换换成了每个选中章节一次付费模型调用——最多 2,048 次、串行、被一个 202 接受且没有成本提示——并引入了发现 4 和发现 5 所针对的整个校验面。其陈述的理由是逐条直接转换"丢失历史上下文、无法与已有原生记忆协调"。这很可能成立,但它是断言,而且是整个设计的承重假设。在一份真实的 Codex 记忆集上做一次 before/after——确定性转换与整理后结果并排对比——就能判断模型层是否值回它的成本和复杂度。两个 PR 都没有人展示过这个。

Stage 3 直接问我的模式问题:这是同一作者两天内为同一功能提交的第二个大 PR,两个都是自审,都没有关联 issue、都没有独立人工审查,第一个带着 /review 的九个 Critical 且仍然开着。我按本 PR 自身的价值评估,它明显优于前身——我专门排查了拖垮 #14149 的索引重建、锁释放和包含检查缺陷是否重演,没有发现。但"比上一个好"不等于"被需要",我也不会让数量把方向问题磨成一次批准。

两个具体的流程请求,都不需要我来做:

为什么是转交而不是批准,具体地说:我抓取时 CI 尚未结束(4 个 pull_request 运行未完成,Qwen Code CI 仍为 in_progress,这个 head 上的 1,826 行生产代码没有任何 Lint 或单测结论),而我没有轮询。所以没有任何绿的结果可供批准,而你的"559 项 CLI / 31 项 core 用例"和 SIGKILL 恢复结果仍然是你本人在 macOS 上的声明,我无法重跑——这是无人值守的 CI 运行,门禁禁止我执行 PR 派生的代码。我也刻意没有在本评论留下延迟批准指令:该机制只用于批准结论,而我的结论是转交。因此 CI 变绿时不会有任何东西自动批准本 PR——这里的批准需要人,而不是 CI 信号。

而且它需要人的理由,是体量策略捕捉不到的。你有写权限、是既定 core owner,因此外部两级核心门禁豁免,本次审查没有任何策略性封顶——我的 3/5 反映的是真实的保留意见,不是规则。但你不是 core-memory 的 owner:.github/issue-owners.json 把 packages/core/src/memory/ 路由到 core-memory 领域,其唯一 owner 是 @wenshao。基于标签的解析器在这里返回空(PR 没有标签、没有审查),所以我按策略文件自身的路径前缀路由解析——最长前缀优先,packages/core/src/memory/ 胜过 packages/core/——并核实了权限。你的描述请求维护者架构评审;这就是我把它转给拥有该架构的维护者。

⏸️ **转交给 @wenshao。**有四件事需要你的判断,都无法从 diff、测试或描述中得出结论:

  1. **这是我们想要拥有的能力吗?**导入某个特定其他 agent 的记忆格式,是一份比 PR 更长久的维护承诺。两次尝试都没有关联 issue,也没有用户诉求。云端运行时的缺口是真实的,但"缺口真实"和"我们应该长期维护它"是两个不同的问题,而这已经是第二个来问这件事的 PR。
  2. **导入层应该属于仓库范围吗?**发现 2:它忽略 QWEN_CODE_MEMORY_PROJECT_SCOPE,并在 monorepo 中跨子目录工作区共享。这是记忆隔离语义的决定,属于领域 owner,不属于门禁。
  3. **模型整理值回它的成本和复杂度吗?**每批最多 2,048 次串行付费调用,以及"章节 + 已有记忆"512 KiB 的上限——它会让功能随着用户记忆增长而停止工作(发现 5)。确定性转换是前身 PR 的做法;替换它的理由是断言,不是论证。
  4. 无论上述结论如何,发现 1 都应在合入前修掉——getProjectAutoMemoryRoots 里无缓存的同步遍历是影响每个用户(包括从不导入的用户)的核心路径回退。

Stage 2 中的发现 3、4、6、7 较小,可以作为后续跟进。我没有发现安全缺陷:包含检查、符号链接、独占创建、白名单和不可信数据处理都是可靠的,而且我逐个核实了本 PR 跨包导入的符号在 main 上确实存在,而不是采信 typecheck 声明。

— Qwen Code · qwen3.8-max-2026-09-02

Reviewed at 1b387a3c529b58f50bb377399184e659aedfae07 · re-run with @qwen-code /triage

@doudouOUC doudouOUC left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-verified the current head (d2acc053f4) against the triage findings. The shape of this is right and I could not find a security defect: destinations are server-derived, the symlink/exclusive-create/containment checks hold, the consolidation input is framed as untrusted data with no tools, and every route is workspace-qualified with no fallback to the primary runtime. The durability model (ledger, intent→commit, edit/forget conflict protection, lease recovery) is the part that makes this more than a nice-to-have, and it is executed carefully.

One item I would want fixed before merge:

  • getProjectAutoMemoryRoots now resolves the imported root through an uncached, synchronous walk that goes from the project root up to the filesystem root (existsSync per ancestor, then lstat/realpath on .git, then a sha256) — and it runs for every user, including those who never import, on a path every project-scope memory scan already takes at least twice (structured-scan.ts 645/705/791, metadata-migration.ts:226). Because the service snapshots existing memory two to three times per section, an N-section import pays this roughly 6N times. The sibling root resolver is memoized in the same file for exactly this reason; a module-level memo for the git identity (cleared next to the existing cache) removes essentially all of it.

Smaller items, none of which need to hold the merge, and the first two are also worth a line of documentation rather than code:

  • The imported layer is always repository-keyed while ordinary project memory follows QWEN_CODE_MEMORY_PROJECT_SCOPE. Besides the monorepo case, the published layer is therefore visible to every trusted workspace of the same repository, not just linked worktrees. Either honour the setting or state the rule explicitly in the design and API docs.
  • Published documents carry no provenance marker and are indistinguishable from native memory in recall. I checked whether that is a new trust boundary and concluded it is not (the mutation gate applies, and an authenticated client can already author memory through the remember route), but the design doc's "source paths are provenance" wording reads as if the published artifact carried provenance — please clarify that it does not.
  • A runtime directory inside a Git checkout is rejected permanently, with a message that does not name the cause and is not listed among the v1 limitations in the API doc. Fail-closed is right; make it actionable.
  • Up to 2,048 serial paid calls are accepted with a 202 and no cost signal, and the 512 KiB consolidation input (which includes all existing same-scope memory) is only enforced per section. A preview-level call estimate and one up-front batch check would make both host-visible instead of surfacing as a generic failed item.
  • Please either justify or drop memory in the launcher's in-process fast path, and note the legacy-path-override rejection the daemon performs but the CLI entry point does not (with QWEN_CODE_MEMORY_BASE_DIR set, a user-scope CLI import fails per section with a retry that can never succeed).

Also: main has moved since this head, and its last two merges touch the same capability/expected-feature contract files this PR edits. The textual merge is clean, but please rebase and re-run the affected suites before landing.

中文说明:

我按当前 head(d2acc053f4)复核了此前各阶段的结论。整体形态是对的,我没有发现安全缺陷:写入目标由服务端推导,符号链接、独占创建、包含检查都成立;整理输入的模型调用被框定为不可信数据且无工具;所有路由都限定工作区、不回退主运行时。持久化模型(台账、intent→commit、编辑/遗忘冲突保护、租约恢复)是让它不止于"锦上添花"的部分,实现也很细致。

有一项我希望在合入前修掉:

  • getProjectAutoMemoryRoots 现在通过一次无缓存、同步、逐级向上直到文件系统根的遍历来解析导入根目录(每级一次 existsSync,再对 .git 做 lstat/realpath,最后算 sha256)。它对所有用户都会执行——包括从不使用该功能的人——而这条路径每次项目范围记忆扫描至少走两次(structured-scan.ts 645/705/791、metadata-migration.ts:226);服务每个章节会做两到三次已有记忆快照,因此 N 个章节的导入大约要付 6N 次。同文件里的另一个根目录解析器正是出于这个原因做了 memo,给 git 身份加一个模块级缓存(与现有缓存一并清理)即可基本消除。

其余较小的项,都不需要阻塞合入,前两项其实更适合用一行文档说明而非改代码:

  • 导入层永远以仓库为键,而普通项目记忆遵循 QWEN_CODE_MEMORY_PROJECT_SCOPE。除了 monorepo 场景,已发布层因此对同一仓库的所有可信工作区可见,而不只是 linked worktree。要么遵循该设置,要么在设计文档和接口文档中明确写出这条规则。
  • 已发布的文档不带来源标记,在召回时与原生记忆无法区分。我核实过这是否构成新的信任边界,结论是不构成(变更门禁仍然适用,且已认证客户端本就能通过 remember 路由写记忆),但设计文档中"来源路径即来源信息"的措辞容易被读成发布产物带有来源标记——请澄清它其实没有。
  • 位于 Git 检出内的运行时目录会被永久拒绝,错误信息没有指出真正原因,接口文档的 v1 限制列表里也没有这一条。fail-closed 方向是对的,但信息应当可操作。
  • 最多 2,048 次串行付费调用被一个 202 接受且没有任何成本提示,而 512 KiB 的整理输入(含同范围全部已有记忆)只在单章节层面校验。在预览里给出调用数估算、并在提交时做一次批次级检查,可以让宿主提前看到而不是最终以一条泛化的失败项呈现。
  • 请要么解释、要么去掉启动脚本进程内快速路径里的 memory;另外请说明守护进程会做、而 CLI 入口没做的旧式路径覆盖拒绝(设置了 QWEN_CODE_MEMORY_BASE_DIR 时,CLI 的用户范围导入会按章节失败,且重试永远不会成功)。

另外:该 head 之后 main 已有推进,且最近两次合入触及了本 PR 修改的同一些能力/期望特性契约文件。文本合并是干净的,但请在合入前 rebase 并重跑受影响的用例。

@qwen-code-review-bot qwen-code-review-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partially reviewed — gaps disclosed.

Not reviewed: reverse audit — stopped after the round 1/2 convergence pair; rounds 3-5 were not run, so the two-consecutive-dry-rounds convergence criterion was never met (the orchestrator ran out of context, not the review time budget — the pair REPORTED findings, it did not go dry).

Not reviewed: build-and-test (test half) — the build phase consumed the whole 600s Agent 7 budget: all three changed workspaces compiled green (19/19 per-workspace builds exit 0), but no unit suite executed and the test-efficacy probe was inconclusive (harnessValidated null — its disposable tree lacks built dist/).

Not reviewed: verifier mutation probing — review scratch-tree refused for every shard (repo-local git includeIf chain resolving to a missing credentials file), so guard-removal mutations could not run in a sanctioned tree; verifiers substituted read-only probes against built dist/ artifacts and the product own injection points, and several findings carry a witness: not run line.

Not explored to full depth (tool budget reached): "agent reverse-audit (round 2)": none — I also verified and dismissed, rather than leaving unchecked: rebuildAutoMemoryIndexAtRoot 's if (!existsSync(root)) return '' early-out (so forget.ts…; "agent reverse-audit (round 2)": did not verify by execution that a repo-local-root forget leaves <projectRoot>/.qwen/memory/MEMORY.md stale (no test run; Finding 1 rests on reading getProje…; "agent reverse-audit (round 2)": did not open packages/cli/src/serve/memory-import.test.ts to check whether an existing preview test already pins a route-level size bound (Finding 2's witness…; "agent reverse-audit (round 1)": none — but two checks were left unfinished for scope, not budget: I did not walk run() / prepareProposal / importSection as their own territory (they sit past….

[Critical] R1-39: A repeated --source is collected by yargs into an array, fails the typeof === 'string' test, and is silently discarded in favour of the default directory Failure scenario: qwen memory import codex --apply --source /work/proj-a-memories --source /work/proj-b-memories --selection s.json --idempotency-key k - or, far more likely, a shell wrapper or host that appends its own --source after one the user already supplied. The typeof check fails, the default is used instead, and the confirmed import reads, consolidates and publishes from /.codex/memories while the operator believes they imported /work/proj-b-memories. Paid model calls are made and native memory is written from a directory the user never named; nothing in the output identifies which directory was actually read. The same silent-default shape applies to any non-string value yargs can produce for the option. Witness: [probe] Three arms reading which directory was actually opened: arm 1 (single --source) READ dirA=false dirB=true — the control, honours the flag; arm 2 (repeated --source) READ dirA=false dirB=false codexHomeDefault=true — identical to arm 3 (no flag at all). The explicitly named directory is silently replaced by the default with no diagnostic. Suggested fix: Reject rather than default when the flag was supplied but is not a single string: if (args['source'] !== undefined && typeof args['source'] !== 'string') throw new Error('--source must be given exactly once, as a single directory path.'); and only fall back to the CODEX_HOME/default resolution when the flag is absent. Alternatively declare the option non-repeating so yargs itself errors. The fix must not violate this existing fact: The default resolution the else-branch falls through to is the documented one - docs/design/external-memory-import.md names memory import codex --source <directory> and the CODEX_HOME//.codex/memories fallback - so the absent-flag case must keep working; only the supplied-but-not-a-single-string case changes. Acceptance criterion — the test that must go red if this fix is removed: packages/cli/src/commands/memory.test.ts - assert that parsing ['memory','import','codex','--dry-run','--source','a','--source','b'] rejects with a message naming --source, and that a single --source is still honoured. Red today (the array is discarded and the default used).

[Critical] R1-6: privateDirectory's blanket symlink rejection is applied to the user memory destination root, which core deliberately supports as a symlink (dotfiles layout) Failure scenario: A dotfiles user with ln -s ~/dotfiles/qwen-memories ~/.qwen/memories. Recall, snapshot('user') and the scan all work (they follow the root symlink). Import proceeds: the model call is made, task.usage.modelCalls/reservedTokens are incremented and persisted, the stage directory is written and the ledger records phase 'intent'. Then the walk reaches ~/.qwen/memories, lstat reports a symlink, and it throws. run()'s catch discards that message (only ImportBudgetError/ImportContextError reasons survive) and stores the generic 'Import could not finish. Existing memories were preserved; retry after resolving runtime, model or storage availability.' retry() re-enters the previous.phase === 'intent' branch, calls the same privateDirectory and throws again - so user-scope import is permanently broken for that user, with a paid model call already spent and an error that never names the symlink. Nothing in docs/design/external-memory-import-api.md states the destination root must not be a symlink (it excludes symlinked SOURCES, L20). Witness: [probe] Control/symlink pair with <memory base>/memories a symlink (the dotfiles layout core supports): control | {coreScanComplete:true,proposeCalls:1,modelCallsCharged:1,firstStatus:"added",ledgerPhase:"committed"} vs symlinked | {coreScanComplete:true,proposeCalls:1,modelCallsCharged:1,firstStatus:"failed",retryStatus:"failed",ledgerPhase:"intent"}. Deterministic split; the model call is charged before the throw. Suggested fix: Do not anchor the destination-root check on the runtime-state invariant. Verify the publication target through the mechanism core already uses for that root - getMemoryRootTrustedAnchor(root) / resolveTrustedMemoryFile - and confine privateDirectory's symlink walk to paths under this.base (runtime state, stage, admission). If a symlinked destination root must be refused, refuse it at admission, before prepareProposal spends budget, and surface that specific reason instead of the generic one. The fix must not violate this existing fact: Core grants symlink tolerance to the USER root only: { followRootSymlink: scope === 'user', onUnreadableDir } with the comment 'Project and team roots can live INSIDE the repository ... where a committed symlink would redirect the scan' (packages/core/src/memory/structured-scan.ts:441-447). A fix must not extend symlink tolerance to project or team roots. Acceptance criterion — the test that must go red if this fix is removed: packages/cli/src/memory-import/service.test.ts - a user-scope case ({ ...selection()[0], scope: 'user' }, as at L125/L395) that creates <runtime>/memories as a symlink to a sibling real directory and asserts the section ends status: 'added' with the document published under the symlinked root. Goes red against the current walk. If the author instead chooses up-front refusal, the witness is that submit()/run() rejects before propose is called (assert the propose mock was not invoked).

中文说明

仅完成部分审查,审查缺口已披露。

未审查(原文为英文):reverse audit — stopped after the round 1/2 convergence pair; rounds 3-5 were not run, so the two-consecutive-dry-rounds convergence criterion was never met (the orchestrator ran out of context, not the review time budget — the pair REPORTED findings, it did not go dry).

未审查(原文为英文):build-and-test (test half) — the build phase consumed the whole 600s Agent 7 budget: all three changed workspaces compiled green (19/19 per-workspace builds exit 0), but no unit suite executed and the test-efficacy probe was inconclusive (harnessValidated null — its disposable tree lacks built dist/).

未审查(原文为英文):verifier mutation probing — review scratch-tree refused for every shard (repo-local git includeIf chain resolving to a missing credentials file), so guard-removal mutations could not run in a sanctioned tree; verifiers substituted read-only probes against built dist/ artifacts and the product own injection points, and several findings carry a witness: not run line.

未探索到全部深度(达到工具调用预算):"agent reverse-audit (round 2)":none — I also verified and dismissed, rather than leaving unchecked: rebuildAutoMemoryIndexAtRoot 's if (!existsSync(root)) return '' early-out (so forget.ts…;"agent reverse-audit (round 2)":did not verify by execution that a repo-local-root forget leaves <projectRoot>/.qwen/memory/MEMORY.md stale (no test run; Finding 1 rests on reading getProje…;"agent reverse-audit (round 2)":did not open packages/cli/src/serve/memory-import.test.ts to check whether an existing preview test already pins a route-level size bound (Finding 2's witness…;"agent reverse-audit (round 1)":none — but two checks were left unfinished for scope, not budget: I did not walk run() / prepareProposal / importSection as their own territory (they sit past…。

[Critical] R1-39: A repeated --source is collected by yargs into an array, fails the typeof === 'string' test, and is silently discarded in favour of the default directory Failure scenario: qwen memory import codex --apply --source /work/proj-a-memories --source /work/proj-b-memories --selection s.json --idempotency-key k - or, far more likely, a shell wrapper or host that appends its own --source after one the user already supplied. The typeof check fails, the default is used instead, and the confirmed import reads, consolidates and publishes from /.codex/memories while the operator believes they imported /work/proj-b-memories. Paid model calls are made and native memory is written from a directory the user never named; nothing in the output identifies which directory was actually read. The same silent-default shape applies to any non-string value yargs can produce for the option. Witness: [probe] Three arms reading which directory was actually opened: arm 1 (single --source) READ dirA=false dirB=true — the control, honours the flag; arm 2 (repeated --source) READ dirA=false dirB=false codexHomeDefault=true — identical to arm 3 (no flag at all). The explicitly named directory is silently replaced by the default with no diagnostic. Suggested fix: Reject rather than default when the flag was supplied but is not a single string: if (args['source'] !== undefined && typeof args['source'] !== 'string') throw new Error('--source must be given exactly once, as a single directory path.'); and only fall back to the CODEX_HOME/default resolution when the flag is absent. Alternatively declare the option non-repeating so yargs itself errors. The fix must not violate this existing fact: The default resolution the else-branch falls through to is the documented one - docs/design/external-memory-import.md names memory import codex --source <directory> and the CODEX_HOME//.codex/memories fallback - so the absent-flag case must keep working; only the supplied-but-not-a-single-string case changes. Acceptance criterion — the test that must go red if this fix is removed: packages/cli/src/commands/memory.test.ts - assert that parsing ['memory','import','codex','--dry-run','--source','a','--source','b'] rejects with a message naming --source, and that a single --source is still honoured. Red today (the array is discarded and the default used).

[Critical] R1-6: privateDirectory's blanket symlink rejection is applied to the user memory destination root, which core deliberately supports as a symlink (dotfiles layout) Failure scenario: A dotfiles user with ln -s ~/dotfiles/qwen-memories ~/.qwen/memories. Recall, snapshot('user') and the scan all work (they follow the root symlink). Import proceeds: the model call is made, task.usage.modelCalls/reservedTokens are incremented and persisted, the stage directory is written and the ledger records phase 'intent'. Then the walk reaches ~/.qwen/memories, lstat reports a symlink, and it throws. run()'s catch discards that message (only ImportBudgetError/ImportContextError reasons survive) and stores the generic 'Import could not finish. Existing memories were preserved; retry after resolving runtime, model or storage availability.' retry() re-enters the previous.phase === 'intent' branch, calls the same privateDirectory and throws again - so user-scope import is permanently broken for that user, with a paid model call already spent and an error that never names the symlink. Nothing in docs/design/external-memory-import-api.md states the destination root must not be a symlink (it excludes symlinked SOURCES, L20). Witness: [probe] Control/symlink pair with <memory base>/memories a symlink (the dotfiles layout core supports): control | {coreScanComplete:true,proposeCalls:1,modelCallsCharged:1,firstStatus:"added",ledgerPhase:"committed"} vs symlinked | {coreScanComplete:true,proposeCalls:1,modelCallsCharged:1,firstStatus:"failed",retryStatus:"failed",ledgerPhase:"intent"}. Deterministic split; the model call is charged before the throw. Suggested fix: Do not anchor the destination-root check on the runtime-state invariant. Verify the publication target through the mechanism core already uses for that root - getMemoryRootTrustedAnchor(root) / resolveTrustedMemoryFile - and confine privateDirectory's symlink walk to paths under this.base (runtime state, stage, admission). If a symlinked destination root must be refused, refuse it at admission, before prepareProposal spends budget, and surface that specific reason instead of the generic one. The fix must not violate this existing fact: Core grants symlink tolerance to the USER root only: { followRootSymlink: scope === 'user', onUnreadableDir } with the comment 'Project and team roots can live INSIDE the repository ... where a committed symlink would redirect the scan' (packages/core/src/memory/structured-scan.ts:441-447). A fix must not extend symlink tolerance to project or team roots. Acceptance criterion — the test that must go red if this fix is removed: packages/cli/src/memory-import/service.test.ts - a user-scope case ({ ...selection()[0], scope: 'user' }, as at L125/L395) that creates <runtime>/memories as a symlink to a sibling real directory and asserts the section ends status: 'added' with the document published under the symlinked root. Goes red against the current walk. If the author instead chooses up-front refusal, the witness is that submit()/run() rejects before propose is called (assert the propose mock was not invoked).

— qwen3.8-max via Qwen Code /review (v0.25.2)

Comment thread packages/cli/src/memory-import/service.ts Outdated
Comment thread packages/cli/src/memory-import/service.ts Outdated
Comment thread packages/cli/src/memory-import/source.ts
Comment thread packages/cli/src/memory-import/service.ts Outdated
Comment thread packages/cli/src/memory-import/service.ts Outdated
Comment thread packages/cli/src/memory-import/service.test.ts
Comment thread packages/cli/src/memory-import/source.test.ts
Comment thread packages/cli/src/acp-integration/acpAgent.ts
Comment thread packages/cli/src/cli.ts
Comment thread packages/core/src/memory/paths.ts

@qwen-code-review-bot qwen-code-review-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partially reviewed — gaps disclosed.

Not reviewed: reverse audit — stopped after the round 1/2 convergence pair; rounds 3-5 were not run, so the two-consecutive-dry-rounds convergence criterion was never met (the orchestrator ran out of context, not the review time budget — the pair REPORTED findings, it did not go dry).

Not reviewed: build-and-test (test half) — the build phase consumed the whole 600s Agent 7 budget: all three changed workspaces compiled green (19/19 per-workspace builds exit 0), but no unit suite executed and the test-efficacy probe was inconclusive (harnessValidated null — its disposable tree lacks built dist/).

Not reviewed: verifier mutation probing — review scratch-tree refused for every shard (repo-local git includeIf chain resolving to a missing credentials file), so guard-removal mutations could not run in a sanctioned tree; verifiers substituted read-only probes against built dist/ artifacts and the product own injection points, and several findings carry a witness: not run line.

Not explored to full depth (tool budget reached): "agent reverse-audit (round 2)": none — I also verified and dismissed, rather than leaving unchecked: rebuildAutoMemoryIndexAtRoot 's if (!existsSync(root)) return '' early-out (so forget.ts…; "agent reverse-audit (round 2)": did not verify by execution that a repo-local-root forget leaves <projectRoot>/.qwen/memory/MEMORY.md stale (no test run; Finding 1 rests on reading getProje…; "agent reverse-audit (round 2)": did not open packages/cli/src/serve/memory-import.test.ts to check whether an existing preview test already pins a route-level size bound (Finding 2's witness…; "agent reverse-audit (round 1)": none — but two checks were left unfinished for scope, not budget: I did not walk run() / prepareProposal / importSection as their own territory (they sit past….

[Critical] R1-39: A repeated --source is collected by yargs into an array, fails the typeof === 'string' test, and is silently discarded in favour of the default directory Failure scenario: qwen memory import codex --apply --source /work/proj-a-memories --source /work/proj-b-memories --selection s.json --idempotency-key k - or, far more likely, a shell wrapper or host that appends its own --source after one the user already supplied. The typeof check fails, the default is used instead, and the confirmed import reads, consolidates and publishes from /.codex/memories while the operator believes they imported /work/proj-b-memories. Paid model calls are made and native memory is written from a directory the user never named; nothing in the output identifies which directory was actually read. The same silent-default shape applies to any non-string value yargs can produce for the option. Witness: [probe] Three arms reading which directory was actually opened: arm 1 (single --source) READ dirA=false dirB=true — the control, honours the flag; arm 2 (repeated --source) READ dirA=false dirB=false codexHomeDefault=true — identical to arm 3 (no flag at all). The explicitly named directory is silently replaced by the default with no diagnostic. Suggested fix: Reject rather than default when the flag was supplied but is not a single string: if (args['source'] !== undefined && typeof args['source'] !== 'string') throw new Error('--source must be given exactly once, as a single directory path.'); and only fall back to the CODEX_HOME/default resolution when the flag is absent. Alternatively declare the option non-repeating so yargs itself errors. The fix must not violate this existing fact: The default resolution the else-branch falls through to is the documented one - docs/design/external-memory-import.md names memory import codex --source <directory> and the CODEX_HOME//.codex/memories fallback - so the absent-flag case must keep working; only the supplied-but-not-a-single-string case changes. Acceptance criterion — the test that must go red if this fix is removed: packages/cli/src/commands/memory.test.ts - assert that parsing ['memory','import','codex','--dry-run','--source','a','--source','b'] rejects with a message naming --source, and that a single --source is still honoured. Red today (the array is discarded and the default used).

[Critical] R1-6: privateDirectory's blanket symlink rejection is applied to the user memory destination root, which core deliberately supports as a symlink (dotfiles layout) Failure scenario: A dotfiles user with ln -s ~/dotfiles/qwen-memories ~/.qwen/memories. Recall, snapshot('user') and the scan all work (they follow the root symlink). Import proceeds: the model call is made, task.usage.modelCalls/reservedTokens are incremented and persisted, the stage directory is written and the ledger records phase 'intent'. Then the walk reaches ~/.qwen/memories, lstat reports a symlink, and it throws. run()'s catch discards that message (only ImportBudgetError/ImportContextError reasons survive) and stores the generic 'Import could not finish. Existing memories were preserved; retry after resolving runtime, model or storage availability.' retry() re-enters the previous.phase === 'intent' branch, calls the same privateDirectory and throws again - so user-scope import is permanently broken for that user, with a paid model call already spent and an error that never names the symlink. Nothing in docs/design/external-memory-import-api.md states the destination root must not be a symlink (it excludes symlinked SOURCES, L20). Witness: [probe] Control/symlink pair with <memory base>/memories a symlink (the dotfiles layout core supports): control | {coreScanComplete:true,proposeCalls:1,modelCallsCharged:1,firstStatus:"added",ledgerPhase:"committed"} vs symlinked | {coreScanComplete:true,proposeCalls:1,modelCallsCharged:1,firstStatus:"failed",retryStatus:"failed",ledgerPhase:"intent"}. Deterministic split; the model call is charged before the throw. Suggested fix: Do not anchor the destination-root check on the runtime-state invariant. Verify the publication target through the mechanism core already uses for that root - getMemoryRootTrustedAnchor(root) / resolveTrustedMemoryFile - and confine privateDirectory's symlink walk to paths under this.base (runtime state, stage, admission). If a symlinked destination root must be refused, refuse it at admission, before prepareProposal spends budget, and surface that specific reason instead of the generic one. The fix must not violate this existing fact: Core grants symlink tolerance to the USER root only: { followRootSymlink: scope === 'user', onUnreadableDir } with the comment 'Project and team roots can live INSIDE the repository ... where a committed symlink would redirect the scan' (packages/core/src/memory/structured-scan.ts:441-447). A fix must not extend symlink tolerance to project or team roots. Acceptance criterion — the test that must go red if this fix is removed: packages/cli/src/memory-import/service.test.ts - a user-scope case ({ ...selection()[0], scope: 'user' }, as at L125/L395) that creates <runtime>/memories as a symlink to a sibling real directory and asserts the section ends status: 'added' with the document published under the symlinked root. Goes red against the current walk. If the author instead chooses up-front refusal, the witness is that submit()/run() rejects before propose is called (assert the propose mock was not invoked).

中文说明

仅完成部分审查,审查缺口已披露。

未审查(原文为英文):reverse audit — stopped after the round 1/2 convergence pair; rounds 3-5 were not run, so the two-consecutive-dry-rounds convergence criterion was never met (the orchestrator ran out of context, not the review time budget — the pair REPORTED findings, it did not go dry).

未审查(原文为英文):build-and-test (test half) — the build phase consumed the whole 600s Agent 7 budget: all three changed workspaces compiled green (19/19 per-workspace builds exit 0), but no unit suite executed and the test-efficacy probe was inconclusive (harnessValidated null — its disposable tree lacks built dist/).

未审查(原文为英文):verifier mutation probing — review scratch-tree refused for every shard (repo-local git includeIf chain resolving to a missing credentials file), so guard-removal mutations could not run in a sanctioned tree; verifiers substituted read-only probes against built dist/ artifacts and the product own injection points, and several findings carry a witness: not run line.

未探索到全部深度(达到工具调用预算):"agent reverse-audit (round 2)":none — I also verified and dismissed, rather than leaving unchecked: rebuildAutoMemoryIndexAtRoot 's if (!existsSync(root)) return '' early-out (so forget.ts…;"agent reverse-audit (round 2)":did not verify by execution that a repo-local-root forget leaves <projectRoot>/.qwen/memory/MEMORY.md stale (no test run; Finding 1 rests on reading getProje…;"agent reverse-audit (round 2)":did not open packages/cli/src/serve/memory-import.test.ts to check whether an existing preview test already pins a route-level size bound (Finding 2's witness…;"agent reverse-audit (round 1)":none — but two checks were left unfinished for scope, not budget: I did not walk run() / prepareProposal / importSection as their own territory (they sit past…。

[Critical] R1-39: A repeated --source is collected by yargs into an array, fails the typeof === 'string' test, and is silently discarded in favour of the default directory Failure scenario: qwen memory import codex --apply --source /work/proj-a-memories --source /work/proj-b-memories --selection s.json --idempotency-key k - or, far more likely, a shell wrapper or host that appends its own --source after one the user already supplied. The typeof check fails, the default is used instead, and the confirmed import reads, consolidates and publishes from /.codex/memories while the operator believes they imported /work/proj-b-memories. Paid model calls are made and native memory is written from a directory the user never named; nothing in the output identifies which directory was actually read. The same silent-default shape applies to any non-string value yargs can produce for the option. Witness: [probe] Three arms reading which directory was actually opened: arm 1 (single --source) READ dirA=false dirB=true — the control, honours the flag; arm 2 (repeated --source) READ dirA=false dirB=false codexHomeDefault=true — identical to arm 3 (no flag at all). The explicitly named directory is silently replaced by the default with no diagnostic. Suggested fix: Reject rather than default when the flag was supplied but is not a single string: if (args['source'] !== undefined && typeof args['source'] !== 'string') throw new Error('--source must be given exactly once, as a single directory path.'); and only fall back to the CODEX_HOME/default resolution when the flag is absent. Alternatively declare the option non-repeating so yargs itself errors. The fix must not violate this existing fact: The default resolution the else-branch falls through to is the documented one - docs/design/external-memory-import.md names memory import codex --source <directory> and the CODEX_HOME//.codex/memories fallback - so the absent-flag case must keep working; only the supplied-but-not-a-single-string case changes. Acceptance criterion — the test that must go red if this fix is removed: packages/cli/src/commands/memory.test.ts - assert that parsing ['memory','import','codex','--dry-run','--source','a','--source','b'] rejects with a message naming --source, and that a single --source is still honoured. Red today (the array is discarded and the default used).

[Critical] R1-6: privateDirectory's blanket symlink rejection is applied to the user memory destination root, which core deliberately supports as a symlink (dotfiles layout) Failure scenario: A dotfiles user with ln -s ~/dotfiles/qwen-memories ~/.qwen/memories. Recall, snapshot('user') and the scan all work (they follow the root symlink). Import proceeds: the model call is made, task.usage.modelCalls/reservedTokens are incremented and persisted, the stage directory is written and the ledger records phase 'intent'. Then the walk reaches ~/.qwen/memories, lstat reports a symlink, and it throws. run()'s catch discards that message (only ImportBudgetError/ImportContextError reasons survive) and stores the generic 'Import could not finish. Existing memories were preserved; retry after resolving runtime, model or storage availability.' retry() re-enters the previous.phase === 'intent' branch, calls the same privateDirectory and throws again - so user-scope import is permanently broken for that user, with a paid model call already spent and an error that never names the symlink. Nothing in docs/design/external-memory-import-api.md states the destination root must not be a symlink (it excludes symlinked SOURCES, L20). Witness: [probe] Control/symlink pair with <memory base>/memories a symlink (the dotfiles layout core supports): control | {coreScanComplete:true,proposeCalls:1,modelCallsCharged:1,firstStatus:"added",ledgerPhase:"committed"} vs symlinked | {coreScanComplete:true,proposeCalls:1,modelCallsCharged:1,firstStatus:"failed",retryStatus:"failed",ledgerPhase:"intent"}. Deterministic split; the model call is charged before the throw. Suggested fix: Do not anchor the destination-root check on the runtime-state invariant. Verify the publication target through the mechanism core already uses for that root - getMemoryRootTrustedAnchor(root) / resolveTrustedMemoryFile - and confine privateDirectory's symlink walk to paths under this.base (runtime state, stage, admission). If a symlinked destination root must be refused, refuse it at admission, before prepareProposal spends budget, and surface that specific reason instead of the generic one. The fix must not violate this existing fact: Core grants symlink tolerance to the USER root only: { followRootSymlink: scope === 'user', onUnreadableDir } with the comment 'Project and team roots can live INSIDE the repository ... where a committed symlink would redirect the scan' (packages/core/src/memory/structured-scan.ts:441-447). A fix must not extend symlink tolerance to project or team roots. Acceptance criterion — the test that must go red if this fix is removed: packages/cli/src/memory-import/service.test.ts - a user-scope case ({ ...selection()[0], scope: 'user' }, as at L125/L395) that creates <runtime>/memories as a symlink to a sibling real directory and asserts the section ends status: 'added' with the document published under the symlinked root. Goes red against the current walk. If the author instead chooses up-front refusal, the witness is that submit()/run() rejects before propose is called (assert the propose mock was not invoked).

— qwen3.8-max via Qwen Code /review (v0.25.2)

@wenshao

wenshao commented Oct 17, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /resolve

@wenshao

wenshao commented Oct 17, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /takeover

@qwen-code-dev-bot qwen-code-dev-bot added the autofix/takeover Summon the autofix loop to manage this PR (remove to release; needs triage+) label Oct 17, 2026
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤝 Takeover engaged: the autofix loop now manages this PR — it will address new review feedback and resolve base conflicts until the label is removed or the round cap is reached. Remove the autofix/takeover label (or comment @qwen-code /takeover stop) to release.

中文说明

🤝 已接管:autofix 循环现在管理此 PR —— 将持续处理新的评审反馈与 base 冲突,直到移除标签或达到轮次上限。移除 autofix/takeover 标签(或评论 @qwen-code /takeover stop)即可释放。

@qwen-code-dev-bot

qwen-code-dev-bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Collaborator

✅ AutoFix round 15 finished — view run. See this round's report below.

中文说明

✅ AutoFix 第 15 轮已完成 —— 查看运行。本轮报告见下方。

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Could not produce a passing fix for this feedback (round 1/100). This item now needs a human; the loop stays engaged and still picks up new feedback and base conflicts, but will not retry this item on its own.

What I found before stopping:
Qwen hit the tool-call loop guard during address-review. A human should take over this feedback batch.

See the Qwen Autofix agent step logs for model/tool output.

中文说明

🤖 未能为该反馈产生可通过验证的修复(第 1/100 轮)。此项现在需要人工处理;循环保持在线,仍会拾取新反馈与 base 冲突,但不会自行重试此项。

Run log: https://github.com/QwenLM/qwen-code/actions/runs/39003076111


🧠 Handled by Qwen Code · model/模型 kimi-k3 · CLI 0.25.2

qwen-autofix and others added 2 commits October 17, 2026 07:18
Resolve scripts/cli-entry.js: keep both the 'memory' in-process fast path (this PR) and the managed-runtime-bootstrap fast paths (main).

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
AppContainer mount effects from earlier tests can still be in flight
(the beforeEach leaves the real config.initialize() un-awaited), and an
unscoped getInteractiveModSession mockReturnValue lets those stale
effects call the current test's initialize() mock, intermittently
inflating the exact-once assertion to 51+ calls. Scoping the mock by
Config identity makes the stale calls a no-op, matching the real
function's behavior for unregistered configs.
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🔀 Base updated: red check(s) [Serve A/B (ubuntu-latest, Node 22.x), TUI parity snapshots (ink vs opentui), SDK Java (fast) / ubuntu-latest / Java 21, Test (ubuntu-latest, Node 22.x), OpenTUI no-flicker gate, Lint & Static (ubuntu-latest, Node 22.x), Integration Tests (no-AK, No Sandbox), web-shell E2E Smoke (ubuntu-latest, Node 22.x), SDK Java (fast) / Real daemon E2E / Java 11, Managed Agent fast tier] pass on current main — merged current main via update-branch; CI will re-run.

中文说明

🔀 已更新 base:红色检查 [Serve A/B (ubuntu-latest, Node 22.x), TUI parity snapshots (ink vs opentui), SDK Java (fast) / ubuntu-latest / Java 21, Test (ubuntu-latest, Node 22.x), OpenTUI no-flicker gate, Lint & Static (ubuntu-latest, Node 22.x), Integration Tests (no-AK, No Sandbox), web-shell E2E Smoke (ubuntu-latest, Node 22.x), SDK Java (fast) / Real daemon E2E / Java 11, Managed Agent fast tier] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。

@github-actions

Copy link
Copy Markdown
Contributor

Managed Agent gate failed

Run 39348013816 · commit eda05046dc3e

No test identifier was available; follow the run link for job logs and annotations.

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 AutoFix updated a stale base — the fix did not pass verification, but this PR was behind main, so it merged current main in via update-branch and will retry on the next scan. A stale base (a dependency or symbol main already changed) can fail the build without being the fix's fault; if it still fails once current, it hands off to a human.

What I found before stopping:

Autofix round blocked: required npm run build fails on a pre-existing CLI heap cap

Blocker

npm run build fails in this checkout at the repository's fixed 4 GiB Node heap, in the packages/cli TypeScript build:

packages/cli build: FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
packages/cli build: Failed
[ERR_PNPM_RECURSIVE_RUN_FIRST_FAIL] @qwen-code/qwen-code@0.25.3 build: `node ../../scripts/build_package.js`

The same failure is present at the pre-round head (eda05046dc) before any round edit: the first build log captured in this session, started before the working tree was touched, ends with the identical heap failure at the same step. It is therefore not caused by this round's changes and is not a type error.

Evidence that the candidate code is type-correct:

  • npm run typecheck — passed (exit 0).
  • npm run lint — passed (exit 0), run after all edits.
  • cd packages/cli && NODE_OPTIONS="--max-old-space-size=8192" npx tsc --build — passed (exit 0).
  • Focused Vitest: service.test.ts, source.test.ts, commands/memory.test.ts, serve/memory-import.test.ts, local.test.ts — 97/97 passed.

Why this round cannot fix it

The 4 GiB limit is set by cross-env NODE_OPTIONS="--max-old-space-size=4096" in the root build script, and scripts/build_package.js runs tsc --build with that inherited setting. Raising the heap, or reducing the CLI build's memory, requires editing the

中文说明

🤖 AutoFix 更新了一个过期的 base —— 修复未通过验证,但本 PR 落后于 main,因此已通过 update-branch 合入当前 main,并将在下次扫描时重试。过期的 base(main 已改动的依赖或符号)可能让构建失败而并非修复本身的错;若 base 更新后仍然失败,将移交人工处理。

停止前我了解到的情况:

Autofix 轮次受阻:必需的 npm run build 因既有的 CLI 堆上限而失败

阻塞点

npm run build 在当前检出中以仓库固定的 4 GiB Node 堆失败,失败发生在 packages/cli 的 TypeScript 构建阶段:

packages/cli build: FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
packages/cli build: Failed
[ERR_PNPM_RECURSIVE_RUN_FIRST_FAIL] @qwen-code/qwen-code@0.25.3 build: `node ../../scripts/build_package.js`

同样的失败在本轮改动前的 head(eda05046dc)上就已存在:本次会话中第一次构建的日志是在工作树尚未被修改时开始的,结尾是同一构建步骤的相同堆失败。因此它不是本轮改动造成的,也不是类型错误。

候选代码类型正确的证据:

  • npm run typecheck — 通过(退出码 0)。
  • npm run lint — 通过(退出码 0),在所有编辑之后运行。
  • cd packages/cli && NODE_OPTIONS="--max-old-space-size=8192" npx tsc --build — 通过(退出码 0)。
  • 聚焦 Vitest:service.test.ts、source.test.ts、commands/memory.test.ts、serve/memory-import.test.ts、local.test.ts — 97/97 通过。

本轮无法修复的原因

4 GiB 上限由根 build 脚本中的 cross-env NODE_OPTIONS="--max-old-space-size=4096" 设定,而 scripts/build_package.js 在该继承设置下运行 tsc --build。提高该上限或降低 CLI 构建的内存占用,需要修改仓库构建脚本或根清单,这超出了本 PR 的改动范围,并且明确属于 autofix 机器人不得触碰的区域(仓库 scripts/、工作区根配置与清单位于 scripts 字段)。本 PR 的维护者验证评论也记录了同一已知上限,并单独以 8 GiB 堆重建了 packages/cli。

按不提交规则保留在工作树中的候选修复

工作树中有意保留了已验证的候选改动;git status --short 列出这些文件属预期行为。

  • packages/cli/src/memory-import/service.ts — R1-37:selectImportContext 现在会从尾部移除 indexOnly 条目,直到序列化上下文符合 MEMORY_IMPORT_BUDGET.contextBytes(32 KiB),同时保留相关的完整正文;如果仅这些正文就超出预算,仍会按失败关闭处理。修复前,130 篇普通既有文档会产生 43,221 字节的元数据下界,该 scope 中的每次导入都会在模型调用之前失败。
  • packages/cli/src/memory-import/service.test.ts — 新增回归测试 truncates the discovery index instead of failing a large existing store:写入 130 篇文档,断言 completed/added,并断言发送的 existing 上下文不超过 32,768 字节。变异探针:删除截断循环会使其失败;恢复后通过。
  • packages/cli/src/commands/memory.test.ts — R3-2 的见证测试原本用 1,000 次 setImmediate 轮询等待真实文件系统 I/O,在此环境中确定性失败;现在改为使用 `vi.waitFor(..., { timeout:

Run log: https://github.com/QwenLM/qwen-code/actions/runs/39349724079


🧠 Handled by Qwen Code · model/模型 deepseek-v4.1-flash-1017 · CLI 0.25.3

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 AutoFix deferred a stale-base refresh — the fix did not pass verification and this PR is behind main, but a lifecycle review of this PR is still in flight, and merging main now would cancel it or invalidate its posting (#10110). Command-triggered reviews are invisible to this probe (af-155). The refresh and this item's retry move to the next scan.

⚠️ This change was NOT pushed — any commit referenced below was made only in the runner workspace and has been discarded. What the agent reported:

Autofix blocked: the deterministic build rejection is a repository heap ceiling, not this round's change

PR #14281, branch codex/external-memory-import, head 83fa927460.

Blocker

The deterministic gate rejects the round with:

$ npm run build
...
packages/cli build: FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
packages/cli build: Error: Command failed: tsc --build
[ERR_PNPM_RECURSIVE_RUN_FIRST_FAIL] @qwen-code/qwen-code@0.25.3 build: node ../../scripts/build_package.js

The build script runs the CLI compiler with NODE_OPTIONS="--max-old-space-size=4096". The compiler's live heap reaches roughly 4,010 to 4,033 MB and cannot compact, so it aborts. I reproduced this twice with the exact gate command; the gate log quoted in feedback.md is the same failure class.

Evidence this is not caused by the round

  • Parent commit: I restored the two source files changed by 83fa927460 from parent a0c4304225 and ran the same CLI compile at 4096 MB. It aborted with the same heap error. The round's 73-line delta is therefore not the trigger.
  • Round file removed: With packages/cli/src/memory-import/service.test.ts removed from the compiler program, the 4096 MB compile still aborted at roughly 4,010 MB live heap.
  • Headroom measurement: The identical cd packages/cli && tsc --build succeeds at --max-old-space-size=4160 (4.06 GiB), 4224, 4608 and 8192, and fails at 4096. The branch is r
中文说明

🤖 AutoFix 暂缓了一次过期 base 刷新 —— 修复未通过验证且本 PR 落后于 main,但该 PR 上仍有一轮生命周期评审在运行,现在合入 main 会取消该评审或使其发布失效(#10110)。命令触发的评审对本探测不可见(af-156)。base 刷新与此项的重试推迟到下次扫描。

⚠️ 此改动未被推送 —— 下文引用的任何提交都只存在于 runner 工作区,已被丢弃。以下是 agent 的报告:

Autofix 受阻:确定性构建拒绝来自仓库的堆上限,而非本轮改动

PR #14281,分支 codex/external-memory-import,head 83fa927460。

阻塞项

确定性验证门以如下错误拒绝了本轮:

$ npm run build
...
packages/cli build: FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
packages/cli build: Error: Command failed: tsc --build
[ERR_PNPM_RECURSIVE_RUN_FIRST_FAIL] @qwen-code/qwen-code@0.25.3 build: node ../../scripts/build_package.js

构建脚本以 NODE_OPTIONS="--max-old-space-size=4096" 运行 CLI 编译器。编译器的存活堆达到约 4,010 至 4,033 MB 后无法继续压缩,因而中止。我用与验证门完全相同的命令复现了两次;feedback.md 中引用的验证门日志属于同一类失败。

证据:这不是本轮改动造成的

  • 父提交:我从父提交 a0c4304225 恢复了 83fa927460 修改的两个源文件,并以 4096 MB 运行同一 CLI 编译;它以相同的堆错误中止。因此本轮 73 行的增量不是触发因素。
  • 移除本轮文件:将 packages/cli/src/memory-import/service.test.ts 从编译器程序中移除后,4096 MB 的编译仍在约 4,010 MB 存活堆处中止。
  • 余量测量:相同的 cd packages/cli && tsc --build 在 --max-old-space-size=4160(4.06 GiB)、4224、4608 和 8192 下成功,在 4096 下失败。该分支超出仓库固定上限约 50 至 70 MB。
  • 功能行为为绿:npx vitest run src/memory-import/service.test.ts src/memory-import/source.test.ts src/serve/memory-import.test.ts src/commands/memory.test.ts 通过 4 个文件、93 个测试,包括 R1-37 的成熟记忆库回归。

已尝试的操作

  • 两次运行 npm run build(验证门的确切命令):均在 packages/cli 的 tsc --build 步骤中止。
  • 对本轮修改的两个文件做父提交 A/B 对照:相同中止。
  • 从编译器程序移除本轮的测试文件:相同中止。
  • 堆上限扫描:4096 失败;4160、4224、4608 和 8192 通过。
  • 临时移动的文件已恢复;git status --short 为空。

为何未应用本范围内修复

唯一有效的杠杆是构建配置,而不是功能代码:

  1. 提高根 build 脚本中的堆上限,该脚本目前固定为 --max-old-space-size=4096。
  2. 在 packages/cli/tsconfig.json 中排除大型测试文件,或以其他方式缩小 packages/cli 的编译器程序。

两者都是本 PR 足迹之外的验证与构建机制改动。根构建脚本就是仓库自己的验证门命令;修改它属于维护者决策,并会影响所有包。从构建中排除测试会在无提示的情况下放弃当前可编译文件的类型检查。若无维护者输入,应用任一方案都会违反本轮的范围规则。

请求维护者采取的行动

请选择其一:

  • 将根构建堆上限提高到至少 4608 MB(其他仓库工作流已使用 8192 MB);

Run log: https://github.com/QwenLM/qwen-code/actions/runs/39359034712


🧠 Handled by Qwen Code · model/模型 deepseek-v4.1-flash-1017 · CLI 0.25.3

@github-actions

Copy link
Copy Markdown
Contributor

Managed Agent gate failed

Run 39356073240 · commit a0c430422538

No test identifier was available; follow the run link for job logs and annotations.

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Addressed the latest review feedback (round 15/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 15/100 轮)。改动内容与我反驳保留之处如下:

Address review — PR #14281

Feedback triaged

Review rv:5564888794 (CHANGES_REQUESTED, automated reviewer) carried 8 Critical inline findings: R5-1, R5-2, R5-3, R1-3, R1-5, R1-37, R2-2, R3-2. Critical-only mode is active; the deferred non-Critical feedback was left untouched.

Dispositions

R1-37 — reproduced and fixed this round

[rc:4308687648] At the round's start, a probe with 150 valid native user memories and an unrelated source produced status: 'partial' and the item reason Existing memory index or relevant evidence exceeds the 32 KiB import context budget... — the index floor grew with the store even though every entry was index-only.

Fix: selectImportContext now separates relevant full documents (evidence) from index-only metadata and admits metadata entries only while the exact serialized context fits MEMORY_IMPORT_BUDGET.contextBytes. Non-fitting index-only entries are omitted; non-fitting relevant evidence still throws the same fail-closed ImportContextError.

Test: a new service.test.ts case seeds 150 valid user documents whose metadata alone exceeds 32 KiB and asserts completed/added, a non-empty bounded index of fewer than 150 index-only entries, and JSON.stringify(existing) <= 32768 bytes. Mutation probe: replacing the skip with a throw makes the test fail; restoring it passes.

Docs: the English and Chinese design documents now both state that the index is bounded to the context budget and that only non-fitting index-only entries are omitted.

Verified fixed at HEAD (no code change this round)

  • R5-1 [rc:4308687386] — retry() calls get(id) before prepare()/locked(), so an unknown id fails on task.json ENOENT without creating the owner/task directory. service.test.ts "does not allocate state for unknown retry IDs" passes.
  • R5-2 [rc:4308687392] — each locked() acquisition owns a local AbortController; onCompromised aborts only that ownership signal, not every controller. The model/state-write lock-loss tests show the unrelated task completes.
  • R5-3 [rc:4308687396] — writeJson() checks the held ownership signal before writing the temporary file and again before rename, and locked() skips release after compromise; the replacement holder's task bytes and usage counters survive. Both lock-loss tests pass.
  • R1-3 [rc:4308687473] — previewSource accumulates expandedBytes before joining ancestor preface text and rejects above IMPORT_LIMITS.totalBytes (8 MiB); source.test.ts passes.
  • R1-5 [rc:4308687567] — selectImportContext now uses parseAutoMemoryTopicDocument and builds the metadata from parsed fields; the LF/CRLF closing-fence test passes.
  • R2-2 [rc:4308687742] — pre-parse admission counts newlines and inline markers independently of heading syntax, so emphasis/setext inputs are rejected before parser.parse; source.test.ts passes.
  • R3-2 [rc:4308687817] — the import codex handler calls ignoreBrokenPipe() before writing; commands/memory.test.ts passes.

Changes

  • packages/cli/src/memory-import/service.ts — bounded index in selectImportContext.
  • packages/cli/src/memory-import/service.test.ts — mature-store regression test.
  • docs/design/external-memory-import.md, docs/design/external-memory-import.zh-CN.md — synchronized bounded-index wording.

Conflicts

None. --conflict false, so no merge was performed.

Budget note

The feedback carried a budget warning for the previous round. This round reproduced and fixed only the one Critical that still stood (R1-37), re-verified the other seven against HEAD, and kept the change to a 30-line production change, one focused regression test, and a 2-line documentation sync.

Verification

  • COREPACK_HOME=/tmp/corepack-home npm run build — passed. The default corepack cache /home/github-runner/.cache/node/corepack/v1 is not writable in this sandbox, so a writable COREPACK_HOME was used; no check was skipped.
  • COREPACK_HOME=/tmp/corepack-home npm run typecheck — passed.
  • COREPACK_HOME=/tmp/corepack-home npm run lint — passed.
  • cd packages/cli && npx vitest run src/memory-import/service.test.ts src/memory-import/source.test.ts src/commands/memory.test.ts src/serve/memory-import.test.ts — 93 passed / 4 files.
  • R1-37 pre-fix probe — 150 existing documents produced partial with the 32 KiB context reason on the unmodified code.
  • R1-37 mutation probe — negating the bounded-index skip made the new test fail; restoring it passed (the restored run is included in the 93).
  • R1-37 relevant-evidence mutation probe — removing the !entry.indexOnly throw (so oversized relevant evidence is silently dropped) made the existing "sends metadata..." test fail; restoring it passed.
中文说明

处理评审 — PR #14281

已分类的反馈

评审 rv:5564888794(CHANGES_REQUESTED,自动评审器)携带 8 条 Critical 行内发现:R5-1、R5-2、R5-3、R1-3、R1-5、R1-37、R2-2、R3-2。当前处于仅处理 Critical 模式;被延后的非 Critical 反馈未改动。

处置

R1-37 — 本轮复现并修复

[rc:4308687648] 本轮开始时,用 150 篇合法原生用户记忆和一个不相关的来源构造探针,得到 status: 'partial',条目原因为 Existing memory index or relevant evidence exceeds the 32 KiB import context budget...——即便所有条目都是仅有索引,索引下界仍随记忆库增长。

修复:selectImportContext 现在把相关的完整文档(证据)与仅有索引的元数据分开,只在序列化上下文确实能放入 MEMORY_IMPORT_BUDGET.contextBytes 时接纳元数据条目。放不下的仅有索引条目被省略;放不下的相关证据仍抛出相同的 fail-closed ImportContextError。

测试:service.test.ts 新增用例,种入 150 篇合法用户文档(其元数据本身已超过 32 KiB),断言 completed/added、索引非空且条目数少于 150、全部为 indexOnly,并且 JSON.stringify(existing) 不超过 32768 字节。变异探针:把跳过改为抛错会使该测试变红;恢复后转绿。

文档:英文和中文设计文档现在都说明索引按上下文预算有界,只省略放不下的仅有索引条目。

在 HEAD 上验证为已修复(本轮无代码改动)

  • R5-1 [rc:4308687386] — retry() 在 prepare()/locked() 之前先调用 get(id),未知 id 在读取 task.json 时以 ENOENT 失败,不会创建 owner/task 目录。service.test.ts 的 “does not allocate state for unknown retry IDs” 通过。
  • R5-2 [rc:4308687392] — 每次 locked() 获取都有本地 AbortController;onCompromised 只中止该所有权信号,不再中止所有 controller。模型/状态写入阶段的锁丢失测试显示无关任务仍能完成。
  • R5-3 [rc:4308687396] — writeJson() 在写临时文件前和 rename 前都检查持有的所有权信号,locked() 在 compromise 后跳过 release;替换持有者的 task 字节和 usage 计数得以保留。两个锁丢失测试均通过。
  • R1-3 [rc:4308687473] — previewSource 在拼接祖先 preface 之前累计 expandedBytes,超过 IMPORT_LIMITS.totalBytes(8 MiB)即拒绝;source.test.ts 通过。
  • R1-5 [rc:4308687567] — selectImportContext 现在使用 parseAutoMemoryTopicDocument,并从解析后的字段构造元数据;LF/CRLF 结束围栏测试通过。
  • R2-2 [rc:4308687742] — 解析前准入独立于标题语法统计换行与行内标记,因此强调/setext 输入会在 parser.parse 之前被拒绝;source.test.ts 通过。
  • R3-2 [rc:4308687817] — import codex 处理函数在写入前调用 ignoreBrokenPipe();commands/memory.test.ts 通过。

变更

  • packages/cli/src/memory-import/service.ts — selectImportContext 的有界索引。
  • packages/cli/src/memory-import/service.test.ts — 成熟记忆库回归测试。
  • docs/design/external-memory-import.md、docs/design/external-memory-import.zh-CN.md — 同步的有界索引表述。

冲突

无。--conflict false,未执行合并。

预算说明

反馈包含上一轮的超时预算警告。本轮只复现并修复仍然存在的唯一 Critical(R1-37),对其余七条在 HEAD 上重新验证;改动为 30 行生产代码、一个针对性回归测试以及 2 行文档同步。

验证

  • COREPACK_HOME=/tmp/corepack-home npm run build — 通过。本沙箱中默认 corepack 缓存 /home/github-runner/.cache/node/corepack/v1 不可写,因此使用可写的 COREPACK_HOME;没有跳过任何检查。
  • COREPACK_HOME=/tmp/corepack-home npm run typecheck — 通过。
  • COREPACK_HOME=/tmp/corepack-home npm run lint — 通过。
  • cd packages/cli && npx vitest run src/memory-import/service.test.ts src/memory-import/source.test.ts src/commands/memory.test.ts src/serve/memory-import.test.ts — 93 通过 / 4 个文件。
  • R1-37 修复前探针 — 未修改代码上,150 篇已有文档产生 partial 和 32 KiB 上下文原因。
  • R1-37 变异探针 — 否定有界索引的跳过分支后新测试变红;恢复后转绿(恢复后的运行已包含在上述 93 个测试中)。
  • R1-37 相关证据变异探针 — 删除 !entry.indexOnly 抛错(从而静默丢弃超限的相关证据)会使现有 “sends metadata...” 测试变红;恢复后转绿。
## Deferred non-Critical feedback

Critical-only mode is active: 5 change-producing rounds are complete. The workflow excluded the non-Critical feedback below from this round's actionable sections; the items remain open for human follow-up. Maintainer feedback is deferred only after its author has used 2 regular feedback batches in this window's Critical-only tail; authors at that budget, if any, are named below. (@qwen-code /retry starts a fresh counting window.)

中文说明

已进入仅处理 Critical 的模式:已完成 5 个产生改动的轮次。本轮可执行区域已排除下方非 Critical 反馈;这些条目保持开放,留待人工跟进。维护者反馈仅在其本人于本窗口 Critical-only 阶段已使用 2 批常规反馈预算后才会延后;达到预算的作者(如有)在下方点名。(评论 @qwen-code /retry 可开启新的计数窗口。)

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。

🧵 Resolved all 8 selected review thread(s). · 已关闭全部选中的 8 条评审线程。

Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。


🧠 Handled by Qwen Code · model/模型 deepseek-v4.1-flash-1017 · CLI 0.25.3

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

📊 Takeover milestone — round 15/100, in the current window. Census: 4 pushed fix(es), 0 no-change review(s), 1 timeout(s), 2 rejected attempt(s), 0 deliberate stop(s) under instruction (deferred to a human), 8 other round(s) (crash / model error / gate error / infra), 6 base update(s).

This many rounds deserves a human look. Options: keep going (fine — nothing changes), split or reduce the PR if rounds keep accumulating, or release takeover (remove the autofix/takeover label or comment @qwen-code /takeover stop). Management continues unchanged unless you act.

中文说明

📊 接管里程碑 —— 第 15/100 轮(当前窗口)。统计:推送修复 4 次、审阅无需改动 0 次、超时 1 次、验证拒绝 2 次、按指示有意停止(移交人工)0 次、其他轮次(崩溃/模型错误/门错误/infra)8 次、base 更新 6 次。

轮次到这个量值得人工看一眼。可选:继续(无需操作);若轮次持续累积,考虑拆分或缩减 PR;或释放接管(移除 autofix/takeover 标签或评论 @qwen-code /takeover stop)。不操作则托管照常继续。

@callmeYe
callmeYe dismissed stale reviews from qwen-code-review-bot, qwen-code-review-bot, qwen-code-review-bot, qwen-code-review-bot, qwen-code-review-bot, and qwen-code-review-bot October 20, 2026 10:06

Stale at 3db4352. Criticals in the review bodies and inline ledgers have been fixed and rechecked, including R1-37 bounded context (29fb, independent red/green) and final R2-3 native capacity (46b, three actual-service cases). Earlier ownership, recovery, auth, parser, publication and Git-cache fixes remain intact after the normal main merge. All 67 threads are resolved. Remaining Suggestion dispositions are individually linked as fixed/deferred under the repository Critical-only-after-five-rounds rule in comment6188530836. No live human CHANGES_REQUESTED or approval exists. This removes obsolete bot blocking state; it does not approve the PR or bypass current CI/maintainer approval.

@wenshao

wenshao commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator

✅ Local maintainer verification (round 2): merge-ready — 222/222 aggregate checks

Verified head 3db4352af6448f38b1b01cd387420e9771f2421f against base ea7078a565ccfc035afd6eab58c67927946baef2 (the PR's merge-base; the diff leaves pnpm-lock.yaml and every package.json untouched, so the two trees differ only by this PR). Aggregate: 35 CLI harness assertions + 13 core/new-commit assertions + 28 real-daemon assertions + 97 CLI unit-gate tests + 48 core unit-gate tests + 1 mutation kill = 222 pass, 0 fail. verdict.txt: merge-ready.

中文摘要

结论:merge-ready(222/222)。 上一轮(head e323e63d,2026-10-18)标为“未覆盖”的 HTTP daemon 路由、取消、强制杀进程后的租约恢复、linked worktree/独立 clone 隔离,本轮全部在真实构建的双树上跑通;同时复测了上一轮的 12 个 CLI 单元并覆盖了 head 之后新增的 3 个生产提交。

上一轮 2 项非阻塞观察的复测: ① 不受信任工作区的拒绝已变成单行错误(不再打印 yargs 帮助和堆栈)——已修复;② CLI 预览仍不读取未被引用的 rollout summary(HTTP 预览会给出 warning)——仍然成立,属有意的本地读取限制。

本轮新证据: 真实 daemon 只在 head 上宣告 memory_import_v1(base 158→head 158 项中仅多这一项);HTTP 预览/提交/状态/来源读取都在“可信非主工作区”上完成,主工作区零写入;未信任工作区返回 403 untrusted_workspace、未知工作区返回 400,均不回退主运行时;取消后零发布;SIGKILL 后在 118 秒租约到期后自动恢复为 completed,且每个文档只发布一次。

head 之后的新提交: 原生索引容量优先(46b716e)在真实构建上承重——base 在“长原生链接 + 大量短导入条目”下丢弃原生条目(12→11、长链接被丢),head 全部保留;仓库身份缓存重校验(03663c6)在祖先出现 .git 与项目软链移动两种场景下都返回新身份。

未覆盖: 成熟记忆库的 32 KiB existing-context 端到端探针(本轮 rig 中扫描器未把造出的原生文档送入请求,故只由 PR 自带单测覆盖);“失败项重试”无法稳定注入(500 被 workspace worker 的有界重试吸收);typecheck/lint/全仓套件、Windows/Linux、TUI、生产计费。

Previous-finding status (round 1 → this head)

Round 1 (comment of 2026-10-18, head e323e63d) was merge-ready with two non-blocking observations. Both re-measured here on freshly rebuilt arms:

# Round-1 observation Status at 3db4352a Evidence
P1 Untrusted-workspace rejection printed yargs help + a stack trace before the correct error Fixed — one stderr line, no Options: help, no stack C4 (stderrLines=1)
P2 CLI preview never reads an unreferenced rollout_summaries/*.md, so the “unreferenced resource excluded” warning only exists on the HTTP path Stands — CLI warnings=[], HTTP preview warns Unreferenced supporting resource excluded: rollout_summaries/unreferenced.md C1 vs H1

Round-1 “Not covered” items that this round now covers: HTTP routes, cancellation, SIGKILL lease recovery, linked-worktree sharing, independent-clone isolation.

Central claim and A/B

Claim re-measured at the new head: the build exposes memory import codex, previews offline with no model traffic or import state, consolidates only the explicitly selected sections into native project memory, and never falls back to the primary runtime.

Both trees were installed with the pinned pnpm and fully built (npm run build, exit 0 on both); every cell drove each tree's own packages/cli/dist/index.js or its own serve daemon, with an isolated HOME/QWEN_HOME outside any Git checkout, a trusted scratch workspace, and a loopback OpenAI-compatible peer recording raw request bodies.

Cell Head 3db4352a Base ea7078a5
memory import codex --source <fixture> --dry-run --json exit 0; version:1, kind:"preview", 5 sections, 4 groups; alpha group joins the referenced rollout section exit 1; yargs unknown-command
Preview side effects 0 model calls; 0 imported documents command absent
Scoped apply (2 alpha sections, project scope) completed; both added; exactly 1 model batch; wire has ALPHA_MARKER+ALPHA_DETAIL_MARKER, no BETA/MIXED/USER/UNREFERENCED not reachable
Published native memory 2 documents under imported/<hash>/0.md; no unselected marker not reachable
Same idempotency key same batch id; 0 calls; 0 new documents not reachable
New key, same selection both duplicate; 0 calls; 0 new documents not reachable
Edit an imported doc, resubmit edited item conflict, sibling duplicate; edit preserved; exit 1; 0 calls not reachable
12 selectable sections preview estimates 2 calls; wire batches [8, 4]; all 12 skipped under a skip response not reachable
Untrusted workspace apply rejected before source/model (Memory import requires a trusted workspace…); 0 calls not reachable
Bare-mode preview / confirmed import preview returns the versioned envelope; confirmed import rejected unavailable in bare or safe mode not reachable
Daemon capability features[] contains memory_import_v1 (158 features) absent (157 features)
HTTP preview on a trusted non-primary workspace 200; preview envelope; warns about the unreferenced resource; 0 calls route absent
HTTP submit → status → retained source 202 → completed (added,added); source fetch returns the selected text; unselected section refused; 2 docs published; wire only alpha route absent
Untrusted / unknown destination 403 untrusted_workspace / 400 workspace_mismatch; 0 calls; primary untouched route absent
Cancel during a slow consolidation 200 → cancelled; 0 documents published route absent
SIGKILL before publication, restart recovered to completed after 118 s lease; each document published once route absent
Linked worktree / independent clone linked shares the imported identity; clone gets a different identity not applicable (feature absent)

Screenshots: 01-cli-ab-controls.png (CLI A/B and control cells, fresh run), 02-new-commit-probes.png (index-capacity A/B, identity revalidation, worktree/clone, fresh run), 03-http-daemon-scoping.png (recorded daemon run: capability A/B, scoping, cancel, 118 s recovery).

New production commits since the previously verified head

e323e63d..3db4352a contains a merge of main plus three PR-authored fixes. Each was probed on the real build:

Probe Head Base Result
Native index capacity (46b716e): 1 long native link + 201 short imports (29,688 chars) native link kept (167 lines) native link dropped (183 lines) fix is load-bearing
12 long native links + 20 imports (26,159 chars) all 12 natives kept 11 kept load-bearing
Nearly full budget (26,752 chars) long native kept, output 24,984 ≤ 25,000 long native dropped, 24,479 load-bearing
Oversized metadata index (77,082 chars) reduced to 24,836 ≤ 25,000 n/a bounded
Repository identity revalidation (03663c6): ancestor becomes a repo / project symlink moves new identity in both cases module does not exist in base (new file in this PR) fix responds to both changes
Linked worktree vs independent clone shared vs distinct imported identity n/a matches the claim

Gates

Gate Head 3db4352a Base ea7078a5
CLI: src/memory-import, commands/memory.test.ts, serve/memory-import.test.ts 97 passed, 0 failed (5 files) command/route absent
Core: memory/import-model, memory/import-paths, memory/indexer 48 passed, 0 failed (3 files) module set differs
Mutation M1: force assembleIndex(..., 0) so imported entries lose the native priority boundary killed — indexer.test.ts 1 failed / 34 passed; source restored byte-identically (sha256sum -c OK) and re-run green n/a

Findings

No blocking finding introduced by this PR. Non-blocking observations only:

  1. An unselected section's retained-source endpoint answers 400 memory_import_rejected rather than 404 (H1). Fail-closed either way; only the status class differs from a missing batch.
  2. The 32 KiB existing-context bounding (29fb4384) was not measured end-to-end in this rig: a matured native store planted on disk did not surface as existing entries through the CLI scan, so the model request carried existing: []. The PR's own new test (bounds the metadata index when a mature memory store exceeds the context budget) passed in the CLI gate, but I am not presenting that as behavioural proof from this round.

Not covered

  • Matured-store 32 KiB context bounding and the oversized-relevant-evidence fail-closed path end-to-end (rig limitation above).
  • A durable failed-item + POST /retry cycle: an injected provider 500 was absorbed by the workspace worker's bounded retry, so the batch completed instead of producing a failed item. The retry endpoint itself answered 202 and the cancelled/failed bookkeeping was not independently exercised.
  • Typecheck, lint, formatting, the full repository suite, Windows/Linux, any TUI surface, and production provider billing. CI status is not assessed here; this evidence is local and advisory.

Methodology

Two detached worktrees at the verified head and the merge-base, each installed with the pinned pnpm and fully built (exit 0 both). harness/cli-cells.mjs drives each tree's built CLI against a synthetic Codex source set (two projects, an ambiguous section, a user-preference summary, one referenced and one unreferenced rollout summary), a scratch trusted workspace, and a loopback OpenAI-compatible peer that records raw request bodies and answers schema queries with the product's respond_in_schema tool shape. harness/http-cells.mjs boots each tree's real qwen serve daemon on an ephemeral port and exercises capability, workspace registration, preview/submit/status/source, untrusted/unknown refusals, cancellation, and a SIGKILL/restart recovery. harness/core-cells.mjs drives the built core modules for the index-capacity and repository-identity fixes and computes both worktree identities. Raw logs, JSON reports, harnesses and captures live under tmp/pr14281-verify-r2/.


Verified locally by @wenshao with Qwen Code. Artifacts: tmp/pr14281-verify-r2/ (report.md, verdict.txt, assertions.json, harness/, logs/, evidence/).

Evidence captures

  1. CLI A/B and control cells, fresh run — base rejects, head previews, scoped apply, idempotency, edit-conflict, [8, 4] packing: 01-cli-ab-controls.png
  2. New-commit probes, fresh run — native index capacity base-vs-head, identity revalidation, worktree/clone: 02-new-commit-probes.png
  3. Real daemon run (recorded log) — capability A/B, workspace scoping, untrusted/unknown refusal, cancel, 118 s SIGKILL recovery: 03-http-daemon-scoping.png

01-cli-ab-controls

02-new-commit-probes

03-http-daemon-scoping

Merge state at posting: DIRTY — the PR currently conflicts with main (f4f3749f). This report verifies head 3db4352a against its own merge-base ea7078a5; a re-sync is required before merge, and the merge resolution itself is not covered by this round.

@wenshao

wenshao commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /resolve

@callmeYe
callmeYe enabled auto-merge (squash) October 20, 2026 15:13

@qwen-code-review-bot qwen-code-review-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Downgraded from Approve to Comment: PR head advanced during review: reviewed 3db4352, PR is now at d95f523 (+34 unreviewed commit(s) touching 197 file(s)). Partially reviewed — gaps disclosed.

Not explored to full depth (tool budget reached): "agent reverse-audit (round 1)": I did not read docs/design/external-memory-import-api.md 's failure-mapping table, so I could not confirm whether "managed memory unavailable" is a declared te…; "agent reverse-audit (round 1)": I did not measure how many runtimes a hosted daemon has registered at createServeApp time, nor how many batches typically survive a restart, so finding 3's N×…; "agent reverse-audit (round 1)": my assigned range ends mid-file at diff line 5498, inside packages/core/src/core/baseLlmClient.test.ts ; any further hunks of that file lie in the next chunk a…; chunk 16: could not execute the suite to confirm the six tests pass — the review worktree has no node_modules , so verification is static-only (notably the untested-at-r…; chunk 5: did not execute packages/cli vitest to confirm the two open questions above empirically (would require npm run build for the workspace dist/ prerequisites…, and 1 more.

Not reviewed: reverse audit — stopped before round 3 by the review time budget.

Deferred under the convergence posture (round 6, not a blocker) — recorded, not requested in this round; 1 Critical(s) among them are deferred by their axes — fails-closed on new surface, where no wrong result is certified and the merge base had neither the surface nor the defect — and remain follow-up work recorded in the findings artifact:

  • packages/cli/src/memory-import/service.ts:827 — [review] Critical [fails-closed] [new-surface] The new import snapshot gate treats the scan's *informational* ref_collision status — emitted whenever two project memory roots contain the sam…
  • docs/design/external-memory-import-api.md:34 — [review] The API contract enumerates every source limit but omits the enforced per-submission selection cap of 2,048 items, while affirmatively telling hosts they may select *every* selectable …
  • packages/cli/src/acp-integration/acpAgent.ts:10819 — [review] The new agent-side ext-method handler — including its fail-closed availability/trust gate — has no test anywhere in the tree.
  • packages/cli/src/cli.ts:431 — [review] The bootstrap memory help hardcodes the subcommand name and its describe text that actually live in packages/cli/src/commands/memory.ts , and — unlike the mcp equivalent — nothing pins them together…
  • packages/cli/src/cli.ts:506 — [review] runMemoryFastPath is a near-verbatim copy of runMcpFastPath — the parser construction, the fail handler, .exitProcess(false) and the hasFlag(argv.slice(2), '--help', '-h') help intercept are …
  • packages/managed-agent-server/src/test/java/com/alibaba/qwen/code/managedagent/HostedMcpResultDeliveryIT.java:39 (+2 locations) — [review] The PR’s own scope statement is falsified by three unrelated changes in the diff, and the head commit…
  • packages/cli/src/commands/memory.ts:151 — [review] The API doc shipped in this diff promises retry-by-idempotency-key, but the CLI reaches service.retry() only after a fresh submit() of the *current* local source, so any drift in the Co…
  • packages/cli/src/memory-import/service.ts:359 — [review] The documented crash-recovery guarantee rests entirely on one untested constant: no committed test exercises stale-lease takeover, so the only evidence for it is a manual SIGKILL prob…
  • packages/core/src/memory/import-paths.ts:134 — [review] New exported core symbol memoryImportRepositoryIdentity has zero consumers anywhere in the tracked tree — it is a dead public API on the core package.
  • packages/cli/src/serve/memory-import.test.ts:86 — [review] The runtime double never sets routeFileSystemFactory.sshWorkspace , so the SSH trust-boundary refusal inside active() has no test anywhere in the repository.
  • packages/core/src/memory/import-budget.ts:66 — [review] The estimate charges the *ceiling* prompt and context allowance per batch (74,752 fixed), so exceedsBudget trips at 7 batches regardless of content, while the same object reports li…
  • packages/core/src/memory/import-paths.ts:12 — [review] readMetadata hand-rolls a git-metadata read whose flag expression silently collapses to a plain symlink-following O_RDONLY open on Windows, instead of reusing the hardened readers t…
  • packages/core/src/memory/structured-scan.ts:466 — [review] The new cap-priority tie-break demotes imported documents only in user scope, but this same PR is what first puts imported documents into the *project*-scope scan, where the predi…
  • packages/core/src/memory/paths.ts:222 — [review] The warn-once latch is a process-global boolean guarding a **per-repository** failure, so the first broken repository consumes the diagnostic for every other repository in the same process.
  • packages/cli/src/memory-import/local.ts:20 — [review] A locally generated sessionId is passed to loadCliConfig without the sessionIdGenerated: true flag that marks an id as freshly generated, so it takes the caller-supplied-id occupan…
  • packages/cli/src/commands/memory.ts:60 — [review] The option's own describe — the text qwen memory import codex --help prints — says the value is a JSON array, while the handler treats it as a file path, contradicting the closing hint i…
  • packages/cli/src/memory-import/service.test.ts:508 — [review] The test is named "cancels before publication **and supports explicit retry**", but its body never calls retry() — it asserts only status === 'cancelled' and that nothing was…
  • packages/cli/src/memory-import/service.test.ts:1030 — [review] The budget-exhaustion test passes vacuously when zero model calls are made, so it cannot distinguish "stopped at the limit" from "never started".
  • packages/cli/src/memory-import/service.test.ts:948 — [review] The too-large submit's result is discarded, so the first half of the comment's claim — a processed source skips the model and its context — is asserted nowhere.
  • packages/cli/src/memory-import/service.ts:206 — [review] selectImportContext 's relevance test tokenizes on runs of ≥4 \p{L}\p{N}_- characters, which for CJK text yields whole punctuation-delimited runs instead of words, so a Chinese mem…
  • …and 58 more (see the run report)
中文说明

⚠️ 已从批准降级为评论:PR head advanced during review: reviewed 3db4352, PR is now at d95f523 (+34 unreviewed commit(s) touching 197 file(s))。 仅完成部分审查,审查缺口已披露。

未探索到全部深度(达到工具调用预算):"agent reverse-audit (round 1)":I did not read docs/design/external-memory-import-api.md 's failure-mapping table, so I could not confirm whether "managed memory unavailable" is a declared te…;"agent reverse-audit (round 1)":I did not measure how many runtimes a hosted daemon has registered at createServeApp time, nor how many batches typically survive a restart, so finding 3's N×…;"agent reverse-audit (round 1)":my assigned range ends mid-file at diff line 5498, inside packages/core/src/core/baseLlmClient.test.ts ; any further hunks of that file lie in the next chunk a…;chunk 16:could not execute the suite to confirm the six tests pass — the review worktree has no node_modules , so verification is static-only (notably the untested-at-r…;chunk 5:did not execute packages/cli vitest to confirm the two open questions above empirically (would require npm run build for the workspace dist/ prerequisites…,另有 1 条。

未审查:反向审计——评审时间预算不足,未能开始第 3 轮。

收敛姿态下延后(第 6 轮,非阻断)——已记录,本轮不要求修改;其中 1 条 Critical 按其失败方向与对照基线延后——fails-closed 且 new-surface:未认证任何错误结果,且 merge base 既无该功能面也无该缺陷——作为后续工作记录在 findings 工件中:共 78 条(原文未翻译,列表见上方英文部分)。

— qwen3.8-max via Qwen Code /review (v0.25.3)

@qwen-code-review-bot

Copy link
Copy Markdown
Collaborator

⏳ Historical-head review — head moved to d95f523bbcbc80ab8b502c324514c8f58b8c0126 while this review was in flight (past the salvage threshold), so the run finished and posted against the head it reviewed: 3db4352af6448f38b1b01cd387420e9771f2421f (#10110). The next automatic review covers the delta from that anchor. Full log in the workflow run.

中文说明

⏳ 历史 head 评审 —— 本次评审进行中 head 移动到了 d95f523bbcbc80ab8b502c324514c8f58b8c0126(已过 salvage 阈值),因此评审跑完并针对其实际评审的 head 3db4352af6448f38b1b01cd387420e9771f2421f 发布(#10110)。下一次自动评审将从该锚点起评审增量。完整日志见 workflow 运行。

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

Qwen Code did not run conflict resolution for this request.

PR #14281 does not currently have merge conflicts with main.

This branch has not been deployed

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

Labels

autofix/takeover Summon the autofix loop to manage this PR (remove to release; needs triage+)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants