Skip to content

feat(memory): bundle Mem0 with the main CLI - #12891

Merged
yiliang114 merged 15 commits into
mainfrom
codex/12596-bundled-mem0
Sep 30, 2026
Merged

yiliang114 merged 15 commits into
mainfrom
codex/12596-bundled-mem0

Conversation

@yiliang114

@yiliang114 yiliang114 commented Sep 28, 2026 •

Copy link
Copy Markdown
Collaborator

What this PR does

This PR adds an opt-in Mem0 connection to the main Qwen Code CLI. Configure memory.mem0 with an endpoint and envKey, and define the credential value in the top-level settings env field, just like model providers. Qwen registers the MCP server automatically, supplies a stable user/repository scope, and exposes search without requiring a separately published extension package, a shell export, or a hand-authored MCP configuration. Shell exports and .env files remain supported credential sources.

envKey defaults to MEM0_API_KEY. The legacy credentialEnv alias remains supported; different names supplied together produce an explicit configuration error. The existing environment loader and source precedence are reused, and generated binding configuration contains only the variable reference. Credentials stored in settings JSON are plaintext and should stay in user settings, outside version control and shared reports.

Search is read-only by default. Explicitly enabled writes in interactive CLI sessions automatically receive the existing exact-content confirmation Hook, including in YOLO mode. Noninteractive/ACP sessions and sessions with Hooks disabled retain search only. Workspace settings cannot enable or redirect the operator's connection.

The configuration selects a bounded protocol contract: mem0-v2 by default, mem0-v3, or the pinned mem0-oss-2026-08 contract. Existing preset IDs remain accepted. The new V2 contract sends limit; the historical PolarDB preset keeps top_k until live verification establishes whether a migration is safe. This does not claim compatibility with every service called Mem0 V2.

Why it's needed

The existing direct integration is private, while the separate extension delivery path requires additional setup. Neither provides the intended normal-install experience. This change reuses the existing adapters and confirmation behavior, ships the runtime with the main CLI, and removes a standalone npm publication from the critical path.

It also fills the missing OSS interactive write coverage through the new automatic configuration path.

Reviewer Test Plan

How to verify

  1. In user settings, configure memory.mem0.baseUrl and envKey (default MEM0_API_KEY), and define that variable's value in the top-level env object. Start the bundled CLI in a trusted local repository without exporting the Mem0 key. The external-context MCP server should appear without separate registration, and only context_search should be available by default. Legacy credentialEnv still works; conflicting aliases must produce an error.
  2. Search through the configured provider. Confirm the selected protocol's path, authentication and request fields. Restart from a Git subdirectory: the default scope should remain the same. Set an explicit supported scope to reuse existing memory.
  3. Enable writes and restart the interactive CLI. Approving exact content should send one infer: false write and render the provider result; cancellation should send no write. YOLO should still show exact-content confirmation.
  4. Try to override the endpoint through workspace settings, including replacing the parent memory object: the operator binding should remain unchanged. Bare/safe/untrusted/provisional/SSH-workspace sessions should not activate the local binding.
  5. Inspect the prepared npm/standalone distribution: both Mem0 runtime files must be present. The runtime should still initialize and search when copied outside the repository without node_modules or NODE_PATH.

Evidence (Before & After)

Before: a normal installation required a separately configured integration, explicit MCP registration and a manually installed write-confirmation Hook.

After: the real interactive CLI originally passed eight controlled-model/provider scenarios without retries. The latest credential follow-up passed 19 focused unit tests and reverified the three OSS cases using a custom envKey whose value exists only in user settings.env, with no inherited/default credential, manual MCP, or user Hook. Approval, cancellation with zero HTTP writes, and YOLO content confirmation passed without retries. The five unchanged V3 cases were not repeated for the credential-only follow-up. Independent SDK/process checks verified default read-only discovery, V2 requests and stable restart scope; a repository-independent copy of the two runtime files also passed. Separate PR comments record commands and observed results.

An additional actual tmux/Ink session showed the retrieved memory on screen, followed by ordinary MCP permission and the exact-content confirmation. Esc returned to the input prompt; the controlled service received one authenticated search and zero writes. The verification comment includes terminal excerpts and the initial controlled-model fixture failure that was corrected without changing source.

Subsequent actual kimi-k3 + PolarDB-hosted Mem0 tmux acceptance observed one approved infer: false write (HTTP 200) and same-ID retrieval after restart (HTTP 200, limit: 5). The instance returns direct-import content as a serialized single-user-message array; the adapter now unwraps only the verified PolarDB-style infer: false shape. A read-only real-service follow-up confirmed the exact two-line plain text in both the visible CLI and the model's tool response, with zero additional writes. The initial automation failures and successful read-only evidence audit are preserved in the live acceptance report, not relabeled green. Connectivity was through the existing restricted SSH/private-instance tunnel, not public-direct access. One acceptance record remains in its isolated scope; deletion was not tested. A final direct-settings session used the real model endpoint without an evidence relay, manual MCP, or user Hook.

Tested on

OS Status
🍏 macOS ✅ local build, targeted tests, controlled-provider/package acceptance, and actual PolarDB write/restart retrieval through a private tunnel
🪟 Windows ⚠️ not tested locally
🐧 Linux ⚠️ not tested locally

Environment (optional)

Local Node.js runtime and built main CLI bundle; sandbox disabled for PTY testing. Earlier controlled-provider checks were followed by actual kimi-k3 and a real PolarDB-hosted Mem0 instance through the existing restricted SSH tunnel. Exactly one isolated acceptance record was written and retained; no pre-existing record was modified or deleted.

Risk & Scope

  • Main risk or tradeoff: protocol contracts differ in auth, paths, scope and response semantics. The default V2 contract is PolarDB-style, not universal V2 compatibility. Plain non-loopback HTTP requires explicit opt-in and sends credentials unencrypted.
  • Not validated / out of scope: public-direct PolarDB connectivity, general compatibility across PolarDB/Hologres/OSS/Platform versions (only the tested real PolarDB-style V2 instance is established), native Windows/Linux execution, automatic recall, arbitrary dialects, and complete consolidation/removal of both existing integration delivery paths. A remote-aligned review-gate / separate pr-ui-verify skill was unavailable locally; existing consolidated source review and PTY/process acceptance were reused, not represented as those automated gates passing.
  • Breaking changes / migration notes: the feature is absent unless configured. Existing advanced integrations remain supported. An explicitly configured external-context MCP server conflicts with the built-in setting; do not enable both. The built-in binding takes precedence over the same project .mcp.json name. Moving the repository or using another checkout changes the default scope; use explicit scope to retain existing memory. Async accepted is not completed persistence, and ambiguous writes must not automatically retry.

Design: English · 简体中文. Both versions cover the same behavior, limits and acceptance gates.

Linked Issues

Addresses #12596 (main-CLI delivery; live compatibility and full advanced-path consolidation remain open).

Fixes #9964.

中文说明

本 PR 的改动

