World state, not documents¶
Concept 1 of 8
Telha does not store your documents and let you search them. It stores facts, projected out of your documents and systems, as versioned nodes and relationships in a graph. Documents are evidence for those facts; the facts are the product.
Normative spec
The version shape referenced here is defined by version-record-model. Ingestion, which is how documents become fact versions, is defined by ingestion-provenance.
A contract is not a PDF¶
Ingest a contract PDF and Telha does not keep "a PDF with some tags." It parses the document into a set of fact versions: the parties, the clauses, the effective dates, the amounts, each one a separate versioned node or relationship in the graph. Every one of those fact versions knows exactly which bytes of which source document version it came from (see Provenance to the span).
The practical difference shows up the moment you ask a question. Search over the PDF gives you back the PDF, or a passage of it, and you re-derive the answer yourself. A query over Telha's state gives you the answer directly, as data: "who are the parties," "what is the termination clause," "which risks reference this supplier," each backed by a record you can filter, sort, join, and time-travel.
NodeVersion and EdgeVersion¶
Conceptually, everything Telha knows is one of two shapes:
- A NodeVersion: an entity at a point in its history. A
RISK, aPERSON, aCONTRACT, aCLAUSE. It carries labels (what kind of thing it is, UPPER_SNAKE by convention:RISK,PERSON,CLARIFICATION) and properties (its data:severity,status,display_name). - An EdgeVersion: a relationship between two entities at a point in its history.
OWNED_BY,MEMBER_OF,HAS_ATTACHMENT,CLARIFIES. Edges carry their own properties too (an edge can have anordfor array position, or aconfidence, independent of the nodes it connects).
Both are versions, not rows you overwrite. A correction to a risk's severity does not edit the existing record, it writes a new NodeVersion for the same logical entity. The old version stays reconstructable. That append-only discipline is what lets Telha answer "what did we believe on this date" months later; the full mechanics (the three clocks, the winner rule, tombstones) are the tritemporal model.
flowchart LR
subgraph doc["Source document"]
PDF["report.pdf<br/>(raw bytes)"]
end
subgraph facts["World state"]
N1["NodeVersion: RISK<br/>severity=7, status=open"]
N2["NodeVersion: PERSON<br/>display_name=Sarah"]
E1["EdgeVersion: OWNED_BY<br/>RISK -> PERSON"]
end
PDF -->|ingest & project| N1
PDF -->|ingest & project| N2
N1 --> E1
E1 --> N2 Labels, properties, relationships¶
A record's label is what your query grammar matches on: {"find": "RISK"} returns every current RISK node. Labels are how the same graph holds contracts, risks, people, clauses, and clarifications side by side without a fixed schema migration for each new kind of fact; Telha infers structure from what is written, rather than requiring it up front.
Properties are the record's data, addressable with dotted paths in queries ("data.owner"). They come from whatever your source actually contains: a CSV column, a JSON field, an extracted contract clause. There is no requirement that every RISK have the same properties as every other RISK; the schema is inferred and can be inspected (and time-traveled) through the schema endpoint.
Relationships are first-class edges, not foreign keys buried in a properties blob. RISK -[OWNED_BY]-> PERSON, MESSAGE -[HAS_ATTACHMENT]-> DOCUMENT, CLARIFICATION -[CLARIFIES]-> RISK. Because edges are versioned like nodes, the graph can answer "who owned this risk in March" the same way it answers "what was this risk's severity in March."
How ingestion turns documents into fact versions¶
The path from a source document to queryable state is a projection, not a copy:
- A document (PDF, spreadsheet, email, web page, code) arrives at
POST /v1/ingest, or a connector pulls it from a source system. - A worker (or, for structured JSON, core itself) parses it into a canonical extracted text plus a set of proposed nodes, edges, and spans (the exact byte ranges of the canonical text that support each fact).
- Core writes the source version, the canonical text, the span rows, and the resulting node and edge versions together.
- Re-ingesting byte-identical content is a no-op (content-hash dedup); a changed document produces a new source version, and facts derived from it can be corrected or superseded without losing the old belief.
The same source can back many facts (a ten-page contract might yield a dozen CLAUSE nodes and a handful of PERSON/ORG nodes), and the same fact can be reinforced by more than one source over time. Neither direction requires choosing: the graph and the span ledger both stay intact.
Structured JSON skips the worker fleet
When you ingest with format: "json", core parses and projects the record directly (no worker round trip), and the response is synchronous (201 Created with the resulting node and edge ids) rather than a job to poll. Every other format (docling, tabular, email, web, code) is asynchronous, because it goes through a Python format worker.
Why this shape, not a document store¶
A document store answers "which documents mention Meridian Logistics." A graph of versioned facts answers "what is our current exposure to Meridian Logistics," "who owns that risk," and "what did we believe about it in March," because those are questions about state, not about text. Projecting documents into state once, at ingestion time, means every later query, generation, or diff operates on structured facts instead of re-reading and re-interpreting prose on every request.
This is also why provenance has to be exact rather than document-level: a fact that is wrong needs to be traceable to the specific sentence that produced it, not just "somewhere in this PDF." That mechanism is Provenance to the span.
Related¶
- The tritemporal model - the three clocks stamped on every NodeVersion and EdgeVersion.
- Provenance to the span - how a fact version stays linked to the exact source bytes.
- Architecture › The version record model - storage layout and the write path.
- Quickstart - ingest a record and query it end to end.