Skip to content

Book Outline (Draft)

Working title. Subtitle options: Strong Types, Provenance, and Ontology Alignment in High-Stakes Domains / A Practical Guide to Reliable Knowledge Extraction.


Introduction: Why Machines Need to Show Their Work

Core claim: In high-stakes domains -- medicine, law, infrastructure -- LLM hallucination is not a quirk to be tolerated but a structural failure mode with real costs. The book argues that typed knowledge graphs with first-class provenance and ontology alignment are a principled mitigation: not a replacement for LLMs, but a discipline that constrains and audits what they produce.

Three base vectors introduced here and elaborated throughout:

  1. Strong typing -- every entity and every claim has a declared type; domain and range constraints are enforced by the type system, not runtime string parsing. Connection to the OCaml/ML tradition: the insight that types prevent entire classes of errors was discovered there, and the same logic applies to knowledge representations. Reference points: Xavier Leroy and CompCert (a formally verified C compiler); the safety guarantees of the ML type discipline. The argument is not "use OCaml" -- it is "these people found something real, and it applies here."

  2. Provenance tracking -- every claim carries its origin. A proposition without provenance is an unverifiable assertion. When provenance is a first-class schema requirement (not an afterthought), any fact can be traced to its source in a field lookup, not a search. This discipline makes dispute resolution and epistemic auditing tractable.

  3. Alignment with authoritative ontologies -- canonical IDs sourced from community-curated authorities (MeSH, UMLS, Wikidata, Baker Street Wiki) rather than minted ad hoc. Ad-hoc IDs are a traceability failure: they cannot be correlated across documents, systems, or time.

Brief tour: Two full worked examples (Holmes corpus, medical literature), domain service and identity server as the architectural response to the "surprises" each domain produces, and a chapter on querying and one on production scale.


Chapter 1: The Base Vectors in Depth

Purpose: Build the conceptual vocabulary and architectural overview. No code from the worked examples yet -- a hypothetical ingestion walkthrough keeps it domain-neutral.

1.1 Strong Typing as a Knowledge Discipline

  • Types are Python classes, not strings. Domain and range enforcement is the type system's job.
  • The schema layer (types, field schemas, traits) is fixed at design time; the instance layer is populated at ingestion.
  • What a predicate type IS: a directed, typed, truth-bearing proposition class. What a predicate instance IS: one concrete claim, with a subject, object, truth status, and provenance.
  • Contrast with informal property graphs (Neo4j) and flat triple stores (RDF): similar topology, different discipline.
  • The E ⊆ V insight: every predicate instance is a full member of the vertex set. This enables higher-order predication without reification. Brief preview; full treatment in Appendix.

1.2 Provenance as a First-Class Citizen

  • The provenance sub-schema: what fields are required on every predicate type, why they are required.
  • truth_status vs. provenance: status is the graph's current epistemic commitment; provenance is the audit trail behind it. These are distinct and both necessary.
  • The truth status lifecycle: hypotheticalasserted_truedisputed / retracted. Promotion is a separate pass from extraction; the pipeline produces hypotheticals.
  • Two id regimes: pipeline-extracted (unique per extraction event, full audit trail) vs. content-addressed via statement_id() (idempotent re-extraction treated as confirmation). These should not be mixed in a single loading pass.

1.3 Ontology Alignment

  • Why canonical IDs sourced from authorities are worth the cost. Ad-hoc IDs cannot be correlated across documents, systems, or time.
  • Ontology authority as a domain-level choice: different domains have different authorities. For medicine: MeSH, UMLS, UniProt. For Sherlock Holmes: Baker Street Wiki.
  • Provisional IDs (provisional:N) as a principled fallback when no authority match exists, not as an excuse to skip lookup.
  • id vs. __str__: the id is never parsed for type dispatch; display is a one-way presentation artifact generated by __str__.

1.4 A Hypothetical Ingestion -- End to End

