Skip to content

feat(memory): expose platform memory as an agent tool - #319

Open
dpoulopoulos wants to merge 1 commit into
mainfrom
feat/memory-tool
Open

feat(memory): expose platform memory as an agent tool#319
dpoulopoulos wants to merge 1 commit into
mainfrom
feat/memory-tool

Conversation

@dpoulopoulos

@dpoulopoulos dpoulopoulos commented Jul 20, 2026

Copy link
Copy Markdown
Member

Description

Exposes platform memory as a gateway-managed agent tool: Tool.MEMORY ("otari_memory") alongside the existing otari_web_search / otari_code_execution types.

  • Adds MemoryBackend (src/gateway/services/memory_backend.py), which exposes memory_search / memory_store / memory_forget as model-callable tools and dispatches them to the platform's /gateway/memory/{search,store,forget} endpoints (dual-auth: gateway token + user token).
  • Adds CompositeToolBackend (src/gateway/services/composite_backend.py): memory is the one gateway tool designed to run alongside whichever other backend is active (MCP, web search, or sandbox) rather than being mutually exclusive with them, so this fans out the tool-loop protocol (openai_tools, owns_tool, purpose_hints, call_tool) across an ordered list of backends.
  • Wires _extract_memory_tool / _resolve_platform_memory into _pipeline.py's shared tool-context builder, used by all three request formats (chat completions, Anthropic messages, Responses API) — otari_memory is hybrid-mode only (400 if standalone) and gated on the workspace's platform-side memory config (403 if not enabled there).

PR Type

  • New Feature
  • Bug Fix
  • Refactor
  • Documentation
  • Infrastructure / CI

Relevant issues

Checklist

  • I understand the code I am submitting.
  • I have added or updated tests that cover my change (tests/unit, tests/integration).
  • I ran the Definition of Done checks locally (make lint, make typecheck, make test).
  • Documentation was updated where necessary.
  • If the API contract changed, I regenerated the OpenAPI spec (uv run python scripts/generate_openapi.py).

