Skip to main content
Everything you store in Khora fits into three layers: a tenancy layer that isolates datasets, a content layer that holds what you ingested and what was extracted from it, and an event layer that records every change. 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. A remember() 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, via source_document_ids and source_chunk_ids:
  • Document “Meeting Notes” is split into Chunk #1, #2, #3.
  • Entity “Alice” is extracted from chunks 1–3. Its source_chunk_ids point back to all three.
  • Relationship “Alice WORKS_FOR Acme” records the same source chunks.
This is what makes provenance (“where did we learn this?”), citation (“here’s the source”), and cascading cleanup work: 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_at on chunks, and valid_from / valid_until on entities and relationships (when something was true in the real world).
  • System time: created_at (never changes) and updated_at (last modified).
The dream phase adds an invalidation layer on top: relationships and facts carry 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 → many Chunks. Extraction over its chunks yields Entitys.
      • EntityRelationshipEntity: 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.