Walkthrough of a simple domain (e.g. a short biography passage) through the five pipeline stages:

  1. Sentencize -- raw text to numbered sentence JSONL. Why JSONL, why flat sequential IDs.
  2. Coreference resolution -- chunk-level entity/mention clusters. The carry-in context problem at chunk boundaries.
  3. Entity merge -- global entity table with canonical IDs and alias lists. Wiki/ontology lookup; provisional IDs for misses.
  4. Event/moment extraction -- discrete events and temporal anchors as first-class entities, not just narrative backdrop.
  5. Triplet extraction -- predicate instances as JSONL. Slot-filling against a known schema; why a finite predicate vocabulary makes this tractable.

The hypothetical keeps design choices abstract. The worked examples in Chapters 2 and 3 will show what those choices look like under domain-specific pressures.

1.5 Architecture Overview: Pipeline, Domain Service, Identity Server

  • Pipeline stages as stateless transforms over JSONL. Resume support as a first-class requirement for multi-hour runs.
  • The domain service as the wall between domain knowledge and the rest. Everything that surprised us about Holmes belongs inside that wall. Brief preview of what it contains; full treatment in Chapter 4.
  • The identity server as the domain-agnostic core for entity resolution at scale. Brief preview; full treatment in Chapter 5.
  • What is NOT covered in this chapter: BFS and graph querying (Chapter 6), production scaling (Chapter 7).

1.6 Graph Traversal -- A First Look

  • The graph as an in-memory index over instances: get(), edges_from(), edges_to().
  • BFS as the fundamental query primitive: hop layers, truth-status filtering, symmetric traversal.
  • Why the asserted graph (truth_status = asserted_true) is the default traversal surface.
  • Brief taste of transitive closure.
  • Full treatment deferred to Chapter 6.

Chapter 2: The Holmes Corpus

Purpose: A complete worked example. Every design choice from the formal spec lands on concrete code and data. The goal is not just "how we built it" but "why the domain shaped it this way."

2.1 Why Holmes?

  • Rich epistemic structure: Holmes makes deductions, Watson narrates, the King conceals, Irene deceives. A story about belief and knowledge is a demanding test case for epistemic modeling.
  • Literary circumlocution: "His Majesty", "my client", "the woman" all refer to characters without naming them. This stresses entity resolution in ways medical abstracts do not.
  • Thin external ontology: Baker Street Wiki is a fan wiki, not a curated database. Its coverage is good for major characters and poor for incidental ones. The system must handle misses gracefully.
  • Short, self-contained: "A Scandal in Bohemia" is \~8,000 words -- large enough to produce real data, small enough to inspect manually.

2.2 The Holmes Schema

Walk through holmes_schema.py:

  • Entity types: Person, Location, Object, Document, Event, Moment, Persona, Plan
  • Predicate types and their domain/range:
  • First-order: Knows, AssociatedWith, LocatedIn, Possesses, DisguisedAs, HasTrueIdentity, Involves, OccurredAt
  • Higher-order: KnewAt (when a person came to know a fact -- range includes BaseStatement), Contradicts (one claim disputes another)
  • Deferred: Executes (requires Plan entities, separate extraction pass)
  • Traits declared: LocatedIn is Transitive; Knows is Symmetric; HasTrueIdentity is Inverse of DisguisedAs
  • TruthStatus lifecycle in the Holmes context: Watson's narration is the primary source of asserted_true; Holmes's hypotheses start as hypothetical
  • The ProvenanceMixin: story_id, paragraph_index, asserting_narrator, extraction_method, extraction_confidence
  • __str__ as presentation: "Sherlock Holmes" from a Person, "Knows(Sherlock Holmes → Dr Watson)" from a predicate instance

2.3 The Ingestion Pipeline

Each stage in detail, with actual output samples from bohemia_*.jsonl:

sentencize.py -- LLM-based splitting (not spaCy). Why: Doyle's abbreviations ("Dr.", "Mr.") trip naive splitters; the LLM handles them without a custom rule list. One paragraph per call, carry-in offset for continuous numbering. Resume support via highest-id scan.

coref.py -- chunk-level entity/mention extraction. Chunk size, overlap, context leak guard (filter mentions outside current chunk). Output schema: entity label, mention list with sentence_ids and spans. Carry-over context for cross-chunk coref chains. Runs locally on the G533 (high volume, low-latency not required).

