Skip to main content
  1. Posts/

Zettelkasten Memory for AI Agents — Semantic Knowledge Graphs That Grow With Every Conversation

AI agents forget everything. Every conversation starts from zero. Every tool call, every decision, every piece of context the agent worked hard to gather — gone the moment the session ends. Long-context windows help, but they’re a workaround, not a memory system. They don’t prioritize, don’t connect ideas, and don’t decay gracefully.

What if agents could build a knowledge graph that grows with every interaction — where important, well-connected memories surface first, and stale, isolated ones fade away?

That’s the idea behind zettelkasten-memory: a semantic memory layer for AI agents inspired by Niklas Luhmann’s Zettelkasten method.


What Is a Zettelkasten?
#

The Zettelkasten (“slip box” in German) is a knowledge management system invented by sociologist Niklas Luhmann, who used it to produce over 70 books and 400 academic papers across four decades. The system works on a few deceptively simple principles:

  1. Atomic notes. Each note captures one idea, one concept, one observation.
  2. Connections over hierarchy. Notes aren’t filed into folders — they’re linked to other notes. The value isn’t in any single note but in the web of connections between them.
  3. Emergence through linking. As the network grows, unexpected connections surface. Ideas cluster. Themes emerge that you didn’t plan for.
  4. Active retrieval. You don’t just store knowledge — you re-encounter it through the links. Every search traverses the graph.

What makes this relevant for AI agents: the Zettelkasten doesn’t require a schema. It doesn’t need categories defined upfront. It builds structure from the content itself. That’s exactly what a memory system for autonomous agents needs — structure that emerges from the work the agent does, not from a framework the developer imposes.


The Problem With Agent Memory Today
#

Most agent memory implementations fall into one of three buckets:

1. Context window stuffing. Append everything to the prompt. Works until you hit the token limit, then you’re truncating the oldest memories — which might be the most important ones.

2. Simple vector stores. Embed everything, retrieve by cosine similarity. Better, but every memory is an island. There’s no concept of connections between memories, no importance scoring, no decay. A memory retrieved once a day and a memory never touched again have equal standing.

3. Key-value stores. Fast and simple, but no semantic search. You need to know the exact key to retrieve something, which defeats the purpose when agents need to discover relevant context they didn’t explicitly plan for.

None of these capture what makes human memory useful: the ability to prioritize what matters, connect related ideas, and let irrelevant information fade.


Architecture
#

zettelkasten-memory treats every stored memory as a zettel — an atomic note with metadata, tags, connections, and an importance score. The system auto-links new memories to existing ones based on semantic similarity, creating a knowledge graph that grows organically.

ZettelMemory engine with pluggable backends and framework adapters
ZettelMemory engine with pluggable backends and framework adapters

What Happens When You Store a Memory
#

When an agent calls mem.add("The customer's Snowflake account uses key-pair auth with Azure AD"), the system:

  1. Generates an ID — SHA-256 hash of content + timestamp, truncated to 16 characters.
  2. Extracts tags — TF-IDF fit on the text pulls out the top terms (e.g., snowflake, key-pair, azure-ad, auth).
  3. Finds connections — queries the existing index for all zettels above a cosine similarity threshold (default 0.25). Each match gets a bidirectional link.
  4. Stores the zettel — with metadata, tags, connections, access timestamps, and an importance score.

The result: a new memory that’s automatically woven into the existing knowledge graph. No manual tagging, no folder structure, no schema.

Auto-linked knowledge graph with composite scoring — thicker lines indicate stronger semantic connections
Auto-linked knowledge graph with composite scoring — thicker lines indicate stronger semantic connections

What Happens When You Search#

When an agent calls mem.search("authentication setup"), the system computes a composite score that balances four factors:

score = similarity * importance * (0.7 + 0.3 * recency) * connection_boost
FactorWhat It Captures
SimilarityCosine similarity to the query (semantic relevance)
ImportanceUser-assigned or system-inferred priority (0.0–1.0)
RecencyTime decay: 1 / (1 + days_since_access) — recent memories rank higher
Connection boostWell-connected memories rank higher: 1 + 0.1 * min(connections, 10)

This means a highly-connected, recently-accessed, important memory will surface before an isolated, stale, low-importance one — even if the raw semantic similarity scores are close. That’s the Zettelkasten principle at work: well-connected ideas are more valuable than isolated ones.

Graph Traversal
#

Beyond flat search, agents can walk the knowledge graph. get_connected(zettel_id, max_hops=2) performs a BFS traversal from any zettel, following connections up to N hops. This surfaces related context the agent didn’t directly search for — the kind of serendipitous discovery that makes the Zettelkasten method powerful for humans.

Eviction
#

The system caps at max_zettels (default 5000). When full, it evicts the lowest-value memories using a survival score:

survival = importance * (1 + access_count) * recency

Memories that are important, frequently accessed, and recent survive. Stale, unused, low-importance memories are pruned. The knowledge graph self-maintains.


Pluggable Backends
#

The library ships with two backends and a protocol for building your own.

TF-IDF Backend (default) — uses scikit-learn’s TfidfVectorizer with English stop words and bigram support. Zero configuration, works offline, no API keys. Good for most agent workloads.

