Cross-Session Memory¶
By default, each agent session is isolated — when a new session starts, prior context is lost. The Memory Service enables agents to recall information from past sessions, making them smarter over time.
This is especially valuable for DevOps/SRE use cases:
- Correlate a current incident with a similar one from last week
- Recall what steps resolved a previous Kafka consumer lag spike
- Detect recurring patterns ("this pod crashes every Monday morning")
How It Works¶
sequenceDiagram
participant User
participant Agent
participant LoadMemoryTool
participant MemoryPlugin
participant SecureMemoryService
User->>Agent: "Why is Kafka lagging again?"
Agent->>LoadMemoryTool: load_memory("Kafka consumer lag") (model decides to look)
LoadMemoryTool->>SecureMemoryService: search_memory(query)
SecureMemoryService-->>LoadMemoryTool: Past incidents about Kafka lag
LoadMemoryTool-->>Agent: Return matches as tool result
Agent-->>User: "This looks similar to the lag spike on March 15..."
Note over MemoryPlugin: After session completes
MemoryPlugin->>SecureMemoryService: add_session_to_memory(session)
Note over SecureMemoryService: Redact secrets, enforce limits, store The memory system has these components:
| Component | Role |
|---|---|
SecureMemoryService | Security wrapper — redacts secrets and caps storage, then delegates to a swappable inner backend |
DatabaseMemoryService | Persistent inner backend (PostgreSQL) — durable, cross-restart, shared across replicas |
create_memory_service() | Factory that assembles the two from a DB URL (Postgres when set, in-memory otherwise) |
MemoryPlugin | Auto-saves sessions to memory after the root agent completes |
LoadMemoryTool | ADK tool the model invokes (load_memory) to search past memories when it judges them relevant |
SecureMemoryService is always the outer layer; its inner backend is either ADK's in-memory InMemoryMemoryService (default) or the Postgres-backed DatabaseMemoryService for durable recall.
Security Hardening¶
The SecureMemoryService wraps whichever backend it delegates to (in-memory or the Postgres-backed DatabaseMemoryService) and adds two layers of protection — so redaction and storage caps apply regardless of where memories are persisted:
Sensitive Data Redaction¶
All event content is redacted at write time — secrets never enter the memory store. The following patterns are automatically detected and replaced with [REDACTED]:
password=...,token=...,secret=...,api_key=...,bearer=...- PEM private key blocks (
-----BEGIN RSA PRIVATE KEY-----) - Any key-value pair where the key matches common secret names
You can also supply custom patterns:
import re
from orrery_core import SecureMemoryService
memory = SecureMemoryService(
sensitive_patterns=[
re.compile(r"(?i)my_custom_secret\s*[:=]\s*\S+"),
]
)
Bounded Storage¶
Memory is capped at 500 events per user by default (configurable via max_entries_per_user). When the limit is reached, the oldest events are evicted (FIFO). This prevents unbounded memory growth in long-running deployments.
User Isolation¶
Memory is scoped by app_name and user_id — inherited from ADK's design. User A cannot search User B's memory, and the orrery_assistant app cannot see memories from the ops_journal app.
Setup¶
Enable Memory in Persistent Mode¶
run_persistent auto-wires memory to match the session store: when DATABASE_URL (or a db_url) is set it co-locates recall in PostgreSQL via create_memory_service(), otherwise it uses an in-memory backend. You only need to switch on the plugin:
import asyncio
from orrery_core import default_plugins, run_persistent
from my_agent.agent import root_agent
asyncio.run(
run_persistent(
root_agent,
app_name="my_agent",
# memory_service is auto-created from DATABASE_URL; pass one only to override.
plugins=default_plugins(enable_memory=True),
)
)
enable_memory=True activates the MemoryPlugin, which auto-saves sessions after each root agent interaction (skipping trivial sessions with fewer than 4 events).
To build the service explicitly — e.g. for a custom Runner — use the factory:
from orrery_core import create_memory_service
# Postgres-backed, redacted, durable recall:
memory = create_memory_service(db_url="postgresql+asyncpg://agents:…@localhost:5432/agents")
# No db_url (and no DATABASE_URL) → in-memory, non-durable.
Persistence fails fast by design
If a DATABASE_URL is set but PostgreSQL is unreachable, create_memory_service raises rather than silently falling back to in-memory recall (which would be lost on restart and split across replicas). Set ORRERY_DB_ALLOW_INMEMORY_FALLBACK=1 to opt into the fallback for local dev. See Troubleshooting → Sessions & storage.
Add Memory Tools to Your Agent¶
For the agent to use memory, add LoadMemoryTool to its tools:
from google.adk.tools.load_memory_tool import LoadMemoryTool
from orrery_core import create_agent
root_agent = create_agent(
name="my_agent",
description="...",
instruction="You have a `load_memory` tool that searches past sessions. "
"Call it before diagnosing a symptom or when the user references an "
"earlier incident; skip it for greetings and simple status checks. "
"Pass a short, specific query (system + symptom), not the whole message.",
tools=[..., LoadMemoryTool()],
)
LoadMemoryTool exposes a load_memory function the model calls only when it judges past context useful, with a query it crafts itself. This keeps simple turns cheap, but recall quality depends on the instruction telling the model when to look — so be explicit about it (as above). The alternative, PreloadMemoryTool, searches memory on every turn and auto-injects the hits into the prompt with no model decision; prefer it only if guaranteed recall matters more than per-turn cost.
Configuration¶
MemoryPlugin Options¶
| Parameter | Default | Description |
|---|---|---|
enable_memory | False | Enable auto-save of sessions to memory |
memory_min_events | 4 | Minimum events before a session is saved (filters out trivial interactions) |
SecureMemoryService Options¶
| Parameter | Default | Description |
|---|---|---|
max_entries_per_user | 500 | Maximum events stored per user; oldest evicted when exceeded |
sensitive_patterns | Built-in set | List of re.Pattern objects for custom redaction rules |
Plugin Order¶
The MemoryPlugin is registered as part of default_plugins() when enabled. It runs after ActivityPlugin and before ErrorHandlerPlugin:
1. GuardrailsPlugin — RBAC + confirmation
2. ResiliencePlugin — circuit breaker
3. MetricsPlugin — Prometheus metrics
4. AuditPlugin — structured audit logs
5. ActivityPlugin — session activity tracking
6. MemoryPlugin — cross-session memory persistence
7. ErrorHandlerPlugin — graceful error recovery
Production Considerations¶
- Persistence — ✅ available today. Set
DATABASE_URLand recall is stored in PostgreSQL viaDatabaseMemoryService— durable across restarts and shared across replicas. The in-memory backend remains the default only when no database is configured (development/testing). - Semantic search — keyword matching may still miss relevant memories.
DatabaseMemoryServicemirrors ADK's keyword-matching semantics (backed by durable storage). For LLM-powered semantic recall, implement a customBaseMemoryServicebacked by PostgreSQL + pgvector, or switch toVertexAiMemoryBankService. - Memory growth — the
max_entries_per_usercap applies per save; monitor table growth in long-running deployments.
The SecureMemoryService uses a delegation pattern, so swapping the inner backend (in-memory ↔ Postgres ↔ a custom service) requires only a constructor change — no agent modifications needed.