merge.py -- global entity table construction: - Pass 1: label clustering via Claude API (single call per story). Why frontier: "His Majesty" and "Count Von Kramm" require narrative world-knowledge to merge; local models at 14B can't reliably do it. - Pass 2: Baker Street Wiki lookup with Claude judgment. Opensearch candidates + LLM accept/reject. Why not blind token overlap: "the King" matches wrong articles; Claude knows Holmes canon. - Pass 3: mention rewriting with canonical IDs. - Deduplication pass: multiple clusters linking to same wiki URL (the "John" / "Dr Watson" problem) -- merge by longest canonical name. - wiki: prefix for linked entities; provisional:N for misses.

events.py -- event and moment extraction via Claude API. Why frontier: distinguishing states ("Irene lives at Briony Lodge") from events ("Holmes arrives at Briony Lodge disguised as a groom") requires narrative reasoning. SlugRegistry for global uniqueness. Progress sidecar for resume. sib: prefix for corpus-local entities.

triplets.py -- predicate instance extraction using local model. Why local: slot-filling against a known schema constrains the output space enough that qwen2.5:14b is reliable. Alias ID scheme (compact person:sherlock_holmes in prompt, expand to canonical URI in validator). Event window filtering (only inject nearby events per chunk). Domain/range validation in Python, not in the prompt. truth_status always hypothetical out of the pipeline -- promotion is a separate pass.

2.4 Loading: The Fixpoint Problem

KnewAt requires a BaseStatement in its range -- it can only be constructed after the statement it references has been hydrated. In file order, a Contradicts record may appear before the KnewAt it points at, which appears before the Knows it points at. The loader resolves this with a fixpoint loop: defer higher-order records, retry after each pass, terminate when the deferred set stops shrinking (not when the global instance set stops growing -- the distinction matters when some referents are genuinely unresolvable).

2.5 The In-Memory Graph

graph.py: get(), edges_from(), edges_to() with predicate-type and truth-status filtering. BFS with hop layers, symmetric traversal. Transitive closure worked example: 221B Baker Street located in London. A brief taste of sentence_cutoff (loading a temporally-bounded subgraph); full treatment deferred to Chapter 6.4.

2.6 Design Decisions and Their Consequences

  • Local vs. frontier allocation: coref and triplets are local (volume); clustering and event extraction are frontier (reasoning quality). Explicit cost/quality tradeoff.
  • The domain service wall: every surprise in the Holmes pipeline (spurious wiki links, the "John"/"Dr Watson" dedup bug, the alias scheme) lived inside the domain service boundary. None forced changes to the loader, graph, or schema.
  • provisional: as a principled fallback, not a failure signal. The pipeline produces a graph that is queryable even with partial wiki coverage.
  • truth_status: hypothetical as a pipeline invariant: the pipeline reports what it extracted, not what is true; promotion is a separate, not-yet-automated pass.
  • The unified Statement model (E ⊆ V) in practice: KnewAt and Contradicts get higher-order predication for free, at the cost of the loader's fixpoint loop.

Chapter 3: Medical Literature

[PLACEHOLDER -- working code and schema not yet written]

Purpose: Show how the same architecture looks when applied to a domain with strong external ontologies, structured document formats, and different epistemic requirements. The contrast with Holmes should be sharp enough that Chapter 3 feels like it uses Chapter 2 rather than repeating it.

3.1 Why Medical Literature?

  • Established, community-curated ontologies: MeSH (diseases, drugs, concepts), UMLS (cross-ontology harmonization), UniProt (proteins), OMIM (genetic conditions). These do most of the entity resolution work that Baker Street Wiki cannot.
  • Structured abstracts: Background, Methods, Results, Conclusions sections provide coarse provenance for free -- a claim in Results has different epistemic weight than one in Discussion.
  • High entity reuse across papers: "IL-6", "interleukin-6", and "interleukin 6" in 300 papers all resolve to the same MeSH ID without any LLM involvement.
  • Epistemic needs are different: belief states and deception (the Holmes machinery) are largely irrelevant. Provenance and confidence carry more weight; disputed maps to conflicting study results rather than character deception.

3.2 The Medical Schema

[PLACEHOLDER]

Sketch of entity types: Drug, Disease, Gene, Protein, ClinicalTrial, Patient (or PatientCohort), Measurement. Predicate types: Treats, AssociatedWith, InhibitsPathway, IndicatesRiskOf, Administered, Measured. Higher-order predicates: likely simpler than Holmes -- SupportedBy (one claim supported by a finding) and Contradicts (same as Holmes, reused).

Domain/range for medical is tighter and more checkable: Treats(Drug, Disease) is wrong if the subject is a Gene. The type system catches this at construction time.

3.3 The Ingestion Pipeline for Medical Literature

[PLACEHOLDER -- design is sketched in BigChat but not implemented]

sentencize -- same approach, minimal change. Medical prose is more regular than literary prose; paragraph-level splitting may be sufficient without per-paragraph LLM calls.

coref -- same local approach. Medical coref is simpler (less circumlocution) but entity mention density is higher.

merge / entity resolution -- the key difference from Holmes: MeSH/UMLS lookup replaces Baker Street Wiki, and most common entities resolve without LLM judgment. The Claude judgment pass only fires on ontology misses -- a small fraction of calls for a mature medical corpus.

events -- largely absent as a concept in medical literature. Replace with findings: a Measurement or Result tied to a study and a cohort. The Event type either repurposed or replaced by a Finding type.

triplets -- same local slot-filling approach. The finite predicate vocabulary does more work here because medical relationship types are more standardized.

3.4 Per-mention Resolution vs. Batch Clustering

The Holmes pipeline batches all labels per story and sends one clustering call to Claude. For medical literature at scale (hundreds of papers), the better approach is per-mention resolution: each label hits the IdentityServer immediately, MeSH/UMLS lookup returns a canonical ID on contact, and the LLM is only invoked for cache misses that also miss the ontology. By paper 200, most common entities are cached; the marginal LLM cost per paper approaches zero.

3.5 The Cushing's Syndrome Case Study

[PLACEHOLDER -- reference to earlier BigChat work on a specific paper]


Chapter 4: Domain Services -- What the Wall Contains

Purpose: Both domain services implement the same interface (DomainPolicy / DomainClient ABC) but look completely different inside. The contrast reveals what belongs inside the domain service wall and why.

4.1 The DomainPolicy Contract

Three methods: - resolve-authority(mention, entity_type, context) → canonical ID or null - select-survivor(cluster) → preferred canonical name when merging clusters - synonym-criteria(entity_type) → similarity threshold for embedding-based auto-merge

Everything outside this wall -- the identity server core, all pipeline stages that call it, the graph and loader -- is domain-agnostic.

4.2 The Holmes Domain Service

  • resolve-authority: Baker Street Wiki opensearch → Claude judgment (accept/reject candidate)
  • select-survivor: prefer longer canonical name ("Dr Watson" beats "John")
  • synonym-criteria: lower threshold than medical (0.75 maybe) -- literary circumlocutions are semantically looser than biomedical synonyms
  • resolve-batch: optional extension for the holistic clustering case ("my client" is ambiguous in isolation, obvious in context of the full label set)

4.3 The Medical Domain Service

[PLACEHOLDER -- design sketched, not implemented]

  • resolve-authority: MeSH/UMLS API lookup (deterministic, no LLM)
  • select-survivor: prefer the ontology's preferred label; fall back to most specific name
  • synonym-criteria: higher threshold -- "IL-6" and "interleukin-6" should merge; "IL-6" and "IL-8" should not
  • resolve-batch: largely unnecessary -- ontology linkage handles what clustering would otherwise do

4.4 What the Contrast Reveals

  • The wall works. Every surprise in the Holmes pipeline lived inside the domain service; the identity server core and all pipeline stages were untouched.
  • The resolve-batch extension is a Holmes-specific optimization driven by the batch-clustering-is-better-than-per-mention property of literary text. It is an extension to the contract, not a change to it.
  • The synonym threshold is domain-specific knowledge. Medical literature needs a tighter threshold; literary text needs a looser one.
  • Scaling characteristics differ: the Holmes domain service makes many LLM calls relative to entity count; the medical service makes almost none for a warm cache.

