Skip to content

Agent Memory (Python SDK)

Add memory to any AI agent in three lines. Pass raw conversations in, get structured, searchable, identity-linked memory out.

python
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

bash
pip install kanoniv

No 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:

  1. Extract - The LLM reads the conversation and pulls out discrete, standalone facts
  2. Deduplicate - The LLM compares new facts against existing memories and decides: add (new info), update (refines existing), or skip (already known)
python
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 quarter

Each 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

python
results = mem.search("EHR migration plans")
for r in results:
    print(r.content)
# Meridian Health is migrating from Epic to Cerner next quarter

Search uses semantic similarity (if sentence-transformers is installed), FTS5 full-text search, or keyword matching as fallback.

3. Retrieve by user

python
# 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

python
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

python
# 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:

python
# 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:

python
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 question

Example: Multi-Agent Research Pipeline

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

python
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)
)
ParameterRequiredDefaultDescription
agent_nameNo"default"Agent name, used as author on memory entries
api_keyNoOPENAI_API_KEY envAPI key for LLM extraction. Without this, add() is unavailable but memorize()/search() still work
modelNo"gpt-4.1-nano"Any model supported by the API
api_base_urlNo"https://api.openai.com"Base URL for OpenAI-compatible API (Ollama, vLLM, etc.)
db_pathNo"~/.kanoniv/memory.db"Path to SQLite database

mem.add()

Primary interface. Extract facts from a conversation and store them.

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

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

Search memories by semantic similarity or keyword.

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

python
# 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.

python
# 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.

python
mem.forget("entry-uuid-here")

mem.sync()

Sync local memories to cloud. Pushes unsynced entries, pulls shared memories from other agents.

python
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)

python
mem = kanoniv.get_memory(api_key="sk-...")

Ollama (local, free)

python
mem = kanoniv.get_memory(
    api_key="ollama",
    api_base_url="http://localhost:11434",
    model="llama3.2",
)

Anthropic (via proxy)

python
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

python
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.db with 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

The identity and delegation layer for AI agents.