Skip to content

feat(memory): MCP-backed memory provider — bind a memory system by config, not by a factory arm - #7661

Draft
serrrfirat wants to merge 4 commits into
mainfrom
feat/memory-mcp-provider
Draft

serrrfirat wants to merge 4 commits into
mainfrom
feat/memory-mcp-provider

Conversation

@serrrfirat

Copy link
Copy Markdown
Collaborator

What this is

The first half of making memory pluggable: a memory provider that is bound to a
backend by configuration instead of by a compiled factory arm.

Today native and mem0 are each a workspace crate plus a match arm in
memory_provider_factory.rs, so "plug in your memory system" means opening a PR
against IronClaw. ironclaw_memory_mcp is one provider that serves any memory
system speaking the memory-over-MCP tool contract — a server URL, a credential,
and two tool names.

The property that makes it generic rather than a single-vendor adapter: tool
names are configuration, not constants
(tool_names_come_from_configuration
pins this). The defaults are the names Mnesis Core publishes in its
manifests/mcp-tools.json, since it is the first implementer.

Scope — this PR does not wire anything

Deliberately provider-only. No composition factory arm, no manifest, no config
plumbing
, so nothing binds this yet and no runtime behavior changes. It is
additive and inert; the wiring is the next PR. See Follow-ups.

Lanes

Lane Mapping
read_long_term search tool (default memory_search)
record_interaction record tool (default memory_add_session)
read_short_term not mapped — manifest must not declare the hook
profile_read not mapped — manifest must not declare the hook

Two lanes is retrieve-before-run and record-after-turn, which is the loop that
makes memory feel like memory. The unmapped lanes fall through to the trait
defaults: the host only calls the lifecycle hooks a provider's manifest declares,
so shipping a subset is a supported shape rather than a partial implementation.

Why the transport is injected

substrates may depend only on contracts/substrates, and ironclaw_mcp is
runtimes — it owns capability adaptation and resource governance, not just the
protocol. So this crate cannot depend on the MCP lane. It takes an injected
McpMemoryTransport, and composition (the one layer permitted to name both
crates) supplies an adapter over the host-mediated McpHostHttpClient.

That keeps the host-mediated egress guarantee intact, keeps the crate inside the
same internal-dependency boundary mem0 already passes, and makes every mapping
unit-testable with no server present. It is the same shape as mem0's
Mem0Transport port.