Chapter 5: The Identity Server

Purpose: The domain-agnostic core for entity resolution at scale. Show when you need it and when you don't.

5.1 What the Identity Server Does

  • Atomic find-or-create for canonical entities: INSERT ... ON CONFLICT DO NOTHING
  • Redis as a read-through cache: per-mention resolution at high throughput
  • on_entity_added: embedding-based similarity search and auto-merge when threshold exceeded
  • The provisional → canonical lifecycle: entities start provisional, upgrade to wiki/ontology-linked when the authority lookup succeeds, merge when they turn out to be the same entity

5.2 The DomainClient / DomainPolicy Boundary

Reference to Chapter 4. The identity server core invokes the domain service for resolve-authority, select-survivor, and synonym-criteria. It knows nothing about Baker Street Wiki or MeSH -- that knowledge lives entirely in the domain service implementation.

5.3 When You Need It

You need the identity server when: - Concurrent ingestion: multiple workers processing documents in parallel; entity resolution must be atomic across workers - Multiple sources: entities appearing in both extracted JSONL and hand-authored instances must resolve to the same canonical ID - Long-running corpus: provisional IDs assigned in batch 1 may be upgraded or merged by batch 100; the identity server tracks the full lifecycle

You do not need it when: - Single-document manual curation (the scandal_instances.py pattern) - Small, single-pass ingestion with no concurrency - Prototype or exploratory work where provisional IDs are acceptable final output

5.4 Advisory Locks and Race Conditions

on_entity_added must fire inside the same transaction as the entity insert to avoid the race where two workers independently insert the same entity and both proceed without merging. The advisory lock pattern in the identity server core handles this.

5.5 Scaling the Identity Server Independently

The identity server is the natural scaling boundary: its cache hit rate grows with corpus size, its write load is bounded by new entity discovery rather than document count, and it exposes a simple HTTP API that any pipeline stage can call synchronously.


Chapter 6: Querying -- The Payoff

Purpose: The graph is only useful if you can ask it questions. This chapter is the "so what does all this buy you" moment.

6.1 The Graph as a Reasoning Surface

  • The in-memory graph: get(), edges_from(), edges_to(), O(degree) neighbor lookup.
  • The asserted graph as the default traversal surface.
  • edges_from, edges_to with truth-status and predicate-type filtering.
  • describe() as a single-step summary delegating to __str__.

6.2 BFS in Depth

  • Hop layers as the return structure.
  • Symmetric traversal: both outward and inward edges. Why this matters for symmetric predicates (Knows) and event participation (Involves).
  • Statement nodes in the hop layer: higher-order predicates become traversable in later hops.
  • truth_values parameter: the default is ('asserted_true',); overriding to include disputed/hypothetical is a deliberate choice.
  • Worked example: all people and events within two hops of Irene Adler.

6.3 Transitive Closure

  • transitive_closure(entity_id, pred_type): follows one predicate type transitively from a starting node.
  • Worked example: 221B Baker Street is in London is in England -- reachable via two LocatedIn hops.
  • The Transitive trait and what it means for schema design.

6.4 Temporal Queries: sentence_cutoff

  • sentence_cutoff as an exclusive upper bound on sentence_ids.
  • Building a pre-revelation subgraph: all facts known before Holmes announces his conclusion.
  • Epistemic temporal reasoning: KnewAt predicates combined with sentence_cutoff to answer "what did Watson know at sentence N?"

6.5 MCP Tools -- Exposing the Graph to LLMs

[PLACEHOLDER -- MCP tools exist in the codebase but are not fully documented here]

  • find_by_type: return all instances of a type, with str(inst) as the label.
  • subgraph_json: BFS from a seed set, serialize to JSON for LLM consumption.
  • Why this architecture is better than RAG for structured relational queries: the graph enforces constraints that a vector store cannot; the LLM synthesizes over a structured, auditable result set rather than raw retrieved passages.

6.6 Graph.from_module

  • Building a graph from a hand-authored module (scandal_instances.py).
  • Useful for gold-standard test fixtures, manual curation, and rapid iteration before the pipeline is run.
  • The place: vs wiki: prefix convention for locations.

Chapter 7: Production Scale

[PLACEHOLDER -- architecture sketch from BigChat exists; not yet implemented or tested]

Purpose: What changes when the corpus is hundreds of papers and ingestion must run continuously.

7.1 The SQS / ECS Worker Pattern

  • Documents arrive on an SQS queue; ECS workers consume and process them through the five pipeline stages.
  • The identity server as an independently scalable service that all workers call synchronously.
  • The pipeline stages are stateless transforms over JSONL; any worker can pick up any document.

7.2 Local Inference at Scale

  • The G533 / RX 9060 XT role: high-throughput batch inference for NER and triplet extraction. Local for cost, not for latency.
  • ROCm vs. CUDA considerations for Ollama-based local inference.
  • Model size tradeoffs: 14B fits in 16GB VRAM for coref and triplet extraction; 32B is a tight fit and slower.
  • The G533 is suited for overnight batch runs; frontier cloud models handle the low-volume, high-reasoning passes.

7.3 Auto-scaling Groups

[PLACEHOLDER]

7.4 Monitoring and Audit

[PLACEHOLDER]

  • Provenance as the audit surface: every claim is traceable to its source document and extraction pass.
  • Disputed claim dashboards: when two papers produce conflicting claims about the same entity pair and predicate, the graph surfaces the conflict rather than silently overwriting.

Appendix: Formal Definition

The complete formal definition of the typed knowledge graph model. This is the reference document -- definitions are precise, notation is used, and nothing is left to interpretation.

Sections: 1. The 4-tuple: \(G = (T, \Phi, V, \tau)\) 2. Schema layer: \(T_\text{ent}\), \(T_\text{pred}\), \(\Phi\), domain, range, traits 3. Instance layer: \(V\), \(\tau\), the derived edge set \(E \subseteq V\) 4. Canonical identity axiom: injectivity of id on \(V\) 5. The validity constraint: field conformance, domain/range enforcement via Pydantic 6. Trait vocabulary: Symmetric, Transitive, Functional, InverseFunctional, Inverse(\(p'\)), Rule(\(\phi \Rightarrow \psi\)) 7. Rule(\(\phi \Rightarrow \psi\)) as a Datalog Horn clause: decidability, least fixed point, Datalog restrictions - Implementation-status callout: no rule engine exists yet; named traits are inert markers, transitive_closure is query-only, not materializing. See docs/datalog_rules.md for the practitioner's-guide treatment (worked examples, fixed-point runner sketch). 8. Truth status: the asserted graph, the closed-world assumption, the lifecycle 9. Provenance: the sub-schema requirement, the two id regimes (pipeline-extracted vs. content-addressed) 10. Hard rules R1–R10 11. Python enforcement pattern: the class hierarchy, frozen models, mixin traits 12. Vocabulary table: terms to use, terms to avoid, terms to use carefully 13. Non-goals: not RDF/OWL, not Neo4j, not ER, not a general ontology language, not stringly-typed


Notes on Sources

Chapter Primary source
Introduction, Ch 1 BigChat.md §§ Introduction through 1.5; formal_spec.md Introduction
Ch 2 schema src/ner_20260608/holmes_schema.py, docs/formal_spec.md
Ch 2 pipeline BigChat.md §§ 2.1–2.6; src/{sentencize,coref,merge,events,triplets}.py
Ch 2 loader/graph src/ner_20260608/{loader,graph}.py, docs/cookbook.md
Ch 3 BigChat.md §§ 3.x (medical discussion); no working code yet
Ch 4 BigChat.md §§ 4.x (domain service discussion); identity server code not in this repo
Ch 5 BigChat.md §§ 5.x; identity server code not in this repo
Ch 6 src/ner_20260608/graph.py, docs/cookbook.md; MCP tools partially documented
Ch 7 BigChat.md (SQS/ECS sketch); not implemented
Appendix docs/formal_spec.md (verbatim with minor reorganization)
Appendix, Rule(\(\phi \Rightarrow \psi\)) note docs/datalog_rules.md; no rule engine implemented yet