Skip to content

Matrices

A matrix is the one rendered grid output of the zettelkasten — rows against typed columns, every cell grounded in the corpus. It arises two ways, and keeping them apart is the whole point of this page. One is the single-spine matrix — the flat grid a single spine definition projects, its row axis against its own columns. The other is the spine-group matrix — the Layer 2 comparison surface that appears when several spines built from the same spine-schema line up into one grid. Both render through the identical matrix UI, but they answer different questions and are produced by different machinery.

The zettelkasten dashboard Workshop in Matrix mode: the matrix builder on the left with rows grouped by a spine's dimensions and typed columns, and the synthesized grid filling in on the right

A matrix in the Workshop: rows are a spine's dimensions, columns are what each row is compared on, and every cell is a grounded synthesis routed from note membership.

Two sources, one grid

The single-spine matrix is the grid of a single spine definition: its row_axis enumerates the rows, its columns are the dimensions, and each cell is filled by deterministic routing (exact-tag or spine-side membership) plus optional agent synthesis. Rows can be sources, notes, tags, note types, or a group aggregation — the row axis is selectable and symmetric with the columns (Configurable matrix row axis (pivot) + builder wizard, Generalizable Group row axis (field/link/semantic aggregation) replaces the stubbed Spine axis). A proposed spine is live and read-only; a saved matrix is the same grid cached under _reviews/<name>.tables.json (v2 cells carry both their members and a summary). This is a matrix of one spine.

The spine-group matrix is the Layer 2 object. It is what the organizing principle is ultimately for: many spines, each built from the same spine-schema, folded into a single comparison (Organizing principle: semantic base -> spine -> matrix (two kinds of uniting)). Here rows are the spine apexes (the topics) and columns are the shared dimensions. It is a matrix across spines.

A comparison matrix: Swim, Bike, and Run spines as rows against shared Technique, Endurance, Equipment, and Muscles columns, each cell rolling up member notes

A spine-group matrix: three same-spine-schema spines (rows = topics) folded against their shared dimensions (columns). Each cell rolls up its member notes, shown by the stacked cards and +N pill.

≥ 2 spines share a fingerprint Spine A topic 1 Spine B topic 2 Spine C topic 3 spine-schema fingerprint = the sorted set of column keys Spine-group matrix rows = topics · columns = shared dimensions
Independent spines fold into one comparison only when they share a spine-schema — the same sorted set of column keys. A singleton fingerprint stays an individual spine.

How same-spine-schema spines fold together

The alignment key is deliberately simple. A spine's column_key_fingerprint is just its sorted set of column keys — two spines share a spine-schema iff they expose the same dimension-tag set, regardless of per-spine labels or graph names. group_spines_by_spine_schema() buckets promoted spines by that fingerprint into a spine-group and only forms a group when at least two spines share it; a singleton spine-schema stays an individual spine and is not grouped. spine_group_matrix() then builds the read-only grid: each row is a member spine's apex, each cell reads that topic's dimension-node body (falling back to the joined evidence titles when the node has no synthesized body yet) plus its rolled-up evidence members for drill-down. The grid is ephemeral — materialized at view time and never persisted unless the populated-matrix wizard chooses to.

This is exactly the distinction that once caused confusion with the persona spines: running the extraction six times produced six standalone apexes (six Layer-1 spines) but no Layer-2 grouping, so the UI showed six separate spines instead of one comparison. A shared spine-schema — the same column-key fingerprint across the group — is precisely what turns independent strands into a matrix (Organizing principle: semantic base -> spine -> matrix (two kinds of uniting)).

Spine-schema is not the extraction schema

The spine-schema here is the shared column-key fingerprint that groups sibling spines — a grouping rubric. It is distinct from the grounded-extraction schema, the extraction rubric a run reads sources against (create_extraction_graph, the entries in extraction-schemas.yaml). The spine-schema is derived from that schema's dimension tags, but the two name different jobs; the extraction layer keeps the plain word schema, and the grouping layer is always spine-schema.

The populated matrix builder

Building a spine-group matrix is a curate-and-fill flow, presented side by side: on the left you curate the rows and columns; on the right a streaming populated grid fills in as you go. The step that makes cells read like a comparison rather than a bag of titles is per-cell paragraph synthesis. At build time, _synthesize_dimension_paragraph() authors a short 2–4 sentence, strictly grounded portrait of how one topic handles one dimension, over that cell's routed member claims and their verbatim quotes. The paragraph is then persisted back onto the backing spine dimension node and its status flipped to synthesis_status='materialized', so it shows everywhere the node is read — the matrix, the spine's own views, and any ephemeral comparison.

