SmartMemory
Concepts

Zettelkasten Memory

The SmartMemory system includes a powerful Zettelkasten memory type that implements atomic, interconnected knowledge management inspired by the traditional Zettelkasten method used by researchers and knowledge workers.

What is Zettelkasten Memory?

Zettelkasten (German for "slip box") is a method of knowledge management that stores atomic ideas as individual notes with unique identifiers and creates dense networks of cross-references between related concepts.

Key Characteristics:

  • Atomic Notes: Each memory item contains a single, focused idea
  • Unique Identifiers: Every note has a permanent, linkable ID
  • Bidirectional Linking: Notes automatically link to related content
  • Emergent Structure: Knowledge organization emerges from connections
  • Contextual Discovery: Find information through associative pathways

How It Works in SmartMemory

Creating Notes

Zettel notes are a first-class core memory type (zettel). The supported public way to create one is through the standard SmartMemory write API with memory_type="zettel":

from smartmemory import SmartMemory, MemoryItem

memory = SmartMemory()

# Store a note explicitly typed as a zettel
item_id = memory.add(MemoryItem(
    content="Machine learning models require large datasets for training effectiveness",
    memory_type="zettel",
    metadata={"title": "ML data requirements", "tags": ["#ml", "#data", "#training"]},
))

Over the REST API, the dedicated POST /memory/zettel/notes endpoint creates a note directly without LLM re-classification:

curl -X POST "$API_URL/memory/zettel/notes" \
  -H "Authorization: Bearer $TOKEN" -H "X-Team-Id: $TEAM" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Machine learning models require large datasets for training effectiveness",
    "title": "ML data requirements",
    "tags": ["#ml", "#data", "#training"]
  }'
# → { "note_id": "...", "memory_type": "zettel", "title": "...", "tags": [...] }

When notes are created, the system:

  • Parses [[wikilinks]], #hashtags, and concept references from the content
  • Resolves wikilink titles to existing notes and writes bidirectional LINKS_TO edges
  • Stores tags and title as queryable properties on the note
  • Builds an interconnected knowledge graph

Notes can also be derived automatically: the EpisodicToZettelEvolver promotes qualifying episodic memories into zettel notes during evolution (origin evolver:episodic_to_zettel).

Bidirectional Auto-Linking

When you add new notes, the system automatically:

  1. Wikilink Linking: Parses [[Target]] references and creates bidirectional LINKS_TO edges to resolved notes
  2. Backlink Tracking: Every link is queryable from both directions via get_backlinks() and notes_linked_to()
  3. Dynamic Relations: Arbitrary typed relations (e.g. INSPIRED_BY, CONTRADICTS, ELABORATES_ON) can be added between notes
  4. Tag / Concept Linking: Notes are discoverable by shared tags, concepts, and mentioned entities
from smartmemory import SmartMemory, MemoryItem

memory = SmartMemory()

# Add foundational concepts
memory.add(MemoryItem(content="Neural networks learn through backpropagation",
                      memory_type="zettel",
                      metadata={"title": "backpropagation", "tags": ["#neural-networks"]}))
memory.add(MemoryItem(content="Gradient descent optimizes model parameters",
                      memory_type="zettel",
                      metadata={"title": "gradient-descent", "tags": ["#optimization"]}))

# Add a related concept that explicitly references the earlier notes.
# The [[wikilinks]] resolve to existing notes and create bidirectional edges.
memory.add(MemoryItem(
    content="Deep learning combines [[backpropagation]] and [[gradient-descent]] across many layers",
    memory_type="zettel",
    metadata={"title": "deep-learning"},
))

Interactive Graph Visualization

The system includes a React-based graph viewer that lets you:

  • Visualize connections between all your notes
  • Navigate associatively by clicking through related concepts
  • Discover knowledge gaps by seeing sparse areas
  • Track knowledge growth over time

The subgraph backing a viewer is served by GET /memory/zettel/{note_id}/graph, which returns the notes and edges around a starting note for rendering.

Graph Maintenance and Analytics

The Zettelkasten memory exposes maintenance and analysis utilities on the ZettelMemory class:

