Agent Memory (Python SDK)
Add memory to any AI agent in three lines. Pass raw conversations in, get structured, searchable, identity-linked memory out.
import kanoniv
mem = kanoniv.get_memory(agent_name="support-agent", api_key="sk-...")
mem.add([
{"role": "user", "content": "I'm Bill from Acme, switch me to annual billing"},
{"role": "assistant", "content": "Done! Updated to annual billing."},
], user_id="[email protected]")That's it. The LLM extracts facts ("Bill prefers annual billing", "Bill works at Acme"), deduplicates against existing memories, and stores each as a structured entry in a local SQLite database.
Installation
pip install kanonivNo signup required. Memory works locally with zero config.
How It Works
1. Add conversations, get memories
add() runs a two-phase LLM pipeline on your messages:
- Extract - The LLM reads the conversation and pulls out discrete, standalone facts
- Deduplicate - The LLM compares new facts against existing memories and decides: add (new info), update (refines existing), or skip (already known)
mem = kanoniv.get_memory(agent_name="research-agent", api_key="sk-...")
results = mem.add([
{"role": "user", "content": "My name is Sarah Chen, I'm the CTO at Meridian Health"},
{"role": "assistant", "content": "Nice to meet you Sarah! How can I help?"},
{"role": "user", "content": "We're migrating from Epic to Cerner next quarter"},
], user_id="[email protected]")
for entry in results:
print(f"[{entry.entry_type}] {entry.content}")
# [knowledge] Sarah Chen is the CTO at Meridian Health
# [decision] Meridian Health is migrating from Epic to Cerner next quarterEach fact becomes a separate MemoryEntry with:
- content - The extracted fact
- entry_type - Classified as
knowledge,decision,investigation,task, etc. - entity_fields - Identifying info (email, name, company) for identity resolution
- author - Which agent created it
- metadata - user_id, agent_id, run_id for scoping
2. Search by meaning
results = mem.search("EHR migration plans")
for r in results:
print(r.content)
# Meridian Health is migrating from Epic to Cerner next quarterSearch uses semantic similarity (if sentence-transformers is installed), FTS5 full-text search, or keyword matching as fallback.
3. Retrieve by user
# Get all memories for a specific person
memories = mem.get_all(user_id="[email protected]")
# Filter by agent
memories = mem.get_all(user_id="[email protected]", agent_id="research-agent")
# Filter by session
memories = mem.get_all(run_id="session-42")4. Sync to cloud for cross-agent sharing
from kanoniv import Client
client = Client(api_key="kn_live_...")
mem.sync(client)Once synced, identity resolution kicks in. "Bill Smith" from one agent and "William Smith" from another get linked to the same canonical entity. Their memories merge automatically.
Identity Resolution + Memory
This is where Kanoniv differs from every other memory system. Memory is linked to canonical identities, not just string IDs.
The Problem
Agent A talks to [email protected]. Agent B talks to [email protected]. Both are the same person, but a typical memory system stores them separately. Agent B never sees Agent A's context.
The Solution
# Agent A - Support agent
mem_a = kanoniv.get_memory(agent_name="support-agent", api_key="sk-...")
mem_a.add([
{"role": "user", "content": "I'm Bill from Acme, switch me to annual billing"},
{"role": "assistant", "content": "Done!"},
], user_id="[email protected]")
# Agent B - Sales agent (different agent, different email for same person)
mem_b = kanoniv.get_memory(agent_name="sales-agent", api_key="sk-...")
mem_b.add([
{"role": "user", "content": "This is William Smith, I need a quote for 50 seats"},
{"role": "assistant", "content": "Sure! Let me pull that together."},
], user_id="[email protected]")
# Both agents sync to cloud
from kanoniv import Client
client = Client(api_key="kn_live_...")
mem_a.sync(client)
mem_b.sync(client)After sync, Kanoniv's identity resolution matches [email protected] and [email protected] to the same canonical entity. Now any agent resolving either email gets all memories from both agents:
# Agent C resolves the entity and gets full context
client.resolve_rt.realtime(
source_name="crm",
external_id="contact_123",
data={"email": "[email protected]"},
include_memory=True,
)
# Returns:
# entity_id: "ENT_7f82"
# memory: [
# "Bill prefers annual billing" (from support-agent)
# "William Smith needs a quote for 50 seats" (from sales-agent)
# ]No shared database. No message passing between agents. Identity resolution is the glue.
Example: Customer 360
Build a customer memory that accumulates across every touchpoint:
import kanoniv
mem = kanoniv.get_memory(agent_name="customer-360", api_key="sk-...")
# Support conversations
mem.add([
{"role": "user", "content": "I keep getting charged twice for the Pro plan"},
{"role": "assistant", "content": "I see the issue - there's a duplicate subscription. Fixed."},
], user_id="[email protected]")
# Sales conversations
mem.add([
{"role": "user", "content": "We're growing fast, need to upgrade to Enterprise"},
{"role": "assistant", "content": "Great! Enterprise includes SSO and priority support."},
], user_id="[email protected]")
# Onboarding conversations
mem.add([
{"role": "user", "content": "Can we import data from our Snowflake warehouse?"},
{"role": "assistant", "content": "Yes, we have a native Snowflake connector."},
], user_id="[email protected]")
# Any agent can now search across all interactions
mem.search("billing issues") # finds the duplicate charge
mem.search("upgrade plans") # finds the Enterprise interest
mem.search("data integration") # finds the Snowflake questionExample: Multi-Agent Research Pipeline
import kanoniv
# Agent 1: Prospect researcher
researcher = kanoniv.get_memory(agent_name="researcher", api_key="sk-...")
researcher.add([
{"role": "user", "content": "Research Meridian Health for our sales pipeline"},
{"role": "assistant", "content": "Meridian Health: 2,400 employees, HQ in Austin. "
"CTO is Sarah Chen. They use Epic EHR but evaluating Cerner. "
"Revenue ~$800M. Series D funded."},
], user_id="meridian-health")
# Agent 2: Competitive intel
intel = kanoniv.get_memory(agent_name="competitive-intel", api_key="sk-...")
intel.add([
{"role": "user", "content": "What EHR systems does Meridian Health use?"},
{"role": "assistant", "content": "Currently on Epic MyChart. Internal docs suggest "
"they're unhappy with Epic's pricing model. Budget review in Q3."},
], user_id="meridian-health")
# Agent 3: Outreach composer - gets context from both agents
outreach = kanoniv.get_memory(agent_name="outreach", api_key="sk-...")
context = outreach.get_all(user_id="meridian-health")
# Returns memories from BOTH researcher and competitive-intel agents:
# - "Meridian Health has 2,400 employees, HQ in Austin"
# - "CTO is Sarah Chen"
# - "Currently on Epic MyChart, evaluating Cerner"
# - "Unhappy with Epic's pricing model, budget review in Q3"API Reference
kanoniv.get_memory()
Create a memory instance.
mem = kanoniv.get_memory(
agent_name="my-agent", # Name of the agent (used as author)
api_key="sk-...", # OpenAI API key (or compatible provider)
model="gpt-4.1-nano", # LLM model for extraction (default: gpt-4.1-nano)
api_base_url="https://api.openai.com", # Any OpenAI-compatible API
db_path="~/.kanoniv/memory.db", # SQLite path (default)
)| Parameter | Required | Default | Description |
|---|---|---|---|
agent_name | No | "default" | Agent name, used as author on memory entries |
api_key | No | OPENAI_API_KEY env | API key for LLM extraction. Without this, add() is unavailable but memorize()/search() still work |
model | No | "gpt-4.1-nano" | Any model supported by the API |
api_base_url | No | "https://api.openai.com" | Base URL for OpenAI-compatible API (Ollama, vLLM, etc.) |
db_path | No | "~/.kanoniv/memory.db" | Path to SQLite database |
mem.add()
Primary interface. Extract facts from a conversation and store them.
results = mem.add(
messages, # List of {"role": ..., "content": ...} dicts
user_id="[email protected]", # Scope memories to this user
agent_id="support-agent", # Tag with agent ID in metadata
run_id="session-42", # Tag with session ID in metadata
metadata={"source": "slack"}, # Extra metadata
deduplicate=True, # Check existing memories (default: True)
)Returns a list of MemoryEntry objects (one per extracted fact).
Requires an API key. Raises RuntimeError if none is configured.
mem.memorize()
Low-level interface for manually storing a memory entry.
entry = mem.memorize(
"Customer prefers annual billing",
title="Billing preference",
entry_type="decision", # decision, investigation, pattern, knowledge, etc.
entity_fields={"email": "[email protected]"},
visibility="shared", # "shared" or "agent" (private)
metadata={"confidence": 0.95},
)Does not require an API key.
mem.search()
Search memories by semantic similarity or keyword.
results = mem.search("billing preferences", limit=10)Uses semantic search (cosine similarity on embeddings) if sentence-transformers is installed, otherwise falls back to FTS5, then keyword LIKE matching.
mem.get_all()
Retrieve all memories with optional filters.
# All memories
mem.get_all()
# By user
mem.get_all(user_id="[email protected]")
# By user + agent
mem.get_all(user_id="[email protected]", agent_id="support-agent")
# By session
mem.get_all(run_id="session-42")mem.recall()
Retrieve memories by entity ID or entity fields.
# By entity ID (from identity graph)
mem.recall(entity_id="ENT_7f82")
# By raw fields (local fuzzy match)
mem.recall(entity_fields={"email": "[email protected]"})mem.forget()
Delete a memory by ID.
mem.forget("entry-uuid-here")mem.sync()
Sync local memories to cloud. Pushes unsynced entries, pulls shared memories from other agents.
from kanoniv import Client
client = Client(api_key="kn_live_...")
mem.sync(client)Using with Different LLM Providers
add() works with any OpenAI-compatible chat completions API.
OpenAI (default)
mem = kanoniv.get_memory(api_key="sk-...")Ollama (local, free)
mem = kanoniv.get_memory(
api_key="ollama",
api_base_url="http://localhost:11434",
model="llama3.2",
)Anthropic (via proxy)
mem = kanoniv.get_memory(
api_key="sk-ant-...",
api_base_url="https://openai-proxy.anthropic.com",
model="claude-sonnet-4-6",
)vLLM / Together AI / any OpenAI-compatible
mem = kanoniv.get_memory(
api_key="your-key",
api_base_url="https://api.together.xyz",
model="meta-llama/Llama-3-70b-chat-hf",
)Architecture
Conversation messages
|
v
FactExtractor.extract() -- LLM call 1: extract discrete facts
|
v
FactExtractor.deduplicate() -- LLM call 2: compare against existing (optional)
|
v
LocalMemory.memorize() -- Store each fact in SQLite
|
v
MemoryStore (SQLite + FTS5) -- Local persistence, full-text search
|
v (optional)
mem.sync(client) -- Push to cloud, identity resolution links entities- Local storage: SQLite at
~/.kanoniv/memory.dbwith WAL mode, FTS5 full-text search - Embeddings: Optional local embeddings via
sentence-transformers(all-MiniLM-L6-v2, 384 dims) - LLM extraction: Any OpenAI-compatible API. Default:
gpt-4.1-nano(fast, cheap) - Cloud sync: Pushes local memories, pulls shared memories. Cloud resolves entity fields to canonical entities via identity resolution
