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 node | Status | Runtime truth |
|---|---|---|
| User | active | Identity and memory provenance. |
| Agent | active | Physical 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. |
| Memory | active | Atomic fact and the core retrieval unit. The eight user-facing ontology types are values of memory_type, not eight physical node kinds. |
| RbacGroup | active | Concrete access workspace. |
| RbacDedupGroup | active (optional instance) | Created only when an administrator configures a dedup federation. |
| RbacAssignment | active | Auditable global/group role grant. |
| RbacConfig | active | Permanent RBAC bootstrap and schema-policy state. |
| MemoryChunk | active | Oversized raw-source reconstruction; chunks are not retrieval units (#86). |
| Entity | active | Extracted and deduplicated named entity. |
| Concept | active | Fixed ontology hierarchy and memory classification target. |
| Context | active | Situational 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. |
| HistoryEvent | active | UPDATE/SUPERSEDE/DELETE audit trail. |
| PendingInput | active, transient | Buffered write queue; successful entries are drained. |
| MemoryNotice | active, transient | Deferred write outcomes delivered back to the owner. |
| Category | active | Clotho controlled vocabulary and Moirai routing substrate. |
| Session | reserved | Declared, but no live code path creates Session nodes. Helixir is not a chat-history store. |
| Constraint | reserved | No live producer or consumer; contextual validity already uses VALID_IN. |
| Reasoning | deprecated | No 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, ErrorCode | reserved | No Rust producer; not a shipped documentation-ingest feature. |
Vector indexes are a separate physical family:
| Vector type | Status | Runtime truth |
|---|---|---|
| MemoryEmbedding | active | Main vector projection used by hybrid retrieval. |
| EntityEmbedding | reserved | HQL helper exists, but no live Rust producer persists it. |
| ChunkEmbedding | reserved | Belongs to the inactive DocChunk pipeline; MemoryChunk is deliberately not embedded. |
| ConceptEmbedding | reserved | No live producer; concept classification uses the fixed ontology/cache path. |
| CategoryEmbedding | reserved | Clotho 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 declaration | Lifecycle | Owner |
|---|---|---|
N::User | active | core/rbac |
N::RbacGroup | active | core/rbac |
N::RbacDedupGroup | active | core/rbac |
N::RbacAssignment | active | core/rbac |
N::RbacConfig | active | core/rbac |
N::Session | reserved | core/session |
N::Agent | active | tooling_manager/swarm |
N::Memory | active | tooling_manager/add_pipeline |
N::Entity | active | tooling_manager/add_pipeline |
N::Concept | active | mind_toolbox/ontology |
N::Context | active | tooling_manager/add_pipeline |
N::Constraint | reserved | core/charter |
N::Reasoning | deprecated | mind_toolbox/reasoning |
N::HistoryEvent | active | tooling_manager/add_pipeline |
N::MemoryChunk | active | mind_toolbox/chunking |
N::DocPage | reserved | reserved/document-ingest |
N::DocChunk | reserved | reserved/document-ingest |
N::CodeExample | reserved | reserved/document-ingest |
N::ErrorCode | reserved | reserved/document-ingest |
N::PendingInput | active | tooling_manager/ingest |
N::MemoryNotice | active | tooling_manager/ingest |
N::Category | active | agents/clotho |
V::MemoryEmbedding | active | mind_toolbox/memory |
V::EntityEmbedding | reserved | mind_toolbox/entity |
V::ChunkEmbedding | reserved | reserved/document-ingest |
V::ConceptEmbedding | reserved | mind_toolbox/ontology |
V::CategoryEmbedding | reserved | agents/clotho |
E::RBAC_MEMBER_OF | active | core/rbac |
E::MEMORY_IN_RBAC_GROUP | active | core/rbac |
E::RBAC_GROUP_IN_DEDUP_GROUP | active | core/rbac |
E::MEMORY_IN_RBAC_DEDUP_GROUP | active | core/rbac |
E::HAS_MEMORY | active | tooling_manager/add_pipeline |
E::INSTANCE_OF | active | mind_toolbox/ontology |
E::MENTIONS | active | mind_toolbox/entity |
E::EXTRACTED_ENTITY | active | mind_toolbox/entity |
E::IS_A | reserved | mind_toolbox/ontology |
E::HAS_SUBTYPE | active | mind_toolbox/ontology |
E::RELATES_TO | active | mind_toolbox/entity |
E::PART_OF | reserved | mind_toolbox/entity |
E::VALID_IN | active | tooling_manager/add_pipeline |
E::CREATED_IN | reserved | core/session |
E::AGENT_CREATED | active | tooling_manager/add_pipeline |
E::HAS_HISTORY | active | tooling_manager/add_pipeline |
E::HAS_CHUNK | active | mind_toolbox/chunking |
E::MEMORY_RELATION | active | mind_toolbox/reasoning |
E::IMPLIES | active | mind_toolbox/reasoning |
E::BECAUSE | active | mind_toolbox/reasoning |
E::CONTRADICTS | active | tooling_manager/contradictions |
E::SUPERSEDES | active | tooling_manager/add_pipeline |
E::HAS_EMBEDDING | active | mind_toolbox/memory |
E::ENTITY_HAS_EMBEDDING | reserved | mind_toolbox/entity |
E::CHUNK_TO_EMBEDDING | reserved | reserved/document-ingest |
E::CONCEPT_RELATED_TO | reserved | mind_toolbox/ontology |
E::SUBCATEGORY_OF | active | agents/clotho |
E::ALIAS_OF | active | agents/clotho |
E::TAGGED_AS | active | agents/clotho |
E::MOIRAI_DERIVED_FROM | active | core/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.
| Artifact | Shape | Notes |
|---|---|---|
| Category node | category_id, name (normalized, English-canonical), kind, description, created_at | Dictionary entry. Seeded by Clotho::seed_dictionary. |
| CategoryEmbedding node | name | Vector 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 edge | Memory → Category, {confidence, source} | The tag. Clotho::auto_tag (source="clotho-embed"). |
SUBCATEGORY_OF edge | Category → Category | Persisted hierarchy. Clotho/tooling writes it; current query-time ancestor propagation still uses the in-memory seed table. |
ALIAS_OF edge | Category → Category | Synonyms (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:
| Edge | Properties | Created in |
|---|---|---|
SUPERSEDES | reason, superseded_at, is_contradiction | decision verdict SUPERSEDE (addMemorySupersession) |
CONTRADICTS | resolution, resolved, resolution_strategy | verdict 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
| Edge | From → To | Properties | Created in |
|---|---|---|---|
HAS_MEMORY | User → Memory | context, access_count | tooling_manager/add_pipeline/cross_user.rs; consensus user_count derives from these (#54) |
INSTANCE_OF | Memory → Concept | confidence | ontology mapping in add pipeline |
MENTIONS | Memory → Entity | salience, sentiment | entity manager |
EXTRACTED_ENTITY | Memory → Entity | confidence, method | extractor output |
RELATES_TO | Entity → Entity | relationship_type, strength, bidirectional | extractor relations |
VALID_IN | Memory → Context | priority, exclusive | add_pipeline/context_link.rs (creates the Context on miss) |
AGENT_CREATED | Agent → Memory | timestamp, method | tooling helpers — ensure-then-link: the Agent node is auto-created on first sight |
HAS_HISTORY | Memory → HistoryEvent | — | every UPDATE/SUPERSEDE/DELETE |
HAS_CHUNK | Memory → MemoryChunk | chunk_index | chunking manager |
MEMORY_RELATION | Memory → Memory | relation_type, strength, created_at, metadata | SUPPORTS plus associative relations — see above |
IMPLIES / BECAUSE | Memory → Memory | probability/strength + reasoning id | reasoning engine and contradiction reconciliation |
SUPERSEDES / CONTRADICTS | Memory → Memory | see above | decision/reasoning engines — see above |
HAS_EMBEDDING | Memory → MemoryEmbedding | embedding_model | add pipeline |
HAS_SUBTYPE | Concept → Concept | — | ontology loader (seed; self-healing against duplicate trees, #67) |
TAGGED_AS | Memory → Category | confidence, source | Clotho::auto_tag (§2.1) |
SUBCATEGORY_OF / ALIAS_OF | Category → Category | — | category tooling / Clotho vocabulary convergence |
MOIRAI_DERIVED_FROM | Memory → Memory | source, created_at | Admin-only generated hypothesis → source-memory provenance; not traversed by ordinary reasoning/search |
RBAC_MEMBER_OF | User → RbacGroup | role/grant metadata | RBAC assignment service |
MEMORY_IN_RBAC_GROUP | Memory → RbacGroup | assignment audit | write path and migration; materialized visibility boundary |
RBAC_GROUP_IN_DEDUP_GROUP | RbacGroup → RbacDedupGroup | assignment audit | federation administration |
MEMORY_IN_RBAC_DEDUP_GROUP | Memory → RbacDedupGroup | assignment audit | write 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_AandCONCEPT_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 entityRELATES_TOedges;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.MemoryChunkdeliberately 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::initialize →
OntologyManager::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.
- Memory.user_id is non-empty for every Memory reachable from
HAS_MEMORY. Schema declaresDEFAULT ""— see §6 issue #12. - HAS_EMBEDDING is 1:1. Every Memory has at most one MemoryEmbedding. Enforced only by convention; no DB constraint.
- SUPERSEDES is acyclic. The decision engine relies on chasing
SUPERSEDESedges backward to find the live memory. - 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.
- INSTANCE_OF points to an
Attribute-subtree orEvent-subtree leaf. The mapper rejects non-leaf classifications. - CONTRADICTS is symmetric in intent. Code writes a single directed edge; queries that walk contradictions handle both directions.
- Hive Memory:
Memory.user_countis monotone non-decreasing for any givenmemory_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 noBooltype; convention is0 = false, 1 = true. - Identity fields with
DEFAULT "".Memory.user_id,Memory.deleted_at,Memory.deleted_by. An insert withoutuser_idis legal at the schema level and produces a node with emptyuser_id. - JSON-in-string.
Memory.metadata,Entity.properties,Entity.aliases,Concept.propertiesareStringcolumns holding serialized JSON. No schema validation; every read pays a JSON parse. - Time-type variation.
Memory.created_atisString DEFAULT "{{timestamp}}", whileMemoryEmbedding.created_atisDate. - Denormalized parent links.
Concept.parent_id: Stringexists alongside theIS_Aedge. smart_traversalmodule name. The_v2suffix is a naming artifact from an earliersmart_traversalthat 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:
- Edit
schema.hxandqueries.hx. - Run
helixir-deploy --host … --port … --schema-dir helixir/schema. - 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.