Skip to content

feat(external-context): Out-of-the-box Mem0 provider with built-in protocol IDs #12596

Description

@yiliang114

What would you like to be added?

One out-of-the-box Mem0 provider: install one package, fill in baseUrl and an API key, and it connects to any Mem0-compatible service. This answers the direction question raised in #10113 (2026-08-26): the administrator-only route is not the repository direction for Mem0 integration.

Direction

1. Mem0-compatible is a protocol, not a vendor. Storage, indexing, retrieval, and recall quality belong to the Mem0 service and its database (PolarDB, Hologres, pgvector, …). The client only has to speak the wire protocol, and the observed variation is between a few Mem0 API flavors, not per backend:

Flavor Search Auth Evidence
Mem0 Platform V3 POST /v3/memories/search/ Authorization: Token existing adapter
Stock self-hosted Mem0 REST POST /search X-API-Key official server source, #9952
Mem0 Platform API v2 generation (v1 add, v2 search), as mirrored by managed services POST /v2/memories/search Authorization: Token live PolarDB Mem0 (#9952); Hologres observed at /v2/memories/search/ with limit (#10634)

That set is small and stable, so Qwen maintains it as immutable built-in protocol IDs: mem0-v2 (Mem0 v1/v2 endpoint family, the default), mem0-v3, and mem0-oss-2026-08 (the OSS server is unversioned, so the date keeps the ID immutable). The IDs shipped in v0.24.1 (aliyun-polardb-mysql-2026-08, mem0-platform-v3, mem0-oss-rest-2026-08) stay as aliases. A future Mem0 v4 gets a new ID; existing IDs never change. The provider is not pinned to one version: protocol is an optional config field. It resolves in this order: explicit config, then a value written once by a setup step, then the mem0-v2 default. The runtime never probes.

2. Out of the box is the goal: install one package, fill in baseUrl and an API key, and it works. The API key is read from an environment variable for now (the current credentialEnv rule); storing it directly in settings or a keychain is a later decision. Everything else has a default: the preset defaults to the Mem0 Platform API v2 generation and can be overridden, the scope is derived automatically per user and repository, and filling in the config registers the MCP server. There is no required dialect file, QWEN_EXTERNAL_CONTEXT_CONFIG path variable, or MCP registration step. Administrator-owned dialect files stay as an advanced option for deployments that do not match a built-in preset.

3. Keep the existing safety boundary. The model-facing surface stays context_search({ query }), plus context_remember({ content }) only when writes are explicitly enabled. Endpoint, credential, and scope stay out of model control. No request templates, JSONPath, protocol probing, or fallback. Auto recall stays opt-in and off by default.

Why is this needed?

Consolidation

We now have two implementations of the same thing: the direct integrations/external-context provider, which has the built-in presets and writes (#9952), and external-context-mem0, which has administrator dialects and no presets. Neither is installable today: the first is private, and @qwen-code/external-context-mem0 is not on npm because the bootstrap publish never happened. They should converge into one installable package that carries the built-in presets and keeps dialect files as the escape hatch.

Additional context

Open decisions for the follow-up

中文说明

希望增加什么?

一个开箱即用的 Mem0 provider:装一个包,填上 baseUrl 和 API key,就能连接任意兼容 Mem0 的服务。本 issue 回应 #10113 中 2026-08-26 提出的方向确认问题:“仅由管理员配置”不是 Mem0 接入的仓库方向。

方向

1. “兼容 Mem0”指的是协议,不是厂商。 存储、索引、检索、召回效果都由 Mem0 服务及其底层数据库(PolarDB、Hologres、pgvector 等)负责。客户端只需要实现通信协议。实际观察到的差异只存在于少数几种 Mem0 API 形态之间,跟底层存储无关:

形态 检索 鉴权 依据
Mem0 Platform V3 POST /v3/memories/search/ Authorization: Token 现有 adapter
标准自托管 Mem0 REST POST /search X-API-Key 官方 server 源码,#9952
Mem0 Platform API v2 这一代(v1 写入、v2 检索),托管服务沿用的形态 POST /v2/memories/search Authorization: Token PolarDB Mem0 真实实例验证(#9952);Hologres 实测为 /v2/memories/search/ 且用 limit(#10634)

这个集合小而稳定,所以由 Qwen 维护成不可变的内置协议 ID:mem0-v2(Mem0 v1/v2 端点族,默认值)、mem0-v3、mem0-oss-2026-08(开源 server 没有版本号,用日期保证 ID 不变)。v0.24.1 已发布的 ID(aliyun-polardb-mysql-2026-08、mem0-platform-v3、mem0-oss-rest-2026-08)保留为别名。以后 Mem0 出 v4 就新增一个 ID,已有 ID 永不改动。provider 不绑定单一版本:protocol 是可选配置项,按以下顺序确定:配置里显式填写的值 → setup 步骤探测一次后写入的值 → 默认 mem0-v2。运行时从不探测。

2. 目标是开箱即用:装一个包,填上 baseUrl 和 API key 就能用。 API key 暂时从环境变量读取(沿用现在的 credentialEnv 规则),以后再决定是否支持直接写进 settings 或 keychain。其余都有默认值:preset 默认用 Mem0 Platform API v2 这一代,也可以手动覆盖;scope 按“用户 + 仓库”自动生成;配置填好后自动注册 MCP server。不需要手写 dialect 文件、设置 QWEN_EXTERNAL_CONTEXT_CONFIG 配置路径变量,也不需要手动注册 MCP。管理员自己的 dialect 文件保留,作为与内置 preset 不匹配的部署的高级选项。

3. 现有安全边界不变。 模型侧只有 context_search({ query });只有显式开启写入时才有 context_remember({ content })。endpoint、密钥、scope 不受模型控制。不做请求模板、JSONPath、协议探测或降级。自动召回需显式开启,默认关闭。

为什么需要?

收敛

目前同一件事有两套实现:直连的 integrations/external-context provider(有内置 preset 和写入能力,#9952),以及 external-context-mem0(用管理员 dialect,没有 preset)。两者现在都装不了:前者是 private;后者因为从没做过首次引导发布,npm 上查不到 @qwen-code/external-context-mem0。应当收敛成一个可安装的包,内置 preset,同时保留 dialect 文件作为兜底。

补充信息

后续待定事项

Activity

  1. doudouOUC commented on Sep 24, 2026

    @doudouOUC
    Collaborator

    Independent source verification against main — all three structural claims and the PolarDB top_k regression confirmed.

    1. The top_k-vs-limit regression in the shipped PolarDB preset — CONFIRMED

    This is the highest-value flag in the issue, so verifying it end-to-end:

    • At commit 5702f63 (the 2026-08-25 live PolarDB verification you cite), the adapter was the old Mem0OssAdapter, and its search() body sent the literal key limit (providers.ts @ 5702f63, the POST /v2/memories/search call):
      const body = { query: input.query, limit: input.limit, filters: { user_id: ... } };
    • The mem0-presets.ts file (which introduces the limitField: 'top_k' | 'limit' abstraction) did not exist yet at 5702f63 — contents/integrations/external-context/src/mem0-presets.ts?ref=5702f63 returns 404. It was introduced later, and the aliyun-polardb-mysql-2026-08 preset ships with limitField: 'top_k' (mem0-presets.ts:103-109).
    • Mem0CompatibleAdapter.search() (providers.ts:205-212) now builds the body from [this.preset.search.limitField]: Math.min(input.limit, MAX_PROVIDER_ITEMS) — so the PolarDB preset currently sends { ... "top_k": 5 } where the verified-good run sent { ... "limit": 5 }.
    • MAX_PROVIDER_ITEMS = 5 (providers.ts:46) is the only cap. If PolarDB silently ignores an unknown top_k, the 5-result cap simply stops being applied — exactly as you describe.

    So the regression is real: the field was renamed from limit → top_k for the PolarDB family without a recorded live run against the preset version. The design doc (docs/design/direct-external-context-mem0-presets.md:148) lists Limit: top_k for the PolarDB preset as a stated contract, but the only documented live evidence (5702f63) used limit. This needs a live Hologres/PolarDB check before mem0-v2 (which would inherit the same top_k default, per your direction-1 table) becomes the default.

    One additional nuance: Hologres was observed at /v2/memories/search/ with a trailing slash (#10634) while the PolarDB preset path is /v2/memories/search without the trailing slash (mem0-presets.ts:106). buildUrl (providers.ts:262-267) uses new URL(this.origin) + url.pathname = basePath + path verbatim — it does not normalize the trailing slash. Whether /v2/memories/search vs /v2/memories/search/ matters depends on the server (many Mem0 services redirect one to the other, some 404). Worth including in the live verification.

    2. Two implementations, neither installable — CONFIRMED

    • integrations/external-context/package.json has "private": true (line 5) — confirmed, this package is not npm-publishable as-is.
    • integrations/external-context-mem0/package.json has no private field (defaults to publishable) but also has no prepublishOnly/publish config and no provenance/signing step — consistent with "the bootstrap publish never happened," so @qwen-code/external-context-mem0 is not on npm.
    • Both are @qwen-code/* scoped, version 0.24.4, so a consolidated publishable package is a real convergence target, not a rename.

    3. Safety boundary + protocol-ID design — sound against source

    • The model-facing MCP surface stays context_search({ query }) (+ context_remember({ content })); endpoint/credential/scope are not in the tool schema — confirmed by applyMem0Scope (providers.ts:304-320) mapping userId/agentId/appId into user_id/agent_id/app_id strictly from config.scope, never from the request. The model cannot steer these.
    • No request templates / JSONPath / protocol probing in the search path — search() builds a fixed body shape from the preset, postJson, parse. No fallback across presets. Confirmed.
    • The immutable-protocol-ID design (MEM0_PRESET_IDS in types.ts:50-55, keyed by id not version) matches your direction-1 proposal — adding a mem0-v4 is additive, existing IDs unchanged. The current aliyun-polardb-mysql-2026-08/mem0-platform-v3/mem0-oss-rest-2026-08 → alias mapping is consistent with keeping them as aliases of mem0-v2/mem0-v3/mem0-oss-2026-08.
    • Auto recall opt-in/off-by-default: createMemoryWriter (providers.ts:24-33) returns the writer only when the preset has a write block AND config write.enabled/autoRecall is set — consistent with "writes only when explicitly enabled."

    Recommendation

    Before merging the mem0-v2 default:

    1. Live-verify the top_k field against a PolarDB and a Hologres Mem0 instance — if top_k is ignored, change aliyun-polardb-mysql-2026-08 and the new mem0-v2 preset's limitField to 'limit'. The verified-good evidence (5702f63) used limit; top_k has no recorded live run for the PolarDB family.
    2. Verify the trailing-slash /v2/memories/search vs /v2/memories/search/ on the same instances.
    3. Consolidation is straightforward from here — the preset abstraction (mem0-presets.ts) already carries the cross-flavor differences, so merging the dialect-file package into the preset package is mostly wiring + the npm bootstrap publish.

    The direction (protocol-not-vendor, immutable IDs, out-of-the-box, unchanged safety boundary) is well-aligned with the current source structure. The top_k regression is the one concrete blocker.

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions