Skip to content

Zettelkasten

The zettelkasten is angelo's multi-source literature-review knowledge graph: a place to read a whole corpus — papers, repos, a PM's letters, product pages — into structured notes and then organize those notes many different ways without ever forking the underlying truth. This page is the map. It explains the one idea that holds the system together — three additive layers over a single persistent corpus — and points you at the dedicated explainers for each piece.

From semantic graph to spine to matrix

The same corpus, re-projected: a semantic graph becomes a spine, and sibling spines fold into a comparison matrix.

The one idea: three additive layers

Most knowledge tools force a choice between a free-form graph (expressive but shapeless) and a rigid schema (structured but lossy). The zettelkasten refuses the choice. It keeps one corpus and stacks three layers on top of it, where each higher layer is a re-projection of the one below and none of them replaces what they sit on:

spine-member edges shared column keys Layer 0 · Semantic base notes in per-source graphs + a _cross synthesis graph Layer 1 · Spine a spine-schema-backed synthesis tree over the base Layer 2 · Spine-group matrix sibling spines sharing a spine-schema, lined up to compare
Three additive layers over one corpus. Each higher layer is a re-projection of the one below (dashed) — never a replacement.
  • Layer 0 — the semantic base (always on). Every source becomes its own note graph; a _cross graph holds cross-source synthesis. Notes are linked by source membership, tags, typed semantic edges (defines, depends-on, contrasts, …), and embeddings. This is the substance of the corpus and it is always there.
  • Layer 1 — the spine. A deliberate arrangement laid over the base: an apex (the topic), dimension nodes (the columns of a rubric), and optional row hubs. Base notes join a spine through cross-graph spine-member edges. Its uniting principle is many sources → one apex. See Spines.
  • Layer 2 — the spine-group matrix. When two or more spines share the same spine-schema fingerprint — the identical sorted set of column keys — they fold into a single comparison surface: rows are topics (each spine's apex), columns are the shared dimensions. See Matrices.

Why re-projection instead of copies

The alternative to layering is to materialize each arrangement as a fresh copy of the notes — which forks the truth the moment two copies drift — or to encode each as a saved query, which makes arrangements invisible and unselectable. Layering keeps exactly one corpus and makes every arrangement a switchable overlay on it. The load-bearing invariant: spines and matrices are re-projections of the base, so promoting, resyncing, or deleting one never mutates a base note (Spines are additive/separate/selectable layers: NOT project sources, spine-side edges, 'Organize by' picker). Membership lives on the spine side, as outgoing spine-member edges — never as a mutation of the note it points at.

The pieces, at a glance

Everything below is a projection of the same corpus. Start here, then follow the link into the dedicated explainer.

Sources

A source is one ingested document (a paper, a repo, a letter) with its own note graph and a _meta.yaml. Ingestion is source-first: a document is ingested and content-hashed before any note is written, so every extracted note is anchored to a specific, verifiable version of its source (Source-first ingestion for the zettelkasten agent (ingest_source + content-hash + full-text cache)). Cached fulltext under .angelo/zettel/fulltext/ (gitignored) is what quotes are later checked against.

Notes, claims, and quotes — the evidence contract

Notes come in two families. Extracted notes (claim, finding, method, definition, model, example, quote) live in a source's own graph; generative notes (concept, question, synthesis, critique) usually live in _cross. The contract that keeps the graph honest: a load-bearing claim/finding should have a companion quote note with an outgoing supports edge to it, and that quote is verified verbatim against the cached fulltext at add time. A claim without grounding is a liability, not a fact — the Grounded extraction pipeline enforces this pairing.

The dialectical layer — where claims disagree

A mature corpus contradicts itself, and those tensions are the point. On top of the read-only claim engine (zettelkasten/claims.py) sit three propose-only surfaces exposed as claim actions — discover_contradictions, debate_map, and find_supersessions. They write nothing: discover_contradictions clusters the claim vectors, gates candidate pairs on subject-overlap, a near-duplicate exclusion, and a similarity band, then hands survivors to a conservative injectable stance classifier (kept none unless the claims truly oppose the same thing); debate_map renders the stance edges as a tension graph with contested/resolved/thin camps; find_supersessions proposes supersedes edges when a stronger, newer, support-overlapping claim contradicts an older one. A confirmed edge is authored only through the gated producer (honoring ZK_PROPOSE_ONLY). See Claim mining.

Projects

A project (_projects/*.yaml) is a manifest that scopes a working set of the corpus: which sources it includes, a curated cross: list, and a default_spine. It is the unit a dashboard and an extraction run operate against. Spines are not listed among a project's sources — they are selectable overlays, not corpus members.

Spine definitions

A spine definition is a named, owner-scoped (project or graph) matrix definition — a saved way of arranging the corpus. It exists in one of two states: a proposed spine (a live, read-only projection computed on demand — nothing is written to the corpus) or a materialized spine (a synthesis graph). The lifecycle — promote (proposed → spine), demote, resync, verify, delete — is available both from the dashboard and as the spine(action=…) MCP tool, so an agent can drive it directly. See Spines → The spine definition.

Spines

A spine is the materialized form of a spine definition: a real synthesis tree in a dedicated graph, with an apex, dimension nodes, and row hubs, and durable node_ids so human corrections survive a rebuild (v2 spine model: structure tree + first-class spine-side membership relation (supersedes grid framing)). It is the bridge between "a nice way to look at this" and "a spine I can attach evidence to and compare against others." See Spines.

Matrices

A matrix is what you get for free once several spines agree on a spine-schema: a grid whose rows are topics and columns are shared dimensions. It is the payoff of the whole design — extract many subjects against one rubric and they line up for comparison automatically. See Matrices.

Getting a corpus in — and organizing it

Two workflows feed and shape the layers:

  • Grounded extraction reads a corpus against a goal. A coordinator pipeline (prep → extractor → scribe → auditor → linker → synthesizer → memory) turns raw sources into grounded claim+quote notes mapped to a schema's dimensions, and — for spine-backed runs — attaches them to the right dimension node so the spine fills in as it reads. It is available as a coordinator capability when the zettelkasten bundle is enabled (Implemented grounded-extraction bundle via a generic coordinator capability-contribution seam).
  • Re-mining goes the other way: it takes an existing corpus and reclassifies it into a matrix without re-extracting, reusing the same skeleton/attach/reconcile machinery a fresh promotion uses.

To see and steer all of this, launch the zettelkasten dashboard — a separate UI from the memory dashboard — via the zettelkasten MCP's launch_dashboard (console script angelo-zk-dashboard). Its Concepts tab renders the semantic base as clusters, a materialized spine as a radial tree, and a spine-group matrix as a colored grid, revealing more controls as you materialize a spine. See Dashboard.

Enabling it

pip install "angelo[zettelkasten] @ git+https://github.com/natashahirt/angelo"
angelo init --with-zettelkasten

Data lives in .zettelkasten/ (committed to git, so the corpus travels with the repo and its history — Moved zettelkasten storage to committed .zettelkasten/ (mirrors .memory/)); only embedding and fulltext caches under .angelo/ are ignored. Enabling this also installs the stream ingestion rule — see Stream.

Commands

Natural-language phrasings that trigger the zettelkasten — say the thing, the agent runs it. Spine-specific commands live on the Spines page; see the full command cheatsheet.

Say this What runs
"note this", "add to the zettelkasten", "remember this definition" note(action="add")
"grow this note", "add depth to X" note(action="update")
"what should this link to", "find connections", "any related notes" suggest(kind="connections")
"suggest structure", "what synthesis notes are we missing", "find the hubs" suggest(kind="structure") / suggest(kind="concept-hubs")
"research X", "what do the sources say about X", "any disagreements on X" query_topic
"frame this question", "map the landscape for X" frame_question
"build the syllabus", "assemble the reading list", "what should I read next" syllabus(action=…)
"remine this org", "reclassify into the dimensions" remine
"look this up in Zotero", "find the paper on X" zotero(action="lookup")
"open the zettelkasten dashboard", "show me the graph" launch_dashboard

Design notes

The decisions behind this, drawn as a slice of the memory tree.

Semantically related entries from the memory graph.