internal/schema/embedfs/graph-schema/knowledge-garden/ in the fracta repo (see internal/schema/embedfs/graph-schema/knowledge-garden for the loader-validated source of truth) and is resolved at runtime as embed://graph-schema/knowledge-garden. It plugs into the broader fracta schema via the core/DomainSource cross-family edge, so Reading Garden nodes show up in the same 4-tier resolution chain (DomainSource -> DataStore -> MCPServer -> MCPTool) as every other family.
The shape
Every later section reads against this diagram. The two anchor node types are Highlight (the raw unit of capture) and Concept (the atomic, publishable unit of distilled thought). Everything else exists to connect those two through the CODE flow (Capture -> Organize -> Distill -> Express).Layers
The schema family follows fracta’s two-layer convention:- Universal layer (
nodes/): one node —Topic. Universal nodes are user-curated and re-used across particulars. - Particular layer (
particulars/): seven nodes —Highlight,Document,Concept,Entity,Claim,Question,Publication. Particulars are discovered by strategies and tied to a writer-of-record.
layer membership; you cannot register a particular node under nodes/.
Nodes
Topic
A user-curated theme that groups Concepts and Questions. The only scaffold-authority node in this family — populated by the user (or a future curation strategy), never by the ingest pipeline.Highlight
A single highlight pulled from a source — typically a Kindle/article highlight in Readwise. The atomic unit of capture.
Populated by
highlight-distill. Every Highlight must be wired to a DomainSource via CAPTURED_FROM (enforced by checkpoint highlight_missing_captured_from).
Denormalised book fields. Since v0.5.2 the Readwise binding pulls a set of
book_* fields (book_title, book_author, book_category, book_source, book_source_url, book_cover_image_url, book_document_note) alongside each highlight via the explicit response_fields argument. These are NOT stored on the Highlight node — they’re used only to MERGE the parent Document node (one per book_id). Resolve denormalised metadata by walking Highlight -[:PART_OF]-> Document. Closes Bug 13: the v1 reader-documents namespace mismatch meant PART_OF never fired; v3 derives Documents from highlights themselves.Document
The container a Highlight comes from — a book, article, podcast, paper, or web page.v3 Document derivation (Bug 13 fix). v1/v2 attempted to MERGE Documents from the separate
reader_list_documents MCP call, which lives in a different ID namespace from Readwise highlights — so Highlight -[:PART_OF]-> Document edges never fired. v3 derives Documents directly from the highlights themselves (MERGE (d:Document {id: 'readwise:book:' + book_id})) using the denormalised book_* properties listed above. The Readwise binding’s response_fields argument is what makes those fields non-null on every highlight.Concept
The atomic, publishable unit — a distilled idea that recurs across highlights. The other anchor of this ontology.extraction_score vs confidence is intentional. They are separate properties with distinct writers-of-record. extraction_score reflects what the NLP extractors saw at ingest time (per-extractor agreement and signal, see Strategies — highlight-distill). confidence reflects graph-wide signal computed later (recency × frequency × source diversity, folded with mean extraction_score). The checkpoint rule concept_low_extraction_high_confidence flags drift between them as alias-suspicion — the canonical surfacing of “this Concept is probably an alias to another one.” Mirror this seam if you author a strategy that touches Concept scoring.Entity
A typed real-world referent — a person, place, organisation, work, or product — mentioned in Highlights or Documents. Typed entities ground Concepts; “Karl Popper” the Person is distinct from “falsifiability” the Concept.
Authoritative typer is
concept-gliner (the only extractor with calibrated per-span probability and caller-supplied taxonomy). concept-spacy NER acts as a fallback typer; concept-keybert is untyped and never produces Entity routes.
Claim
A statement asserted or questioned in the source material. Forward-looking —highlight_distill does not produce Claims in v1; the node type exists for future strategies that perform claim extraction.
Question
An open inquiry the reader (or a future strategy) wants to track. LikeTopic, primarily user-curated in v1.
Publication
A record of a Concept (or Topic / Document) published to an external sink. The idempotency anchor —notion_publish reads Publication.content_hash before deciding whether to write.
Publication.sink is the future-proofing seam. A future mintlify_publish strategy populates the same node type with sink: "mintlify". The graph stays canonical; sinks multiply. Indexed on both id and external_id for fast idempotent lookup.Edges
Provenance
Every node written by this pattern carries_source = 'strategy:<name>', matching fracta’s convention. Cross-family writes follow the same rule: when highlight_distill MERGEs the Readwise Highlights DomainSource, it sets _source = 'strategy:highlight_distill' on creation only — subsequent runs do not overwrite the originating attribution.
Encourage the same convention for any new pattern: one writer-of-record per node, declared in the strategy name, traceable via _source. The checkpoint rules in internal/schema/embedfs/graph-schema/knowledge-garden/checkpoint.yaml lean on this — for example, concept_low_extraction_high_confidence only makes sense when extraction_score and confidence have separate authors.
Checkpoint rules
Eight validation rules ship with the family. The two most consequential:highlight_missing_captured_from(error): every Highlight must be wired to acore/DomainSource. Without this, the 4-tier resolution chain breaks.concept_low_extraction_high_confidence(warning): flags Concepts whereconfidence > 0.7butextraction_score < 0.4(or null) — the alias-drift detector. A graph-corroborated Concept that no NLP extractor strongly endorsed is almost always an alias to another Concept, surfaced for hand-merging until a future strategy (embedder MCP or LLM-based resolution) can do it automatically.
orphaned_concept_no_mentions, publication_missing_parent_concept, high_confidence_concept_without_topic, publication_missing_required_props, claim_without_evidence, concept_confidence_status_mismatch) live in internal/schema/embedfs/graph-schema/knowledge-garden/checkpoint.yaml. Run graph_checkpoint(mcp_servers='notion,readwise,concept-keybert,concept-gliner,concept-spacy') after any ingest to surface them.
Publishing layer (Notion three-database mirror)
Since v0.5.2 the published artefact is a navigable Notion structure, not a flat dump of Concept pages. The publishing layer maps the graph’s three-tierDomainSource -> Document -> Highlight onto three Notion databases connected by RELATION columns:
Each tier is idempotent independently: a
Publication node is MERGEd per page with sink: 'notion:<tier>' and a tier-specific external_id (readwise_book_id, readwise_highlight_id, concept_name). Re-running notion-publish after no graph changes is a no-op (content-hash skip) at all three levels.
The three RELATION values are written as JSON-stringified arrays of page IDs, not native arrays — that is the on-the-wire convention the hosted Notion MCP expects for properties of type RELATION.
See Strategies — notion-publish for the call shape and the 7-step DAG (load_target_concepts -> load_supporting_highlights -> load_sources -> publish_sources -> publish_highlights -> render_concepts -> publish_concepts).

