Data model (datadesign)

August 25, 2026 · View on GitHub

Reflects code as of v0.18.0. Last verified: 2026-08-25.

Authoritative source: helixir/schema/schema.hx (node + edge definitions) and helixir/schema/queries.hx (191 HQL queries that materialize the contract). Anything below disagreeing with those files is the bug.

1. Storage at a glance

The diagram is the active conceptual core, not a substitute for the complete node/edge tables below.

%%{init: {"theme":"base","themeVariables":{"primaryColor":"#fff3d6","primaryTextColor":"#17130d","primaryBorderColor":"#c88613","lineColor":"#6f675b","secondaryColor":"#eee9ff","tertiaryColor":"#e7f7ef","fontFamily":"Inter, ui-sans-serif, system-ui"}}}%%
flowchart LR
    User(("User")) -->|HAS_MEMORY| Memory["Memory<br/>atomic fact"]
    Agent(("Agent")) -->|CREATED| Memory
    Memory -->|MEMORY_IN_RBAC_GROUP| Group["RbacGroup"]
    User --> Assignment["RbacAssignment"] --> Group

    Memory -->|MENTIONS| Entity(("Entity"))
    Memory -->|INSTANCE_OF| Concept(("Concept"))
    Memory -->|TAGGED_AS| Category(("Category"))
    Memory -->|BECAUSE · IMPLIES · CONTRADICTS| Related["Memory"]
    Memory -->|typed MEMORY_RELATION| Related
    Memory -->|HAS_CHUNK| Chunk["MemoryChunk"]
    Embedding[("MemoryEmbedding")] -. "vector projection" .-> Memory

    Dedup["RbacDedupGroup"] -->|RBAC_GROUP_IN_DEDUP| Group
    Moirai["Moirai hypothesis<br/>reserved moirai"] -->|MOIRAI_DERIVED_FROM| Memory

The physical contract declares 22 node types, 5 vector-index types, 30 edge types, and 191 named HQL queries. Declaration is not the same as runtime use. The tables below label every surface as active, optional, or reserved; reserved entries have no live producer and are not product capabilities. The default embedding dimension is 768.

There is no relational database or Redis. Every durable memory, reasoning, identity and RBAC fact lives in HelixDB. Host-local configuration, operation journals, model files and recovery archives are operational state, not a second knowledge or authorization store.

2. Node taxonomy

Nodes group into identity, content, semantics, reasoning, authorization, categories, buffering, and a reserved document pipeline.