Safety properties, pinned by tests

  • Scope is host-supplied. Tool arguments carry the trusted ResourceScope;
    the query string is the only model-influenced field. A memory server cannot be
    steered across tenants by prompt content.
  • The provider shapes nothing the model sees. read_long_term returns raw
    candidate text; the host keeps cross-scope filtering, sanitization, the
    untrusted-memory envelope, and every model-visible budget. This division is
    what makes accepting a third-party memory backend defensible.
  • Remote results are bounded before host budgeting. A remote provider is
    untrusted input and may answer a limit: 4 request with thousands of rows.
  • A failure degrades as Unavailable, never as empty. This is what lets the
    degradation note from fix(memory): ranked recall retrieval + a visible difference between broken and empty memory (#7185) #7553 keep telling "retrieval broke" apart from "nothing
    matched" — the distinction Memory not reliably recalled across conversations #7185 showed is corrosive to lose.
  • A disabled memory-context profile makes no network call at all.

Test Strategy

  • Integration tier: Not applicable: this PR wires nothing into a running
    composition, so there is no production-wired path to drive.
    Integration
    coverage lands with the composition PR that binds the provider.
  • Crate tier: 8 unit tests over the lane→tool mapping, covering each safety
    property above plus cross-vendor response-envelope tolerance.
  • Architecture tier: ironclaw_memory_mcp added to MEMORY_PROVIDER_CRATES
    while nothing depends on it, so the gate covers it from its first commit and
    the wiring PR must add a residue row deliberately.
    reborn_dependency_boundaries — 42 passed.
  • Not yet run: the shared cross-provider MemoryService conformance suite
    (ironclaw_memory::test_support). It needs a stateful fake MCP server, the way
    mem0's does; that lands with the wiring PR.

Verified locally: cargo fmt, cargo clippy -p ironclaw_memory_mcp --all-features --tests -- -D warnings (clean), cargo test -p ironclaw_memory_mcp (8 passed), cargo test -p ironclaw_architecture_tests --test reborn_dependency_boundaries (42 passed).

Follow-ups

  1. Composition wiring — MCP-backed transport adapter, config surface, and
    one manifest-keyed factory arm serving every MCP provider rather than one
    arm per vendor. Plus the conformance suite against a fake server.
  2. Mnesis as proving consumer — manifest and an end-to-end scenario.
    Blocked on a licensing question: neo-sky/mnesis-core currently publishes no
    license, so we cannot ship or recommend an integration that depends on it.
  3. Contract publication — the memory-over-MCP tool contract as a versioned,
    vendor-facing document plus an externally runnable conformance suite. Held at
    provisional until a second independent implementer exists, so we are not
    freezing a contract designed from one data point.

Risk / rollback

Additive and unbound: no existing provider, binding, or runtime path changes.
Rollback is deleting the crate and its MEMORY_PROVIDER_CRATES entry.

Refs #7185.

serrrfirat and others added 2 commits August 14, 2026 15:49
Adds `ironclaw_memory_mcp`: a memory provider bound to a backend by
CONFIGURATION rather than by a compiled factory arm. Native and mem0 each
require a crate plus a match arm, so adding a memory system meant changing
IronClaw; a system speaking the memory-over-MCP tool contract now binds
with a server URL, a credential, and two tool names.

Implements two lanes -- `read_long_term` and `record_interaction` -- which
is retrieve-before-run and record-after-turn. `read_short_term` and
`profile_read` fall through to the trait defaults; the host only calls the
lifecycle hooks a manifest declares, so a subset is a supported shape.

Boundary: `substrates` may depend only on contracts/substrates, and
`ironclaw_mcp` is `runtimes` (it owns capability adaptation and resource
governance, not just protocol). So the MCP client is injected through the
`McpMemoryTransport` seam by composition, the one layer permitted to name
both crates. Same shape as mem0's transport port, and it makes every
mapping unit-testable with no server present.

Safety properties pinned by tests: tool arguments carry the trusted
ResourceScope and the query is the only model-influenced field; result
sets are bounded before host budgeting; a transport failure degrades the
lane as Unavailable rather than returning empty, so the degradation note
from #7553 can tell broken from empty.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds `ironclaw_memory_mcp` to MEMORY_PROVIDER_CRATES while nothing depends
on it yet, so the wiring change has to add a residue row deliberately
instead of discovering the rule after the fact.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@railway-app

railway-app Bot commented Aug 14, 2026 •

Copy link
Copy Markdown

🚅 Deployed to the ironclaw-pr-7661 environment in ironclaw-ci-preview

Service Status Web Updated (UTC)
ironclaw ✅ Success (View Logs) Web Aug 20, 2026 at 8:20 am

@coderabbitai

coderabbitai Bot commented Aug 14, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 72e12c59-e633-418e-b004-914b8765258a

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-7661 August 14, 2026 15:21 Destroyed
@github-actions github-actions Bot added scope: dependencies Dependency updates size: XL 500+ changed lines risk: medium Business logic, config, or moderate-risk modules contributor: core 20+ merged PRs labels Aug 14, 2026
@ironloopai

ironloopai Bot commented Aug 14, 2026 •

Copy link
Copy Markdown
Contributor

🧭 IronLoop Run · Review

This comment updates in place as the Run moves through its stages.

🟩 Final result · Completed

🟨 Queued → 🟦 Working → 🟦 Posting results → 🟩 Completed

Automatic trigger · attempt 1 of 3 · completed in 3m 29s

IronLoop completed the review and posted it to GitHub.

🔗 Result

Open submitted review →

Run details

Run: b0c06a67-f832-44e2-b385-3e410c3714bd
Base: main at 7c5d576
Head: feat/memory-mcp-provider at 9b7de6b
Created: 2026-08-14 15:26 UTC
Updated: 2026-08-14 15:29 UTC

@ironloopai ironloopai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 IronLoop review

The new MCP provider can bypass the host’s cross-scope memory admission guard.

Findings: 🔴 High 1

🔴 High · Preserve remote candidate scope for host admission

Inline on crates/extensions/packages/memory-mcp/src/service.rs:149. See the inline comment for details.

Validation

  • ✅ MCP provider unit tests — All 8 crate unit tests passed.
Review details
  • Run: b0c06a67-f832-44e2-b385-3e410c3714bd
  • Workflow: Review
  • Attempts: 1

Comment on lines +146 to +149
tenant_id: scope.tenant_id.as_str().to_string(),
user_id: scope.user_id.as_str().to_string(),
agent_id: scope.agent_id.as_ref().map(|id| id.as_str().to_string()),
project_id: scope.project_id.as_ref().map(|id| id.as_str().to_string()),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 IronLoop review · Inline finding

🔴 High · Preserve remote candidate scope for host admission

Every remote candidate is relabeled with the invoking scope before it reaches the host. If a buggy or malicious MCP server returns another tenant’s/user’s record, its original ownership is discarded and the host’s cross-scope filter sees it as in-scope, allowing its content into the current turn’s prompt. Require and validate response scope fields (dropping missing/mismatched rows), or otherwise establish an equivalent trusted isolation boundary before constructing snippets.

@serrrfirat

Copy link
Copy Markdown
Collaborator Author

Tracking issue for the remaining work — composition wiring, Mnesis as first consumer, and publishing the contract: #7664

serrrfirat and others added 2 commits August 20, 2026 11:09
The target-tree gate failed because §5 draws no home for the new package.
Amending §5 is the right fix rather than an EXCEPTIONS row: the crate sits
exactly where a [memory] provider package belongs, next to memory-native
and mem0 — §5 simply had not been told about it. EXCEPTIONS is for owned
deltas between the drawing and the tree, and there is no delta here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

This branch was successfully deployed

1 active deployment
ironclaw-ci-preview / ironclaw-pr-7661 — b67a0a47 Deployed Aug 20, 2026 by railway-app[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: core 20+ merged PRs risk: medium Business logic, config, or moderate-risk modules scope: dependencies Dependency updates scope: docs Documentation size: XL 500+ changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant