Pydantic AI

Give a Pydantic AI agent memory of its users. recall-pydantic-ai starts every run with what the agent knows about the user, and gives the model a remember tool that stores new facts in Recall.

Facts are typed: each one is a kind of fact from a fixed list, with a value. So when a user says "actually, call me Sam", the new name replaces the old one instead of sitting next to it, and the old one stays in the history. Reading memory needs no second model and no embedding service.

Install and use

pip install recall-pydantic-ai "pydantic-ai-slim[anthropic]"

recall-pydantic-ai also installs the recall binary and the polign_db server for Linux, macOS and Windows, so there is no separate database to download. The anthropic extra is only for the model in this example.

from dataclasses import dataclass

from polign_recall import Client
from pydantic_ai import Agent
from recall_pydantic_ai import RecallMemory


@dataclass
class Deps:
    user_id: str


recall = Client(local_dir="./recall-data", typed=True)   # one per process

agent = Agent(
    "anthropic:claude-sonnet-5-5",
    deps_type=Deps,
    instructions="You help engineers with their projects.",
    capabilities=[RecallMemory(recall, subject=lambda ctx: ctx.deps.user_id)],
)

agent.run_sync("I'm Alex. We deploy to Fly.io.", deps=Deps("user-42"))
result = agent.run_sync("Where do we deploy again?", deps=Deps("user-42"))
print(result.output)   # You deploy to Fly.io.

The second run gets no chat history. It answers from memory: the first run stored name and deployment_target through the remember tool, and the second run started with both in its instructions.

What happens in a run

subject is a fixed string or a function of the run context. Use a stable identifier that comes from your sign-in, never a session or conversation id.

The tools

ToolWhen the model gets itWhat it does
rememberBy defaultStores one fact. The list of allowed kinds comes from Recall's registry, so the model cannot invent new ones.
forgetWith forget_tool=TrueWithdraws a fact, or every value of one kind, when the user asks. The record of the change is kept.
search_memoryWhen the user has more than limit factsSearches the facts that did not fit in the instructions.

Options

OptionDefaultWhat it does
clientrequiredA polign_recall.Client. Create one per process and share it.
subjectrequiredThe user, as a string or lambda ctx: ...
limit20Facts loaded into the instructions.
who"the user"How the instructions and tool descriptions refer to the person.
template<recall_memory> blockWrapper around the facts; must contain {context}.
remember_toolonOff makes the memory read-only.
forget_tooloffLets users withdraw a fact by asking.
search_when_overflowedonSearch for the prompt when the user has more than limit facts.
read_timeout, write_timeout2 s, 10 sHow long to wait for Recall before giving up.

Your own kinds of facts

The built-in registry covers names, preferences, project facts and free-form notes. To use your own, write it as JSON and point the client at it:

{
  "name":       { "cardinality": "single", "value_type": "string", "description": "The name the user asks to be called" },
  "plan_tier":  { "cardinality": "single", "value_type": "string", "description": "The user's subscription plan" },
  "open_issue": { "cardinality": "multi",  "value_type": "string", "description": "A problem the user reported that is not resolved yet" }
}
recall = Client(local_dir="./recall-data", env={"POLIGN_PREDICATES": "registry.json"}, typed=True)

single means a newer value replaces the old one. multi means each value is one more fact. value_type is string, number, or boolean, and the tool converts what the model sends: "1,200" becomes 1200 and "yes" becomes true. Your file replaces the built-in kinds, except note, which Recall always keeps for statements nothing else fits.

Where the memory lives

Client(local_dir="./recall-data", typed=True) keeps the memory on this machine. typed=True asks for the typed tools this capability files beliefs with (recall 0.18 or later). The first process to open the directory starts a polign_db server for it in the background, listening only on localhost, and other processes share it. The server keeps running after your program exits; its process id is in recall-data/runtime.json.

Agents on several machines need one shared memory. Run recall serve where they can all reach it, storing into your S3, GCS or Azure bucket (Run polign_db shows how), and connect to it:

recall = Client(url="https://memory.internal:8443", api_key=os.environ["RECALL_API_KEY"], typed=True)  # a recall serve started with -typed

Client.from_env() reads RECALL_URL and RECALL_API_KEY, and starts a local process when RECALL_URL is not set, so the same code runs on a laptop and in production.

When Recall is down

Limits

Source & license

recall-pydantic-ai is open source under the Apache License 2.0 and lives next to the Python client in github.com/Polign/polign. Bug reports and pull requests go there.