此 PR 为 Qwen Code 主 CLI 增加主动启用的 Mem0 连接。通过 memory.mem0 配置地址和 envKey,并像 modelProviders 一样在顶层 settings env 定义凭证值后,Qwen 自动注册 MCP 服务并提供稳定的用户/仓库 scope,无需单独发布扩展包、shell export 或手写 MCP 配置即可使用搜索。shell export 和 .env 仍是受支持的凭证来源。

envKey 默认值为 MEM0_API_KEY。保留历史 credentialEnv 别名,两者同时指定不同变量名会明确报错。复用现有环境加载器和来源优先级,生成的绑定配置只含变量引用。JSON 中的凭证是明文,应保存在用户设置中,不提交到版本库或公开到报告。

默认只读。在交互 CLI 中显式启用写入后,自动接入现有精确内容确认 Hook,YOLO 模式也会确认。非交互/ACP 和禁用 Hooks 的会话仅保留搜索。工作区设置不能启用或重定向操作者的连接。

配置选择有边界的完整协议合同:默认 mem0-v2,也可选择 mem0-v3 或固定的 mem0-oss-2026-08 合同。旧 preset ID 保留。新 V2 合同发送 limit;历史 PolarDB preset 继续发送 top_k,等待实测决定安全迁移方式。不承诺兼容所有标称 Mem0 V2 的服务。

为什么需要

现有直连集成是 private,单独扩展的交付路径又需要额外设置,都不符合普通安装即可使用的目标。本改动复用已有适配器和确认行为,把运行文件随主 CLI 交付,不再把单独 npm 发布作为前置条件。

同时,通过新的自动配置路径补齐 OSS 交互写入覆盖。

Reviewer 验证计划

验证方式

  1. 在用户设置配置 memory.mem0.baseUrl 和 envKey(默认 MEM0_API_KEY),在顶层 env 对象定义该变量的值;不 export Mem0 密钥,在可信本地仓库启动打包 CLI。应自动出现 external-context MCP 服务,默认仅提供 context_search。旧 credentialEnv 仍可使用,冲突别名必须报错。
  2. 检索并核对所选合同的路径、认证、请求字段。从 Git 子目录重启,默认 scope 应保持一致;也可显式指定受支持 scope 复用已有记忆。
  3. 启用写入并重启交互 CLI。批准精确内容应发送一次 infer: false 写入并呈现结果;取消不能发送写入;YOLO 仍要确认原文。
  4. 通过工作区设置覆盖地址,包括替换父级 memory 对象,操作者绑定都应保持不变。bare/safe/不可信/临时/SSH 工作区会话不应激活本地绑定。
  5. 检查 npm/standalone 分发包含两个 Mem0 运行文件。仅复制它们到仓库外、无 node_modules/NODE_PATH 时,仍应能初始化并搜索。

前后证据

此前:普通安装需要单独配置集成、手工注册 MCP 和安装写入确认 Hook。

之后:原始实现的真实交互 CLI 八个受控模型/服务场景无重试全部通过。最新凭证补齐通过 19 个定向单测,三个 OSS 场景复验使用自定义 envKey,其值只存在用户 settings.env,没有继承/默认凭证、手工 MCP 或用户 Hook;批准、取消零 HTTP 写入和 YOLO 内容确认均无重试通过。此次凭证改动未重复运行五个未改动 V3 场景。独立 SDK/进程检查验证默认只读、V2 请求和重启 scope;仓库外双文件拷贝也通过。另附 PR 评论记录命令及实际结果。

另外一个实际 tmux/Ink 会话在屏幕显示检索到的记忆,再出现普通 MCP 许可和精确内容确认。Esc 取消后回到输入框;受控服务只收到一次正确认证的搜索,零次写入。验收评论包含实际终端摘录及首轮受控模型 fixture 失败说明,该修正没有改动源码。

后续已使用真实 kimi-k3 与 PolarDB-hosted Mem0 完成实际 tmux 验收:批准后仅发送一次 infer: false 写入,HTTP 200;重启后通过 limit: 5 搜索取回同一真实 ID,HTTP 200。实例把直接写入内容返回为单 user-message 数组的 JSON 字符串,适配器现只解包已验证的 PolarDB 风格 infer: false 形状;随后真实服务只读复验确认,CLI 可见结果与模型 tool response 都保留完整两行纯文本,额外写入为零。真实验收报告 保留首轮自动脚本失败与后续只读证据审核通过的区别,没有把失败重标为成功。网络经现有受限 SSH 私网隧道,不是公网直连;唯一验收记录仍保留,未测试删除。最终直接 settings 会话使用真实模型地址,无证据 relay、手工 MCP 或用户 Hook。

本地测试平台

OS 状态
🍏 macOS ✅ 本地构建、定向测试、受控服务/打包验收,以及真实 PolarDB 私网隧道下的写入和重启检索
🪟 Windows ⚠️ 未本地验证
🐧 Linux ⚠️ 未本地验证

环境

本地 Node.js、构建后的主 CLI,PTY 测试关闭 sandbox。此前受控服务检查之后,使用真实 kimi-k3 与现有受限 SSH 隧道连接真实 PolarDB-hosted Mem0;仅新增并保留一条独立 scope 的验收记录,没有修改或删除既有记忆。

风险与范围

  • 主要风险/取舍:不同合同的认证、路径、scope 和响应语义不同。默认 V2 是 PolarDB 风格,并非通用 V2 兼容。非回环纯 HTTP 必须显式开启,且凭证不加密传输。
  • 未验证/范围外:PolarDB 公网直连、跨 PolarDB/Hologres/OSS/Platform 版本的通用兼容性(仅已测试的真实 PolarDB 风格 V2 实例得到验证)、Windows/Linux 原生运行、自动召回、任意 dialect,以及两套已有集成交付路径的彻底合并/删除。本地没有可用的 remote-aligned review-gate/独立 pr-ui-verify skill,复用此前完整源码复核与 PTY/进程验收,不声称这些自动 gate 通过。
  • 兼容/迁移:未配置时不开启,保留高级集成。显式配置的同名 external-context MCP 服务会冲突,不应同时开启;内置绑定优先于项目 .mcp.json 同名项。移动仓库或使用其他 checkout 会改变默认 scope,复用旧记忆需显式 scope。异步 accepted 不代表持久化完成,结果不确定的写入不可自动重试。

设计:English · 简体中文。两个版本的行为、限制和验收门槛完整同步。

关联 Issue

关联 #12596(主 CLI 交付;真实兼容性和高级路径完整收敛仍待处理)。

修复 #9964。

@github-actions github-actions Bot added the review/self-reported The linked issue was opened by the PR author (self-reported) label Sep 28, 2026
@yiliang114

Copy link
Copy Markdown
Collaborator Author

Verification report — controlled providers

The local implementation passed the checks below. The checked working-tree source is now committed as 46d1101f5f70. These results are from macOS, not a full CI matrix or live PolarDB acceptance.