Physical nodeStatusRuntime truth
UseractiveIdentity and memory provenance.
AgentactivePhysical presence/provenance row for one execution instance, with explicit owning RBAC principal_id and a bounded lease. The durable logical-agent registry is the distinct principal-family projection; child rows (agent_id != principal_id) are counted separately as sub-agents.
MemoryactiveAtomic fact and the core retrieval unit. The eight user-facing ontology types are values of memory_type, not eight physical node kinds.
RbacGroupactiveConcrete access workspace.
RbacDedupGroupactive (optional instance)Created only when an administrator configures a dedup federation.
RbacAssignmentactiveAuditable global/group role grant.
RbacConfigactivePermanent RBAC bootstrap and schema-policy state.
MemoryChunkactiveOversized raw-source reconstruction; chunks are not retrieval units (#86).
EntityactiveExtracted and deduplicated named entity.
ConceptactiveFixed ontology hierarchy and memory classification target.
ContextactiveSituational validity such as work or project:name. name is indexed and the write pipeline reuses an exact-name match before creating a node. A transport or missing-route error is never treated as a graph miss.
HistoryEventactiveUPDATE/SUPERSEDE/DELETE audit trail.
PendingInputactive, transientBuffered write queue; successful entries are drained.
MemoryNoticeactive, transientDeferred write outcomes delivered back to the owner.
CategoryactiveClotho controlled vocabulary and Moirai routing substrate.
SessionreservedDeclared, but no live code path creates Session nodes. Helixir is not a chat-history store.
ConstraintreservedNo live producer or consumer; contextual validity already uses VALID_IN.
ReasoningdeprecatedNo HQL query creates this node. Live justifications are first-class memory edges; the backup-first retirement procedure below replaces this duplicate shape.
DocPage, DocChunk, CodeExample, ErrorCodereservedNo Rust producer; not a shipped documentation-ingest feature.

Vector indexes are a separate physical family:

Vector typeStatusRuntime truth
MemoryEmbeddingactiveMain vector projection used by hybrid retrieval.
EntityEmbeddingreservedHQL helper exists, but no live Rust producer persists it.
ChunkEmbeddingreservedBelongs to the inactive DocChunk pipeline; MemoryChunk is deliberately not embedded.
ConceptEmbeddingreservedNo live producer; concept classification uses the fixed ontology/cache path.
CategoryEmbeddingreservedClotho computes category similarity in process and does not persist these vectors.

2.1 Machine-checked physical lifecycle ledger

src/schema_inventory/ is the versioned product-lifecycle contract layered on top of schema.hx. CI parses the HQL declarations and requires an exact match: an active declaration names producer, consumer and DB-backed E2E evidence; a reserved declaration names an owner and milestone; a deprecated declaration names its migration. The control plane reads this same Rust inventory and combines it with the three server-side aggregate census queries. The table below is a checked documentation projection, not a second source of truth.

Physical declarationLifecycleOwner
N::Useractivecore/rbac
N::RbacGroupactivecore/rbac
N::RbacDedupGroupactivecore/rbac
N::RbacAssignmentactivecore/rbac
N::RbacConfigactivecore/rbac
N::Sessionreservedcore/session
N::Agentactivetooling_manager/swarm
N::Memoryactivetooling_manager/add_pipeline
N::Entityactivetooling_manager/add_pipeline
N::Conceptactivemind_toolbox/ontology
N::Contextactivetooling_manager/add_pipeline
N::Constraintreservedcore/charter
N::Reasoningdeprecatedmind_toolbox/reasoning
N::HistoryEventactivetooling_manager/add_pipeline
N::MemoryChunkactivemind_toolbox/chunking
N::DocPagereservedreserved/document-ingest
N::DocChunkreservedreserved/document-ingest
N::CodeExamplereservedreserved/document-ingest
N::ErrorCodereservedreserved/document-ingest
N::PendingInputactivetooling_manager/ingest
N::MemoryNoticeactivetooling_manager/ingest
N::Categoryactiveagents/clotho
V::MemoryEmbeddingactivemind_toolbox/memory
V::EntityEmbeddingreservedmind_toolbox/entity
V::ChunkEmbeddingreservedreserved/document-ingest
V::ConceptEmbeddingreservedmind_toolbox/ontology
V::CategoryEmbeddingreservedagents/clotho
E::RBAC_MEMBER_OFactivecore/rbac
E::MEMORY_IN_RBAC_GROUPactivecore/rbac
E::RBAC_GROUP_IN_DEDUP_GROUPactivecore/rbac
E::MEMORY_IN_RBAC_DEDUP_GROUPactivecore/rbac
E::HAS_MEMORYactivetooling_manager/add_pipeline
E::INSTANCE_OFactivemind_toolbox/ontology
E::MENTIONSactivemind_toolbox/entity
E::EXTRACTED_ENTITYactivemind_toolbox/entity
E::IS_Areservedmind_toolbox/ontology
E::HAS_SUBTYPEactivemind_toolbox/ontology
E::RELATES_TOactivemind_toolbox/entity
E::PART_OFreservedmind_toolbox/entity
E::VALID_INactivetooling_manager/add_pipeline
E::CREATED_INreservedcore/session
E::AGENT_CREATEDactivetooling_manager/add_pipeline
E::HAS_HISTORYactivetooling_manager/add_pipeline
E::HAS_CHUNKactivemind_toolbox/chunking
E::MEMORY_RELATIONactivemind_toolbox/reasoning
E::IMPLIESactivemind_toolbox/reasoning
E::BECAUSEactivemind_toolbox/reasoning
E::CONTRADICTSactivetooling_manager/contradictions
E::SUPERSEDESactivetooling_manager/add_pipeline
E::HAS_EMBEDDINGactivemind_toolbox/memory
E::ENTITY_HAS_EMBEDDINGreservedmind_toolbox/entity
E::CHUNK_TO_EMBEDDINGreservedreserved/document-ingest
E::CONCEPT_RELATED_TOreservedmind_toolbox/ontology
E::SUBCATEGORY_OFactiveagents/clotho
E::ALIAS_OFactiveagents/clotho
E::TAGGED_ASactiveagents/clotho
E::MOIRAI_DERIVED_FROMactivecore/rbac_moirai

The deprecated Reasoning node remains declared and read-only until a dedicated HelixDB v2.3.5 migration proves reasoning_count=0. Its removal requires a verified cold volume backup, stopped writers, helix check with the pinned CLI, rebuild/recreate against the same volume, and read-only post-deploy census verification. If the census is non-zero, removal stops and the rows are first projected into the authoritative first-class edge model. There is no in-place destructive shortcut.

2.2 Category subgraph (Clotho, 2026-06)

The controlled-vocabulary substrate the Moirai route over (d8edc85). A deliberate third axis over the flat memory graph: a memory's category membership lets it bridge to distant memories that share it.

ArtifactShapeNotes
Category nodecategory_id, name (normalized, English-canonical), kind, description, created_atDictionary entry. Seeded by Clotho::seed_dictionary.
CategoryEmbedding nodenameVector for embedding-match tagging. Reserved — Clotho v0 matches by in-memory cosine (SearchV exposes no readable score), so no producer wires this yet.
TAGGED_AS edgeMemory → Category, {confidence, source}The tag. Clotho::auto_tag (source="clotho-embed").
SUBCATEGORY_OF edgeCategory → CategoryPersisted hierarchy. Clotho/tooling writes it; current query-time ancestor propagation still uses the in-memory seed table.
ALIAS_OF edgeCategory → CategorySynonyms (collapses "raw material"/"сырьё"); Clotho writes canonical aliases at mint time.
CategoryEmbedding is a reserved vector type without a graph edge in the
current schema. Clotho embeds category names in process for matching; it does
not persist category vectors yet.

Routing reads: getMemoryCategories, getMemoriesByCategory (membership + global-admin-only cross-domain bridge in connect_memories); category_member_ids feeds Lachesis PMI subset-overlap (ln(\|A∩B\|·N / (\|A\|·\|B\|))). The planned Category —CO_OCCURS{count, pmi}→ Category edge + Insight journal nodes are the next schema step (persists what PMI v0 computes on the fly).

3. Edge taxonomy

   IDENTITY                CONTENT                     SEMANTICS
   ────────                ────────                    ─────────
   User HAS_MEMORY ───►Memory◄─── HAS_CHUNK ── MemoryChunk
                          │                       │
                          │ MENTIONS ─────────► Entity
                          │ EXTRACTED_ENTITY ─► Entity
                          │ INSTANCE_OF ──────► Concept
                          │ TAGGED_AS ────────► Category
                          │ VALID_IN ─────────► Context
                          │                       │
                          │ HAS_EMBEDDING ────► MemoryEmbedding
   Agent AGENT_CREATED ──►│                       │
                          │ HAS_HISTORY ──────► HistoryEvent
                          │                       │
   REASONING (Memory→Memory):  7 semantic types via dedicated + generic edges
   DECISION  (Memory→Memory):  SUPERSEDES · CONTRADICTS

Memory→memory relations: seven semantics, two storage shapes

The reasoning API exposes seven typed memory↔memory relations. IMPLIES, BECAUSE, and CONTRADICTS use dedicated physical edges. SUPPORTS, RELATES_TO, PART_OF, and IS_A use MEMORY_RELATION with the semantic type in relation_type. search_reasoning_chain and get_memory_graph project the semantic type rather than requiring consumers to know the storage shape. (src/toolkit/mind_toolbox/reasoning/types.rs, edges.rs; query addMemoryRelation.)

The decision pipeline additionally uses these dedicated edges:

EdgePropertiesCreated in
SUPERSEDESreason, superseded_at, is_contradictiondecision verdict SUPERSEDE (addMemorySupersession)
CONTRADICTSresolution, resolved, resolution_strategyverdict CONTRADICT / cross-user contradiction (addMemoryContradiction); resolved/resolution_strategy are what the Atropos reconcile pass flips

There is no dedicated SUPPORTS schema edge; support is deliberately stored as MEMORY_RELATION{relation_type="SUPPORTS"}.

Active edges

EdgeFrom → ToPropertiesCreated in
HAS_MEMORYUser → Memorycontext, access_counttooling_manager/add_pipeline/cross_user.rs; consensus user_count derives from these (#54)
INSTANCE_OFMemory → Conceptconfidenceontology mapping in add pipeline
MENTIONSMemory → Entitysalience, sentimententity manager
EXTRACTED_ENTITYMemory → Entityconfidence, methodextractor output
RELATES_TOEntity → Entityrelationship_type, strength, bidirectionalextractor relations
VALID_INMemory → Contextpriority, exclusiveadd_pipeline/context_link.rs (creates the Context on miss)
AGENT_CREATEDAgent → Memorytimestamp, methodtooling helpers — ensure-then-link: the Agent node is auto-created on first sight
HAS_HISTORYMemory → HistoryEventevery UPDATE/SUPERSEDE/DELETE
HAS_CHUNKMemory → MemoryChunkchunk_indexchunking manager
MEMORY_RELATIONMemory → Memoryrelation_type, strength, created_at, metadataSUPPORTS plus associative relations — see above
IMPLIES / BECAUSEMemory → Memoryprobability/strength + reasoning idreasoning engine and contradiction reconciliation
SUPERSEDES / CONTRADICTSMemory → Memorysee abovedecision/reasoning engines — see above
HAS_EMBEDDINGMemory → MemoryEmbeddingembedding_modeladd pipeline
HAS_SUBTYPEConcept → Conceptontology loader (seed; self-healing against duplicate trees, #67)
TAGGED_ASMemory → Categoryconfidence, sourceClotho::auto_tag (§2.1)
SUBCATEGORY_OF / ALIAS_OFCategory → Categorycategory tooling / Clotho vocabulary convergence
MOIRAI_DERIVED_FROMMemory → Memorysource, created_atAdmin-only generated hypothesis → source-memory provenance; not traversed by ordinary reasoning/search
RBAC_MEMBER_OFUser → RbacGrouprole/grant metadataRBAC assignment service
MEMORY_IN_RBAC_GROUPMemory → RbacGroupassignment auditwrite path and migration; materialized visibility boundary
RBAC_GROUP_IN_DEDUP_GROUPRbacGroup → RbacDedupGroupassignment auditfederation administration
MEMORY_IN_RBAC_DEDUP_GROUPMemory → RbacDedupGroupassignment auditwrite path; persisted dedup provenance

Reserved edges

Schema-declared and HQL-ready, but not written by a live product flow:

  • CREATED_IN (Memory→Session): helper/query exists, but Session creation is not wired;
  • IS_A and CONCEPT_RELATED_TO (Concept→Concept): reserved internal representation for the fixed ontology, not runtime ontology extension;
  • PART_OF (Entity→Entity): helper/query exists, while current extraction persists typed entity RELATES_TO edges;
  • ENTITY_HAS_EMBEDDING: query exists for a future persisted entity-resolution index, but no Rust caller writes it;
  • CHUNK_TO_EMBEDDING: belongs only to the reserved documentation pipeline. MemoryChunk deliberately has no vector edge.

The schema contains exactly 30 edge declarations. Names from older releases such as BELONGS_TO_CATEGORY, NEXT_CHUNK, OCCURRED_IN, IN_SESSION, APPLIES_IN, CHUNK_MENTIONS_CONCEPT, CONCEPT_HAS_EXAMPLE, and ERROR_REFERENCES_CONCEPT are historical and are not part of the current contract.

4. Ontology hierarchy (instances of Concept)

Thing
 ├── Attribute
 │     ├── Fact
 │     ├── Preference
 │     ├── Skill
 │     ├── Goal
 │     ├── Opinion
 │     └── Trait
 ├── Event
 │     ├── Action
 │     ├── Experience
 │     └── Achievement
 ├── Entity
 │     ├── Person
 │     ├── Organization
 │     ├── Location
 │     ├── Object
 │     └── Technology
 ├── Relation
 └── State

Loaded once at process boot (ToolingManager::initializeOntologyManager::load). Held in an in-process map; HelixDB is the persistent copy, but reads at runtime hit the in-memory cache.

The 8 user-facing concept types referenced by search_by_concept map to the leaves under Attribute and Event: skill, preference, goal, fact, opinion, experience, achievement, action.

5. Invariants

These are the assumptions the rest of the code relies on. Violating any of them is a data-integrity bug.

  1. Memory.user_id is non-empty for every Memory reachable from HAS_MEMORY. Schema declares DEFAULT "" — see §6 issue #12.
  2. HAS_EMBEDDING is 1:1. Every Memory has at most one MemoryEmbedding. Enforced only by convention; no DB constraint.
  3. SUPERSEDES is acyclic. The decision engine relies on chasing SUPERSEDES edges backward to find the live memory.
  4. HAS_CHUNK positions are ordered and unique per memory. Memory chunks are reconstructed by their stored position; the current schema has no chunk-to-chunk edge.
  5. INSTANCE_OF points to an Attribute-subtree or Event-subtree leaf. The mapper rejects non-leaf classifications.
  6. CONTRADICTS is symmetric in intent. Code writes a single directed edge; queries that walk contradictions handle both directions.
  7. Hive Memory: Memory.user_count is monotone non-decreasing for any given memory_id.

6. Schema patterns to recognize

These are recurring shapes in schema.hx that affect how Rust code reads and writes the data. Tracked variants of these patterns may have open issues — listed here so contributors recognize them without re-deriving from grep.

  • Booleans encoded as I64. immutable, verified, is_deleted, active, resolved, bidirectional, exclusive. HelixDB has no Bool type; convention is 0 = false, 1 = true.
  • Identity fields with DEFAULT "". Memory.user_id, Memory.deleted_at, Memory.deleted_by. An insert without user_id is legal at the schema level and produces a node with empty user_id.
  • JSON-in-string. Memory.metadata, Entity.properties, Entity.aliases, Concept.properties are String columns holding serialized JSON. No schema validation; every read pays a JSON parse.
  • Time-type variation. Memory.created_at is String DEFAULT "{{timestamp}}", while MemoryEmbedding.created_at is Date.
  • Denormalized parent links. Concept.parent_id: String exists alongside the IS_A edge.
  • smart_traversal module name. The _v2 suffix is a naming artifact from an earlier smart_traversal that was removed; the current module is the only implementation.

7. Migration approach (for future schema changes)

There is no arbitrary per-tag data-migration framework today. For a manual schema change, the low-level playbook is:

  1. Edit schema.hx and queries.hx.
  2. Run helixir-deploy --host … --port … --schema-dir helixir/schema.
  3. HelixDB accepts the new schema but does not migrate existing data; adding a non-nullable field to a populated node is therefore not safe.

The v0.16 installer does provide a higher-level transactional deployment orchestrator for a Helixir-managed local database: it identifies ownership, takes and verifies a cold backup before schema deployment, resumes an interrupted plan, and verifies the live contract afterwards. That safety layer does not synthesize arbitrary data transformations and never assumes lifecycle authority over an existing-local or remote database.

8. RBAC graph and compatibility bootstrap

RBAC is stored in HelixDB and is the single source of truth shared by the CLI, MCP server, and library facade. RbacGroup names a team, while RbacAssignment is an auditable grant (subject_id, role, group_id, active, grant/revoke timestamps). RBAC_MEMBER_OF is the traversable principal-to-group edge and MEMORY_IN_RBAC_GROUP links authored memories to the groups active for their author. RbacConfig holds the enforcement switch, the one-way phase (pending, migrating, active), and a once-chosen fresh|legacy branch. The storage-level enabled = 0 default exists only for the pre-bootstrap checkpoint; there is no product-level disabled profile. Bootstrap creates reserved default, onboarding, and moirai, verifies all grants and legacy-memory edges, enables enforcement, and marks the transition active. Failure leaves migrating in HelixDB so the next run resumes.

The default workspace intentionally stores an empty Memory.rbac_scope and uses the legacy unsalted content_key, while MEMORY_IN_RBAC_GROUP supplies the access boundary. Pre-RBAC principals receive equal group-admin rights there. onboarding, moirai, custom groups, and dedup federations use salted security domains. The membership-free moirai workspace holds first-class hypotheses generated across groups. MOIRAI_DERIVED_FROM points from those hypotheses to source memories but is omitted from ordinary reasoning traversal.

An active RBAC_MEMBER_OF edge into onboarding marks a new principal as enrolled. Active or historical membership in either reserved workspace makes the principal visible in the administrative registry. Revocation retains the User node and RbacAssignment audit row; no second registry is persisted.

All three system workspaces are reserved: management APIs reject deactivation and dedup-federation membership because either operation would break the migration, registry, or legacy-fingerprint contract. Enabled policy also rejects revocation of its last global administrator.

The existing Memory.user_id remains the author/owner and is never replaced by a group id. Authorization resolves the actor's active assignments, derives the groups and owners reachable through the graph, and then applies the role matrix: global admin is unrestricted; group admin can read/write and manage roles in one or more assigned non-reserved groups; moderator can read/write its groups; worker can write only their own authored memories; viewer is read-only. Revocation deactivates the assignment and preserves its audit history.

At the API boundary, actor_id is intentionally distinct from Memory.user_id: the former is the principal being authorized, the latter is the owner whose memory is read or written. Agent integrations must supply a stable actor id.