Materialization is careful about not clobbering work. A node is authored only while it is still a spine skeleton (a placeholder body or scaffold status); a body that a human or agent already materialized is never overwritten unless force is set. Cells with no members are left blank — there is nothing to ground — rather than fabricating prose.

Per-row staleness and drift

Because a spine-group matrix is assembled from independent member spines, its freshness is naturally per row. Each row is one spine, and a row goes stale when its member spine drifts — new in-scope notes attach to no dimension, or its membership no longer matches what the grid last recorded. Rather than a single global "this matrix is stale" badge, staleness is attributed to the specific rows whose backing spine changed, so a resync can target exactly the spine that moved. The cheap drift signal is the count of in-scope notes attached to no cell — the "N new notes unrouted · Resync" cue — surfaced without rebuilding the grid.

Reconcile: identity-stable reprojection

A matrix is rebuilt constantly, and every rebuild is a chance to lose identity. If a row hub, an induced column, or a depth node were keyed on its position or label, then renaming a column or drifting a cluster by one member would orphan the prior durable id — and with it every spine-member edge and human correction hanging off that node. The fix is to derive identity from member-set overlap instead.

reconcile.py is the pure primitive for this. reconcile_ids(prior, proposed, threshold=0.5) computes an optimal (maximum-total-overlap) one-to-one assignment between the prior materialized sets and the newly proposed ones, reusing a prior durable id whenever a proposed set substantially overlaps it (Jaccard ≥ threshold) and minting a fresh id only when nothing overlaps enough. The matching is globally optimal rather than order-greedy, so it never orphans a prior id that a different pairing could have preserved (Optimal matching + dashboard reconcile parity (closes the two follow-ups)). It is deterministic and pure — no graph, no IO — and the greenfield case (no prior spine) is byte-identical to minting every id in order.

That one primitive is reused everywhere identity must survive a rebuild: remine's column reconcile, induced dimensions, matrix row and column re-materialization, and spine promotion. It is the reason a corrected matrix stays corrected through the next rebuild. (This is distinct from review.reconcile(), which merges a deterministic projection with an author overlay — a different concern.)

Where remine fits

Deterministic routing only sees exact-tag matches and spine-side membership — a note that belongs to a dimension but was never tagged for it is invisible to the grid. Closing that gap is the job of remine, the agent-semantic tier that classifies notes into dimensions (and can induce the dimensions themselves), then applies an approved grid back through spine promotion. A matrix is the surface; remine is how you fill and shape it when tags alone are not enough.

Design notes

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

  • D Organizing principle: semantic base -> spine -> matrix (two kinds of uniting)active
    The canonical conceptual model for the whole zettelkasten organization layer.
  • D Configurable matrix row axis (pivot) + builder wizardactive
    Generalized the synthesis matrix from hard-wired "one row per source" to a selectable row axis, symmetric with columns.
  • D Generalizable Group row axis (field/link/semantic aggregation) replaces the stubbed Spine axisactive
    The matrix row builder could not express "a row per persona" (or any aggregation into an emergent entity).
  • D Optimal matching + dashboard reconcile parity (closes the two follow-ups)active
    Resolved both follow-ups left from the identity-stability hardening.
  • R Angelo
    • P zettelkastenactive
      • P Phase 2: Literature Review Systemactive
        • P Workshop tab: Reading Syllabus (learn) + claim-centric Review/Outline (write)active
          • P Phase M3: Review authoring + generative loopactive
            • D Configurable matrix row axis (pivot) + builder wizardactive
          • P Organizations: project-level lenses + spines (matrix builder redesign)active
            • D Generalizable Group row axis (field/link/semantic aggregation) replaces the stubbed Spine axisactive
            • P Schema matrix views (ephemeral hub) + user-facing Spines renameactive
              • D Organizing principle: semantic base -> spine -> matrix (two kinds of uniting)active
            • P Re-mining: Schema-Aware Structure Induction over existing notes (constraint lattice)active
              • P Hardening: identity-stability invariant (overlap reconcile)active
                • D Optimal matching + dashboard reconcile parity (closes the two follow-ups)active

Semantically related entries from the memory graph.