narrative-graph reads prose and returns candidate (subject, relation, object) facts with a confidence score and the exact source span each one came from. No API key, no network call, no model download. It stores nothing and queries nothing — it's a producer you point at any store, or at no store at all.
The default build has zero dependencies. cargo tree prints the crate and nothing else; the extraction core compiles against the standard library alone, and CI fails if that ever stops being true. Serde, TypeScript bindings, the Node addon, and the CLI are each opt-in features that serve a boundary, never the core.
Input:
Elena is Marco's sister. Marco mentors Dev, who works at the Archive.Output (measured,
cargo run --features cli -- extract -):dev --works_at--> archive 0.75 [39..68] elena --sister_of--> marco 0.85 [0..14] marco --mentors--> dev 0.78 [25..42]
Works with Rust 1.77+ and Node.js 18+, on macOS, Linux, and Windows. Pairs naturally with holographic-memory's Meaning Memory — memorizeTriplet / relate_phase expect exactly this shape — but has no dependency on it and no opinion on what you do with a candidate.
Install
npm
Cargo
[]
= "0.1"
GitHub
Cargo features
| Feature | Adds | Dependencies |
|---|---|---|
| (default) | The heuristic extraction pipeline | none |
serde |
Serialize/Deserialize on the public types |
serde |
json |
JSON output | serde, serde_json |
bindings |
TypeScript declarations via ts-rs |
serde, ts-rs |
node-api |
The Node addon | napi, napi-derive, napi-build |
cli |
The narrative-graph binary |
clap, anyhow |
What It Extracts
Sentences
Segmentation is hand-rolled rather than a naive split on ./!/?, because narrative prose is made of exactly the constructs a naive splitter breaks on: honorifics (Dr. Smith went home.), initials (J. R. R. Tolkien), ellipses (She paused... then left.), and quoted dialogue with attribution ("Who are you?" she asked. is one sentence, not two). Sentences are returned as borrowed slices of the input, so segmentation allocates nothing per sentence.
Entities
Capitalized-token detection, pronoun-antecedent linking within a sentence, and an optional caller-supplied alias list so "Marcus" and "the detective" resolve to the same entity when you already know that.
You supply:
{ aliases: { "the detective": "marcus" } }Result: mentions of "the detective" resolve tomarcusin every candidate.
Relations
Verb-phrase heuristics between two entity candidates in the same clause — familial, social, professional, and spatial relations ("sister of," "mentors," "works at," "located in"). Surface forms normalize to a small controlled vocabulary (sister_of, mentors, works_at, ...) rather than passing through raw verb phrases, so "trains," "is the mentor of," and "mentors" all collapse to the same relation type. Supply your own ontology map to override or extend the defaults.
Explainability
Every candidate reports the rule that produced it, not just a score — possessive-sister-pattern, verb-mentor-pattern, verb-works-at-pattern, relative-mentor-pattern, relative-works-at-pattern. A confidence number alone tells you nothing about why the pipeline believes something; the rule name does.
Confidence and Span
Every candidate carries a confidence score from the extraction rule's pattern strength and how close the subject and object are in the source text, plus the byte offset of its extracted span — a range covering the subject, the object, and the token that licensed the relation. Gate on a threshold, route low-confidence candidates to human review, or fetch source context for display.
API
| Item | What it does |
|---|---|
extract_candidate_triples(text, &Options) |
Run the heuristic pipeline over a passage |
Options { aliases, min_confidence, ontology } |
Alias map, confidence floor, relation-vocabulary overrides |
TripleCandidate { subject, relation, object, confidence, span, rule } |
One candidate fact; span is a byte range into the input, rule names the pattern that produced it |
Installed alongside the crate (cargo install narrative-graph --features cli provides the narrative-graph binary), the same way holographic-memory ships hms-admin and hms-eval as bins in its own crate rather than separate packages. Useful for shell pipelines, CI checks, and inspecting output before wiring the library into an application.
| Function | What it does |
|---|---|
extractCandidateTriplesNapi(text, opts?) |
Run the heuristic pipeline over a passage |
opts accepts aliases, minConfidence, and ontology. Full TypeScript definitions ship in the package (index.d.ts), generated from the Rust types via ts-rs — treat them as the source of truth over this table.
Guides
- Architecture -- the extraction pipeline and confidence scoring
- Integration with holographic-memory -- extracting, thresholding, and feeding a Meaning Memory store
- Evaluation -- what is measured, what is not, and the recall and precision numbers on real prose
- Contributing -- development setup and conventions
Requirements
- Rust 1.77+ or Node.js 18+
- No API key, no network access, no external service of any kind
Development
&&
TypeScript definitions in bindings/ are generated from the Rust types via ts-rs — don't hand-edit them.
Why narrative-graph
| Hand-written regex | General-purpose NLP toolkit | LLM-based extraction | narrative-graph | |
|---|---|---|---|---|
| Runs offline, no API key | yes | yes | no | yes |
| Tuned for narrative prose (dialogue attribution, familial/social relations) | you build it | no | depends on prompt | yes |
| Confidence score per candidate | no | model-dependent | inconsistent across calls | yes |
| Source span on every candidate | you build it | sometimes | rarely | yes |
| Cost per extraction | free, but brittle | free, general-purpose | API cost + latency | free, microsecond-scale |
| Output shape ready for a relational store | you build it | you build it | you parse it | yes -- matches memorizeTriplet/relate_phase |
narrative-graph isn't trying to out-perform an LLM at open-domain relation extraction — it's trying to be the thing you reach for when a manuscript's characters and relationships need to become structured facts, offline, cheaply, and repeatably, with enough provenance on each candidate that a human or a downstream store can decide what to trust.
Contributing
We welcome contributions of all sizes. Check the issue tracker for good first issue labels, or see CONTRIBUTING.md for development setup.
The sentence-level pipeline is complete and useful on its own. Where the project goes next is tracked as enhancement issues — whole-document aggregation and contradiction detection are the highest-leverage next steps, and each issue states what needs deciding before any code. Design discussion on those is as valuable as a patch.
Areas where help is especially welcome:
- Additional relation-verb patterns for non-English narrative conventions
- Integration examples for stores other than
holographic-memory
Security
Found a vulnerability? Please report it privately — see SECURITY.md.
License
Apache-2.0 © WritersLogic, Inc.