The tenancy layer is covered in Namespaces & isolation; where
the rows physically live is covered in Storage backends.
This page is the content and event model.
Content models
Document
The raw content you store, the starting point for everything. Aremember() call
creates one document, then chunks, embeds, and extracts from it.
Documents move through a lifecycle:
PENDING (created, queued) → PROCESSING
(chunking/embedding/extraction) → COMPLETED or FAILED. A document can also be
ARCHIVED: retained but excluded from active processing (re-ingest under the same
external_id skips it unless you pass reprocess_archived=True).
Chunk
Document pieces optimized for embedding and retrieval. Each chunk carries the vector that semantic search runs against.chunk.score on a recall result is a normalized rank within that result, not a
raw similarity. For confidence, read result.engine_info["max_raw_vector_score"]
(see Core APIs).
Entity
A named concept extracted from your content.Khora doesn’t enforce a taxonomy.
entity_type and relationship_type are free
strings you supply via the required entity_types / relationship_types
arguments on every remember(), or through a richer ExpertiseConfig. Common
conventions are PERSON, ORGANIZATION, LOCATION, PRODUCT, CONCEPT,
EVENT, TECHNOLOGY, but the names are yours. See the
ontology example.Relationship
A typed, directed edge between two entities.Episode
An event with temporal extent. It connects multiple entities to a point or span in time (occurred_at, duration_seconds, entity_ids). Useful for “what happened,
when, and who was involved.”
The source chain
Every entity and relationship remembers where it came from, viasource_document_ids and source_chunk_ids:
Document“Meeting Notes” is split intoChunk #1,#2,#3.Entity“Alice” is extracted from chunks 1–3. Itssource_chunk_idspoint back to all three.Relationship“AliceWORKS_FORAcme” records the same source chunks.
forget(document_id) removes the
document and updates the entities and relationships that referenced it.
Bi-temporal time
Khora separates two notions of time, which is what lets it answer “what did we believe then?” as well as “what changed?”:- Event/validity time:
source_timestamp/occurred_aton chunks, andvalid_from/valid_untilon entities and relationships (when something was true in the real world). - System time:
created_at(never changes) andupdated_at(last modified).
invalidated_at / invalidated_by, so a superseded
row is soft-deleted (kept for audit) rather than destroyed.
Event layer
MemoryEvent
Every change is recorded as an immutable event, an append-only audit trail.
Event types span the lifecycle of each resource:
Correlation IDs make the log queryable as a causal chain: one
remember() call
emits a document event, several chunk events, and many entity/relationship events,
all sharing one correlation_id, so “what happened as a result of X?” is one query.
How it fits together
Namespace: the container for everything below.Document→ manyChunks. Extraction over its chunks yieldsEntitys.Entity→Relationship→Entity: typed, directed edges between entities.Entity→ participates in →Episode: an event with temporal extent.
MemoryEvent→ records every change to all of the above.
Storage backends
Where these rows physically live: PostgreSQL + pgvector + Neo4j, or the
embedded sqlite_lance stack.
Namespaces & isolation
The tenancy layer: the dual-ID scheme, isolation contract, and versioning.