Memory
Reference for the Memory protocol and its in-RAM and SQLite-backed implementations — conversation history, persistent facts, and recall.
Memory gives an agent a place to keep three things: conversation history, persistent key/value facts, and a way to recall those facts by free-text search. Attach an implementation with Agent(memory=...), and any tool whose first parameter is annotated Context can reach it via ctx.memory.
Two concrete implementations ship with the SDK. Memory keeps everything in process RAM and loses it on restart. MemorySQLite persists to a SQLite file with FTS5 full-text search. Both satisfy the MemoryProto protocol, so they are interchangeable.
For the conceptual overview — when to use memory, how facts differ from history, and patterns for tools — see Memory concepts.
Memory stores conversation messages and durable facts. History is a separate concern: it is a compaction policy that trims the transcript sent to the model on each turn. They are independent and can be combined.
MemoryProto
MemoryProto is the structural protocol every memory implementation satisfies. It is runtime_checkable, so isinstance(obj, MemoryProto) works. You only need it when writing your own backend or type-annotating a parameter that accepts any memory.
from agentroute import Memory # MemoryProto is exposed publicly as the type surface@runtime_checkable
class MemoryProto(Protocol):
async def get_messages(self, limit: int | None = None) -> list[Message]: ...
async def add_messages(self, messages: list[Message]) -> None: ...
async def clear_messages(self) -> None: ...
async def remember(self, key: str, value: str) -> None: ...
async def recall(self, query: str, limit: int = 10) -> list[str]: ...
async def forget(self, key: str) -> None: ...Every method is async. The surface splits into two groups: message methods (get_messages, add_messages, clear_messages) manage conversation history, and fact methods (remember, recall, forget) manage durable key/value knowledge.
The shared method surface
Both Memory and MemorySQLite implement the methods below identically in signature and contract. Differences are only in durability and search backend, noted per implementation.
get_messages
async def get_messages(self, limit: int | None = None) -> list[Message]Return stored conversation messages in chronological order.
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | int | None | None | Maximum number of messages to return. None returns everything. A value <= 0 returns an empty list. When a limit is set, the most recent limit messages are returned (still in chronological order). |
add_messages
async def add_messages(self, messages: list[Message]) -> NoneAppend messages to the conversation. Existing messages are preserved.
| Parameter | Type | Default | Description |
|---|---|---|---|
messagesrequired | list[Message] | — | Messages to append, in order. |
clear_messages
async def clear_messages(self) -> NoneRemove all stored conversation messages. Facts are untouched.
remember
async def remember(self, key: str, value: str) -> NoneStore a fact under key. Writing an existing key overwrites its value (upsert).
| Parameter | Type | Default | Description |
|---|---|---|---|
keyrequired | str | — | Identifier for the fact. |
valuerequired | str | — | The fact text to store and index for recall. |
recall
async def recall(self, query: str, limit: int = 10) -> list[str]Search stored facts and return matching values. An empty query returns up to limit values with no filtering.
| Parameter | Type | Default | Description |
|---|---|---|---|
queryrequired | str | — | Free-text search. Matching is implementation-specific (substring vs. FTS5) — see each implementation below. |
limit | int | 10 | Maximum number of values to return. |
forget
async def forget(self, key: str) -> NoneDelete the fact stored under key. A missing key is a no-op.
| Parameter | Type | Default | Description |
|---|---|---|---|
keyrequired | str | — | The key to delete. |
Source: memory/__init__.py
Memory
Memory is the default in-RAM implementation. Conversation messages and facts live in process memory and are lost on restart. Use it for short-lived sessions, tests, or when persistence is not needed.
from agentroute import Memoryclass Memory:
def __init__(self) -> None: ...The constructor takes no arguments. recall does a case-insensitive substring match over both keys and values, returning the values of the first limit hits.
Example
import asyncio
from agentroute import Memory
async def main():
mem = Memory()
# Store a couple of facts.
await mem.remember("user_name", "Ada")
await mem.remember("favorite_lang", "Python")
# Recall by substring (case-insensitive over keys and values).
hits = await mem.recall("python")
print(hits) # ["Python"]
# Forget one, then list everything (empty query).
await mem.forget("favorite_lang")
print(await mem.recall("")) # ["Ada"]
asyncio.run(main())Attach it to an agent and reach it from a tool through ctx.memory:
from agentroute import Agent, Context, Memory
agent = Agent(
name="assistant",
model="claude-sonnet-4",
memory=Memory(),
)
@agent.tool
async def remember_preference(ctx: Context, key: str, value: str) -> str:
"""Save a user preference for later in the conversation."""
await ctx.memory.remember(key, value)
return f"Saved {key}."
result = agent.run("Remember that my timezone is CET.")
print(result)Source: memory/__init__.py
MemorySQLite
MemorySQLite persists messages and facts to a SQLite file, so they survive process restarts. Fact search uses FTS5 full-text matching when the SQLite build supports it, falling back to LIKE substring matching otherwise. The database file is created on first use.
from agentroute import MemorySQLiteclass MemorySQLite:
def __init__(self, path: str | Path) -> None: ...| Parameter | Type | Default | Description |
|---|---|---|---|
pathrequired | str | Path | — | Filesystem path to the SQLite database file. Created automatically if it does not exist. |
It implements the full MemoryProto surface with the same signatures as Memory. Two behaviors are worth knowing:
recalluses FTS5. When full-text search is available,queryis passed as an FTS5MATCHexpression. A malformed FTS query falls back toLIKEsubstring matching, so plain words always work.- Writes are serialized. Each operation runs on a background thread under an internal lock, keeping the event loop unblocked while ensuring consistent writes.
close
async def close(self) -> NoneClose the underlying SQLite connection. Optional — useful in tests or when you want to release the file handle before reopening. The connection is reopened lazily on the next operation.
Example
import asyncio
from agentroute import MemorySQLite
async def main():
mem = MemorySQLite("agent.db") # created on first use
await mem.remember("project", "AgentRoute is a PaaS for AI agents.")
await mem.remember("stack", "Python SDK, FastAPI, Next.js dashboard.")
# FTS5 full-text search.
print(await mem.recall("agents")) # ["AgentRoute is a PaaS for AI agents."]
await mem.close() # optional; reopens lazily next call
asyncio.run(main())Attach it for an agent that remembers across runs:
from agentroute import Agent, MemorySQLite
agent = Agent(
name="researcher",
model="claude-sonnet-4",
memory=MemorySQLite("researcher.db"),
)
# Facts written in one run are still there in the next process.
print(agent.run("What did we decide last time about the database?"))Source: memory/sqlite.py