Linking

  • Wikilink resolution: [[Target]] references resolve to existing notes and write bidirectional LINKS_TO edges
  • Auto-link toggle: enable_auto_linking() / disable_auto_linking() control whether new notes are wikilink-parsed (auto_link flag)
  • Dynamic relations: add_dynamic_relation() adds arbitrary typed edges

Pruning

  • Low-quality / duplicate detection: get_low_quality_or_duplicates(...) returns prune candidates (short content, few connections, high Jaccard similarity)
  • Soft delete: prune_or_merge(item) archives a note rather than hard-deleting it

Analytics

  • Knowledge clusters: detect_knowledge_clusters() finds emergent groupings
  • Bridges and emergence: find_knowledge_bridges(), detect_concept_emergence()
  • System overview: get_zettelkasten_overview() reports total notes, total connections, connection density, cluster count, and health
  • Knowledge density: Connectivity and coverage analysis

Integration with Other Memory Types

Zettelkasten memory works seamlessly with other SmartMemory types:

Evolution from Episodic Memory

# Episodic memories can be promoted into Zettel notes by the
# EpisodicToZettelEvolver during the pipeline's evolve stage.
# These derived notes carry origin="evolver:episodic_to_zettel".
memory.add(MemoryItem(content="Learning about transformers", memory_type="episodic"))

# During evolve, qualifying episodic items are re-derived as zettel notes
# and linked into the existing knowledge graph.

Retrieving and Reading Notes

# Zettel notes are retrieved through the standard SmartMemory read API.
item = memory.get(item_id)               # fetch a single note by id
results = memory.search("transformers")  # full-text / vector search across memory

Note: there is no public "promote zettel → semantic" method. Cross-type transformation happens through the evolution pipeline, not a direct call.

Best Practices

Atomic Note Principle

Good: "Attention mechanisms allow models to focus on relevant input parts" ❌ Bad: "Transformers use attention and are good for NLP and have encoder-decoder..."

Effective Tagging

# Tags are passed in the note's metadata and stored as queryable properties.
memory.add(MemoryItem(
    content="Attention mechanisms allow models to focus on relevant input parts",
    memory_type="zettel",
    metadata={
        "title": "attention-mechanism",
        # Use hierarchical tags spanning domain and granularity
        "tags": ["#ml/attention", "#architecture/transformer", "#concept"],
    },
))

Cross-Reference Patterns

# In your note content
This builds on [[gradient-descent.md]] and relates to [[neural-networks.md]].
See also: [[backpropagation.md]], [[optimization-theory.md]]

Advanced Features

These methods live on the ZettelMemory class. Construct it directly when you need the richer graph-analysis surface beyond simple create/read:

from smartmemory.memory.types.zettel_memory import ZettelMemory

zettel = ZettelMemory()
# All notes that link TO a given note
backlinks = zettel.get_backlinks(note_id)

# Complete bidirectional view (incoming + outgoing) of a note's connections
connections = zettel.get_bidirectional_connections(note_id)

# Explicit typed relations between notes
zettel.add_dynamic_relation(source_id, target_id, "ELABORATES_ON")

Emergent Structure

# Detect emergent knowledge clusters from connection patterns
clusters = zettel.detect_knowledge_clusters(min_cluster_size=3)

# Find notes that bridge different knowledge domains
bridges = zettel.find_knowledge_bridges()

# Detect concepts that are emerging from connection patterns
emerging = zettel.detect_concept_emergence()

Serendipitous Discovery

# Suggest related notes for a starting note
suggestions = zettel.suggest_related_notes(note_id, suggestion_count=5)

# Find paths between two notes through the graph
paths = zettel.find_knowledge_paths(start_note_id, end_note_id, max_depth=5)

# Random walk for serendipitous exploration
walk = zettel.random_walk_discovery(note_id, walk_length=5)

Note on imports/integration: there is no built-in Obsidian importer or graph-export method on ZettelMemory. External vault import is handled by the separate vault-sync application surface, and graph data for visualization is served by the REST GET /memory/zettel/{note_id}/graph endpoint rather than a Python export_graph call.

Use Cases

