Entities
The entity graph backing Brain — canonical nouns with a stable EntityId, resolved per tenant, that statements are about and relations connect.
The entity graph is what separates Brain from a flat vector index. When the extractor lifts "Priya works at Arc Labs" from a memory, it resolves two entities (Priya, Arc Labs), creating them if they don't exist, and writes a relation between them. Later, "tell me about Priya" can return statements she's the subject of, events about her, and memories where she appears as the object ("I interviewed Priya"). That last category is where flat similarity search fails — Priya isn't the lexical center of "I interviewed Priya," but she's clearly the topic.
The entity record
An entity is the identity of a referent — distinct from the memories that mention it, the statements about it, and the relations that connect it.
struct Entity {
id: EntityId, // UUIDv7 — stable across renames, aliases, attribute edits
namespace_id: NamespaceId, // owning tenant
agent_id: AgentId, // owning agent
entity_type_id: EntityTypeId,
canonical_name: String, // normalized display name
aliases: Vec<String>, // alternative surface forms
attributes: EntityAttributes,
created_at_unix_nanos: u64,
last_seen_at_unix_nanos: u64,
tombstoned: bool,
merged_into: Option<EntityId>, // Some(id) after a merge — a redirect
}The EntityId is stable: renames change canonical_name, alias additions extend the list, and attribute updates happen in place — the id never moves. It changes only through a merge, where the merged-away entity's id becomes a redirect.
Entity types
entity_type_id is interned from the active schema's type declarations. The seeded brain: system schema ships built-in types; user schemas add deployment-specific ones.
| Built-in type | Description |
|---|---|
Person | A named human |
Organization | A company, group, or institution |
Project | A named body of work |
Place | A geographic location or venue |
Concept | A topic, idea, or fallback |
Event | A named occurrence |
An entity's owning namespace is distinct from the namespace of its type: an entity owned by tenant acme can carry the shared brain:Person type.
Resolution is per tenant
Turning a surface form like "Priya" into an EntityId runs through the resolver gauntlet — exact-name, then alias, then trigram similarity, and (for hard cases) an LLM tier. All of those indexes are keyed under a leading (namespace_id, agent_id) scope prefix, so a surface form resolves only against entities the caller's own tenant owns. "Priya Patel" in tenant acme and "Priya Patel" in tenant globex are two separate entities with two separate ids — one tenant's resolution can never reach another's rows.
Aliases are what let "Priya," "Priya P.," and "Priya Patel" collapse to one entity. When resolution is genuinely ambiguous, the extractor keeps the surface form rather than mis-attributing it.
Traversal at retrieval time
The graph retriever operates on the entity graph — entities, relations, and statement subjects — from an anchor entity resolved out of the query. It runs in three modes:
- Star — anchor → its outgoing/incoming relations → neighbouring entities; returns statements about them and memories that mention them.
- Path — from entity A to entity B, find connecting paths up to a depth bound; returns the relations and entities along the way.
- Subgraph — anchor → its k-hop neighbourhood; returns the set of entities, relations, and statements.
Traversal defaults to depth 3 (capped at 5); one to two hops are O(log N) fast, deeper hops are cost-capped by the planner. Results carry a proximity score (1 / (hop_distance + 1)) that feeds RRF fusion as one lane among three — see Read pipeline. The two directions aren't symmetric: "what do we know about Priya" (she's the subject) and "where does Priya appear" (she's an object) surface different, both-useful sets.
Merging duplicates
Names drift — "Arc Labs," "Arc Labs Inc.," "ArcLabs" — and the resolver isn't perfect. When two entity rows should be one, a merge collapses them. The survivor becomes canonical; the merged entity's merged_into points at the survivor and its id transparently redirects; every statement and relation that referenced the merged id is re-routed to the survivor, aliases and attributes fold in, and an audit row records the merge. A merge is complete and unmerge-able the moment its transaction commits, with a grace-period unmerge path.
Merges are driven either by the resolver (when the auto-merge confidence threshold passes) or by an operator via the ENTITY_MERGE wire op. Over the HTTP edge, entity administration is exposed under /v1/entities — see API: entities.
Why this is the wedge
Without typed entities, "tell me about Priya" is a similarity search against the verbatim string "tell me about Priya" — a flat store returns whatever's lexically closest, possibly a generic biography, possibly nothing. With entities, the retriever resolves the anchor and expands: run the graph lane from Priya, weight subject hits, walk relations one hop for context, and fuse with semantic and lexical search. That's a meaningfully different recall surface, and it exists only because the graph knows what an entity is.
Fact / Preference / Event statements and how relations encode (subject, predicate, object).
Where the graph retriever fires and how its ranks fuse.
NamespacesThe (namespace, agent) scope that resolution is keyed under.
Create, resolve, get, traverse, relations — the endpoint reference.
Was this page helpful?
Namespaces
A namespace is Brain's tenant boundary — it owns memories, the entity graph, and schema declarations. It is not a folder. Read this before you create one.
Write pipeline
How encode works — a synchronous fast path (validate → embed → reserve → persist) split from asynchronous derivation by the WAL-fsync acknowledgement barrier.