# m3-memory ↔ PydanticAI

Give a [PydanticAI](https://ai.pydantic.dev) agent **persistent, local-first,
cross-agent memory** — backed by m3.

```bash
pip install "m3-memory[pydantic-ai]"
```

PydanticAI ships **no built-in persistent memory** (each run starts fresh). This
adapter adds it two ways — pick whichever fits.

## Tier 1 — tools + auto-recall (the quick path)

```python
from pydantic_ai import Agent
from m3_memory.pydantic_ai import M3Deps, register_m3_tools, m3_recall_processor

agent = Agent(
    "anthropic:claude-sonnet-5",
    deps_type=M3Deps,
    history_processors=[m3_recall_processor()],   # optional: auto-inject recalled memories
)
register_m3_tools(agent)                            # adds remember / recall / forget tools

agent.run_sync("remember I prefer dark roast", deps=M3Deps(user_id="alice"))
```

- **`register_m3_tools(agent)`** attaches three tools the model can call:
  `remember(content, importance)`, `recall(query, limit)`, `forget(memory_id)`.
- **`m3_recall_processor(k=5)`** is a
  [history processor](https://ai.pydantic.dev): on each turn it searches m3 for
  the latest user message and prepends the most relevant memories as context —
  automatic recall with no glue code. Bounded (latest turn only, top-`k`), and
  never raises into the run.

## Tier 2 — a first-class toolset (formal conformance)

`M3MemoryToolset` subclasses PydanticAI's concrete `FunctionToolset`, so it **is**
a PydanticAI `AbstractToolset` — attach it like any native toolset, compose it
(`.prefixed()`, `.filtered()`), introspect it:

```python
from pydantic_ai import Agent
from m3_memory.pydantic_ai import M3Deps, M3MemoryToolset

agent = Agent(
    "anthropic:claude-sonnet-5",
    deps_type=M3Deps,
    toolsets=[M3MemoryToolset()],
)
agent.run_sync("remember I like dark roast", deps=M3Deps(user_id="alice"))
```

`isinstance(M3MemoryToolset(), AbstractToolset)` is `True`.

## `M3Deps` — the injected memory service

`user_id` is **required** — m3 enforces per-tenant isolation (there is no
anonymous/global mode). Pass one `M3Deps` per user/session:

```python
M3Deps(
    user_id="alice",     # required — the tenant key
    scope="agent",       # where this agent's memories live (default)
    call_timeout=30.0,
)
```

## Why m3 for PydanticAI

- **Cross-agent memory.** A memory your PydanticAI agent writes is immediately
  searchable by every other m3 agent (Claude Code, a CrewAI crew, a LangChain
  app) sharing the store — and vice-versa. One store, every agent.
- **Real memory dynamics** for free: contradiction-aware supersession, recency
  that refreshes on recall, bitemporal history, commanded forgetting (GDPR),
  hybrid FTS+vector+MMR retrieval — all local, no server, no API key.
- **Backend-agnostic.** The adapter only speaks m3's tool dispatch, so it works
  over SQLite and PostgreSQL with no per-backend code.

## Requirements

- **pydantic-ai ≥ 2.0, < 3** (either `pydantic-ai` or the lighter
  `pydantic-ai-slim`). Older versions fail loud with an upgrade hint.
- **No Python cap.** Unlike the CrewAI adapter, PydanticAI is built on Pydantic v2
  (no chromadb / pydantic-v1), so it installs and runs on **Python 3.14** with a
  normal `pip install` — same interpreter m3 itself runs on.

> **Verified 2026-07-17 on Python 3.14.6** against **pydantic-ai-slim 2.12.0**:
> `import` succeeds with a plain `pip install` (no override), `isinstance(
> M3MemoryToolset(), AbstractToolset)` holds, all three tools register, an `Agent`
> built with the toolset runs every tool via `TestModel`, and a `remember()` →
> `recall()` round-trip returns the written memory.

## How it maps

| PydanticAI | m3 |
|---|---|
| `remember` tool / `M3Deps.remember` | `memory_write` (auto-classified, tenant-stamped) |
| `recall` tool / `M3Deps.recall` | `memory_search_scored` (hybrid FTS+vector+MMR; m3 embeds the text query) |
| `forget` tool / `M3Deps.forget` | `memory_delete_bulk` (bi-temporal soft-delete) |
| `m3_recall_processor` | `memory_search_scored` on the latest user turn → prepended context |
| `deps=M3Deps(user_id=…)` | per-tenant isolation (§7) |

See the repo's [`docs/EXTENDING.md`](../../../docs/EXTENDING.md) for the general
framework-adapter recipe this follows (Recipe 2).