Research & Learning

  • Literature review: Connect papers, concepts, and insights
  • Course notes: Build interconnected understanding across subjects
  • Project documentation: Link decisions, implementations, and learnings

Software Development

  • Architecture documentation: Connect components, patterns, and decisions
  • Code knowledge: Link algorithms, implementations, and examples
  • Bug tracking: Connect issues, solutions, and root causes

Creative Work

  • Idea development: Connect inspirations, concepts, and iterations
  • Project planning: Link requirements, resources, and constraints
  • Knowledge synthesis: Combine insights from multiple domains

API Reference

Creating and reading notes (SmartMemory)

from smartmemory import SmartMemory, MemoryItem

memory = SmartMemory()

# Create a zettel note
item_id = memory.add(MemoryItem(content, memory_type="zettel", metadata={"title": ..., "tags": [...]}))

# Retrieve and search
note = memory.get(item_id)
results = memory.search(query, top_k=10)

Graph operations (ZettelMemory)

from smartmemory.memory.types.zettel_memory import ZettelMemory

zettel = ZettelMemory()

# Tag / property / relation queries
notes = zettel.find_notes_by_tag(tag_name)
notes = zettel.find_notes_by_property(key, value)
notes = zettel.notes_linked_to(note_id)
notes = zettel.notes_mentioning(entity_id)
notes = zettel.query_by_dynamic_relation(source_id, relation_type)

# Links and backlinks
backlinks = zettel.get_backlinks(note_id)
connections = zettel.get_bidirectional_connections(note_id)
zettel.create_bidirectional_link(source_id, target_id, link_type="LINKS_TO")
zettel.add_dynamic_relation(source_id, target_id, relation_type)

# Emergent structure
clusters = zettel.detect_knowledge_clusters(min_cluster_size=3)
bridges = zettel.find_knowledge_bridges()
emerging = zettel.detect_concept_emergence()

# Discovery
suggestions = zettel.suggest_related_notes(note_id, suggestion_count=5)
paths = zettel.find_knowledge_paths(start_note_id, end_note_id, max_depth=5)
walk = zettel.random_walk_discovery(note_id, walk_length=5)

# Wikilink parsing and system overview
parsed = zettel.parse_wikilinks(content)        # → {wikilinks, concepts, hashtags}
overview = zettel.get_zettelkasten_overview()   # totals, density, clusters, health

REST endpoints (/memory/zettel/*)

POST   /memory/zettel/notes                       Create a note
GET    /memory/zettel/by-tag/{tag}                 Notes by tag
GET    /memory/zettel/by-property                  Notes by property (key, value)
GET    /memory/zettel/mentioning/{entity_id}       Notes mentioning an entity
GET    /memory/zettel/{note_id}/backlinks          Incoming links
GET    /memory/zettel/{note_id}/forward-links      Outgoing links
GET    /memory/zettel/{note_id}/connections        All connections
GET    /memory/zettel/{note_id}/path/{target_id}   Path between two notes
GET    /memory/zettel/{note_id}/suggestions        Suggested related notes
GET    /memory/zettel/{note_id}/graph              Subgraph for visualization
GET    /memory/zettel/clusters                     Knowledge clusters
GET    /memory/zettel/bridges                      Bridge notes
GET    /memory/zettel/concept-emergence            Emerging concepts
POST   /memory/zettel/wikilink/parse               Parse wikilinks from content

Configuration

The main runtime knob is automatic wikilink linking, controlled by the auto_link constructor flag (default True) and the toggle methods:

from smartmemory.memory.types.zettel_memory import ZettelMemory

# Disable automatic wikilink parsing at construction time
zettel = ZettelMemory(auto_link=False)

# Or toggle it at runtime
zettel.enable_auto_linking()
zettel.disable_auto_linking()

Broader memory configuration (storage backend, embeddings, scoping) is supplied through the standard MemoryConfig passed to ZettelMemory(config=...) / SmartMemory, the same as every other memory type.

The Zettelkasten memory type transforms SmartMemory into a powerful knowledge management system that grows smarter and more connected over time, supporting both personal learning and collaborative knowledge building.

On this page