docs/design/external-context-mem0-extension.md
Status: Partially implemented through PR1 (no live presets)
Date: 2026-08-26
Related profile: External Context Provider Extensions
Related implementation proposal: PR #9952
Mem0-compatible services integrate through a self-contained local stdio
Extension named external-context-mem0. The Extension implements External
Context MCP Profile v1 and translates the fixed context_search({ query })
contract into a bounded, versioned REST dialect selected by administrator
configuration.
External Context MCP Profile v1 remains the only public Qwen interoperability
boundary. Qwen Core does not gain a provider registry, a public provider SDK,
dynamic module loading, or new third-party cases in its private
ProviderConfig union. The existing direct mem0-platform-v3 integration
remains available for compatibility, but it does not become a registry for
Mem0 product variants.
This document defines the architecture and compatibility policy. PR1 adds the self-contained runtime skeleton, configuration schemas, packaging, and synthetic contract tests. Live provider presets and provider-specific tests remain later rollout steps.
context_search({ query }) while
endpoint, credential, scope, timeout, and dialect remain operator-owned.flowchart LR
Q["Qwen Core"] --> P["External Context MCP Profile v1"]
P --> R["Provider-owned Remote MCP"]
P --> E["external-context-mem0 local stdio Extension"]
E --> C["Administrator-owned instance configuration"]
E --> D["Versioned bounded dialect preset"]
C --> X["Bounded request engine"]
D --> X
X --> S["Mem0-compatible service"]
The local Extension owns the HTTP translation and ships as a self-contained artifact. Qwen sees only the MCP profile. A service with a protocol outside the bounded grammar owns a separate MCP implementation instead of extending the grammar or Qwen Core.
Four independent version axes must remain explicit:
schemaVersion: 1.dialectVersion: 1.An upstream product must not be labeled simply "V1", "V2", or "V3" at the Extension boundary. For example, a Hologres long-memory deployment can expose a mixture of versioned operation paths; its preset records the exact verified contract for each operation.
An instance selects one immutable preset and supplies deployment-specific values. The following shape is illustrative; PR1's published canonical schema is authoritative.
{
"schemaVersion": 1,
"preset": "aliyun-polardb-mysql-2026-08",
"endpoint": {
"origin": "http://10.0.0.8:8080",
"basePath": "",
"allowInsecureHttp": true
},
"credentialEnv": "MEMORY_API_KEY",
"scope": {
"userId": "repository-memory",
"agentId": "qwen-code"
},
"timeoutMs": 5000
}
The endpoint is split into an origin and a base path so the implementation can
validate the authority separately from path joining. Credentials are
referenced by environment-variable name and are never stored in this document.
Scope values are fixed operator input, not tool arguments. A preset declares
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
"scope": { "appId": "shared-repository" } instead of the PolarDB-oriented
scope above.
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
when the path is relative, the file is unavailable, the schema is unsupported,
or the selected preset is unknown. The instance file can name the environment
variable holding a credential, but cannot contain the credential value.
An Extension setting may populate this non-secret path after its installation-to-child-process behavior is covered by E2E. Managed deployments instead supply it through an administrator-owned launcher, system settings, or pinned MCP configuration. Repository-controlled configuration is trusted only when the repository itself is inside the deployment's trust boundary.
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.
A preset describes only the small set of differences required to issue a retrieval request and normalize its response. For example:
{
"dialectVersion": 1,
"id": "aliyun-polardb-mysql-2026-08",
"auth": "authorization-token",
"search": {
"method": "POST",
"path": "/v2/memories/search",
"queryLocation": "json",
"userIdLocation": "json.filters",
"agentIdLocation": "json",
"appIdLocation": "omit",
"limitField": "limit"
},
"response": {
"collection": "results",
"idField": "id",
"contentField": "memory",
"titleField": "omit",
"uriField": "omit",
"scoreField": "score",
"updatedAtField": "omit"
}
}
The initial grammar is deliberately closed:
authorization-token, authorization-bearer, or
x-api-key.GET or POST.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.top_k, limit, or omit.results or a root array.id and
memory_id; content fields are selected from memory, content, and
text. Other normalized fields follow the same explicit allowlist model.threshold and rerank are typed preset fields,
not free-form request fragments.Presets cannot define arbitrary headers, body interpolation, JSONPath, code, environment-variable expansion, redirects, or response transformations. If a new service requires one of those capabilities, it does not fit this grammar.
Built-in preset identifiers include a provider and a stable contract version, for example:
mem0-platform-v3mem0-oss-rest-2026-08aliyun-polardb-mysql-2026-08aliyun-hologres-mem0-2.0.6aliyun-rds-postgresql-memory-2026-08The names above reserve design intent; each preset lands only after its exact request and response contract is verified. A published identifier never silently changes to an incompatible mapping. A breaking mapping receives a new identifier.
The integration decision is mechanical:
A later implementation may accept an administrator-owned custom preset file for retrieval-only deployments. Such a file must use an absolute path, pass the same closed schema and semantic validation as built-in presets, and contain neither credentials nor executable behavior. Custom presets do not expand the grammar and cannot enable write operations.
This keeps new data sources configurable where their differences are data, and plugin-owned where their differences require behavior. It avoids making every new provider a Qwen release while also avoiding an unbounded HTTP programming language in configuration.
query. It cannot select the endpoint, credential,
user, agent, filter, dialect, timeout, or result limit.allowInsecureHttp opt-in intended for trusted private networks.origin and basePath separately and rejects
embedded credentials, query strings, fragments, dot traversal, and encoded
traversal.userId, agentId, and appId values are routing values, not
authorization boundaries. Shared multi-user deployments should use a
provider-operated Remote MCP service with OAuth subject binding and
provider-side authorization.The portable Mem0 Extension v1 manifest exposes exactly:
{
"includeTools": ["context_search"]
}
Memory creation, update, and deletion require a separate future profile or Extension. A custom preset cannot enable them. Write protocols differ in idempotency, duplicate handling, ambiguous timeout outcomes, asynchronous status, inference behavior, and identifier formats; treating them as another search mapping would hide data-loss and duplication risks.
PR #9952 provides useful
PolarDB protocol and test evidence. This proposal is independent of that
branch and does not add a polardb-mem0 case to the private ProviderConfig
union.
A later Extension implementation can port verified request fixtures and normalization cases without cherry-picking the full provider-specific change. If PR #9952 merges first, its behavior remains backward-compatible until a separate migration decision. If it does not merge, superseding it with the Extension requires an explicit maintainer decision; this design document alone does not make that decision.
Each implementation pull request remains independently reviewable and can be rolled back by disabling or uninstalling the Extension. No step silently migrates existing direct-provider configuration.
PR0 changes documentation only. It introduces no user-visible behavior and
does not require an E2E plan. Verification consists of Markdown formatting,
link consistency, git diff --check, and two consecutive clean design audits
covering architecture boundaries, failure paths, compatibility,
maintainability, complexity, security, testing strategy, and simpler
alternatives.
PR1 keeps the built-in preset registry empty, so the shipped Extension fails closed instead of enabling a live provider. Verification covers the canonical schemas, startup configuration boundary, bounded request engine, normalized result profile, MCP tool surface, package build and typecheck, release wiring, and synthetic GET and POST dialect fixtures. Live provider verification belongs to the pull request that adds each preset.