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.

The same corpus, re-projected: a semantic graph becomes a spine, and sibling spines fold into a comparison matrix.
- Package overview:
zettelkasten/README.md - Behavioral contract:
.cursor/rules/zettelkasten.mdc - Tool reference: MCP tools → zettelkasten
- Across project evidence: Synapse connects this canon to memory practice and supports grounded navigation over heterogeneous evidence.
- Verified answers: Grounded navigation explains the answer-or-abstain loop; MemDSL v1 specifies its internal machine-checkable contract.
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:
- Layer 0 — the semantic base (always on). Every source becomes its own note
graph; a
_crossgraph 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-memberedges. 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.
- D Moved zettelkasten storage to committed .zettelkasten/ (mirrors .memory/)active
Zettelkasten note data now lives in a committed.zettelkasten/tree at the repo root, mirroring how.memory/is committed, instead of the gitignored.angelo/zettelkasten/. - D v2 spine model: structure tree + first-class spine-side membership relation (supersedes grid framing)active
Design refinement raised during the Graph C spine-promotion review. - D Spines are additive/separate/selectable layers: NOT project sources, spine-side edges, 'Organize by' pickeractive
User requirement: building a new spine must be ADDITIVE to the cluster graph but SEPARATE, and the viewer must be able to pick which spine / organizational principle is active. - D Implemented grounded-extraction bundle via a generic coordinator capability-contribution seamactive
A grounded-extraction run applies a fixedextractor -> scribe -> auditor(+ trailingmemory) pipeline to N sources, parameterized by one named schema (a rubric). - D Source-first ingestion for the zettelkasten agent (ingest_source + content-hash + full-text cache)active
Made every source enter the zettelkasten through one canonical front door so we always have (1) an anchored citation record and (2) a reproducible full-text reference.
- R Angelo
- P zettelkastenactive
- P Phase 2: Literature Review Systemactive
- D Moved zettelkasten storage to committed .zettelkasten/ (mirrors .memory/)active
- D Source-first ingestion for the zettelkasten agent (ingest_source + content-hash + full-text cache)active
- P Workshop tab: Reading Syllabus (learn) + claim-centric Review/Outline (write)active
- P Organizations: project-level lenses + spines (matrix builder redesign)active
- P Spine promotion: materialize a matrix lens into a graph (cols+rows, bulk-attach)active
- P Organizations: project-level lenses + spines (matrix builder redesign)active
- P Phase: Grounded Extraction Pipeline + Reusable Schemas (coordinator capability)active
- P Phase 2: Literature Review Systemactive
- P zettelkastenactive
Related¶
Semantically related entries from the memory graph.