Check Observed result
Main CLI dependency build, final CLI rebuild and bundle Passed
CLI, direct integration and integration-test TypeScript checks Passed
Changed-file ESLint and Prettier check Passed
Direct integration config/provider/MCP/write-confirmation tests 128 passed
CLI config/settings/Mem0-settings/Hook settings/Hook command tests 732 passed across the focused files, including targeted reruns after fixture correction
Package-asset checks 46 passed
Installer/standalone suite 131 passed, 11 platform-specific skipped; serial rerun with 15-second test budget
Interactive real CLI + controlled OpenAI model + controlled memory HTTP service 8 passed, no retries
SDK stdio + actual bundled runtime/Hook Passed
Only two runtime files copied outside the repository, no node_modules/NODE_PATH Initialize, list tools, search and YOLO content confirmation passed
Actual main-package preparation and npm dry-run Includes both runtime files; unpacked size 111,583,197 bytes

Reproduction commands

npm run build -- --cli-only
DEV=true npm run bundle
npm --prefix integrations/external-context run typecheck
npm --prefix packages/cli run typecheck
npm run typecheck:integration

# Run unit tests from their package directories:
cd integrations/external-context
npx vitest run src/config.test.ts src/providers.test.ts src/mcp.test.ts src/write-confirmation.test.ts
cd ../../packages/cli
npx vitest run src/config/mem0-settings.test.ts src/config/config.test.ts src/config/settings.test.ts src/config/hook-settings.test.ts src/ui/commands/hooksCommand.test.ts
cd ../..

# These installer fixtures temporarily replace dist: do not run them alongside bundling.
npx vitest run scripts/tests/package-assets.test.js
npx vitest run scripts/tests/install-script.test.js --testTimeout=15000
DEV=true npm run bundle
QWEN_SANDBOX=false npx vitest run --root ./integration-tests interactive/external-context-mem0-write.test.ts --retry=0

Observed user-visible behavior

The five existing Platform V3 scenarios still passed. The three OSS scenarios use user memory.mem0 settings rather than a manual MCP or Hook configuration: default approval requires the ordinary MCP permission followed by exact-content confirmation; cancellation sends no HTTP write; YOLO still asks for exact-content confirmation. Successful OSS writes hit /memories, use X-API-Key, bind user_id, send infer: false, preserve the approved content and render stored with an ID.

An independent stdio run selected default V2, sent POST /v2/memories/search with Authorization: Token and limit: 5 (no top_k), filtered invalid responses, retained at most five items and preserved the independently calculated default scope after restart from a Git subdirectory. Default tool discovery exposed only search. Explicit write enablement sent exact content to /v1/memories with infer: false.

Copying only the runtime and confirmation executable outside the repository (plus a module-type package marker) still passed initialize/list/search/Hook invocation without child node_modules or NODE_PATH. This verifies that a separate external-context npm installation is not required.

Limits and initial failures

The first Hook-command run exposed a missing getMcpServers test mock; the corrected fixture passed on rerun. The first installer run raced concurrent bundling because its fixture replaces root dist; the serial rerun passed, and the final bundle was created after fixture restoration. Actual package preparation warned about an absent optional native audio prebuild; audio capture was not tested.

No real model was used: the real CLI ran against a controlled OpenAI server. No current endpoint/credential pair was available for the real memory instance, so live authentication, slash behavior, limit handling, persistence, restart retrieval and targeted cleanup remain unverified. No real memory was read, changed or deleted. Windows/Linux native execution and the remote-aligned review gate were not run. CI state is separate from these local results.

中文验收说明

本地 macOS 构建、打包、类型检查、改动文件 lint/格式检查通过。直连集成 128 个测试、CLI 相关文件 732 个测试、产物测试 46 个通过;安装/standalone 串行复验 131 个通过、11 个平台限定用例跳过;八个交互场景无重试通过。这里不是完整 CI 矩阵,也不是实际 PolarDB 验收。

真实 CLI 使用受控 OpenAI 模型与 HTTP 服务,保留五个 V3 用例,新增三个 OSS 自动配置用例。OSS 不手工配置 MCP 或 Hook:default 先确认普通 MCP 权限再确认精确内容,取消零写入,YOLO 仍确认内容。请求实测是 /memories、X-API-Key、固定 user_id、infer: false,保留批准原文并呈现带 ID 的 stored。

独立 stdio 验收实测默认 V2 的 /v2/memories/search、Token、limit: 5(无 top_k),过滤无效记录且最多五条,从 Git 子目录重启后独立计算的默认 scope 不变。默认仅搜索,显式写入才请求 /v1/memories 和 infer: false。仅将两个运行文件及 module 声明复制到仓库外、无子进程 node_modules/NODE_PATH 时,初始化、列工具、检索和确认 Hook 仍通过,证明无需额外安装 external-context npm 包。实际主包准备和 npm dry-run 包含两个运行文件,解包大小 111,583,197 字节。

首轮 Hook 测试 mock 缺 getMcpServers,修正后通过。首轮安装测试与打包争用临时替换的 dist,串行并以 15 秒预算复验通过,最终打包在恢复后完成。缺失的可选音频原生产物给出警告,本次不验证音频。

没有使用真实模型或联系真实记忆实例。缺少当前 endpoint/凭证,因此真实认证、斜杠、limit、持久化、重启搜索和定向清理尚未验证;未读取、写入或删除真实记忆。Windows/Linux 原生运行及 remote-aligned review gate 未运行,CI 结果与本地结果分开。

@yiliang114

Copy link
Copy Markdown
Collaborator Author

Verification update — settings-defined credentials

Committed in 68fbb0a3d7bd. The checks below used the same source in the local working tree before commit; no product source changed afterward.

The Mem0 settings path now follows model-provider configuration: memory.mem0.envKey selects a variable name and the top-level user env object can define its value. A shell export is not required. credentialEnv remains a compatible alias; conflicting references fail explicitly. This reuses the existing environment loader rather than adding another credential store.

Focused checks

Check Result
CLI build (tsc --build plus package assets) Passed
Final main CLI bundle Passed
Integration-test TypeScript check Passed
Changed-file ESLint / Prettier formatting / diff whitespace check Passed
Mem0 settings unit tests 19 passed: default key, custom reference, legacy/equal aliases, conflict rejection, nonserialization, and existing scope/Hook guards
Real CLI OSS interaction with a settings-only custom credential 3 passed, 5 unchanged V3 cases skipped, 0 failed, no retries

The OSS run removes both the custom variable and default MEM0_API_KEY from the launching environment. Only the isolated user settings define the controlled credential, with no manual MCP or user Hook. Approving sends one correctly authenticated /memories request with exact content and infer: false; cancellation sends zero provider requests; YOLO still confirms exact content. Successful writes reach the model as stored with an ID and the completion marker appears in the rendered transcript. The actual CLI PTY stream and machine-readable results were retained locally.

The first name filter selected zero tests because Vitest truncated generated parameterized names; that skipped run was not counted as verification. The corrected prefix filter executed the intended three cases successfully.

Commands

# CLI package
npx vitest run src/config/mem0-settings.test.ts
npm run build

# Worktree root
npm run typecheck:integration
DEV=true npm run bundle

