Module references
Atlas — the typed entity graph
Atlas is the module that turns names into addressable concepts. It owns the entities, their aliases, the typed directed edges between them, and the mention-level provenance that says which document and which character offsets produced each fact. Everything below is served by pure SQL — no model, no embedding, no reranker.
What it does
Atlas stores a workspace's concepts as typed entities and connects them with typed, directed relations. Duplicates collapse into a single canonical head, and the serve-time edge table (canonical_relations) is a materialized rollup over that head, so a read never has to reason about merges. Every entity carries the aliases it answers to, a mention count, and the documents it was found in; every extracted edge can carry the quote that grounds it. The public surface is a list, a single concept read, a bounded neighborhood expansion, a workspace manifest, and hand-authored writes for entities and relations.
Use it standalone
Atlas is self-contained. The only things it needs are a workspace and an API key with the right scopes.
Give the key the graph scopes
Reads (GET /entities,GET /entities/:id, the neighborhood, the manifest) requirekb:read; the legacyruns:readgrant is still accepted on those four. Writes (POST/PATCH/DELETEon entities and relations) requirekb:write. Scopes are checked by exact membership —kb:writedoes not implykb:read, so a key that both reads and edits the graph needs both. Delegated user keys (eli_uk_) additionally have their owner's live capabilities re-checked:graph:readfor the entity reads,graph:writefor the writes, andworkspace:readfor the manifest.Import one module
Headless:createGraphClientfrom@eli-ai/client/graph. React:EliKnowledgeGraphfrom@eli-ai/react/graph. Types-only:@eli-ai/contracts/graph. None of the three pulls in another capability module.Seed the graph by hand, or let ingestion fill it
POST /api/v1/entitiesandPOST /api/v1/relationsare enough to build a complete graph with no documents at all. If documents are present, extraction writes into the same tables and the reads are unchanged.
What Atlas does NOT require
No AI provider or model slot — every endpoint on this page is SQL only, so a workspace with no configured provider serves all of them. No documents, chunks, or embeddings: entities and relations can be created directly, and a concept with no mentions is valid workspace metadata. No governance module — the authority and lifecycle labels have working defaults. No agents, runs, evals, live-data connectors, or reports. The React package imports no Next.js module and no router.
The trade-off is honest: with no documents, mentionCount is 0, docIds is empty, the manifest's documents/chunks counts are 0, and pagerank stays null until graph analytics have produced a layout version.
React components
@eli-ai/react/graph exports exactly one component, EliKnowledgeGraph, plus its two prop types (EliKnowledgeGraphProps, EliKnowledgeGraphLabels). It renders a Surface containing an optional create form, a filter input, a flat canvas of clickable concept buttons, and a side panel that loads the selected concept's neighborhood at depth 1. It calls three operations through the headless graph client: listEntities on mount, neighborhood when a concept is selected, and createEntity when the form is submitted. The filter is client-side only — it narrows the already-loaded page by name or type and issues no request.
EliKnowledgeGraph props
No emptyLabel prop
EliKnowledgeGraphProps is Omit<CommonSurfaceProps, "emptyLabel"> plus the graph-specific props above — the empty state is driven by labels.empty instead. The neighborhood panel error and the create-form error use fixed strings and are not configurable.There is no component for the manifest or for relation writes. Both are one hook away — the same generic hooks the packaged components are built on work with any graph-client method and your own markup.
HTTP API
Nine operations across six paths. Every request carries a Bearer key; see Authentication. Examples use https://eli.ai, whose apex 308-redirects, so -L is included on every non-GET example. A resource in another workspace is invisible under row-level security and comes back as 404, never 403.
/api/v1/entitiesBearer · kb:read (runs:read accepted)Paginated canonical entities — merged duplicates resolve to their head — ordered by persisted PageRank descending, nulls last, then name, then id. pagerank is null until graph analytics have written a layout version.
Query parameters
The list rows are deliberately thin
id, name, type, and pagerank. It carries no aliases, mention counts, or governance labels — fetch GET /api/v1/entities/:id for the curation block, or the neighborhood endpoint for full cards./api/v1/entitiesBearer · kb:writeCreate a concept by hand. Origin is recorded as manual and confidence as 1. The name itself, plus every alias, becomes an alias row, so later ingestion reuses this entity by normalized name instead of duplicating it. An unknown type is auto-registered into the ontology, best-effort.
Request body
409 carries the id you collided with
{ "error": "conflict", "message": …, "existingId": "01K…" } at status 409, so the caller can decide between reusing that entity and cancelling. An empty or whitespace-only name or type is a 400./api/v1/entities/:idBearer · kb:read (runs:read accepted)One entity plus its curation block. The entity fields are the same shape POST and PATCH return; curation adds the governance state stored on the same row.
curation.editorialNote is steward-only content
kb:read. If you proxy this response to a browser or into a prompt, strip curation.editorialNote first./api/v1/entities/:idBearer · kb:writeEdit a concept. Works on extracted entities as well as manual ones — renaming and retyping are curator actions. A rename also registers the new name as an alias; aliases are additive; an unknown new type is auto-registered. At least one field must be present or the body is rejected with 400.
Request body
/api/v1/entities/:idBearer · kb:writeDelete the concept, meaning its whole canonical cluster: the head plus every entity merged into it. Relations touching any cluster member are removed outright (cascading their evidence), mentions and alias rows are removed, the entity rows are soft-deleted, and canonical_relations is rebuilt for the head and for every neighbour head so dead edges disappear from the serve-time table.
/api/v1/entities/:id/neighborhoodBearer · kb:read (runs:read accepted)Expand the canonical-relation frontier around one concept, in both edge directions, taking the highest-mention-count edges first at each hop. Returns full cards for every node reached — the seed included — plus the edges and an explicit truncated flag. A merged id resolves to its canonical head.
Query parameters
Card field shapes worth knowing before you render them
aliases is capped at 5 and docIds at 5 (the most-mentioned documents first). topRelations is capped at 8 and each item is a rendered sentence, not a relation type — the literal format is <source name> —<type>→ <destination name>, for example Order Platform —depends_on→ northwind-ops. Treat it as display text; use edges when you need structure. mentionCount is summed across the whole merged cluster.
nodes always includes the seed entity, so nodes.length is at least 1 for a concept with no edges. truncated is set when a cap was reached, so a client can say "there is more" instead of silently showing a partial graph.
/api/v1/relationsBearer · kb:writeCreate a directed, typed edge between two existing live entities, then rebuild canonical_relations for both endpoints' heads so the edge is visible to reads immediately. Re-asserting an edge that already exists promotes it rather than erroring. An unknown relation type is auto-registered, best-effort.
Request body
Domain and range are enforced on write
srcTypes or dstTypes registered in relation_types, the endpoint checks the endpoints' entity types against them and rejects a violation with a 400 naming the offending side and the allowed types. An empty side is unconstrained, which is the default for types created implicitly. A missing endpoint is a 404; a self-edge is a 400. Posting a triple that already exists is not an error: the existing edge is promoted to asserted authority and its id is returned with the usual 201, so this endpoint is safe to replay./api/v1/relations/:idBearer · kb:writeRemove one relation, cascading its evidence rows, and rebuild canonical_relations for both endpoints' canonical heads. An unknown id is a 404.
The delete responses are acknowledgements only
{ deleted, id }. The server computes richer results — the affected canonical heads, and for entities the number of relations and mentions removed — but neither route puts them on the wire. The affectedCanonicalIds field is optional in the contract types for that reason; do not depend on it after a delete. It is returned by POST /api/v1/relations./api/v1/manifestBearer · kb:read (runs:read accepted)One machine-readable snapshot of the workspace: corpus and graph counts, the distribution by type, authority, lifecycle and document verification, the top 25 hub concepts by directed in-degree over the canonical graph, and review staleness. Pure SQL, no model call. Only published concepts are eligible to be hub concepts.
Data elements
Atlas owns ten tables. Every one of them carries a workspace_id column and a tenant-isolation row-level-security policy that constrains both reads and writes to the workspace of the current transaction, so a cross-workspace id is simply not visible rather than forbidden. Ids are ULID strings.
| Table | What one row is | Columns a consumer cares about |
|---|---|---|
| entities | One concept. A canonical head, or a member merged into one. Soft-deleted, never hard-deleted, by the delete endpoint. | id · name · name_norm · type · attrs.description · aliases (via entity_aliases) · origin (extraction | manual | wikilink-unresolved) · canonical_id · merged_into · confidence · authority · lifecycle_status · owner_id · steward_ids · version · deleted_at |
| entity_aliases | One surface form an entity answers to. A manual create stores the entity's own name here as well, which is how later ingestion finds and reuses it. | entity_id · alias · alias_norm (unique per workspace) · source · merge_id |
| relations | One directed, typed edge at the mention level. Manual creates land here with authority 'asserted'. | src_entity_id · dst_entity_id · type · confidence · authority · assertion_rank · deleted_at (live triple is unique per workspace) |
| relation_evidence | The provenance of an edge: one quote that grounds it, optionally located in a document and chunk. | relation_id · quote · doc_id · chunk_id · start_offset · end_offset · extraction_run_id |
| mentions | The provenance of a concept: one occurrence of an entity in a document, with the exact surface text. | entity_id · doc_id · chunk_id · surface_text · start_offset · end_offset · link_method (extraction | alias_exact | name_exact | wikilink | frontmatter | adjudication | manual) · alias_id |
| canonical_relations | The materialized aggregate over relations, rolled up to canonical heads. The only edge table read at serve time; rebuilt for the touched heads by relation create/delete and by entity delete (a rename or retype needs no rebuild — the rollup is id-keyed). | src_canonical · relation_type · dst_canonical · mention_count · sample_evidence_ids |
| entity_types | One registered entity type in the workspace vocabulary. Auto-registered when a create or retype uses an unknown type. | name (part of the primary key) · description · examples |
| relation_types | One registered relation type, optionally with domain and range constraints enforced on manual edge creation. | name (part of the primary key) · description · src_types · dst_types |
| graph_layout | One node's analytics output for one layout version — position, community, PageRank, degree. Where the list endpoint's pagerank comes from. | layout_version · entity_id · x · y · community_id · pagerank · degree (workspace_meta.current_layout_version selects the live version) |
| entity_revisions | One append-only history entry for a concept. Written by every Atlas create, update, and delete; never updated or deleted, and it has no foreign key so it survives entity removal. | entity_id · version · change_kind · snapshot · diff · activity · changed_by · note · created_at |
Atlas also reads two tables it does not own: documents (for the ACL columns and to resolve docIds) and chunks (referenced by mentions and evidence). Three more Atlas-adjacent tables exist but have no v1 endpoint on this page — merge_log, extraction_runs, and rejection_tombstones.
Two visibility rules that shape every read
Serving exclusion. Soft-deleted entities and concepts in draft or pending_review are never served on cards or neighborhoods — and neither are edges to them. deprecated and superseded concepts stay retrievable, labelled rather than hidden.
Document ACL. An entity that has document mentions is readable only when at least one of those documents is readable by the caller; a restricted-only entity is omitted entirely, name and id included. An entity with no mentions — a hand-authored vocabulary entry — is workspace metadata and always visible. Canonical edges follow the same rule through their evidence documents; a manually asserted edge with no evidence rows stays visible.
How it composes
Atlas is usable on its own, and every other module that touches the graph does so through the same tables and the same serve-time functions this page documents.
- Intake — the biggest gain. With documents in the workspace, extraction writes entities, aliases, relations, mentions, and evidence for you, and the fields that are empty in a hand-built graph fill in:
mentionCount,docIds, edgementionCount, and the manifest's document and chunk counts. Without it, nothing breaks — you author the graph throughPOST /entitiesandPOST /relations. - Warrant — required for governance writes. The
authorityandlifecycle_statuscolumns live on the Atlasentitiesrow and Atlas serves them on every card and in thecurationblock, but Atlas has no endpoint to change them. Certifying, setting an owner, stamping a review, deprecating, the revision history endpoint, change requests, and the SKOS export are all Warrant. Without Warrant the defaults hold — manual creates areasserted, extraction ismachine_extracted, everything ispublished— and the serving rules above still apply. - Lineage — reads Atlas from the document side: which concepts a document produced, and how many live relations its evidence grounds. It needs documents, so it is meaningful only alongside Intake.
- Lens — the retrieval pipeline's graph-fusion stage calls the very same frontier expansion this module exposes, to surface evidence that is structurally rather than textually related. Atlas does not need Lens; Lens gets materially better with a populated Atlas. See Retrieval pipeline.
- Conduit — binds a governed live query to an entity id or to an entity type, so Atlas is the addressing scheme for live data. The binding rows live in Conduit's table and reference Atlas ids; Atlas itself neither reads nor needs them.
- Crucible — grades concept recall against entity ids on golden items. Optional, and evaluation-only.
- Ports — exposes the same reads over MCP (
kb_entity_lookup,kb_entity_get,kb_graph_query, and theeli://workspace/manifestresource) for agents that speak that protocol instead of REST. Same server functions, different envelope.
Related reading
Extending Atlas
ctx.data.atlas covers entity search, entity cards, frontier expansion and the documents mentioning an entity under graph:read, and entity and relation writes under graph:write, so ontology constraints and content ACLs apply exactly as they do for the REST surface. Atlas emits graph.extraction.completed, entity.created, entity.merged and entity.deleted. See the extension model for the contract and the trust boundary, and Build a plugin for a worked example.