orchestrata_ultimate.gateway.handlers.memory
Reference › orchestrata_ultimate › gateway › handlers › memory
MemoryHandler serves the memory.store, memory.retrieve, memory.search and memory.delete RPCs on top of the MemoryEngine (SPEC-010; see the memory package). rpc is its importer. The gateway injects its shared engine with set_engine, so the RPC surface and the session-close cold-migration path use one engine; without that, _ensure_engine builds a default engine and initialises it on first use. _require raises RPCError with an invalid-params code when a mandatory parameter is missing. handle_store needs content and session_id and saves to the hot store; handle_retrieve fetches by key, hot store first and then cold; handle_search runs a semantic search with limit and min_similarity parameters; handle_delete removes an entry from both stores. _entry_to_dict shapes entries for the reply. See gateway.
At a glance
| Source | src/orchestrata_ultimate/gateway/handlers/memory.py |
| Lines of code | 128 |
| Classes | 1 |
| Functions | 1 |
| Imports (internal) | orchestrata_ultimate.core.base.traces, orchestrata_ultimate.gateway.exceptions, orchestrata_ultimate.memory.memory_engine |
| Imported by | orchestrata_ultimate.gateway.rpc |
| Tests that import it | tests/gateway/handlers/test_memory.py, tests/gateway/test_rpc_behavioral.py, tests/generated_gateway/test_handlers_memory_generated.py |
Class diagram
Dependency diagram
API
Classes
MemoryHandler
Line 31
memory.* methods backed by the MemoryEngine.
| Method | Signature | Summary |
|---|---|---|
__init__ |
__init__(engine: MemoryEngine | None = None) -> None |
Args: engine: Optional pre-built engine (tests inject a scoped one). |
set_engine |
set_engine(engine: MemoryEngine) -> None |
Inject the shared gateway-owned engine after construction. |
_ensure_engine |
async _ensure_engine() -> MemoryEngine |
|
_require staticmethod |
_require(params: dict[str, Any], name: str) -> Any |
|
handle_store |
async handle_store(connection_id: str, params: dict[str, Any]) -> dict[str, Any] |
Handle memory.store — persist a memory entry to the hot store. |
handle_retrieve |
async handle_retrieve(connection_id: str, params: dict[str, Any]) -> dict[str, Any] |
Handle memory.retrieve — fetch a single entry by key (hot then cold). |
handle_search |
async handle_search(connection_id: str, params: dict[str, Any]) -> dict[str, Any] |
Handle memory.search — semantic search across stored memories. |
handle_delete |
async handle_delete(connection_id: str, params: dict[str, Any]) -> dict[str, Any] |
Handle memory.delete — remove an entry from both stores. |
Functions
| Function | Signature | Summary |
|---|---|---|
_entry_to_dict traces |
_entry_to_dict(entry: Any) -> dict[str, Any] |
Source
"""Gateway handler exposing the memory engine (SPEC-010).
The ``MemoryEngine`` (Redis hot store + Neo4j cold store) is fully implemented but
was unreachable from the gateway — no handler, no ``memory.*`` RPC methods. This
handler registers them, delegating to a lazily-initialized engine (built and
connected on first use so it adds nothing to gateway startup). Tests inject a
test-scoped engine for isolation; production uses the env-configured default.
"""
from __future__ import annotations
import logging
from typing import Any
from orchestrata_ultimate.core.base.traces import traces
from orchestrata_ultimate.gateway.exceptions import RPCError
from orchestrata_ultimate.memory.memory_engine import MemoryEngine
log = logging.getLogger(__name__)
_INVALID_PARAMS = -32602
@traces(spec="010", story="US-001", reqs=["REQ-010-004"])
def _entry_to_dict(entry: Any) -> dict[str, Any]:
# Drop the embedding vector — large and not useful to RPC clients.
dumped: dict[str, Any] = entry.model_dump(mode="json", exclude={"embedding"})
return dumped
class MemoryHandler:
"""``memory.*`` methods backed by the ``MemoryEngine``."""
def __init__(self, engine: MemoryEngine | None = None) -> None:
"""
Args:
engine: Optional pre-built engine (tests inject a scoped one). When
omitted, a default env-configured engine is built lazily.
"""
self._engine = engine
self._initialized = bool(
engine is not None and getattr(engine, "_running", False)
)
def set_engine(self, engine: MemoryEngine) -> None:
"""Inject the shared gateway-owned engine after construction.
Used by the gateway to replace the lazily-built default with the one
DI-registered singleton so the memory RPC surface and the session-close
cold-migration path operate on the same engine (SPEC-010 FR-001).
Args:
engine: The gateway's singleton ``MemoryEngine``.
"""
self._engine = engine
self._initialized = getattr(engine, "_running", False)
@traces(spec="010", story="US-001", reqs=["REQ-010-003"])
async def _ensure_engine(self) -> MemoryEngine:
if self._engine is None:
self._engine = MemoryEngine()
if not self._initialized:
await self._engine.initialize({})
self._initialized = True
return self._engine
@staticmethod
def _require(params: dict[str, Any], name: str) -> Any:
value = params.get(name)
if value in (None, ""):
raise RPCError(
f"memory operation requires '{name}'", rpc_code=_INVALID_PARAMS
)
return value
@traces(
spec="010",
story="US-001",
reqs=["REQ-010-006", "REQ-010-009"],
)
async def handle_store(
self, connection_id: str, params: dict[str, Any]
) -> dict[str, Any]:
"""Handle memory.store — persist a memory entry to the hot store."""
engine = await self._ensure_engine()
entry = await engine.store(
content=self._require(params, "content"),
volk_id=params.get("volk_id", "HUMAN"),
session_id=self._require(params, "session_id"),
key=params.get("key"),
tags=params.get("tags"),
metadata=params.get("metadata"),
)
return {"entry": _entry_to_dict(entry)}
@traces(spec="010", story="US-001", reqs=["REQ-010-011"])
async def handle_retrieve(
self, connection_id: str, params: dict[str, Any]
) -> dict[str, Any]:
"""Handle memory.retrieve — fetch a single entry by key (hot then cold)."""
engine = await self._ensure_engine()
entry = await engine.retrieve(self._require(params, "key"))
return {"entry": _entry_to_dict(entry) if entry else None}
@traces(
spec="010",
story="US-004",
reqs=["REQ-010-015", "REQ-010-019"],
)
async def handle_search(
self, connection_id: str, params: dict[str, Any]
) -> dict[str, Any]:
"""Handle memory.search — semantic search across stored memories."""
engine = await self._ensure_engine()
results = await engine.search(
self._require(params, "query"),
limit=int(params.get("limit", 10)),
min_similarity=float(params.get("min_similarity", 0.7)),
)
return {"results": [_entry_to_dict(e) for e in results]}
async def handle_delete(
self, connection_id: str, params: dict[str, Any]
) -> dict[str, Any]:
"""Handle memory.delete — remove an entry from both stores."""
engine = await self._ensure_engine()
deleted = await engine.delete(self._require(params, "key"))
return {"deleted": deleted}