Memory
Give an agent durable conversation history, persistent facts, and free-text recall — in RAM by default or backed by SQLite with FTS5 search.
By default an agent is stateless: each run() starts from a blank transcript and forgets everything when the process exits. Memory changes that. Attach a memory backend and the agent gains three things that survive across turns — and, with the SQLite backend, across restarts.
The three concerns
Memory bundles three related responsibilities behind one small interface:
- Conversation messages — the running transcript (
get_messages,add_messages,clear_messages). This is what the agent has said and heard. - Persistent facts — a key/value store you write to deliberately (
remember,forget). Facts are not the transcript; they are durable notes the agent chooses to keep. - Recall — free-text search over stored facts (
recall). Ask a question, get back the values of matching facts.
The split matters: conversation history is the raw turn-by-turn record, while facts are a curated long-term store. A long chat can be compacted or dropped without losing the facts the agent deliberately wrote down.
Memory's message methods are the storage layer. To control how long that transcript grows before it is compacted — sliding window, truncation, or LLM summary — use a history policy via Agent(history=...). The two compose: memory persists messages, history decides how many to keep in the prompt.
Attaching memory to an agent
Pass a backend to memory=. With no argument, Memory() keeps everything in process RAM.
from agentroute import Agent, Memory
agent = Agent(
name="assistant",
model="claude-sonnet-4",
memory=Memory(),
)
result = agent.run("My name is Ada.")
print(result) # "Nice to meet you, Ada."The agent's tools can reach the same backend through ctx.memory. The Context object carries the active memory instance, so any tool can read and write facts (see context and usage).
In-RAM vs SQLite
There are two concrete backends, both satisfying the same protocol.
Memory is the default. Conversation and facts live in a Python list and dict inside the process. It is zero-config and fast, but everything is lost when the process exits. recall does a case-insensitive substring match over fact keys and values.
MemorySQLite persists messages and facts to a SQLite file that is created on first use. Facts are searched with SQLite's FTS5 full-text index — so recall("dietary") matches a fact stored under food_preferences if its value mentions dietary needs, with proper tokenization rather than naive substring matching. If the local SQLite build lacks FTS5, it transparently falls back to a LIKE query, and a malformed FTS query also falls back to LIKE, so recall never raises on bad input.
from agentroute import Agent, MemorySQLite
agent = Agent(
name="assistant",
model="claude-sonnet-4",
memory=MemorySQLite("agent.db"),
)Memory() is in-RAM: nothing survives a restart, which is exactly what you want for ephemeral sessions and tests. MemorySQLite("agent.db") writes to disk and survives restarts, at the cost of file I/O and a database to manage. Pick in-RAM for throwaway runs; pick SQLite when the agent must remember between processes.
Remembering across runs
Because facts persist, a fact stored in one run is available in the next — even after the process restarts, with MemorySQLite. Reuse the same backend instance (or the same database path) and the agent picks up where it left off.
from agentroute import Agent, MemorySQLite
agent = Agent(
name="assistant",
model="claude-sonnet-4",
memory=MemorySQLite("agent.db"),
instructions="Remember facts the user shares, and recall them when relevant.",
)
# First run — the agent stores a fact.
agent.run("I'm allergic to peanuts. Please remember that.")
# Later run (even a fresh process) — the fact is recalled from agent.db.
result = agent.run("Suggest a snack for me.")
print(result) # avoids peanutsReading and writing facts from a tool
Tools that annotate their first parameter as Context receive it automatically, and ctx.memory is the live backend. Call remember to store a fact and recall to search.
from agentroute import Agent, Context, MemorySQLite
agent = Agent(name="assistant", model="claude-sonnet-4", memory=MemorySQLite("agent.db"))
@agent.tool
async def save_preference(ctx: Context, key: str, value: str) -> str:
"""Persist a user preference for future conversations."""
await ctx.memory.remember(key, value)
return f"Saved {key}."
@agent.tool
async def lookup(ctx: Context, query: str) -> list[str]:
"""Search stored preferences by free text."""
return await ctx.memory.recall(query, limit=5)recall returns fact values (a list[str]), not keys. An empty query returns up to limit stored values, which is a handy way to dump everything the agent knows.
The MemoryProto interface
Every backend implements the same runtime-checkable protocol. Build your own (Redis, Postgres, a vector store) by satisfying these six async methods:
| Parameter | Type | Default | Description |
|---|---|---|---|
get_messages | (limit: int | None = None) -> list[Message] | — | Return the stored transcript; the most recent limit messages when a limit is given. |
add_messages | (messages: list[Message]) -> None | — | Append messages to the transcript. |
clear_messages | () -> None | — | Drop the entire transcript. |
remember | (key: str, value: str) -> None | — | Upsert a fact under key. |
recall | (query: str, limit: int = 10) -> list[str] | — | Search facts and return up to limit matching values. |
forget | (key: str) -> None | — | Delete the fact stored under key. |
All methods are async. The in-RAM Memory and MemorySQLite both satisfy MemoryProto, so they are interchangeable wherever a backend is expected.