# integration-tests directory, with isolated QWEN_HOME and controlled credentials
QWEN_SANDBOX=false QWEN_E2E_RENDERER=ink npx vitest run interactive/external-context-mem0-write.test.ts -t 'uses two confirmations for the OSS|does not write to the OSS|still asks for content confirmation f' --retry=0

Actual tmux UI check

An additional bounded real CLI session in tmux passed with the Ink renderer and a custom credential defined only in isolated user settings. The screen showed successful context_search and its untrusted result envelope, followed by the ordinary MCP permission and exact-content confirmation. Esc cancelled the proposed write and returned to the input prompt. The controlled service received exactly one authenticated /search request (top_k: 5, fixed user scope), zero /memories writes, and the model received two requests.

Actual terminal excerpts:

✓ context_search (external-context MCP Server) {"query":"settings-only UI memory search"}
◆︎ SEARCH_UI_DONE. Retrieved memory: QWEN_MEM0_UI_SEARCH_VISIBLE: settings-only memory result

Save this exact content to the bound Mem0 repository memory?
"QWEN_MEM0_UI_EXACT [visible](https://controlled.invalid/target) **literal**\nKeep this exact repository policy."

# After Esc: returned to the rendered input prompt
Type your message or @path/to/file

The first tmux fixture incorrectly routed model responses by request index across two prompts; search rendered successfully but the fixture returned an unexpected continuation instead of proposing a write. That failed attempt was retained, not counted as a pass. Updating only the ignored controlled-model script to route from the actual search tool result allowed one bounded rerun to pass; no source or bundle change was required.

These are local macOS results with the real bundled CLI and controlled model/memory HTTP services. The prior CI results belong to the original head, not to the new commit's CI. Live PolarDB authentication, limit/slash behavior, persistence and restart retrieval, and native Windows/Linux CLI execution remain unverified. Ready for review does not mean approval, release, or live-provider acceptance.

中文验收说明

已提交为 68fbb0a3d7bd。以下验证在提交前针对同一工作树源码完成,之后没有修改产品源码。

Mem0 配置现在与 modelProviders 一致:memory.mem0.envKey 选择变量名,顶层用户 env 定义值,无需 shell export。保留 credentialEnv 兼容;两个引用不一致时明确报错,复用已有环境加载器,不新增凭证存储。

本地 CLI 构建(含 TypeScript)、最终 bundle、集成测试类型检查、改动文件 lint/格式及 diff 空白检查通过;Mem0 settings 19 个单测通过。真实 CLI 的三个 OSS 交互场景通过、五个未改动 V3 场景跳过,无失败、无重试。启动环境清除了默认和自定义 Mem0 变量,测试凭证仅存在隔离用户 settings.env,没有手工 MCP 或用户 Hook。批准后恰好写入一次,认证正确、保留原文且 infer:false;取消零请求;YOLO 仍确认具体内容。成功结果以 stored 和 ID 返回模型,最终完成标记实际出现在渲染 transcript 中,已保留真实 PTY 流和机器结果。

首次完整名称筛选因参数化测试名称截短而执行零个测试,这次 skipped 没有被算作成功;修正前缀后实际执行三个目标场景并通过。

另外补了一个实际 tmux/Ink CLI 会话:自定义凭证只来自隔离用户 settings。屏幕实际显示 context_search 成功和不可信结果 envelope,再出现普通 MCP 许可及精确内容确认,Esc 取消后回到输入框。受控服务只收到一次正确认证的 /search(top_k:5、固定用户 scope),/memories 写入为零,模型请求两次;保留了搜索、普通许可、内容确认及取消后的真实 tmux captures,上方是实际终端摘录。

首轮 tmux 脚本跨两个提示按请求序号路由模型响应,搜索可见成功,但 fixture 返回了错误的 continuation,没有发起预期写入。失败证据保留且没有算作成功;只调整 ignored 受控模型脚本,改为依据实际搜索工具结果路由,一次有界复验通过,源码和 bundle 未变化。

本次是本地 macOS 的真实打包 CLI 与受控模型/HTTP 服务,不是真实 PolarDB 验收。原有 CI 结果属于原始 head,不代表新提交 CI 已通过。真实认证、limit/斜杠行为、持久化和重启检索、Windows/Linux 原生 CLI 仍未验证。Ready for review 不代表批准、发布或真实服务验收通过。

@yiliang114
yiliang114 marked this pull request as ready for review September 28, 2026 07:38
Include upstream #12900 so Managed Agent migrations use distinct V16 and V17 versions. Preserve the bundled Mem0 implementation without feature changes.

Co-authored-by: Qwen-Coder <[email protected]>
@yiliang114

Copy link
Copy Markdown
Collaborator Author

Merged main through #12900 in dd12698 to address the SDK Java failures. Both failed jobs in the old run reported Found more than one migration with version 16: the upstream runtime-loss and Session lifecycle changes both introduced V16. This PR's Mem0 commits do not change SDK Java migrations.

Verification: the merged tree has 16 SQL migrations with 16 distinct versions; lifecycle now uses V17. One focused CLI configuration test pass exited 0: 20 passed, 482 filtered/skipped, no retries. Both affected Java jobs passed on #12900's exact head 1f1136066e26f7dbb8de54e057d6e03b8591af84 in its SDK Java run. Java/Maven are unavailable locally, so that is upstream CI evidence, not a local Java/MySQL test.

The branch was pushed normally without rewriting history or changing the Mem0 implementation. New SDK Java CI is pending; this is not a claim that the new PR head is fully green.

中文:已合入主分支 #12900 的修复并正常推送。原先两个 Java job 失败是上游两项变更都使用了 Flyway V16,并非 Mem0 改动引起;合并后生命周期迁移使用 V17,16 个 SQL 迁移版本无重复。一次聚焦配置验证通过 20 个用例,其余 482 个被过滤跳过,无重试。上游修复的对应 Java job 已通过,但本机没有 Java/Maven,没有执行本地 Java/MySQL 验证。当前 PR 新一轮 CI 仍待完成,未宣称全绿,也未重跑 UI 或真实 PolarDB 验收。

# Conflicts:
#	packages/cli/src/config/config.test.ts
@yiliang114
yiliang114 dismissed a stale review via 34898da September 28, 2026 11:18
@yiliang114

yiliang114 commented Sep 28, 2026 •

Copy link
Copy Markdown
Collaborator Author

Round 4 closeout: pushed c84059b and c9a801e to the existing PR branch. Operator Mem0 config is now selected as a whole, explicit null disables it, and a workspace MCP entry cannot hide an operator collision. The historical PolarDB preset keeps raw search content; only mem0-v2 unwraps verified direct-import text. Reserved config environment aliases and the Windows package-asset assertion are fixed. Six-preset documentation now includes linked complete English and Chinese designs.

Verification: build, typecheck, focused ESLint/Prettier, 745 CLI config tests, 51 adapter tests, and 51 package-asset tests passed. Replacing the Windows-safe needle with win32.join made both new Windows regressions fail; restoring it passed all 51 tests. Native Windows and live Mem0 were not rerun in this round. The prior private-tunnel PolarDB acceptance report remains the live evidence: #12891 (comment) . CI for c9a801e is pending; no success is claimed for unfinished jobs.

