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 vs. History

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.

ParameterTypeDefaultDescription
limitint | NoneNoneMaximum 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).
Messages in oldest-to-newest order.

add_messages

async def add_messages(self, messages: list[Message]) -> None

Append messages to the conversation. Existing messages are preserved.

ParameterTypeDefaultDescription
messagesrequiredlist[Message]Messages to append, in order.
ReturnsNone
Nothing.

clear_messages

async def clear_messages(self) -> None

Remove all stored conversation messages. Facts are untouched.

ReturnsNone
Nothing.

remember

async def remember(self, key: str, value: str) -> None

Store a fact under key. Writing an existing key overwrites its value (upsert).

ParameterTypeDefaultDescription
keyrequiredstrIdentifier for the fact.
valuerequiredstrThe fact text to store and index for recall.
ReturnsNone
Nothing.

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.

ParameterTypeDefaultDescription
queryrequiredstrFree-text search. Matching is implementation-specific (substring vs. FTS5) — see each implementation below.
limitint10Maximum number of values to return.
Returnslist[str]
The values of matching facts. Keys are not returned.

forget

async def forget(self, key: str) -> None

Delete the fact stored under key. A missing key is a no-op.

ParameterTypeDefaultDescription
keyrequiredstrThe key to delete.
ReturnsNone
Nothing.

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 Memory
class 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

ram_memory.py
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:

agent_with_memory.py
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 MemorySQLite
class MemorySQLite:
    def __init__(self, path: str | Path) -> None: ...
ParameterTypeDefaultDescription
pathrequiredstr | PathFilesystem 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:

  • recall uses FTS5. When full-text search is available, query is passed as an FTS5 MATCH expression. A malformed FTS query falls back to LIKE substring 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) -> None

Close 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.

ReturnsNone
Nothing.

Example

sqlite_memory.py
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:

persistent_agent.py
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

Choosing an implementation