Embedding Backend — accepts any embedding function with the signature (list[str]) -> np.ndarray. Plug in OpenAI, Cohere, Voyage, sentence-transformers, or a local ONNX model:

from zettelkasten_memory import ZettelMemory
from zettelkasten_memory.backends import EmbeddingBackend
from openai import OpenAI

client = OpenAI()

def embed(texts: list[str]):
    resp = client.embeddings.create(model="text-embedding-3-small", input=texts)
    return [e.embedding for e in resp.data]

mem = ZettelMemory(backend=EmbeddingBackend(embed_fn=embed))

Both backends implement the SearchBackend protocol — build_index(), query(), find_similar(), extract_tags(), and serialization. Swap backends without changing agent code.


Framework Adapters
#

The library integrates with three major agent frameworks out of the box.

CrewAI
#

Drop-in replacement for CrewAI’s memory storage:

from zettelkasten_memory.adapters.crewai import ZettelStorage

storage = ZettelStorage(persist_path="crew_memory.json")

# Use with CrewAI's memory system
storage.save(
    value="Customer prefers Snowflake on AWS with PrivateLink",
    metadata={"agent": "research_agent"},
    score=0.9
)

results = storage.search(query="customer infrastructure")
# Returns: [{"context": "...", "metadata": {...}, "score": 0.87}]

Every save() auto-persists to disk. Memories are tagged with the originating agent, enabling multi-agent knowledge sharing.

LangGraph
#

Implements LangGraph’s BaseStore interface — supports GetOp, PutOp, SearchOp, and ListNamespacesOp:

from zettelkasten_memory.adapters.langgraph import ZettelStore

store = ZettelStore(persist_path="graph_memory.json")

# Namespace isolation (per-user, per-thread, etc.)
await store.aput(("user", "alice"), "pref_1", {"value": "prefers dark mode"})
results = await store.asearch(("user", "alice"), query="preferences")

Namespaces map to zettel metadata, so memories from different users or threads stay isolated while still benefiting from the shared semantic index.

Claude Code (MCP)
#

Runs as an MCP server exposing six tools — memory_store, memory_search, memory_get, memory_delete, memory_connections, and memory_stats:

{
  "mcpServers": {
    "zettelkasten": {
      "command": "python",
      "args": ["-m", "zettelkasten_memory.adapters.mcp_server", "--persist", "memory.json"]
    }
  }
}

Once configured, Claude Code can store and retrieve memories across conversations. The agent builds a persistent knowledge graph that survives session boundaries.


What Problems This Solves
#

1. Cross-Session Continuity
#

Agents working on multi-day tasks — code migrations, incident investigations, customer engagements — lose all context between sessions. With zettelkasten-memory, the agent stores what it learns and retrieves it in the next session, ranked by relevance and recency.

2. Multi-Agent Knowledge Sharing
#

In frameworks like CrewAI, multiple agents work on the same problem but can’t share what they’ve learned. A research agent discovers something, but the writing agent starts from scratch. With a shared ZettelMemory, every agent’s discoveries are available to every other agent — connected and ranked.

3. Context Window Efficiency
#

Instead of stuffing the entire conversation history into the context window, agents can retrieve only the most relevant memories. get_context(query, max_tokens=2000) returns a token-budgeted summary of the most important, most connected, most recent matches.

4. Knowledge Decay Without Maintenance
#

Simple stores accumulate stale information forever. zettelkasten-memory’s composite scoring naturally deprioritizes old, unused memories. The eviction system prunes the least valuable ones when capacity is reached. The knowledge graph maintains itself.

5. Discovery of Non-Obvious Connections
#

The auto-linking system creates connections between memories that share semantic similarity. An agent investigating a network issue might discover that a memory about “DNS configuration” is connected to an earlier memory about “certificate expiration” — a connection the agent didn’t explicitly make but that the graph surfaced.


Getting Started
#

Install with pip:

# Core (TF-IDF backend, no API keys needed)
pip install zettelkasten-memory

# With embedding support
pip install zettelkasten-memory[embeddings]

# With framework adapters
pip install zettelkasten-memory[crewai]
pip install zettelkasten-memory[langgraph]
pip install zettelkasten-memory[mcp]

Basic usage:

from zettelkasten_memory import ZettelMemory

mem = ZettelMemory()

# Store memories
mem.add("Snowflake uses AES-256 encryption at rest with hierarchical key model")
mem.add("Customer requires Tri-Secret Secure with AWS KMS for key sovereignty")
mem.add("Network policies restrict access to PrivateLink endpoints only")

# Search — results ranked by similarity * importance * recency * connections
results = mem.search("encryption and key management")

for r in results:
    print(f"[{r['score']:.2f}] {r['content']}")
    print(f"  Tags: {r['tags']}, Connections: {r['connections']}")

# Walk the graph
connected = mem.get_connected(results[0]["id"], max_hops=2)

# Persist
mem.save("memory.json")

The full source, tests, and adapter examples are on GitHub: KellerKev/zettelkasten-memory.


Related#

Kevin Keller
Author
Kevin Keller
Personal blog about AI, Observability & Data Sovereignty. Snowflake-related articles explore the art of the possible and are not official Snowflake solutions or endorsed by Snowflake unless explicitly stated. Opinions are my own. Content is meant as educational inspiration, not production guidance.
Share this article

Related