Scope: original goal remains opt-in Mem0 in the main CLI without a separate npm release. This fourth review round only addressed verified correctness/compatibility defects and their minimal regressions; no dependency, protocol family or generic MCP framework was added. Broader test matrices, Hologres/universal versions, automatic recall and generic MCP precedence redesign remain deferred. Other reviewer suggestions are not bulk-resolved.

中文:本轮已推送两条修复提交;解决配置跨层拼接与 null 禁用、工作区遮蔽操作者 MCP 冲突、旧预设返回值、Windows 环境变量别名和打包断言,并补齐双语预设设计。定向验证 847 项通过;Windows 原生和真实服务本轮未重跑,最新 CI 尚未完成。普通用户只需等待包含此 PR 的主 CLI 发版,不需要单独发布 Mem0 npm 包。

Disposition update: all 26 previously unresolved Suggestion threads received a current-head decision and are now resolved. Three were already covered by documentation, one Hologres request is outside this bounded protocol, and 22 test-only proposals are explicitly recorded for a later focused PR in the linked comment. This did not change code or increment the substantive round count. The old bot CHANGES_REQUESTED review still awaits re-review/dismissal; CI remains pending, not claimed green.

Round 5 critical closeout: pushed 8bfd4aa. A temporary MCP allow-list filter can no longer delete Mem0's interactive write-confirmation hook during /hooks reload; operator memory.enableManagedAutoMemory=false and enableManagedAutoDream=false survive a trusted workspace memory:null alongside Mem0. Both defects were reproduced before the fix and verified afterward with the built CLI and real bundled runtime (no live provider call). Full build, bundle, typecheck, focused lint/format, and 238 CLI tests passed. Post-push CI snapshot had no failures and several checks queued. The old bot CHANGES_REQUESTED review is against ed45308, not this head.

yiliang114 and others added 3 commits September 29, 2026 01:17
Ignore repository-scoped MCP conflicts, require the genuine bundled runtime for confirmation hooks, and use native artifact paths in packaging assertions. Improve field diagnostics and document existing precedence, scope, and verification limits.

Co-authored-by: Qwen-Coder <[email protected]>
R1-1 (scripts/tests/package-assets.test.js): the two new multi-segment
`missingArtifact` entries are compared against a message built with
`path.join`, so `mem0/main.js` never matches the `\`-joined Windows path.
Normalise the separator in the assertion; single-segment entries and POSIX
behaviour are unchanged.

R1-2 (packages/cli/src/config/config.ts): the mem0/external-context conflict
guard read the merged all-scopes map, so a `external-context` entry that a
trusted repository contributes through its own `.qwen/settings.json`
(`scope: 'workspace'`) aborted `loadCliConfig` for every operator who
configured `memory.mem0` — before `assembleMcpServers` applied the approval
gate that would have held that server pending anyway. Limit the settings leg
to operator scopes via `isGatedMcpScope`; a gated-scope entry is now
overridden by the built-in binding (topTierMcpServers spreads last), exactly
as a `.mcp.json` entry of the same name always has been, matching the design
doc this PR adds.

R1-28 (packages/cli/src/config/mem0-settings.ts): `bundledMem0Hooks`
identified the bundled server by duck-typing an env-var name plus
`includeTools`, so a repository-authored `external-context` entry reaching
the `/hooks` reload path through `getMcpServers()` was filed under
`systemHooks` — the one hook source folder trust does not gate — rendered as
`[System]` configuration and pointed at a different script than the one the
MCP approval dialog showed. Require identity instead: every file-sourced
entry carries a provenance `scope`, the bundled one never does.

Tests: workspace-scoped override + operator-scoped conflict still throws
(config.test.ts), three scoped-entry rejection rows + an unscoped positive
control (mem0-settings.test.ts), and a control/negative pair on the real
reload path (hooksCommand.test.ts). Each fix was mutation-proved red.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmultfgjtdh
…opies

R1-23: nothing in the repo observed `server.command` or `server.timeout`, and
the `memory.mem0.timeoutMs` bounds are declared independently in
mem0-settings.ts (zod, the runtime contract) and settingsSchema.ts (the
settings-dialog copy). Replacing `command: process.execPath` with `'node'`, or
dropping the `+ 5000` slack, left every suite green — a packaged or
GUI-launched install routinely has no `node` on PATH, and the same value gates
`bundledMem0Hooks` and is interpolated as the hook interpreter.

Adds, in mem0-settings.test.ts: `command === process.execPath`,
`config.timeoutMs === 5000` / `server.timeout === 10_000` for the default, an
accept row at the documented ceiling asserting `35_000`, and reject rows for
`0` / `30001` / `1.5`. Adds, in settingsSchema.test.ts, the bound test mirroring
the existing `tools.webSearch.timeoutMs` precedent so the JSON-schema copy is
pinned to the same documented numbers (docs/users/features/mem0.md: "defaults to
5000, between 1 and 30000").

Mutation-proved individually: `process.execPath` -> `'node'` red, `+ 5000`
removed red, `maximum: 30000` -> `60000` red; intact tree green (27 + 61).

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmultfgjtdh

@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.

Thanks — this is a carefully layered change and the containment story (operator-scope-only settings, workspace strip + memory: null restore, trust: false, read-only default, exact-content confirmation even in YOLO, no credential value serialized into the child config) holds up when traced through the code at the current head. The packaging gates on both the npm and standalone paths are also in place.

Nothing here rises above a suggestion, but a few items are worth a follow-up before or shortly after merge:

  1. packages/cli/src/config/mem0-settings.ts:73 — the envKey === 'QWEN_BUNDLED_MEM0_CONFIG' guard is case-sensitive, while isInternalSecretEnvVar right beside it is deliberately case-insensitive because process.env is case-insensitive on Windows. A lowercase envKey: 'qwen_bundled_mem0_config' would pass validation there and then collide with the injected config channel variable in the child environment. A case-insensitive comparison closes it.
  2. integrations/external-context/src/http-client.ts:27-33 — the new control-character/DEL/backslash rejection applies to every provider, not only the bundled path, and http-client.test.ts is untouched. A backslash in an existing admin configuration used to be folded by new URL() and now throws; the new branch deserves direct coverage.
  3. packages/cli/src/config/mem0-settings.ts:151-170 — the mem0RuntimePath() walk-up fallback and the "runtime is missing" throw are never exercised because the test mocks existsSync so the first check always hits. That throw is the message a source checkout sees when npm run bundle was skipped, so one test for it would be valuable.
  4. packages/cli/src/config/mem0-settings.ts:186-201 — the Windows hook command shape (& + PowerShell single-quote doubling + shell: 'powershell') reads correctly, but the macOS/Windows unit jobs are skipped in CI, so this branch has never executed. A small platform-mocked unit test would make it executed somewhere.
  5. packages/cli/src/config/config.ts:2410 — the name-conflict error (and the memory.mem0 … validation errors) are plain Errors without the settings.json: prefix that neighboring settings validation uses; the prefix is what tells the operator which file to fix.

Two non-code items for the record: with this merged there will be three Mem0 delivery paths in the tree, and the extension path's README currently documents an install command that 404s — that cleanup deserves a tracking issue. And since this moves Mem0 from admin-bound to operator-configured while #12596 still carries need-discussion, it would be good to record the direction decision explicitly in the issue rather than letting it be implied by the merge.


感谢——这个改动的分层做得很细,收敛设计(仅 operator 作用域设置、工作区剥离 + memory: null 恢复、trust: false、默认只读、YOLO 下仍要求精确内容确认、子进程配置不序列化凭证值)在当前 head 的代码里逐条核实都成立。npm 与 standalone 两侧的打包门槛也已就位。

没有超过建议级的问题,但有几处值得在合并前或合并后尽快跟进:

  1. packages/cli/src/config/mem0-settings.ts:73——envKey === 'QWEN_BUNDLED_MEM0_CONFIG' 是大小写敏感比较,而紧邻的 isInternalSecretEnvVar 明确按 Windows 上 process.env 大小写不敏感做了处理。小写的 envKey: 'qwen_bundled_mem0_config' 会绕过校验,随后在子进程环境块里与注入的配置通道变量冲突。改成大小写不敏感比较即可堵上。
  2. integrations/external-context/src/http-client.ts:27-33——新增的控制字符/DEL/反斜杠拒绝逻辑对所有 provider 生效,不只是内置路径,而 http-client.test.ts 没有改动。既有 admin 配置里的反斜杠过去会被 new URL() 折叠、现在会抛错,这个新分支值得直接补测试。
  3. packages/cli/src/config/mem0-settings.ts:151-170——mem0RuntimePath() 的回溯查找和“运行时缺失”抛错从未被测试执行:测试 mock 了 existsSync 使第一次检查必中。而那个抛错正是源码 checkout 忘记 npm run bundle 时用户看到的信息,值得补一个用例。
  4. packages/cli/src/config/mem0-settings.ts:186-201——Windows hook 命令形态(& + PowerShell 单引号双写转义 + shell: 'powershell')审读是正确的,但 CI 跳过 macOS/Windows 单测任务,该分支从未被执行过。一个 mock 平台的小单测就能让它至少在某处跑到。
  5. packages/cli/src/config/config.ts:2410——名称冲突报错(以及各 memory.mem0 … 校验错误)是不带 settings.json: 前缀的普通 Error,而相邻的设置校验都有这个前缀;前缀正是告诉操作者该改哪个文件的信息。

另有两件非代码事项请记录在案:本 PR 合并后树里将同时存在三条 Mem0 交付路径,而扩展路径的 README 目前写着一个 404 的安装命令——这项清理值得单开跟踪 issue。另外,本 PR 把 Mem0 从 admin 绑定改为 operator 自行配置,而 #12596 仍挂 need-discussion,建议把方向决策显式记录在 issue 里,而不是让一次合并隐含地完成它。

@yiliang114

Copy link
Copy Markdown
Collaborator Author

Verified the bundled Mem0 path with the configured real kimi-k3 model and a live PolarDB-hosted Mem0 instance, using actual interactive CLI sessions and tmux captures.

  • Settings-only startup connected external-context; the Mem0 credential came from settings.env through a custom envKey, not a shell export or separate npm package.
  • The CLI displayed the full two-line write content for approval. One approved infer:false write returned HTTP 200 and ID 66d6b5e2-94e8-4bdd-ab1e-92bb9833affa. A fresh CLI retrieved the same ID and complete content. A prior model call omitted the second line; it was not approved and sent zero writes.
  • Live testing exposed a response difference: PolarDB returns direct-import memory as a serialized single-user message array. 0edc8f5c2e40 restores the exact text only for the verified PolarDB presets, only when infer:false and the wrapper matches. Other protocols and unmatched/plain content remain unchanged. Post-fix read-only verification checked both the actual TUI result and the actual tool-result payload sent to the real model. A final user-ready session also retrieved the record with the model endpoint configured directly, without a model/evidence relay.
  • Preserved the concurrent fixes by merging, not rewriting history. cc0b66000fd3 corrects the Hook reload test's control fixture to use the actual bundled binding instead of an arbitrary fake runtime path.

Focused verification: provider tests 51 passed, affected CLI Mem0 checks 38 passed, Hook command tests 19 passed, external-context typecheck passed, and the bundle rebuilt successfully. The original live harness exits are retained: one caught a missing model argument before approval; one assumed the raw provider response was plain text; the post-fix capture harness had a regex error, with a separate read-only evidence audit passing. This is observed real-provider acceptance, not a claim that every E2E automation run was green.

Network boundary: the live instance was reached through the previously configured restricted SSH tunnel to its private endpoint. Public-direct access still timed out, so this does not claim public connectivity is fixed. Only one synthetic record was created in a dedicated test scope; it remains available for debugging. Existing memories were not changed. The ineffective temporary whitelist group was removed, existing whitelist groups were retained, and global user settings were not modified. No standalone Mem0 package publication was required; installed-package availability still depends on a main CLI release containing this PR.

中文验收摘要:已经用真实模型、真实 PolarDB 实例和实际 tmux CLI 验证 settings-only 连接、完整内容确认、一次批准写入、返回 ID、重启检索同一记录。发现并修复了 PolarDB direct import 返回消息数组 JSON 的兼容差异,修复后界面和模型收到的工具结果都是完整原文;保留给用户的直连模型调试窗口也已成功检索。51 个 provider 测试、38 个相关 CLI 检查、19 个 Hook 测试和相关 typecheck/bundle 通过。原始自动化失败证据未覆盖,不冒充全绿。当前可用链路是专用 SSH 私网隧道,公网直连问题仍未解决;没有发布独立 npm 包,也没有改全局 settings 或其他用户记忆。

Resolve two conflicts, both "each side appended its own entries to the
same list":

- scripts/prepare-package.js: verifyBundleArtifacts' requiredPaths now
  carries this PR's dist/mem0/{main,write-confirmation}.js AND main's
  dist/sandbox{BwrapRelay,LandlockRelay,FileWorker}.js. Both sets are
  genuinely emitted (esbuild.config.js keeps the mem0 build plus the
  three sandbox relay entry points), and the sibling
  scripts/create-standalone-package.js auto-merged to the same union.

- scripts/tests/package-assets.test.js: the it.each artifact list is the
  union of both sides (export-transcript-document.css deduplicated), and
  the PR's separator-normalising assertion is kept because it is the more
  general form -- it covers main's single-segment sandbox entries as well
  as this PR's multi-segment mem0/* entries.

Verified: scripts/tests/package-assets.test.js passes 48/48 (1 skipped)
with all seven parametrised artifact variants green. The wider
test:scripts run shows 20 failures, but a clean origin/main baseline
with the same node_modules produces a byte-identical 20-failure set, so
none are attributable to this merge.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-conflict/jmum95axqe9
@yiliang114

yiliang114 commented Sep 29, 2026 •

Copy link
Copy Markdown
Collaborator Author

CI attribution for the red Test (ubuntu-latest, Node 22.x) on head 21eb353075 (run 36530420031) — not caused by this PR.

37059 tests pass. The only failures are 7 cases in packages/core/src/agents/background-agent-resume.test.ts:

× BackgroundAgentResumeService > matches the launch-time skill listing when the definition inherits every tool
× ... declares an empty tools list
× ... declares a null tools value
× ... declares a wildcard tools string
× ... declares an empty tools string
× ... declares a non-array disallowedTools value
× ... names exec without skill under CodeModeOnly
  → expected false to be true // Object.is equality   (each, retry x2)

Evidence that these come from main:

  • All 7 are expansions of one parameterized title added by fix(core): withhold the SkillManager from subagents whose tool policy has no Skill tool #12545 (d5a157c45e, merged to main 2026-09-29T05:55:15Z). Its patch adds + 'matches the launch-time skill listing when the definition %s', and modifies both background-agent-resume.ts and background-agent-resume.test.ts.
  • This head is Merge origin/main into codex/12596-bundled-mem0 at 06:15:56Z — 20 minutes after d5a157c45e landed — so the failure is inherited from the base, not introduced here.
  • This PR's diff is 32 files with zero matches for background-agent-resume, src/agents/, or skill. Bundling Mem0 does not touch the failing area.
  • The identical 7 failures with the identical assertion also appear on fix(cli): pin fast model to the selected provider endpoint #12773 at head 53c902ad02 (run 36534233486), an unrelated PR (fast-model endpoint disambiguation, 19 files, also zero matches on that area) whose head merged main at 07:01:32Z. Two unrelated branches failing the same 7 cases the same way points at the shared base.

This is a main-side breakage from #12545 that will red-flag every PR syncing main after 05:55Z, so it needs a fix on main rather than a workaround inside this PR. Not patching around it here.

Note this PR is also over the patrol's +1500-line scope fuse at +1657, so this round made no code changes to it; the 4 findings verified real-and-unfixed in the previous round remain open with their evidence replies.

Update — this is already tracked upstream: issue #12991 (Main CI failed: Qwen Code CI — src/agents/background-agent-resume.test.ts > … > matches the launch-time skill listing when the definition in… (+6 more), opened 2026-09-29T06:44:52Z by qwen-code-dev-bot) reports the identical 7 failures on main itself. That confirms the base is red independently of this PR, so the fix belongs there.

Picks up #12996 (e44e45e), which registers the Skill tool in the
resume-listing fixture. This branch's merge-base is exactly d5a157c (#12545,
"withhold the SkillManager from subagents whose tool policy has no Skill tool"),
so it carries the behavior change without the fixture fix: 7
BackgroundAgentResumeService "launch-time skill listing" cases fail on
`expect(initialMessages.includes('auto-skill-demo')).toBe(true)`.

This branch does not touch packages/core/src/agents/background-agent-resume.*,
so the merge takes main's version wholesale.

Co-authored-by: Qwen-Coder <[email protected]>
@yiliang114

Copy link
Copy Markdown
Collaborator Author

Review disposition at c9a801e: the five correctness/compatibility findings from the last round are fixed and their threads are resolved. I checked the remaining suggestions against the current head; none reports a new production failure.

Already addressed in this PR: R1-35 (user/system/system-defaults are named in both designs), R1-11 (the user guide now distinguishes operator conflicts from workspace/project overrides and warns about extension shadowing), and R1-31 (both designs and the user guide explain worktree scope changes). These threads can be closed without more code.

Outside this bounded contract: R1-8 would require a separately verified Hologres wire preset. The default mem0-v2 ID is explicitly PolarDB-style, not a promise of universal V2 compatibility. I am leaving Hologres protocol work outside this PR.

Non-blocking regression coverage deferred to a focused follow-up PR after merge, not silently treated as implemented:

  • Child/startup: R1-14 (child entry point), R1-15 (activation gates), R1-44 (hooks-off write gate), R1-45 (tier explanation), R1-17 (runtime fallback), R1-3 at both boot and hooks-reload call sites.
  • Config and protocol: R1-4 (base URL character check), R1-5 (unsafe URL inputs), R1-22 (loopback HTTP), R2-6 (non-HTTP scheme), R1-25 (protocol aliases), R1-39 (binding envelope).
  • Hooks and provenance: R1-24 (scope pass-through), R1-47 (same-matcher user hook), R1-21 (PowerShell payload), R2-2 at both identity-guard test locations.
  • Scope/package/result: R1-16 (home hash input), R1-48 (symlink canonicalization), R1-18 (standalone archive), R2-3 (multi-message direct import).

This is a late review round, so I am not expanding #12891 with another test matrix. The implementation guards remain present; build, typecheck and 847 focused tests passed on the pushed head. Native Windows remains unrun here, though both Windows-shaped artifact regressions were mutation-proven locally. The current CI snapshot has no failure, with core jobs still running; I will treat any PR-caused failure as actionable rather than calling pending checks green.

@yiliang114

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

@qqqys qqqys 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.

Reviewed at head 8bfd4aa0b7777fd96108c2f669d1a19bdc2caff8.

Verdict: COMMENT — both historical Criticals are fixed, but my own diff-wide scan did not finish inside the review budget

Both blocking findings from the round-3 request are fixed at this head

I re-read the code rather than relying on the threads being marked resolved, since one of them had explicitly been left open and escalated two hours before that review.

R1-1 — the inverted Windows separator normalisation. Fixed, and now pinned on a platform-independent runner. The needle no longer goes through path.join: the new helper at scripts/tests/package-assets.test.js:34-40 normalises only the message and matches the raw artifact string —

function reportsMissingArtifact(message, missingArtifact, pathFlavor = path) {
  const normalized = String(message).split(pathFlavor.sep).join('/');
  return (
    normalized.includes('Required package artifact not found') &&
    normalized.includes(missingArtifact)
  );
}

The it.each rows at :718-719 are spelled mem0/main.js and mem0/write-confirmation.js, so the comparison is separator-free on every platform: on win32 the producer's backslash message normalises to / and matches, on posix nothing changes. The real assertion at :747 uses the platform default, and :752-758 adds an explicit win32 arm that builds the message with path.win32.join('D:\\publish\\dist', …) and asserts reportsMissingArtifact(message, missingArtifact, path.win32). That matters here specifically because Test (windows-latest, Node 22.x) is gated on merge_group || schedule || workflow_dispatch and reports skipping on this PR, so the lane that would have gone red never runs on the PR page — the injected pathFlavor makes the defect class observable on the Linux lane that does run. The producer in scripts/prepare-package.js is untouched, as the finding required.

R3-1 — the operatorMemory re-injection block's incoherent scope-selection and null semantics. Fixed; all three measured symptoms are gone at packages/cli/src/config/settings.ts:660-700.

  1. Field-wise merge across operator scopes is replaced by whole-value selection. operatorMem0 reduces [systemDefaults, user, system] and takes the entire mem0 object of the highest-precedence scope that defines one, instead of deep-merging fields across scopes. So an enterprise system-scope binding can no longer be combined with a user-scope enableWrites: true — the inversion WORKSPACE_TIGHTEN_ONLY_SETTINGS exists to prevent, one scope level up, is closed. mem0 is then assigned explicitly (mem0: operatorMem0) rather than arriving through the merge.
  2. The undefined-only gate is gone. The reduce returns null both for scope.memory === null and for an explicit scope.memory.mem0 === null, and if (operatorMem0 === null) { delete merged.memory.mem0; } removes the key, so a {"memory":{"mem0":null}} — this codebase's own unset idiom — never reaches createBundledMem0Server's .strict() schema and cannot escape as a bare Error that fails every launch.
  3. The add-only ?? {} is gone for the same reason: an operator scope can now remove the binding, not just add or widen it.

The workspace-clobber concern that had been deferred alongside this block also does not hold at this head: merged.memory is rebuilt by spreading the operator-derived memory and then the existing merged.memory over it, so workspace-contributed memory keys survive and enableManagedAutoMemory / enableManagedAutoMemory-style defaults are not silently reset from {}. operatorExternalContext is likewise resolved from operator scopes only (system → user → systemDefaults), consistent with the R1-2 provenance fix rather than reading the merged map.

The gate I could not complete

Step 3 of this channel's process is an independent Critical-only scan of the complete diff, and this PR is 35 files at roughly +1657/−92. Inside the per-PR budget I read the two Critical sites above, the settings merge block, the Windows assertion and its new pin, and the full review history — I did not independently read the rest, so I cannot certify it. Specifically unexamined by me:

  • packages/cli/src/config/mem0-settings.ts (+238, new) — including the reject-guard at :207 that a deferred item says can throw instead of returning undefined, half-applying a /hooks reload.
  • packages/cli/src/config/config.ts (+47/−6) — the six-term activation gate and the new configuration aborts, which a deferred item says are bare Errors surfacing as a crash rather than FatalConfigError (exit 52).
  • integrations/external-context/src/bundled-mem0.ts (+33, new) — the shipped child-process entry point, which the round-1 review noted has no test at all.
  • packages/cli/src/config/hook-settings.ts (+26/−6) and hooksCommand.ts — the deferred item about a filtered /hooks reload dropping the confirmation hook with nothing re-registering it when the server returns. The head commit is titled "Retain Mem0 safeguards across reloads", which suggests this was addressed, but I did not verify it.
  • esbuild.config.js, scripts/create-standalone-package.js, scripts/prepare-package.js — the packaging and bundling legs.
  • integrations/external-context/src/providers.ts and mem0-presets.ts — the direct-import unwrap guard and preset contract changes.

Eleven items were deferred as non-blocking in round 3, and round 4 at this head posted a single Suggestion. Under this channel's Critical-only policy none of the Suggestions gate the verdict, and I am not reporting any of them as findings. The reason this is a COMMENT rather than an APPROVE is only that my own scan is incomplete, not that I found a defect.

What would settle it: the unexamined list above is the whole gap. If a reviewer with budget reads those six areas and finds no provable Critical, this is approvable — the two things that were actually blocking are demonstrably fixed.

CI

At 8bfd4aa0: 15 checks succeeded, 85 skipped, none failed and none pending. Note that the skips include the Windows, macOS and CLI-integration lanes, so the green set is not evidence about them; the round-3 review disclosed the same gap. Nothing attributable to this PR. Per the review policy, pending checks were not waited on.

@chiga0 chiga0 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.

Tier: Deep (trust boundary: credential handling, external service protocol, new MCP server lifecycle, new settings surface).

Scope: Reviewed all source (non-test, non-doc) changes: mem0-settings.ts, config.ts, hook-settings.ts, settings.ts, settingsSchema.ts, bundled-mem0.ts, providers.ts, mem0-presets.ts, mcp.ts, http-client.ts, config.ts, esbuild.config.js. Not reviewed: test bodies (mem0-settings.test.ts, config.test.ts, providers.test.ts, integration-tests), documentation files, VSCode schema, packaging scripts.


No blocking findings.

The credential design is correct: the JSON config written to QWEN_BUNDLED_MEM0_CONFIG carries only the env-var name (credentialEnv), not its value; the child process reads the credential from the inherited environment. The URL validation in createBundledMem0Server (control-character check, loopback exception, HTTPS enforcement) and the runtime validateProviderBaseUrl together provide defense-in-depth. The operatorMem0 re-injection in settings.ts correctly enforces operator scope precedence over workspace settings while preserving workspace control over non-mem0 fields. The bundledMem0Hooks identity check is layered — no-scope guard, env-var presence, command identity, and runtime path — and ties the hook to the exact bundled server built for this process.

Minor findings (non-blocking):

  • mem0-settings.ts:bundledMem0Hooks — server.scope !== undefined is evaluated twice in the guard (both the first and a later disjunct). The effective check still fires on the first occurrence; the second is dead code. No correctness impact, but the duplicate weakens the human-readability of a security-sensitive check.

  • mem0-settings.ts:createBundledMem0Server — the loopback carve-out (['localhost','127.0.0.1','[::1]'].includes(url.hostname)) allows http://localhost without allowInsecureHttp: true past settings validation, but http-client.ts:validateProviderBaseUrl has no such carve-out and rejects it at runtime. A user who omits allowInsecureHttp for a local endpoint passes validation silently and gets a confusing startup failure rather than a settings-time error.

Cross-check — prior-reviewer Critical findings not independently confirmed:

  • R1-28 (mem0-settings.ts:bundledMem0Hooks, qwen-code-ci-bot): marked "certifies-falsely" — concern about the identity check admitting a repository-authored lookalike server. My analysis: any scope-stamped entry (workspace/project settings) is blocked by the first disjunct; an unstamped user-settings lookalike would need to know the exact mem0RuntimePath() output. This does not rise to a clear privilege-escalation vector in my reading; the duplicate scope check (my minor above) is the concrete weakness I found.

  • R3-1 (settings.ts:660, qwen-code-ci-bot): marked "certifies-falsely" about the operatorMemory re-injection. My analysis found no obviousincorrect ordering: workspace/project memory fields spread after operatorMemory preserve non-mem0 workspace preferences while mem0: operatorMem0 is enforced last. The full text of R3-1 was not visible to me in the cross-check output; I cannot confirm or refute the specific claim.

  • R1-1 (scripts/tests/package-assets.test.js:724, qwen-code-ci-bot): path-separator concern in the new packaging test. Not reviewed by this pass; flagged as an unreviewed test dimension.

Approval blockers: none from my analysis. R3-1 (settings.ts re-injection semantics) is an open cross-check item I could not fully evaluate; flag for the maintainer to re-examine against R3-1's full text before merge.

Reviewed with AI assistance.

@yiliang114
yiliang114 added this pull request to the merge queue Sep 30, 2026
Merged via the queue into main with commit abcf23a Sep 30, 2026
149 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review/self-reported The linked issue was opened by the PR author (self-reported)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

external-context: extend the interactive fake-Mem0 write harness with a mem0 OSS provider variant

4 participants