docs(external-context): Design a configurable Mem0 provider extension - #10113
Conversation
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
|
Thanks for the PR — a carefully structured proposal. The gate passes on everything except direction, which needs a maintainer call before we go deeper. Template: complete — bilingual body, docs-appropriate test plan ✓ Problem: real, not theoretical. There is live demand for Mem0-compatible variants — #9952 is an open request to support PolarDB Mem0 — and the landed profile doc (from PR #9068) explicitly anticipates more providers arriving. The question this doc answers — how those variants integrate without growing the private Direction — escalating here: the landed profile doc's default is that provider owners build and operate their own extensions, with Qwen maintaining only the profile, and reusable tooling considered only after two independent providers demonstrate repeated code. This proposal carves out the Mem0 family instead: one Qwen-maintained stdio extension whose versioned, closed presets cover Mem0 Platform, Mem0 OSS, PolarDB MySQL, Hologres, and RDS PostgreSQL. It respects the profile doc's anti-patterns — no union cases, no registry, no SDK, and the model only ever sees Size: docs only — 317 added lines across two design documents, zero production logic. Core-module gates are not applicable. Approach: right scope for a PR0 — documentation only, staged PR1–PR3, security and failure model spelled out (fail-closed config loading, HTTPS by default, redacted errors, retrieval-only boundary), and a fair treatment of #9952 as protocol evidence rather than an implementation dependency. The honest question before any implementation starts is the sequencing one above — whether a preset-based extension should precede any shipped provider-owned extension — not whether the doc is well-formed; it is. Risk: no elevated risk signals (documentation only, no high-risk paths touched). Stopping at Stage 1 pending the maintainer's direction call — no approval submitted and no changes requested; this is an escalation, not a rejection. Once the direction is settled, re-run with 中文说明感谢贡献——这是一份结构严谨的方案。除方向外,门禁各项均通过;方向问题需要先由维护者决定,再深入审查。 模板:完整——中英文对照,测试计划符合纯文档 PR 的要求 ✓ 问题:真实存在,不是理论假设。Mem0 兼容变体有现实需求——#9952 就是一个支持 PolarDB Mem0 的开放请求;已合入的 profile 文档(来自 PR #9068)也明确预期会有更多 provider 接入。本文档回答的问题——这些变体如何在不逐个扩充私有 方向——在此升级:已合入的 profile 文档的默认路径是:provider 所有者自行开发、运维各自的 extension,Qwen 只维护 profile;且只有在两个独立 provider 证明存在可复用代码之后,才考虑可复用工具。本提案改为 Mem0 家族开一个口子:由 Qwen 维护一个 stdio extension,用版本化、封闭的 preset 覆盖 Mem0 Platform、Mem0 OSS、PolarDB MySQL、Hologres 和 RDS PostgreSQL。它尊重 profile 文档的反模式约束——不加 union case、不做注册表、不提供 SDK、模型永远只见 规模:纯文档——两个设计文档共新增 317 行,零生产逻辑,核心模块门禁不适用。 方案:作为 PR0 范围恰当——仅文档、分阶段 PR1–PR3、安全与失败模型明确(配置加载失败即关闭、默认 HTTPS、错误脱敏、只读检索边界),对 #9952 的定位(协议证据而非实现依赖)也公允。实现启动前值得想清楚的是上面那个顺序问题——基于 preset 的 extension 是否应先于任何已交付的 provider 自有 extension——而不是文档本身是否成形;文档本身是扎实的。 风险:无升级风险信号(纯文档,未触及高风险路径)。 流程在 Stage 1 暂停,等待维护者的方向决定——不提交 approve,也不请求修改;这是升级处理,不是拒绝。方向确定后,用 — Qwen Code · qwen3.8-max Reviewed at |
|
📄 中文说明📄 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed. Suggestions are inline.
Not reviewed: reverse audit — did not converge within the reverse-audit round cap of 10.
中文说明
仅完成部分审查,审查缺口已披露。 建议见行内评论。
未审查:反向审计——在 10 轮的反审轮数上限内未收敛。
— qwen3.8-max via Qwen Code /review (v0.22.0)
| 2. **PR1:** Add the self-contained Extension skeleton, canonical instance and | ||
| dialect schemas, the bounded request engine, and contract tests against | ||
| synthetic fixtures. Do not enable a live provider. |
There was a problem hiding this comment.
[Suggestion] The rollout commits PR1 to adding "the bounded request engine" as a self-contained artifact, but it does not record whether that engine reuses/ports the machinery already implemented in integrations/external-context/src/http-client.ts — the bounded body read (MAX_RESPONSE_BYTES / readBoundedBody), redirect: 'manual', redacted error construction, and validateProviderBaseUrl — or re-derives it. The Security section re-specifies almost exactly the behaviors that file already implements, yet only the porting of PR #9952 fixtures is discussed. If PR1 writes a second copy from scratch, a later safety fix (such as the stream-teardown handling added for #8693) lands in one copy only and the extension silently keeps the stale behavior. State that the request engine should reuse or port the proven http-client.ts machinery, listing the deliberate deviations (basePath joining, allowInsecureHttp beyond loopback, GET support, the three auth styles) — or explicitly state that it re-derives them and why.
中文说明
Rollout 承诺 PR1 会增加“有界请求引擎”作为自包含构件,但没有记录该引擎是复用/移植已在 integrations/external-context/src/http-client.ts 实现的机制——有界响应读取(MAX_RESPONSE_BYTES / readBoundedBody)、redirect: 'manual'、脱敏错误构造、validateProviderBaseUrl——还是重新实现。安全章节几乎逐条重新规定了该文件已实现的行为,却只讨论了移植 PR #9952 的测试夹具。若 PR1 从零写第二份拷贝,后续安全修复(如 #8693 新增的流拆除处理)只会落在其中一个拷贝,扩展会悄悄保留过时行为。建议说明请求引擎应复用或移植 http-client.ts 的成熟机制,并列出有意偏差(basePath 拼接、超出 loopback 的 allowInsecureHttp、GET 支持、三种认证方式)——或明确说明将重新实现及原因。
— qwen3.8-max via Qwen Code /review (v0.22.0)
There was a problem hiding this comment.
Thanks — agreed. PR1 should port the proven bounded HTTP machinery and document its deliberate deviations; I’m proposing that documentation update.
| Each MCP server instance binds exactly one endpoint, preset, and scope. A | ||
| deployment that needs several memory services registers separately named MCP | ||
| server instances. The model does not choose or switch the provider for a call. |
There was a problem hiding this comment.
[Suggestion] A deployment that registers separately named MCP server instances exposes each one as a distinct model-visible tool — Qwen names MCP tools mcp____ (packages/core/src/tools/mcp-tool.ts), and nameOverride is never populated in production — so the model sees N context_search tools and picks one per call. That makes provider selection model-controlled, contradicting "The model does not choose or switch the provider for a call," and the Security section's enumeration of what the model "cannot select" deliberately omits "provider." Nothing tells managed deployments to restrict tool visibility or accept nondeterministic corpus selection. State that each registered instance becomes a distinct model-visible tool, that multi-instance therefore delegates provider choice to the model at tool-selection time, and how a deployment restricts or deduplicates that (per-server includeTools/permissions, or enabling exactly one instance).
中文说明
注册多个不同名 MCP 服务实例的部署会把每个实例暴露为独立的模型可见工具——Qwen 将 MCP 工具命名为 mcp____(packages/core/src/tools/mcp-tool.ts),且生产代码从不填充 nameOverride——因此模型会看到 N 个 context_search 工具并在每次调用时自选其一。这使 provider 选择由模型控制,与“模型不会为某次调用选择或切换 provider”相矛盾;安全章节列举模型“不能选择”的项时也刻意漏掉了“provider”。没有任何内容告知受管部署应限制工具可见性或接受不确定的语料选择。建议说明每个注册实例都会成为独立的模型可见工具,多实例因此会把 provider 选择交给模型在选工具时决定,并说明部署如何限制或去重(按服务的 includeTools/权限,或只启用一个实例)。
— qwen3.8-max via Qwen Code /review (v0.22.0)
There was a problem hiding this comment.
Thanks — agreed. Multiple named instances create multiple model-visible tools; I’m proposing a one-session/one-corpus contract with at most one enabled context_search surface.
|
|
||
| ## Retrieval and write boundary | ||
|
|
||
| The portable Mem0 Extension v1 manifest exposes exactly: |
There was a problem hiding this comment.
[Suggestion] The design never fixes the extension's MCP server name, even though the governing profile requires names that are "stable and globally distinctive" and warns that reusing the generic external-context name "would create collisions." The direct integration already registers server key external-context (integrations/external-context/qwen-extension.json), and getMergedMcpServers merges by key with a silent first-wins drop. If PR1 picks external-context (nothing here forbids it), installing both extensions silently drops one server's contribution, breaking this doc's "existing direct mem0-platform-v3 integration remains available" guarantee with no migration. Pin the server name in this design (e.g. external-context-mem0, matching the extension name) alongside the includeTools snippet, as the profile doc does for provider names.
中文说明
设计从未固定该扩展的 MCP 服务名,尽管其遵循的 profile 要求名称“稳定且全局唯一”,并警告复用通用的 external-context 名“会造成冲突”。直接集成已注册服务键 external-context(integrations/external-context/qwen-extension.json),而 getMergedMcpServers 按键合并并在重名时静默丢弃后者。若 PR1 选用 external-context(本设计并未禁止),同时安装两个扩展会静默丢弃其中一个服务的贡献,破坏本文档“现有 mem0-platform-v3 直接集成保持可用”的承诺且无任何迁移。建议像 profile 文档对 provider 命名那样,在 includeTools 片段旁固定服务名(例如与扩展名一致的 external-context-mem0)。
— qwen3.8-max via Qwen Code /review (v0.22.0)
There was a problem hiding this comment.
Thanks — agreed. The design should pin the MCP server key to external-context-mem0 so it cannot collide with the direct integration.
| - HTTPS is required by default. Plain HTTP requires an explicit | ||
| `allowInsecureHttp` opt-in intended for trusted private networks. |
There was a problem hiding this comment.
[Suggestion] allowInsecureHttp admits plain HTTP for arbitrary origins, widening the family's deliberate loopback-only posture — validateProviderBaseUrl in integrations/external-context/src/http-client.ts permits plain HTTP only for localhost/127.0.0.1/[::1], asserted by providers.test.ts ("requires HTTPS except for explicit loopback HTTP") and documented in direct-external-context-provider.md — without acknowledging the divergence. The canonical instance example pairs credentialEnv with an http://10.0.0.8:8080 origin, so the credential rides an Authorization/x-api-key header in cleartext. On a "trusted" private network with an on-path attacker, that leaks the memory-service credential — the exact class the loopback-only rule exists to prevent. Either keep plain HTTP restricted to loopback/RFC1918 ranges validated at startup, or explicitly document that this design relaxes the existing loopback-only invariant and state the compensating constraint.
中文说明
allowInsecureHttp 允许对任意 origin 使用明文 HTTP,扩大了本族刻意限定的“仅 loopback”约束——integrations/external-context/src/http-client.ts 的 validateProviderBaseUrl 仅允许 localhost/127.0.0.1/[::1] 使用明文 HTTP(providers.test.ts 断言“除显式 loopback 外必须 HTTPS”,direct-external-context-provider.md 亦有记载)——却未说明这一偏离。典型实例示例把 credentialEnv 与 http://10.0.0.8:8080 origin 配对,凭据会以 Authorization/x-api-key 头明文传输。在存在路径上攻击者的“受信任”私有网络中会泄露记忆服务凭据——正是“仅 loopback”规则要防止的那类问题。建议要么把明文 HTTP 限制为启动时校验的 loopback/RFC1918 范围,要么明确说明本设计放宽了现有“仅 loopback”不变量并给出补偿约束。
— qwen3.8-max via Qwen Code /review (v0.22.0)
There was a problem hiding this comment.
Thanks — agreed that the current example silently relaxes the loopback-only rule. I’m surfacing the private-network HTTP tradeoff for an explicit security decision before changing that invariant.
| or `omit` locations supported by the relevant field. Their request names are | ||
| selected from an explicit allowlist such as `query`, `user_id`, `agent_id`, | ||
| and `app_id`. | ||
| - Result limits use `top_k`, `limit`, or `omit`. |
There was a problem hiding this comment.
[Suggestion] The limit grammar selects only the NAME of the limit field (or omit); neither the instance nor the preset schema carries the numeric upstream result limit that External Context MCP Profile v1 mandates ("The provider receives the normalized query and a fixed maximum of five results") and the reference implementation hard-codes (providers.ts: const MAX_PROVIDER_ITEMS = 5; top_k: Math.min(input.limit, MAX_PROVIDER_ITEMS)). A preset may legally set the limit to omit, so every context_search reaches the provider with no limit and the provider's default page size applies — violating the fixed-5 contract, paying per-search billing/quota, and on large pages exceeding the 1 MiB cap so truncated JSON fails parsing and a successful search surfaces as a redacted error. Even for non-omit presets the numeric value and clamp are defined nowhere. State that the request engine always sends the profile's fixed maximum (five) through the preset's selected limit field name, drop omit from the limit grammar (or allow it only with a documented server-side hard cap), and note the constant where the schemas are introduced.
中文说明
limit 语法只选择 limit 字段的名称(或 omit);实例与 preset 结构都不携带 External Context MCP Profile v1 所要求、且参考实现已硬编码的上游结果数量上限(“provider 接收归一化查询和固定最多 5 条结果”;providers.ts 中 const MAX_PROVIDER_ITEMS = 5; top_k: Math.min(input.limit, MAX_PROVIDER_ITEMS))。preset 可合法地把 limit 设为 omit,于是每次 context_search 都不带 limit 到达 provider,应用其默认分页大小——违背固定 5 条的契约、产生按次计费/配额,且大分页会超过 1 MiB 上限导致 JSON 被截断解析失败,使一次成功检索以脱敏错误呈现。即便非 omit 的 preset,其数值与截断语义也未在任何地方定义。建议说明请求引擎总是通过 preset 选定的 limit 字段名发送 profile 规定的固定上限(5),从 limit 语法中移除 omit(或仅在文档化的服务端硬上限下允许),并在引入结构处注明该常量。
— qwen3.8-max via Qwen Code /review (v0.22.0)
There was a problem hiding this comment.
Thanks — agreed. The request engine should always apply the profile’s fixed maximum of five results; omit needs a documented server-side hard cap or should be rejected.
| `ProviderConfig` union. The existing direct `mem0-platform-v3` integration | ||
| remains available for compatibility, but it does not become a registry for | ||
| Mem0 product variants. |
There was a problem hiding this comment.
[Suggestion] The compatibility story never addresses the doubled model-visible context_search surface during the doc's own incremental rollout. With both artifacts installed, Qwen exposes mcp__external-context__context_search (direct integration, server key pinned in integrations/external-context/qwen-extension.json) AND mcp__context_search (mcp-tool.ts names every MCP tool mcp__; no cross-server dedup). Because the rollout is incremental ("Preserve the current direct integration while a portable retrieval-only path is introduced incrementally"), both legitimately run during migration. The model can then call both in one turn — duplicating retrieval against the same corpus (double provider load/billing, possibly conflicting result sets) and violating the direct-provider doc's one-turn-one-query invariant — or arbitrarily pick one, making provider choice model-driven again. Add a coexistence section: while both are installed, state which surface must be disabled (or that a deployment must expose at most one context_search tool) and how (existing per-server includeTools/tool-disable knobs), citing the one-query-per-turn invariant it protects.
中文说明
兼容性叙述没有处理在本文档自身的渐进式交付期间模型可见的 context_search 表面翻倍的问题。两个产物都安装时,Qwen 会同时暴露 mcp__external-context__context_search(直接集成,服务键固定在 integrations/external-context/qwen-extension.json)与 mcp__context_search(mcp-tool.ts 把每个 MCP 工具命名为 mcp__,无跨服务去重)。由于交付是渐进的(“在引入便携式只读路径的同时保留当前直接集成”),迁移期间两者会合法地同时运行。模型于是可能在同一轮调用两者——对同一语料重复检索(双倍 provider 负载/计费,结果集可能冲突),违反直接 provider 文档的“一轮一查询”不变量——或任意择一,使 provider 选择重新由模型决定。建议增加共存章节:在两者都安装时,说明必须禁用哪个表面(或部署至多暴露一个 context_search 工具),以及如何做(现有的按服务 includeTools/工具禁用手段),并引用其所保护的“每轮一次查询”不变量。
— qwen3.8-max via Qwen Code /review (v0.22.0)
There was a problem hiding this comment.
Thanks — agreed. Coexistence should require at most one model-visible context_search surface per session.
| selected from an explicit allowlist such as `query`, `user_id`, `agent_id`, | ||
| and `app_id`. | ||
| - Result limits use `top_k`, `limit`, or `omit`. | ||
| - Response collections are `results` or a root array. |
There was a problem hiding this comment.
[Suggestion] The closed grammar admits two response-collection shapes — a named results field or a root array — but defines a preset encoding for only one: the response block's collection key selects a named field, and no value/omission/sentinel encoding for "root array" appears anywhere in the doc (repo-wide, "root array" appears once and "collection" once). A Mem0-compatible service whose search endpoint returns a bare top-level JSON array fits the declared grammar, yet no documented preset field can express it; the PR1 schema author must invent the encoding with no guidance, and any later re-interpretation silently re-points collection extraction on every preset that used it. Fix the encoding in the grammar bullet — e.g. "collection": null (or omitting collection) means the response body itself is the array — and mirror it in the example preset section.
中文说明
封闭语法允许两种响应集合形态——具名 results 字段或根数组——但只为其中一种定义了 preset 编码:response 块的 collection 键用于选择具名字段,而全文任何地方都没有给出“根数组”的取值/省略/哨兵编码(全仓库中 "root array" 只出现一次,"collection" 也只出现一次)。某个检索端点返回顶层裸 JSON 数组的 Mem0 兼容服务符合所声明的语法,却没有任何文档化的 preset 字段能表达它;PR1 的结构作者只能在没有指引的情况下自创编码,而日后对该编码的任何重新解释都会悄悄改变所有使用该编码的 preset 的集合提取。建议在语法条目中固定该编码——例如 "collection": null(或省略 collection)表示响应体本身即数组——并在示例 preset 章节同步体现。
— qwen3.8-max via Qwen Code /review (v0.22.0)
There was a problem hiding this comment.
Thanks — agreed. The preset schema needs one canonical root-array encoding, such as collection: null.
|
|
||
| One environment variable, `QWEN_EXTERNAL_CONTEXT_MEM0_CONFIG`, points to the | ||
| absolute path of the instance JSON file. The local Extension reads and | ||
| validates that file once at startup and fails closed before exposing its tool |
There was a problem hiding this comment.
[Suggestion] "Reads and validates that file once at startup" ignores that Qwen restarts stdio MCP children mid-session: mcp-client-manager defaults autoReconnect to true, /mcp reconnect and POST /workspace/mcp/:server/restart exist, and every connection builds a fresh StdioClientTransport. A restarted child re-reads the file, so the claimed one-instance/one-endpoint/one-scope binding can silently re-point if the file at that path changed. The sibling direct-external-context-provider.md documents an explicit one-session/one-corpus immutability contract for this exact architecture ("a path must never be overwritten or reused for another corpus"), which this doc asserts ("An instance selects one immutable preset") without carrying forward. If the file is re-pointed at corpus B mid-session and the child restarts, startup validation passes and the session silently queries corpus B — cross-corpus context mixing. Add the lifecycle statement the direct provider doc already uses: the instance path, file contents, and credential-to-corpus binding must remain immutable for the whole Qwen session because a reconnect/restart re-reads the file; switching corpora requires a new session with a new configuration path.
中文说明
“在启动时读取并校验该文件一次”忽略了 Qwen 会在会话中途重启 stdio MCP 子进程:mcp-client-manager 的 autoReconnect 默认为 true,存在 /mcp reconnect 与 POST /workspace/mcp/:server/restart,且每次连接都会新建 StdioClientTransport。重启后的子进程会重新读取该文件,因此若该路径下的文件已被更换,所宣称的“一个实例一个 endpoint 一个 scope”绑定会悄悄改变指向。同族的 direct-external-context-provider.md 针对完全相同的架构记录了明确的“一会话一语料”不可变契约(“路径绝不应被覆写或改用于另一语料”),本文档虽声称“实例选择一个不可变 preset”却未承接该契约。若会话中途文件被改指到语料 B 且子进程重启,启动校验会通过,会话便悄悄查询语料 B——造成跨语料上下文混杂。建议加入直接 provider 文档已使用的生命周期表述:由于重连/重启会重新读取文件,实例路径、文件内容与凭据到语料的绑定必须在整个 Qwen 会话期间保持不可变;切换语料需使用新配置路径的新会话。
— qwen3.8-max via Qwen Code /review (v0.22.0)
There was a problem hiding this comment.
Thanks — agreed. The configuration path, file contents, and credential-to-corpus binding must remain immutable for the full Qwen session, including child restarts.
| Memory creation, update, and deletion require a separate future profile or | ||
| Extension. A custom preset cannot enable them. Write protocols differ in |
There was a problem hiding this comment.
[Suggestion] The retrieval/write boundary presents the Extension as state-change-free on reads, but its first flagship preset (mem0-platform-v3, rollout PR2) can trigger provider-side writes on every search via Mem0 Memory Decay. The sibling direct-external-context-provider.md names and warns about this exactly — "Mem0 Memory Decay is opt-in and off by default. When enabled, every returned memory receives a fire-and-forget reinforcement that updates access history and can change later ranking" — and requires verifying it is disabled for no-state-change deployments; the new doc drops it (zero mentions of decay/state-change). A deployment that reads this doc's retrieval-only framing and has Memory Decay enabled on the tenant then mutates provider-side ranking/access history on every context_search — the state change the doc led them to rule out. Carry the note forward: retrieval-only describes what the Extension sends; Mem0 Platform search can still cause provider-side reinforcement when Memory Decay is enabled, and deployments requiring search with no provider-side state change must verify it is disabled.
中文说明
检索/写入边界把扩展描述为读取时不改变状态,但其首个旗舰 preset(mem0-platform-v3,rollout PR2)可能因 Mem0 Memory Decay 在每次检索时触发 provider 侧写入。同族的 direct-external-context-provider.md 恰好点名并警告了这一点——“Mem0 Memory Decay 为可选且默认关闭。启用后,每条返回的记忆都会收到一次发后即忘的强化,更新访问历史并可能改变后续排序”——并要求无状态变更部署确认其保持禁用;新文档却丢弃了该提示(全文零次提及 decay/状态变更)。若部署按本文档的“只读”表述上线而租户启用了 Memory Decay,则每次 context_search 都会改变 provider 侧的排序/访问历史——正是文档引导其排除的状态变更。建议承接该提示:“只读”描述的是扩展发送的内容;启用 Memory Decay 时 Mem0 Platform 检索仍会造成 provider 侧强化,要求检索不产生 provider 侧状态变更的部署必须确认其已禁用。
— qwen3.8-max via Qwen Code /review (v0.22.0)
There was a problem hiding this comment.
Thanks — agreed. The design should distinguish retrieval-only Extension behavior from provider-side Memory Decay effects and require operators to disable decay when reads must not mutate state.
| "response": { | ||
| "collection": "results", | ||
| "idField": "id", | ||
| "contentField": "memory", | ||
| "scoreField": "score" | ||
| } |
There was a problem hiding this comment.
[Suggestion] The dialect grammar defines how response fields are selected (collection/idField/contentField/scoreField) but never defines per-item validation/failure semantics when an upstream item fails the mapping, even though the sibling direct-integration doc pins this for its Generic HTTP adapter ("envelopes are validated, and invalid individual items are dropped") and this doc otherwise pins many runtime failure policies (no redirects/retries, 1 MiB cap, redaction). The doc explicitly anticipates provider contract drift (versioned preset identifiers), so a verified preset can later receive items that fail the mapping. With the semantics unspecified, one PR1 implementation fails the whole context_search call (discarding valid items alongside the bad one), another emits empty-content items into untrusted context, and presets landed by different contributors in PR2/PR3 diverge item-by-item. Add one bullet to the closed-grammar list: response envelopes are validated, and items whose required mapped fields (idField, contentField) are absent or mistyped are dropped individually without failing the whole search — matching the direct integration's documented envelope-validation behavior.
中文说明
dialect 语法定义了如何选取响应字段(collection/idField/contentField/scoreField),却从未定义当某个上游条目不满足映射时的逐条校验/失败语义;而同族的直接集成文档为其 Generic HTTP 适配器明确规定了这一点(“对响应信封进行校验,无效的单个条目会被丢弃”),本文档在其他方面也规定了许多运行时失败策略(不重定向/重试、1 MiB 上限、脱敏)。文档明确预期 provider 契约会漂移(版本化 preset 标识符),因此已验证的 preset 日后可能收到不满足映射的条目。在语义未定义的情况下,某个 PR1 实现可能让整次 context_search 失败(把有效条目连同坏条目一起丢弃),另一个实现则把空内容条目送入不可信上下文,不同贡献者在 PR2/PR3 提交的 preset 会逐条产生分歧。建议在封闭语法列表中增加一条:对响应信封进行校验,缺失或类型不符必需映射字段(idField、contentField)的条目被逐条丢弃而不使整次检索失败——与直接集成文档记载的信封校验行为一致。
— qwen3.8-max via Qwen Code /review (v0.22.0)
There was a problem hiding this comment.
Thanks — agreed. The design should validate the response envelope and drop individually invalid mapped items while preserving valid results.
|
Direction decision from the author side: proceed with the Extension route defined by this proposal. For this work, new Mem0-compatible variants should integrate through the versioned, bounded @yiliang114 Please confirm this as the repository-level canonical direction. If confirmed, the intended sequence is to land PR0 first, then re-run triage on stacked PR1 #10149. 作者侧方向决定:采用本方案定义的 Extension 路线。本次工作中,新的 Mem0 兼容变体应通过版本化、受限的 @yiliang114 请确认该路线能否作为仓库级 canonical 方向。若确认,预期顺序是先合并 PR0,再在堆叠的 PR1 #10149 上重新运行 triage。 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline.
13 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:
- R1-1 request-engine reuse of http-client.ts machinery unrecorded — already reported (comment 3863432934)
- R1-2 multi-instance MCP tool-name doubling — already reported (comment 3863432947)
- R1-3 extension MCP server name never fixed — already reported (comment 3863432954)
- R1-4 allowInsecureHttp widens loopback-only plain-HTTP posture — already reported (comment 3863432962)
- R1-5 numeric upstream result limit carried nowhere — already reported (comment 3863432969)
- R1-6 fail-closed list omits credentialEnv-unset trigger — already reported (comment 3863432975)
- R1-7 multi-instance mechanics unsupported by single env var — already reported (comment 3863432983)
- R1-8 endpoint-validation failures not wired into startup gate — already reported (comment 3863432992)
- R1-9 doubled context_search surface during rollout unaddressed — already reported (comment 3863432998)
- R1-10 root-array response collection has no preset encoding — already reported (comment 3863433004)
- R1-11 once-at-startup read vs mid-session MCP child restarts — already reported (comment 3863433011)
- R1-12 retrieval/write boundary vs Memory Decay writes on search — already reported (comment 3863433019)
- R1-13 per-item mapping failure semantics undefined — already reported (comment 3863433026)
Not explored to full depth (tool budget reached): "agent 3c": none — no check was cut short..
中文说明
已审查。 建议见行内评论。
本轮确认的 13 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。
未探索到全部深度(达到工具调用预算):"agent 3c":none — no check was cut short.。
— qwen3.8-max via Qwen Code /review (v0.22.2)
| - Query, user, agent, and app values use only `json`, `json.filters`, `query`, | ||
| or `omit` locations supported by the relevant field. Their request names are | ||
| selected from an explicit allowlist such as `query`, `user_id`, `agent_id`, | ||
| and `app_id`. |
There was a problem hiding this comment.
[Suggestion] The closed grammar admits placing the model-controlled query value into the URL query string (query location, and GET search) but never mandates percent-encoding, so model-supplied text can corrupt or inject request parameters — contradicting this doc's own guarantee that "The model supplies only query. It cannot select the endpoint, credential, user, agent, filter, dialect, timeout, or result limit." JSON body placements escape by construction; the query placement has no equivalent statement, and no existing in-repo code path exercises query-string placement (both adapters in integrations/external-context/src/providers.ts are POST-JSON-only via postJson), so the PR1 engine will write this path fresh against a spec that is silent on encoding. Concretely: a preset using queryLocation: "query" (the only viable placement for a GET search against one of the listed on-prem services) that receives context_search({ query: "deploy steps&user_id=other-team#" }) would, in an engine assembling the query string by concatenation, emit ?query=deploy steps&user_id=other-team#… — the injected user_id duplicates or overrides the operator-fixed scope value (per the provider's duplicate-wins parsing) and # truncates every subsequent parameter, so the retrieval runs against a scope the operator did not configure. Suggested fix: add a bullet to the closed-grammar list — values placed in the query location are percent-encoded per RFC 3986 before placement (e.g. built via URLSearchParams), and the request query string is assembled solely from allowlisted name/value pairs so tool-supplied text is never concatenated into the URL.
中文说明
封闭语法允许把模型可控的 query 值放入 URL 查询串(query 位置,以及 GET 检索),但从未要求百分号编码,因此模型提供的文本可以破坏或注入请求参数——与本文档自身的保证("模型只提供 query,不能选择 endpoint、credential、user、agent、filter、dialect、timeout 或结果条数")相矛盾。JSON body 位置天然转义;query 位置没有等价说明,且仓库中现有代码路径都未使用查询串放置(integrations/external-context/src/providers.ts 中的两个 adapter 均通过 postJson 仅发 POST-JSON),所以 PR1 的引擎将在一份对编码保持沉默的规范之上从零实现该路径。具体而言:使用 queryLocation: "query" 的 preset(对列表中的 on-prem GET 检索服务是唯一可行的放置方式)收到 context_search({ query: "deploy steps&user_id=other-team#" }) 时,按拼接方式组装查询串的引擎会发出 ?query=deploy steps&user_id=other-team#…——注入的 user_id 会重复或覆盖运维固定的 scope 值(取决于 provider 对重复参数的取舍),# 会截断其后所有参数,检索将运行在运维并未配置的 scope 上。建议修复:在封闭语法清单中补充一条——放入 query 位置的值在放置前按 RFC 3986 进行百分号编码(例如通过 URLSearchParams 构造),请求查询串只由允许清单中的名字/值对组装,工具提供的文本绝不拼接进 URL。
— qwen3.8-max via Qwen Code /review (v0.22.2)
There was a problem hiding this comment.
Thanks — agreed. Query-string placement must use structured allowlisted parameters with percent-encoding; model text must never be concatenated into the URL.
| - Error results redact the query, endpoint, credential, upstream response body, | ||
| and raw exception. Retrieved content remains untrusted external context. |
There was a problem hiding this comment.
[Suggestion] The design defines fail-closed startup and aggressive error redaction but specifies no operator-visible diagnostic channel anywhere. "Fails closed before exposing its tool" (Configuration ownership and loading) is left ambiguous between exiting with an error and starting while exposing zero tools — if the implementer picks the latter, context_search silently disappears from the model's toolset in any deployment with a relative/unset config path or a typo'd preset, with no error and no log requirement anywhere in this document; the loss surfaces only later as degraded agent behavior. And this redaction list is the doc's only error channel: a runtime search failure returns an error result that redacts the query, endpoint, credential, upstream body, and raw exception, leaving oncall with no identifying information about what failed. The sibling extension already resolves both questions with a diagnostic on stderr plus a non-zero exit code (integrations/external-context/src/main.ts), a convention this design neither references nor pins. Suggested fix: state in "Security and failure behavior" that fail-closed startup must exit non-zero with a diagnostic on stderr (so Qwen's MCP status surfaces it), and that operator-facing logs — distinct from the model-visible redacted result — preserve the preset id, endpoint origin, and failure category. (The related gap — the fail-closed list omitting the credentialEnv-unset trigger — is already tracked by the round-1 thread near line 132 and is out of scope here.)
中文说明
设计定义了启动时失败即关闭(fail-closed)与激进的错误脱敏,但通篇没有规定任何运维可见的诊断通道。"在暴露工具前失败即关闭"一节在"以错误退出"与"启动但不暴露任何工具"之间留有歧义——若实现者选择后者,任何配置路径为相对路径/未设置或 preset 拼写错误的部署中,context_search 会静默地从模型工具集里消失,文档中没有任何报错或日志要求;这种缺失只会在稍后以 agent 行为退化表现出来。而这条脱敏清单是文档唯一的错误通道:运行期检索失败返回的错误结果会脱敏 query、endpoint、credential、上游响应体和原始异常,值班人员拿不到任何可用于定位的信息。同族的既有 extension 已用 stderr 诊断加非零退出码解决了这两个问题(integrations/external-context/src/main.ts),本设计既未引用也未固化该约定。建议修复:在"Security and failure behavior"中写明——失败即关闭的启动必须以非零退出码退出,并在 stderr 输出诊断(使 Qwen 的 MCP 状态能呈现它);运维可见的日志与模型可见的脱敏结果分离,并保留 preset id、endpoint origin 与失败类别。(相关缺口——失败即关闭清单遗漏 credentialEnv 未设置的触发条件——已由第 1 轮在 132 行附近的评论跟踪,不在本条范围内。)
— qwen3.8-max via Qwen Code /review (v0.22.2)
There was a problem hiding this comment.
Thanks — partially agreed. Startup should exit non-zero with a sanitized stderr diagnostic, and operator diagnostics may retain a failure category and preset id; raw endpoint details should remain redacted.
| - The provider timeout is shorter than the enclosing MCP timeout so the | ||
| Extension can return a bounded error. A provider response is capped at 1 MiB | ||
| before parsing. |
There was a problem hiding this comment.
[Suggestion] This timeout invariant is stated as flat fact but has no owner: the Extension reads only its own instance JSON and structurally cannot read the client's MCP request timeout — which lives entirely on Qwen's side (packages/core/src/tools/mcp-client.ts computes it from the per-server timeout setting, defaulting to 10 minutes). Nothing validates the relationship, and the doc never says what happens when it is violated, so an operator who sets the instance's timeoutMs: 5000 while the per-server MCP timeout is configured lower gets a generic MCP-level timeout that looks like an Extension hang instead of the bounded, redacted provider-timeout error this section promises, and the PR1 implementer has no instruction for the case. The sibling profile doc phrases the same duty as "must be shorter" with example budgets but also assigns no owner and no violation behavior, so nothing is inherited that closes the gap. Suggested fix: state who maintains the invariant (operator configuration), state the observed behavior when it is violated (the MCP-level timeout preempts the bounded error), or reword the guarantee to "the Extension returns a bounded error for any provider outcome within timeoutMs; MCP-level timeouts apply beyond it."
中文说明
这条超时不变量以事实的口吻陈述,却没有任何归属方:Extension 只读取自己的实例 JSON,结构上无法读取客户端的 MCP 请求超时——后者完全在 Qwen 一侧(packages/core/src/tools/mcp-client.ts 由每个 server 的 timeout 配置计算,默认 10 分钟)。没有任何机制校验两者的关系,文档也没有说明违反时的行为:当运维把实例的 timeoutMs: 5000 设得比 per-server MCP 超时更大(即 MCP 超时被显式配置得更低)时,慢 provider 会先被 MCP 客户端杀掉,调用方看到的是一个看似 Extension 挂起的通用 MCP 层超时,而不是本节承诺的有界、脱敏的 provider 超时错误,PR1 的实现者对此情形也无任何指引。同族 profile 文档把同一义务表述为"必须更短"并给出预算示例,但同样没有归属方和违反行为,因此也没有可继承的约束来闭合该缺口。建议修复:写明该不变量由谁维护(运维配置)、违反时的可观测行为(MCP 层超时先于有界错误生效),或把保证改写为"Extension 对 timeoutMs 内的任何 provider 结果返回有界错误;超出部分适用 MCP 层超时"。
— qwen3.8-max via Qwen Code /review (v0.22.2)
There was a problem hiding this comment.
Thanks — agreed. The document should assign the timeout relationship to operator configuration and state that an earlier MCP timeout preempts the Extension’s bounded provider error.
|
|
||
| - Authentication is one of `authorization-token`, `authorization-bearer`, or | ||
| `x-api-key`. | ||
| - Search uses `GET` or `POST`. |
There was a problem hiding this comment.
[Suggestion] The closed grammar admits method/location combinations with no defined semantics — most sharply method: "GET" with queryLocation/userIdLocation of json or json.filters — and never states that startup or preset semantic validation rejects them; the location allowlist's "supported by the relevant field" qualifier attaches to the field, never to the method. No code path exercises GET today (both direct adapters in integrations/external-context/src/providers.ts are POST-JSON-only via postJson; http-client.ts hardcodes POST), so the PR1 engine has no reference behavior to inherit. In practice: during PR3, a preset author targeting one of the listed GET-only on-prem search APIs (the only reason GET is in the grammar at all) can pair method: "GET" with queryLocation: "json", startup validation passes it because no rule forbids it, and the engine then either serializes a body onto the GET request (rejected or silently ignored by upstreams/proxies) or drops the json placement — the search executes without the query/scope values and returns an unfiltered upstream result set to the model as if it were the operator-scoped context. Two implementations of this same doc can pick opposite behaviors because the doc fixes neither. Suggested fix: add a grammar bullet — json and json.filters locations are valid only with POST, and presets combining a method with an unsupported location are rejected at startup by the same fail-closed gate as the other startup triggers.
中文说明
封闭语法允许出现没有定义语义的"方法/位置"组合——最典型的是 method: "GET" 搭配 queryLocation/userIdLocation 为 json 或 json.filters——并且从未声明启动校验或 preset 语义校验会拒绝它们;位置允许清单中"由相关字段支持"的限定语约束的是字段,而非方法。目前没有任何代码路径使用 GET(integrations/external-context/src/providers.ts 中的两个直连 adapter 均通过 postJson 仅发 POST-JSON;http-client.ts 硬编码 POST),所以 PR1 的引擎没有可继承的参照行为。实际情形:PR3 阶段,针对列表中某个仅支持 GET 的 on-prem 检索服务(GET 存在于语法中的唯一理由)编写 preset 时,可以把 method: "GET" 与 queryLocation: "json" 配对,由于没有规则禁止,启动校验会放行;引擎随后要么把 body 序列化到 GET 请求上(被上游/代理拒绝或静默忽略),要么丢弃 json 放置——检索将在缺少 query/scope 值的情况下执行,把未经过滤的上游结果集当作运维限定范围的上下文返回给模型。同一份文档的两种实现可以选择相反的行为,因为文档两者都没有固定。建议修复:补充一条语法规则——json 与 json.filters 位置仅与 POST 搭配有效;把方法与不支持的位置组合的 preset 在启动时由同一失败即关闭闸门拒绝。
— qwen3.8-max via Qwen Code /review (v0.22.2)
There was a problem hiding this comment.
Thanks — agreed. json and json.filters placements should be POST-only, with unsupported method/location pairs rejected during startup validation.
| and instance configuration never contain credential values. | ||
| - HTTPS is required by default. Plain HTTP requires an explicit | ||
| `allowInsecureHttp` opt-in intended for trusted private networks. | ||
| - Endpoint validation treats `origin` and `basePath` separately and rejects |
There was a problem hiding this comment.
[Suggestion] The request target is origin + basePath + the preset's search.path, but the URL-safety guarantees this bullet enumerates (no embedded credentials, query strings, fragments, dot traversal, encoded traversal) are scoped only to the two instance endpoint fields. The preset path — free text even in the administrator-owned custom preset files this same doc explicitly allows, whose required "same closed schema and semantic validation" is never enumerated — receives no stated validation; the only other constraint, "Request paths are static exact paths", never defines "static" or "exact" as excluding ?, #, or ... So an operator adopting a custom preset whose search.path carries ?limit=50 (copied from the upstream's own docs) or /../other-app/search gets a joined URL that reintroduces exactly the query-string/fragment/traversal content endpoint validation exists to reject, misrouting or malforming the request — and the failure surfaces as a fully redacted provider error with nothing identifying the malformed path. Suggested fix: extend this bullet to state that the same rejection list applies to every component joined into the request target, including the preset search.path, and enumerate path validation among the semantic checks custom preset files must pass.
中文说明
请求目标由 origin + basePath + preset 的 search.path 组成,但本条列举的 URL 安全保证(拒绝内嵌凭证、查询串、fragment、点号穿越、编码穿越)只约束两个实例 endpoint 字段。preset 路径没有任何校验声明——即便在本文档明确允许的管理员自持自定义 preset 文件中,它也是自由文本,而这些文件必须通过的"同样的封闭 schema 与语义校验"从未被列举;唯一的另一处约束"请求路径是静态精确路径"也没有把"静态"或"精确"定义为排除 ?、#、..。因此,运维若采用 search.path 带有 ?limit=50(从上游文档复制而来)或 /../other-app/search 的自定义 preset,拼接出的 URL 会把 endpoint 校验本要拒绝的查询串/fragment/穿越内容重新引入,导致请求被误导或畸形——而该失败会以完全脱敏的 provider 错误呈现,无法定位到畸形路径。建议修复:把本条扩展为同一拒绝清单适用于拼接进请求目标的每个组成部分(包括 preset 的 search.path),并把路径校验列入自定义 preset 文件必须通过的语义检查。
— qwen3.8-max via Qwen Code /review (v0.22.2)
There was a problem hiding this comment.
Thanks — agreed. The same URL-component validation must cover the preset search.path, including custom presets.
| which of `userId`, `agentId`, and `appId` it consumes; startup validation | ||
| rejects a missing required value or a configured value that the preset does | ||
| not use. For example, a Mem0 Platform V3 instance uses |
There was a problem hiding this comment.
[Suggestion] The closed grammar admits omit as a location for every value — including query and operator-fixed scope fields — but the doc never defines coherence between placement and the preset's declared scope consumption: this startup check covers only presence/absence of scope values, never whether a consumed value is actually placed in the request. An operator configuring a shared multi-tenant memory service with "scope": { "userId": "repository-memory" } whose preset declares userId consumed but sets userIdLocation: "omit" passes every startup check the doc names (file present, schema supported, preset known, required value present), context_search is exposed, and every search runs unscoped — retrieving memories across all users of the store while the Security section promises scope values are operator-fixed routing. Similarly queryLocation: "omit" yields a query-less search even though the model can supply nothing else; no rule makes either state a startup error. Suggested fix: state in the grammar that query must always be placed (never omit), that a scope field the preset declares as consumed must have a non-omit location, and that startup/semantic validation rejects a preset declaring a value required while placing it at omit — and define what omit means for a field with no configured value.
中文说明
封闭语法允许把 omit 用作任何值的位置——包括 query 与运维固定的 scope 字段——但文档从未定义放置方式与 preset 声明的 scope 消费之间的一致性:这里的启动检查只覆盖 scope 值的存在/缺失,从不检查被消费的值是否真的放进了请求。设想运维为共享的多租户记忆服务配置 "scope": { "userId": "repository-memory" },而 preset 声明消费 userId 却设置 userIdLocation: "omit":文档列举的所有启动检查都会通过(文件存在、schema 受支持、preset 已知、必需值存在),context_search 照常暴露,而每次检索都在无 scope 的情况下执行——在该存储的所有用户之间检索记忆,尽管安全一节承诺 scope 值是运维固定的路由。类似地,queryLocation: "omit" 会产生无 query 的检索,而模型除此之外无法提供任何输入;没有任何规则把这两种状态变成启动错误。建议修复:在语法中写明——query 必须始终放置(不允许 omit);preset 声明消费的 scope 字段必须有非 omit 的位置;启动/语义校验应拒绝"声明必需却放置为 omit"的 preset——并定义 omit 对未配置值的字段意味着什么。
— qwen3.8-max via Qwen Code /review (v0.22.2)
There was a problem hiding this comment.
Thanks — agreed. query cannot be omitted, consumed scope fields require non-omit placement, and contradictory presets must fail startup validation.
| - `mem0-platform-v3` | ||
| - `mem0-oss-rest-2026-08` | ||
| - `aliyun-polardb-mysql-2026-08` | ||
| - `aliyun-hologres-mem0-2.0.6` |
There was a problem hiding this comment.
[Suggestion] This identifier embeds what reads only as an upstream product release version — the exact axis conflation the doc's own Version model section forbids ("Upstream product or API version belongs to the provider"), and which its Hologres example says a single version label cannot even represent ("a mixture of versioned operation paths"). The siblings use Extension-owned labels (2026-08 snapshot dates, or the pre-existing mem0-platform-v3 case name kept for compatibility), so the list demonstrates three incompatible "contract version" encodings at once. When an operator's Hologres deployment moves to engine 2.0.7 with an identical search contract, the maintainer must either reuse aliyun-hologres-mem0-2.0.6, mislabeling the product the identifier binds, or mint -2.0.7 for an identical mapping — violating the doc's own policy that only a breaking mapping receives a new identifier, and duplicating preset, fixtures, and contract tests for one contract. The reserved name freezes at PR3 before verification, so the conflation ships unless fixed now. Suggested fix: rebase the identifier onto an Extension-owned contract version consistent with its siblings (e.g. aliyun-hologres-mem0-2026-08), and state that upstream product versions are recorded in the preset's verified-contract metadata, never in the identifier.
中文说明
该标识符嵌入的读起来只是上游产品发布版本——正是文档自身版本模型一节所禁止的轴混用("上游产品或 API 版本归属于 provider"),而该节的 Hologres 示例也说明单一版本标签甚至无法表示这种部署("版本化操作路径的混合体")。其余标识符使用 Extension 自持的标签(2026-08 快照日期,或为兼容保留的既有 mem0-platform-v3 案例名),因此该列表同时展示了三种互不相容的"契约版本"编码。当运维的 Hologres 部署升级到契约完全相同的引擎 2.0.7 时,维护者要么复用 aliyun-hologres-mem0-2.0.6(错误标注该标识符绑定的产品),要么为完全相同的映射新造 -2.0.7——违反文档自身"只有破坏性映射才获得新标识符"的政策,并为同一契约复制 preset、fixture 与契约测试。保留名会在 PR3 验证之前冻结,除非现在修正,否则这一混用会随发布落地。建议修复:把标识符改为与其兄弟一致的 Extension 自持契约版本(例如 aliyun-hologres-mem0-2026-08),并写明上游产品版本记录在 preset 的已验证契约元数据中,绝不进入标识符。
— qwen3.8-max via Qwen Code /review (v0.22.2)
There was a problem hiding this comment.
Thanks — agreed. The Hologres preset id should use an Extension-owned contract version; upstream product versions belong in verified-contract metadata.
|
@qwen-code /triage |
|
Sandboxed verification: This PR changes documentation/assets only — there is no code to execute, so a sandboxed verification has nothing to verify. 中文 — 判定:
|
|
Triage re-run completed without a new review.
The stage comments above were updated with the latest result. View workflow run. 上方各阶段评论已更新为最新结果。查看工作流运行。 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
No blocking issues. LGTM! ✅
Deferred under the convergence posture (round 3, not a blocker) — recorded, not requested in this round:
docs/design/external-context-mem0-extension.md:180 — [review] R2-1 still stands — closed grammar admits URL query-string placement of the model-controlled query value without mandated percent-encodingdocs/design/external-context-mem0-extension.md:250 — [review] R2-2 still stands — fail-closed startup and redacted errors specify no operator-visible diagnostic channeldocs/design/external-context-mem0-extension.md:248 — [review] R2-3 still stands — timeout-ordering invariant has no owner; the Extension cannot read the client's MCP timeoutdocs/design/external-context-mem0-extension.md:176 — [review] R2-4 still stands — closed grammar admits method/location combinations with no defined semantics (GET with json placements)docs/design/external-context-mem0-extension.md:241 — [review] R2-5 still stands — URL-component validation is scoped to origin/basePath and does not cover the preset search.pathdocs/design/external-context-mem0-extension.md:122 — [review] R2-6 still stands — closed grammar admits omit for every value including query, with no placement/scope coherence ruledocs/design/external-context-mem0-extension.md:201 — [review] R2-7 still stands — Hologres preset identifier embeds an upstream product release version, conflating the doc's own version axes
中文说明
无阻断问题。LGTM!✅
收敛姿态下延后(第 3 轮,非阻断)——已记录,本轮不要求修改:共 7 条(原文未翻译,列表见上方英文部分)。
— qwen3.8-max via Qwen Code /review (v0.22.2)
chiga0
left a comment
There was a problem hiding this comment.
Scan-tier review. Both changed files are new Markdown design documents under docs/design/ — no code, no tests, no config changes. The PR title and description accurately describe the content (architecture and compatibility policy for the configurable Mem0 extension). No security or correctness issues applicable to documentation-only changes.
Reviewed with AI assistance.
|
Released in v0.22.3. |
What this PR does
This PR documents a configurable Mem0-compatible External Context Extension architecture. It keeps External Context MCP Profile v1 as Qwen's public boundary, defines a self-contained local stdio Extension with administrator-owned instance configuration and versioned bounded dialect presets, separates profile, configuration, dialect, and upstream API versions, and records security, compatibility, write isolation, and staged rollout decisions.
Why it's needed
Mem0 Platform, Mem0 OSS, PolarDB MySQL, Hologres, RDS PostgreSQL, and future compatible services vary in endpoint paths, authentication, scope placement, and response shapes. Adding every variant to Qwen's private provider union would require repeated core changes, while a fully programmable HTTP template would create an unsafe and difficult-to-maintain configuration language. The proposed boundary lets closed, data-only differences use versioned presets and routes behaviorally different protocols to provider-owned MCP Extensions without expanding Qwen Core.
Reviewer Test Plan
How to verify
Review the proposal and confirm that the model-facing surface remains exactly
context_search({ query }), endpoint, credential, scope, and dialect selection stay operator-owned, preset capabilities are closed and retrieval-only, unsupported protocols use separate MCP Extensions, the current direct Mem0 provider remains compatible, and PR #9952 is treated as protocol evidence rather than an implementation dependency. Confirm that the diff contains documentation only. Runnpx prettier --check docs/design/external-context-mem0-extension.md docs/design/external-context-provider-extensions.mdandgit diff --check origin/main...HEAD; both should complete without errors.Evidence (Before & After)
N/A
Tested on
Environment (optional)
N/A
Risk & Scope
Linked Issues
Related: #9952
中文说明
此 PR 做了什么
此 PR 记录一个可配置的 Mem0 兼容 External Context Extension 架构。它继续以 External Context MCP Profile v1 作为 Qwen 的公开边界,定义一个自包含的本地 stdio Extension、管理员持有的实例配置和版本化的有界 dialect preset,区分 profile、配置、dialect 与上游 API 四类版本,并明确安全、兼容性、写入隔离和分阶段交付决策。
为什么需要它
Mem0 Platform、Mem0 OSS、PolarDB MySQL、Hologres、RDS PostgreSQL 和未来兼容服务在 endpoint 路径、认证、scope 放置方式及响应结构上存在差异。如果把每个变体都加入 Qwen 的私有 provider union,就需要反复修改 Core;如果提供完全可编程的 HTTP 模板,又会形成不安全且难维护的配置语言。该边界允许有界、纯数据差异通过版本化 preset 接入,并让行为差异较大的协议由 provider 自己的 MCP Extension 承担,而不扩展 Qwen Core。
Reviewer Test Plan
如何验证
审阅方案并确认模型侧接口仍严格为
context_search({ query }),endpoint、credential、scope 和 dialect 选择由运维人员控制,preset 能力是封闭且只读检索的,不支持的协议使用独立 MCP Extension,当前直接 Mem0 provider 保持兼容,并且 PR #9952 仅作为协议证据而不是实现依赖。确认 diff 只包含文档。运行npx prettier --check docs/design/external-context-mem0-extension.md docs/design/external-context-provider-extensions.md和git diff --check origin/main...HEAD,两条命令都应无错误完成。证据(变更前后)
N/A
测试平台
环境(可选)
N/A
风险与范围
关联事项
相关:#9952