Notes on the checklist:

  • Ran uv run ruff check src tests scripts (clean), uv run mypy (clean, 199 files), and uv run pytest tests/unit (615 passed). tests/integration requires Testcontainers/Docker Postgres and was not run in this environment.
  • uv run python scripts/generate_openapi.py --check confirms the committed spec is already up to date (the tool-type string doesn't change the request schema).
  • AGENTS.md still only documents otari_code_execution/otari_web_search and hasn't been updated to mention otari_memory — flagging as a known gap rather than silently checking the docs box.

AI Usage

  • No AI was used.
  • AI was used for drafting/refactoring.
  • This is fully AI-generated.

AI Model/Tool used: Claude Code (Claude Sonnet 5)

Any additional AI details you'd like to share:
This PR (and the companion platform-side PR mozilla-ai/otari-ai#1344) were authored by Claude Code across a multi-turn session implementing and wiring up the workspace memory feature.

  • I am an AI Agent filling out this form (check box if true)

Summary

This change adds a new platform-managed “memory” capability exposed as the otari_memory tool. It lets the gateway perform three actions—searching saved memories, storing new memories, and deleting memories—while enforcing that memory works only in hybrid mode and only when workspace policy allows it. The gateway now coordinates this memory tool alongside existing tool sources (MCP, web search, and sandbox) during both chat and Responses-style requests, including correct behavior for streaming tool execution.

It also introduces a shared “composite” tool coordinator so the gateway can route tool calls through the right backend in a consistent way, and improves dispatch/error handling (especially for streaming and hybrid fallbacks). Unit tests were added for the composite coordinator and memory components, and integration tests were updated to assert the new composite pooling behavior. AGENTS.md still needs updating for otari_memory.

Technical notes

  • Memory is resolved via a platform endpoint using dual authentication (gateway token + user token), and the resolved memory backend is injected into per-request tool context.
  • Memory tools are included only after workspace/hybrid-mode policy checks pass.
  • Dispatch logic was refactored so non-streaming and streaming paths use composite coordination with special care to preserve timing and ensure failures surface correctly.
  • Unit tests cover tool exposure/ownership, request wiring, response formatting, and error conditions; integration tests were adjusted for composite pooling assertions.

@github-actions github-actions Bot added the missing-template PR is missing required template sections label Jul 20, 2026
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0acfcfa5-365c-4d36-8eca-e6db3c275421

📥 Commits

Reviewing files that changed from the base of the PR and between 4ef0599 and 0674e94.

📒 Files selected for processing (11)
  • src/gateway/api/routes/_pipeline.py
  • src/gateway/api/routes/_platform.py
  • src/gateway/api/routes/_tools.py
  • src/gateway/services/composite_backend.py
  • src/gateway/services/memory_backend.py
  • tests/integration/test_messages_route_dispatch.py
  • tests/integration/test_responses_route_dispatch.py
  • tests/unit/test_composite_backend.py
  • tests/unit/test_memory_backend.py
  • tests/unit/test_memory_resolve.py
  • tests/unit/test_pipeline_settlement.py
🚧 Files skipped from review as they are similar to previous changes (10)
  • src/gateway/api/routes/_tools.py
  • tests/integration/test_messages_route_dispatch.py
  • src/gateway/api/routes/_platform.py
  • tests/integration/test_responses_route_dispatch.py
  • tests/unit/test_composite_backend.py
  • tests/unit/test_memory_resolve.py
  • tests/unit/test_pipeline_settlement.py
  • src/gateway/services/composite_backend.py
  • src/gateway/services/memory_backend.py
  • src/gateway/api/routes/_pipeline.py

Walkthrough

The gateway adds the otari_memory tool, resolves platform memory policy, implements authenticated memory operations, and composes memory with existing backends across non-streaming, streaming, and fallback execution.

Changes

Memory tooling and composite dispatch

Layer / File(s) Summary
Memory tool contract and policy resolution
src/gateway/api/routes/_tools.py, src/gateway/api/routes/_platform.py, src/gateway/api/routes/_pipeline.py, tests/unit/test_memory_resolve.py
Adds memory extraction, platform policy resolution, hybrid-mode enforcement, and memory configuration on ToolContext.
Memory HTTP backend
src/gateway/services/memory_backend.py, tests/unit/test_memory_backend.py
Adds authenticated search, store, and forget operations with validation, formatting, and error handling.
Composite backend lifecycle and routing
src/gateway/services/composite_backend.py, tests/unit/test_composite_backend.py
Adds multi-backend tool aggregation, ownership-based dispatch, and async lifecycle management.
Pipeline composite dispatch and streaming
src/gateway/api/routes/_pipeline.py, tests/integration/test_messages_route_dispatch.py, tests/integration/test_responses_route_dispatch.py, tests/unit/test_pipeline_settlement.py
Uses composite backends for tool loops, lazy or eager streaming entry, fallback attempts, and updated dispatch assertions and settlement context.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Suggested reviewers: njbrake, tbille

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title is relevant, but it uses feat(memory): instead of the required conventional prefix feat:. Change the title to start with feat: and keep the memory scope/detail in the body if needed.
Docstring Coverage ⚠️ Warning Docstring coverage is 11.76% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The description follows the template well and includes the main sections, checklist, and AI usage details.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/memory-tool
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch feat/memory-tool

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.

@coderabbitai
coderabbitai Bot requested a review from njbrake July 20, 2026 13:46
@dpoulopoulos
dpoulopoulos requested a review from agpituk July 20, 2026 13:48
@github-actions github-actions Bot removed the missing-template PR is missing required template sections label Jul 20, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/gateway/api/routes/_pipeline.py`:
- Around line 925-933: Update the memory setup flow around
`_resolve_platform_memory` and `MemoryBackend` so the resolved `scope` and
`recall_limit` values are forwarded when constructing the backend or issuing
memory requests. Preserve the existing `enabled` permission check and ensure
workspace policy limits are not silently discarded.
- Around line 921-923: Remove the duplicate platform_base pre-check from the
surrounding pipeline flow and rely on _resolve_platform_memory’s existing
validation, preserving the established 500 response for missing
platform.base_url. If the detail is still referenced locally, replace the
repeated "Hybrid mode is misconfigured" literal with a named *_DETAIL constant
following the file’s existing convention.

In `@src/gateway/services/memory_backend.py`:
- Around line 77-79: Update MemoryBackend.__aenter__ in
src/gateway/services/memory_backend.py:77-79 to perform a lightweight
connectivity probe after creating the AsyncClient, so memory-only requests fail
before streaming commits. Keep the eager-open behavior and claims in
src/gateway/api/routes/_pipeline.py:1212-1221 and
src/gateway/api/routes/_pipeline.py:1582-1591 unchanged because they are
corrected by this root-cause fix.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b0eb6877-ac3c-4b9a-8fb5-d705ab618dd9

📥 Commits

Reviewing files that changed from the base of the PR and between 018a06e and c8a7107.

📒 Files selected for processing (9)
  • src/gateway/api/routes/_pipeline.py
  • src/gateway/api/routes/_platform.py
  • src/gateway/api/routes/_tools.py
  • src/gateway/services/composite_backend.py
  • src/gateway/services/memory_backend.py
  • tests/unit/test_composite_backend.py
  • tests/unit/test_memory_backend.py
  • tests/unit/test_memory_resolve.py
  • tests/unit/test_pipeline_settlement.py

Comment on lines +921 to +923
platform_base = ctx.config.platform.get("base_url")
if not platform_base:
raise adapter.error(502, "Hybrid mode is misconfigured", ErrorKind.API)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Status code diverges from the established "misconfigured hybrid mode" convention.

This pre-check raises 502 for a missing platform.base_url, but every other path that hits this exact condition (_resolve_platform_web_search, and _resolve_platform_memory itself if this check were removed) raises 500 with the same "Hybrid mode is misconfigured" detail. Since _resolve_platform_memory already guards this internally, this duplicate check just gives the same failure a different status code depending on which tool triggered it — a minor but confusing inconsistency for anyone debugging via status codes. Also worth promoting the literal string to a named *_DETAIL constant to match the file's convention (MEMORY_HYBRID_ONLY_DETAIL, MEMORY_NOT_ENABLED_DETAIL).

🔧 Suggested fix
-            assert ctx.user_token is not None  # guaranteed by the hybrid-mode preamble
-            platform_base = ctx.config.platform.get("base_url")
-            if not platform_base:
-                raise adapter.error(502, "Hybrid mode is misconfigured", ErrorKind.API)
-            # Platform owns per-workspace memory enablement: 403 when the workspace has it off.
-            memory_policy = await _resolve_platform_memory(config=ctx.config, user_token=ctx.user_token)
+            assert ctx.user_token is not None  # guaranteed by the hybrid-mode preamble
+            # Platform owns per-workspace memory enablement: 403 when the workspace has it off.
+            # `_resolve_platform_memory` itself raises 500 "Hybrid mode is misconfigured" if
+            # base_url is missing, matching every other resolve call's convention.
+            memory_policy = await _resolve_platform_memory(config=ctx.config, user_token=ctx.user_token)
+            platform_base = ctx.config.platform["base_url"]  # guaranteed non-empty by the resolve call above
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
platform_base = ctx.config.platform.get("base_url")
if not platform_base:
raise adapter.error(502, "Hybrid mode is misconfigured", ErrorKind.API)
assert ctx.user_token is not None # guaranteed by the hybrid-mode preamble
# Platform owns per-workspace memory enablement: 403 when the workspace has it off.
# `_resolve_platform_memory` itself raises 500 "Hybrid mode is misconfigured" if
# base_url is missing, matching every other resolve call's convention.
memory_policy = await _resolve_platform_memory(config=ctx.config, user_token=ctx.user_token)
platform_base = ctx.config.platform["base_url"] # guaranteed non-empty by the resolve call above
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/gateway/api/routes/_pipeline.py` around lines 921 - 923, Remove the
duplicate platform_base pre-check from the surrounding pipeline flow and rely on
_resolve_platform_memory’s existing validation, preserving the established 500
response for missing platform.base_url. If the detail is still referenced
locally, replace the repeated "Hybrid mode is misconfigured" literal with a
named *_DETAIL constant following the file’s existing convention.

Comment on lines +925 to +933
memory_policy = await _resolve_platform_memory(config=ctx.config, user_token=ctx.user_token)
if not memory_policy.get("enabled"):
raise adapter.error(403, MEMORY_NOT_ENABLED_DETAIL, ErrorKind.PERMISSION)
use_memory = True
memory_url = f"{platform_base.rstrip('/')}/gateway/memory"
# Memory needs both tokens (dual-auth): the gateway token proves machine identity,
# the user token resolves the caller's workspace + member. Both go to the platform.
memory_gateway_token = ctx.config.platform_token
memory_user_token = ctx.user_token

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

mem0 workspace scope recall_limit memory policy enforcement server-side

💡 Result:

Mem0 manages memory workspace scoping and policy enforcement primarily through entity-based partitioning and server-side authentication [1][2][3]. WORKSPACE SCOPE AND IDENTIFIERS Mem0 separates memories into four dimensions to prevent cross-contamination and ensure data isolation [2]: * user_id: Persistent persona or account. * agent_id: Distinct agent or tool. * app_id: Application or product surface. * run_id: Session-scoped or short-lived flow [4][2]. These identifiers are used as filters during memory operations [1][2]. A critical constraint in the current architecture is that entity scopes are stored separately; consequently, using an AND filter to combine multiple entity IDs (e.g., user_id AND agent_id) will return empty results [1][4][5]. Developers should use OR logic or separate queries to retrieve data across scopes [1][2][5]. RECALL LIMITS AND THRESHOLDS Recall behavior is managed via SDK parameters passed during memory operations [6][5][7]: * top_k: Controls the maximum number of memories returned per recall or search [6][7]. * threshold (or searchThreshold): Sets the minimum similarity score (typically 0–1) required for a memory to be included in results [6][5][7]. The default is often 0.1, which can be increased for stricter relevance [5]. SERVER-SIDE POLICY ENFORCEMENT The Mem0 self-hosted server enforces security and policy boundaries through the following mechanisms: * Authentication: Auth is enabled by default, requiring API keys or JWTs for access [3][8]. The server includes security defaults to protect against unauthorized access [9][8]. * Ownership Checks: Recent hardening efforts have introduced ownership verification on server endpoints [10][9]. For example, destructive operations (e.g., DELETE /memories, DELETE /entities) and unfiltered read operations (e.g., GET /memories without filters) now require administrative roles or explicit ownership checks (validating that the API key belongs to the owner of the memory) [10][9]. * Governance Layers: While core Mem0 remains lean, community patterns and external integrations (such as TealTiger) exist to implement pre-write PII detection, scope enforcement, and audit logging as a governance layer that wraps memory operations [11]. Configuration of settings such as default scopes (e.g., global vs. project) is persisted locally in client-side configuration files (e.g., ~/.mem0/settings.json) or handled as runtime overrides in the server's database [12][3][8].

Citations:


scope and recall_limit are resolved but not forwarded. _resolve_platform_memory returns them, but this block only consumes enabled and then builds MemoryBackend without either value. If the platform is meant to enforce those limits server-side, this is fine; otherwise the gateway is silently ignoring workspace policy here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/gateway/api/routes/_pipeline.py` around lines 925 - 933, Update the
memory setup flow around `_resolve_platform_memory` and `MemoryBackend` so the
resolved `scope` and `recall_limit` values are forwarded when constructing the
backend or issuing memory requests. Preserve the existing `enabled` permission
check and ensure workspace policy limits are not silently discarded.

Comment on lines +77 to +79
async def __aenter__(self) -> MemoryBackend:
self._client = await self._stack.enter_async_context(httpx.AsyncClient(timeout=self._timeout_s))
return self

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== memory_backend outline ==\n'
ast-grep outline src/gateway/services/memory_backend.py --view expanded || true

printf '\n== pipeline outline (targeted symbols if any) ==\n'
ast-grep outline src/gateway/api/routes/_pipeline.py --view expanded || true

printf '\n== search for memory reachability / errors ==\n'
rg -n "MemoryNotReachable|NotReachable|memory.*health|health.*memory|memory.*reach|reach.*memory|__aenter__|enter_async_context\\(CompositeToolBackend|open_stream|run_streaming_with_fallback" src/gateway -S

Repository: mozilla-ai/otari

Length of output: 12282


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== memory_backend excerpt ==\n'
nl -ba src/gateway/services/memory_backend.py | sed -n '1,220p'

printf '\n== pipeline excerpt 1 ==\n'
nl -ba src/gateway/api/routes/_pipeline.py | sed -n '1188,1240p'

printf '\n== pipeline excerpt 2 ==\n'
nl -ba src/gateway/api/routes/_pipeline.py | sed -n '1560,1610p'

printf '\n== memory-specific symbols ==\n'
rg -n "MemoryBackend|MemoryNotReachable|memory backend|memory.*reach|reach.*memory|health" src/gateway -S

Repository: mozilla-ai/otari

Length of output: 224


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path

paths = [
    "src/gateway/services/memory_backend.py",
    "src/gateway/api/routes/_pipeline.py",
]
for path in paths:
    print(f"\n== {path} ==")
    text = Path(path).read_text()
    lines = text.splitlines()
    if path.endswith("memory_backend.py"):
        for start, end in [(1, 120), (170, 230)]:
            print(f"\n-- lines {start}-{end} --")
            for i in range(start, min(end, len(lines)) + 1):
                print(f"{i:4d}: {lines[i-1]}")
    else:
        for start, end in [(1190, 1230), (1578, 1600)]:
            print(f"\n-- lines {start}-{end} --")
            for i in range(start, min(end, len(lines)) + 1):
                print(f"{i:4d}: {lines[i-1]}")
PY

Repository: mozilla-ai/otari

Length of output: 11813


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path

print("== gateway/memory route references ==")
for path in Path("src").rglob("*"):
    if path.is_file():
        try:
            text = path.read_text()
        except Exception:
            continue
        if "/gateway/memory" in text or "memory_search" in text or "memory_store" in text or "memory_forget" in text:
            print(path)
            for needle in ["/gateway/memory", "memory_search", "memory_store", "memory_forget", "health"]:
                if needle in text:
                    print(f"  contains {needle!r}")
PY

Repository: mozilla-ai/otari

Length of output: 470


Memory-only streams still bypass fail-fast entry
MemoryBackend.__aenter__ only creates the client, so a dead memory backend is still discovered on the first tool call after streaming may already have committed. If the eager-open path is meant to cover memory-only requests, add a lightweight probe here; otherwise trim the memory-only pre-200 claim in _pipeline.py.

📍 Affects 2 files
  • src/gateway/services/memory_backend.py#L77-L79 (this comment)
  • src/gateway/api/routes/_pipeline.py#L1212-L1221
  • src/gateway/api/routes/_pipeline.py#L1582-L1591
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/gateway/services/memory_backend.py` around lines 77 - 79, Update
MemoryBackend.__aenter__ in src/gateway/services/memory_backend.py:77-79 to
perform a lightweight connectivity probe after creating the AsyncClient, so
memory-only requests fail before streaming commits. Keep the eager-open behavior
and claims in src/gateway/api/routes/_pipeline.py:1212-1221 and
src/gateway/api/routes/_pipeline.py:1582-1591 unchanged because they are
corrected by this root-cause fix.

@njbrake

njbrake commented Jul 20, 2026

Copy link
Copy Markdown
Member

Note: this comment was drafted by Claude via back-and-forth with @njbrake. The reasoning and decisions are his; the prose is Claude's.

@dpoulopoulos a design question on this one before we go further, not a blocker on the implementation quality (the wiring and tests look solid).

Right now otari_memory lands as a first-class built-in tool with its own dispatch: MemoryBackend advertises memory_search / memory_store / memory_forget, intercepts them in the tool loop, and POSTs to the platform's /gateway/memory/* endpoints. That is roughly 550 lines of memory-specific code in the OSS gateway, and it is hybrid-only (a hard 400 in standalone).

The thing I keep coming back to: functionally this is an MCP server. Three tools, remote dispatch, auth headers. And the gateway already knows how to advertise MCP tools, run the loop, and forward calls to a remote server generically via MCPClientPool, which implements the same openai_tools / owns_tool / purpose_hints / call_tool protocol that MemoryBackend reimplements by hand. McpServerConfig even already carries every field a memory server needs (name, url, authorization_token, purpose_hint, allowed_tools).

So the question: why does any memory code need to live in otari at all, rather than memory being an MCP server hosted by otari-ai?

If it were an MCP server, /gateway/mcp-servers/resolve could hand back a memory server for enabled workspaces (with a short-lived scoped token minted at resolve time, which also removes the dual-auth token plumbing from the gateway). We could keep the {type: "otari_memory"} sugar as a thin flag that just folds that resolved server into the mcp list, so callers get the same one-liner opt-in. Net effect: Tool.MEMORY, MemoryBackend, and _resolve_platform_memory come out of the gateway, the entire memory contract lives in otari-ai and can iterate on a platform deploy instead of a lockstep release across both repos, and standalone stops being a permanent 400 (anyone could point at their own memory MCP server).

The one piece I would keep is CompositeToolBackend, but as a generic capability: today MCP is mutually exclusive with web search and sandbox, and letting the loop drive several backends at once is useful for everyone, not just memory.

Is there a constraint I am missing that makes the built-in-tool approach necessary? The two I can think of are (a) the ergonomic opt-in, which the flag preserves, and (b) automatic non-tool recall injection, which is a separate feature and could live on the platform side of the per-request resolve anyway. If neither is load-bearing, exposing memory as a platform MCP server seems like it keeps the OSS gateway free of a feature only one hosted backend can serve. Curious where you land.

@dpoulopoulos
dpoulopoulos force-pushed the feat/memory-tool branch 2 times, most recently from 35dc974 to 28e46a8 Compare July 20, 2026 14:48
@dpoulopoulos
dpoulopoulos temporarily deployed to integration-tests July 20, 2026 14:49 — with GitHub Actions Inactive
@dpoulopoulos
dpoulopoulos temporarily deployed to integration-tests July 20, 2026 15:02 — with GitHub Actions Inactive
Add a gateway-native memory tool (memory_search / memory_store / memory_forget) that the
model can call, backed by the platform's dual-auth /gateway/memory/{search,store,forget}
endpoints. Mirrors the web-search backend: Tool.MEMORY (otari_memory), a MemoryBackend
implementing the tool-loop protocol, _resolve_platform_memory for enablement, and
prepare_gateway_tools wiring (hybrid-only; 403 when disabled).

Introduce CompositeToolBackend so memory can run alongside any other gateway tool (MCP,
web search, code execution) in a single tool loop: dispatch composes the active backends
into one pool, preserving lazy-MCP-open on the streaming path and eager open elsewhere.
The tool loop itself is unchanged.

Signed-off-by: Dimitris Poulopoulos <dimitris.a.poulopoulos@gmail.com>
@dpoulopoulos
dpoulopoulos temporarily deployed to integration-tests July 21, 2026 07:48 — with GitHub Actions Inactive
@coderabbitai
coderabbitai Bot requested a review from tbille July 21, 2026 07:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants