Repository navigation
feat(external-context): Out-of-the-box Mem0 provider with built-in protocol IDs #12596
Description
Activity
- addedtype/feature-requestNew feature or enhancement requestNew feature or enhancement requestscope/memoryMemory and context managementMemory and context management
on Sep 24, 2026 Independent source verification against
main— all three structural claims and the PolarDBtop_kregression confirmed.1. The
top_k-vs-limitregression in the shipped PolarDB preset — CONFIRMEDThis 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 oldMem0OssAdapter, and itssearch()body sent the literal keylimit(providers.ts@ 5702f63, thePOST /v2/memories/searchcall):const body = { query: input.query, limit: input.limit, filters: { user_id: ... } };
- The
mem0-presets.tsfile (which introduces thelimitField: 'top_k' | 'limit'abstraction) did not exist yet at5702f63—contents/integrations/external-context/src/mem0-presets.ts?ref=5702f63returns 404. It was introduced later, and thealiyun-polardb-mysql-2026-08preset ships withlimitField: '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 unknowntop_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_kfor the PolarDB family without a recorded live run against the preset version. The design doc (docs/design/direct-external-context-mem0-presets.md:148) listsLimit: top_kfor the PolarDB preset as a stated contract, but the only documented live evidence (5702f63) usedlimit. This needs a live Hologres/PolarDB check beforemem0-v2(which would inherit the sametop_kdefault, 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/searchwithout the trailing slash (mem0-presets.ts:106).buildUrl(providers.ts:262-267) usesnew URL(this.origin)+url.pathname = basePath + pathverbatim — it does not normalize the trailing slash. Whether/v2/memories/searchvs/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.jsonhas"private": true(line 5) — confirmed, this package is not npm-publishable as-is.integrations/external-context-mem0/package.jsonhas noprivatefield (defaults to publishable) but also has noprepublishOnly/publishconfig and no provenance/signing step — consistent with "the bootstrap publish never happened," so@qwen-code/external-context-mem0is not on npm.- Both are
@qwen-code/*scoped, version0.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 byapplyMem0Scope(providers.ts:304-320) mappinguserId/agentId/appIdintouser_id/agent_id/app_idstrictly fromconfig.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_IDSintypes.ts:50-55, keyed by id not version) matches your direction-1 proposal — adding amem0-v4is additive, existing IDs unchanged. The currentaliyun-polardb-mysql-2026-08/mem0-platform-v3/mem0-oss-rest-2026-08→ alias mapping is consistent with keeping them as aliases ofmem0-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 awriteblock AND configwrite.enabled/autoRecallis set — consistent with "writes only when explicitly enabled."
Recommendation
Before merging the
mem0-v2default:- Live-verify the
top_kfield against a PolarDB and a Hologres Mem0 instance — iftop_kis ignored, changealiyun-polardb-mysql-2026-08and the newmem0-v2preset'slimitFieldto'limit'. The verified-good evidence (5702f63) usedlimit;top_khas no recorded live run for the PolarDB family. - Verify the trailing-slash
/v2/memories/searchvs/v2/memories/search/on the same instances. - 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_kregression is the one concrete blocker.- At commit
What would you like to be added?
One out-of-the-box Mem0 provider: install one package, fill in
baseUrland 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:
POST /v3/memories/search/Authorization: TokenPOST /searchX-API-KeyPOST /v2/memories/searchAuthorization: Token/v2/memories/search/withlimit(#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, andmem0-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:protocolis an optional config field. It resolves in this order: explicit config, then a value written once by a setup step, then themem0-v2default. The runtime never probes.2. Out of the box is the goal: install one package, fill in
baseUrland an API key, and it works. The API key is read from an environment variable for now (the currentcredentialEnvrule); 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_CONFIGpath 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 }), pluscontext_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-contextprovider, which has the built-in presets and writes (#9952), andexternal-context-mem0, which has administrator dialects and no presets. Neither is installable today: the first isprivate, and@qwen-code/external-context-mem0is 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-v2request shape against a Hologres Mem0 instance, including the trailing slash (/v2/memories/searchvs/v2/memories/search/).5702f63) sentlimit, but the mergedaliyun-polardb-mysql-2026-08preset sendstop_k. I found no live run recorded against the preset version. If PolarDB ignorestop_k, the 5-result cap silently stops applying. This needs a live check beforemem0-v2becomes the default. Hologres was also observed withlimit(feat(external-context): Load administrator-owned Mem0 dialects #10634), solimitmay be the correct field for the whole v1/v2 family.infer: false(the client decides what is stored) orinfer: true(the service extracts).中文说明
希望增加什么?
一个开箱即用的 Mem0 provider:装一个包,填上
baseUrl和 API key,就能连接任意兼容 Mem0 的服务。本 issue 回应 #10113 中 2026-08-26 提出的方向确认问题:“仅由管理员配置”不是 Mem0 接入的仓库方向。方向
1. “兼容 Mem0”指的是协议,不是厂商。 存储、索引、检索、召回效果都由 Mem0 服务及其底层数据库(PolarDB、Hologres、pgvector 等)负责。客户端只需要实现通信协议。实际观察到的差异只存在于少数几种 Mem0 API 形态之间,跟底层存储无关:
POST /v3/memories/search/Authorization: TokenPOST /searchX-API-KeyPOST /v2/memories/searchAuthorization: Token/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-contextprovider(有内置 preset 和写入能力,#9952),以及external-context-mem0(用管理员 dialect,没有 preset)。两者现在都装不了:前者是private;后者因为从没做过首次引导发布,npm 上查不到@qwen-code/external-context-mem0。应当收敛成一个可安装的包,内置 preset,同时保留 dialect 文件作为兜底。补充信息
后续待定事项
mem0-v2的请求形态,包括末尾斜杠(/v2/memories/search与/v2/memories/search/)。5702f63)发送的是limit,但合入的aliyun-polardb-mysql-2026-08preset 发送的是top_k,我没有找到这个 preset 版本打过真实实例的记录。如果 PolarDB 忽略top_k,5 条结果上限会悄悄失效。在把mem0-v2设为默认值之前需要实测确认。Hologres 实测也用的是limit(feat(external-context): Load administrator-owned Mem0 dialects #10634),因此limit可能才是整个 v1/v2 这一代的正确字段。infer: false(由客户端决定存什么)还是infer: true(由服务端提炼)。