Skip to documentation

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.

Writes land on entities/relations (by hand, or from ingestion when Intake is present); reads are served from the canonical rollup plus the mention provenance. graph_layout supplies the pagerank on the list endpoint.

Use it standalone

Atlas is self-contained. The only things it needs are a workspace and an API key with the right scopes.

  1. Give the key the graph scopes

    Reads (GET /entities, GET /entities/:id, the neighborhood, the manifest) require kb:read; the legacy runs:read grant is still accepted on those four. Writes (POST/PATCH/DELETE on entities and relations) require kb:write. Scopes are checked by exact membership — kb:write does not imply kb: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:read for the entity reads, graph:write for the writes, and workspace:read for the manifest.
  2. Import one module

    Headless: createGraphClient from @eli-ai/client/graph. React: EliKnowledgeGraph from @eli-ai/react/graph. Types-only: @eli-ai/contracts/graph. None of the three pulls in another capability module.
  3. Seed the graph by hand, or let ingestion fill it

    POST /api/v1/entities and POST /api/v1/relations are 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.
server-side, Atlas onlyts
import { createEliTransport } from "@eli-ai/client/core";
import { createGraphClient } from "@eli-ai/client/graph";

const graph = createGraphClient(
  createEliTransport({
    baseUrl: "https://eli.ai",
    apiKey: () => process.env.ELI_API_KEY!,
    workspaceId: process.env.ELI_WORKSPACE_ID,
  }),
);

// Hand-author two concepts and the edge between them.
const platform = await graph.createEntity({
  name: "Order Platform",
  type: "system",
  aliases: ["order-platform", "OP"],
});
const owner = await graph.createEntity({ name: "Ada Byron", type: "person" });

await graph.createRelation({
  srcEntityId: owner.id,
  dstEntityId: platform.id,
  type: "owns",
  evidence: "Ada is listed as the service owner in the on-call rota.",
});

// Read it back.
const page = await graph.listEntities({ limit: 25, type: "system" });
const around = await graph.neighborhood(platform.id, { depth: 2, perHopCap: 10 });
console.log(page.total, around.edges.length, around.truncated);

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

labelsPartial<EliKnowledgeGraphLabels>Overrides for the thirteen UI strings. Merged over the defaults, so pass only the keys you want to change.
limitnumberPage size passed to listEntities. Defaults to 100. The server clamps values above 200.
allowCreatebooleanRenders the create toggle and inline form (name, type, comma-separated aliases). Defaults to false. The submit calls POST /api/v1/entities, so the key needs kb:write.
initialEntityIdstringEntity id selected on first render, so the neighborhood panel is populated immediately.
onEntitySelect(entity: EntitySummary | EntityCard) => voidFired when a concept is chosen — from the canvas (an EntitySummary out of the list) or from a neighbor button in the side panel (an EntityCard out of the neighborhood).
onEntityCreated(entity: Entity) => voidFired after a successful create, with the created entity. The component has already closed the form, selected the new entity, and refetched the list.
onDocumentSelect(documentId: string) => voidFired when one of the source-document chips in the side panel is clicked. Those chips render only when the selected concept has docIds; wire this to your own document route.
titlestringSurface heading. Defaults to labels.title (“Knowledge graph”).
descriptionstringSurface sub-heading. Defaults to labels.description.
loadingLabelstringShown while the entity list loads. Defaults to “Loading concepts…”.
errorLabelstringShown when the entity list fails, above a retry button. Defaults to “The knowledge graph could not be loaded.”
onError(error: unknown) => voidCalled on a failed list, neighborhood, or create. Passed straight through to the underlying query and mutation hooks.
classNamestringAppended to the root element's class list after the package's own eli-root class.

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.
EliKnowledgeGraphLabels (keys and defaults)ts
{
  title: "Knowledge graph",
  description: "Explore concepts and the evidence-backed relationships between them.",
  filter: "Filter concepts",
  type: "Type",
  create: "New concept",
  cancel: "Cancel",
  name: "Name",
  aliases: "Aliases",
  save: "Create concept",
  empty: "No concepts found",
  neighborhood: "Neighborhood",
  relations: "Relations",
  sourceDocuments: "Source documents",
}
provider wiring + the componenttsx
import { createEliTransport } from "@eli-ai/client/core";
import { EliProvider } from "@eli-ai/react/provider";
import { EliKnowledgeGraph } from "@eli-ai/react/graph";
import "@eli-ai/react/styles.css";

// EliProvider has no API-key prop by design. Point the transport at a
// same-origin route of your own that injects the Bearer key server-side.
const transport = createEliTransport({ fetch: sameOriginProxyFetch });

export function ConceptExplorer({ workspaceId }: { workspaceId: string }) {
  return (
    <EliProvider transport={transport} workspaceId={workspaceId}>
      <EliKnowledgeGraph
        limit={200}
        allowCreate
        labels={{ title: "Concepts", create: "Add concept" }}
        onEntitySelect={(entity) => track("concept.open", entity.id)}
        onDocumentSelect={(documentId) => openDocument("/documents/" + documentId)}
        onError={(error) => reportError(error)}
      />
    </EliProvider>
  );
}

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.

headless: hub concepts from the manifesttsx
import { createGraphClient } from "@eli-ai/client/graph";
import { useEliQuery } from "@eli-ai/react/hooks";
import type { WorkspaceManifest } from "@eli-ai/contracts/graph";

export function HubConcepts() {
  const manifest = useEliQuery<WorkspaceManifest>(
    "graph.manifest",
    (context, signal) =>
      createGraphClient(context.transport).manifest({
        signal,
        ...(context.workspaceId ? { workspaceId: context.workspaceId } : {}),
      }),
  );

  if (manifest.isLoading) return <p>Loading…</p>;
  if (!manifest.data) return null;

  return (
    <ol>
      {manifest.data.hubConcepts.map((concept) => (
        <li key={concept.id}>
          {concept.name} · in-degree {concept.inDegree} · {concept.authority}
        </li>
      ))}
    </ol>
  );
}

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.

GET/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

limitintegerPage size. Must be an integer >= 1; values above 200 are clamped to 200. Default 50.
offsetintegerRows to skip. Must be an integer >= 0. Default 0.
typestringExact-match filter on the entity type, for example “system” or “person”.
Example requestbash
curl -s "https://eli.ai/api/v1/entities?limit=25&type=system" \
  -H "Authorization: Bearer $ELI_KEY"
Responsejson
{
  "entities": [
    { "id": "01KY0ZW3B8AVDR67NXSE96P81N", "name": "Order Platform", "type": "system", "pagerank": 0.0421 },
    { "id": "01KY10DDDDDDDDDDDDDDDDDDDD", "name": "northwind-ops", "type": "system", "pagerank": null }
  ],
  "total": 27,
  "limit": 25,
  "offset": 0
}

The list rows are deliberately thin

A list row is exactly 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.
POST/api/v1/entitiesBearer · kb:write

Create 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

namerequiredstringDisplay name. 1–400 characters.
typerequiredstringFree-text entity type. 1–120 characters.
aliasesstring[]Alternate surface forms. Each must be non-empty; duplicates by normalized form are dropped.
descriptionstringStored inside the entity's attrs bag and returned as the top-level description field.
Example requestbash
curl -L -X POST https://eli.ai/api/v1/entities \
  -H "Authorization: Bearer $ELI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Fulfillment Console","type":"system","aliases":["fulfillment-console"]}'
Responsejson
{
  "id": "01KY10KKKKKKKKKKKKKKKKKKKK",
  "name": "Fulfillment Console",
  "type": "system",
  "nameNorm": "fulfillment console",
  "origin": "manual",
  "description": null,
  "aliases": ["Fulfillment Console", "fulfillment-console"]
}

409 carries the id you collided with

The uniqueness check is on the normalized name across all live, unmerged entities in the workspace — it is not scoped by type. On a collision the response is { "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.
GET/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.

Example requestbash
curl -s https://eli.ai/api/v1/entities/$ENTITY_ID \
  -H "Authorization: Bearer $ELI_KEY"
Responsejson
{
  "id": "01KY0ZW3B8AVDR67NXSE96P81N",
  "name": "Order Platform",
  "type": "system",
  "nameNorm": "order platform",
  "origin": "extraction",
  "description": "Core order intake and fulfillment system.",
  "aliases": ["order-platform", "OP"],
  "curation": {
    "authority": "curated",
    "lifecycleStatus": "published",
    "ownerId": "usr_01KY0Z9M2E5Q4RS8T0V1WX2Y3Z",
    "stewardIds": [],
    "version": 4,
    "lastReviewedAt": "2026-05-02T10:14:00.000Z",
    "nextReviewAt": "2026-11-02T10:14:00.000Z",
    "supersededBy": null,
    "sourceRefs": [{ "label": "Service catalogue", "url": "https://intranet.example.com/catalogue" }],
    "editorialNote": null
  }
}

curation.editorialNote is steward-only content

The editorial note is a steward-facing field: it is stored for curators and is not meant to reach end users or an LLM context window. This endpoint returns it to any key holding kb:read. If you proxy this response to a browser or into a prompt, strip curation.editorialNote first.
PATCH/api/v1/entities/:idBearer · kb:write

Edit 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

namestringNew display name. 1–400 characters.
typestringNew entity type. 1–120 characters.
aliasesstring[]Additional aliases to register. Existing aliases are kept.
descriptionstringReplaces the description stored in attrs.
Example requestbash
curl -L -X PATCH https://eli.ai/api/v1/entities/$ENTITY_ID \
  -H "Authorization: Bearer $ELI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type":"service"}'
Responsejson
{
  "id": "01KY10KKKKKKKKKKKKKKKKKKKK",
  "name": "Fulfillment Console",
  "type": "service",
  "nameNorm": "fulfillment console",
  "origin": "manual",
  "description": null,
  "aliases": ["Fulfillment Console", "fulfillment-console"]
}
DELETE/api/v1/entities/:idBearer · kb:write

Delete 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.

Example requestbash
curl -L -X DELETE https://eli.ai/api/v1/entities/$ENTITY_ID \
  -H "Authorization: Bearer $ELI_KEY"
Responsejson
{ "deleted": true, "id": "01KY10KKKKKKKKKKKKKKKKKKKK" }
GET/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

depthintegerHops to expand, 1–3. Default 1. Out-of-range or non-integer values are a 400, not clamped.
perHopCapintegerMaximum new nodes admitted per hop, 1–100. Default 25.
totalCapintegerMaximum total nodes in the result, 1–500. Default 200.
Example requestbash
curl -s "https://eli.ai/api/v1/entities/$ENTITY_ID/neighborhood?depth=2&perHopCap=10" \
  -H "Authorization: Bearer $ELI_KEY"
Responsejson
{
  "entity": {
    "id": "01KY0ZW3B8AVDR67NXSE96P81N",
    "name": "Order Platform",
    "type": "system",
    "aliases": ["order-platform", "OP"],
    "mentionCount": 42,
    "topRelations": ["Order Platform —depends_on→ northwind-ops"],
    "docIds": ["01KY10AAAAAAAAAAAAAAAAAAAA"],
    "authority": "curated",
    "lifecycleStatus": "published"
  },
  "nodes": [
    {
      "id": "01KY10DDDDDDDDDDDDDDDDDDDD",
      "name": "northwind-ops",
      "type": "system",
      "aliases": [],
      "mentionCount": 18,
      "topRelations": ["Order Platform —depends_on→ northwind-ops"],
      "docIds": ["01KY10AAAAAAAAAAAAAAAAAAAA"],
      "authority": "machine_extracted",
      "lifecycleStatus": "published"
    }
  ],
  "edges": [
    {
      "src": "01KY0ZW3B8AVDR67NXSE96P81N",
      "type": "depends_on",
      "dst": "01KY10DDDDDDDDDDDDDDDDDDDD",
      "mentionCount": 7
    }
  ],
  "truncated": false
}

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.

POST/api/v1/relationsBearer · kb:write

Create 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

srcEntityIdrequiredstringSource entity id. Must be live.
dstEntityIdrequiredstringDestination entity id. Must be live, and different from the source.
typerequiredstringRelation type, for example depends_on. 1–120 characters.
evidencestringOptional quote backing the edge. Stored as a relation_evidence row with no source document.
Example requestbash
curl -L -X POST https://eli.ai/api/v1/relations \
  -H "Authorization: Bearer $ELI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"srcEntityId":"01KY0ZW3…","dstEntityId":"01KY10KK…","type":"depends_on"}'
Responsejson
{
  "relationId": "01KY10MMMMMMMMMMMMMMMMMMMM",
  "affectedCanonicalIds": ["01KY0ZW3B8AVDR67NXSE96P81N", "01KY10KKKKKKKKKKKKKKKKKKKK"]
}

Domain and range are enforced on write

If the relation type has 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.
DELETE/api/v1/relations/:idBearer · kb:write

Remove one relation, cascading its evidence rows, and rebuild canonical_relations for both endpoints' canonical heads. An unknown id is a 404.

Example requestbash
curl -L -X DELETE https://eli.ai/api/v1/relations/$RELATION_ID \
  -H "Authorization: Bearer $ELI_KEY"
Responsejson
{ "deleted": true, "id": "01KY10MMMMMMMMMMMMMMMMMMMM" }

The delete responses are acknowledgements only

Both delete endpoints return exactly { 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.
GET/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.

Example requestbash
curl -s https://eli.ai/api/v1/manifest \
  -H "Authorization: Bearer $ELI_KEY"
Responsejson
{
  "generatedAt": "2026-07-21T09:15:00.000Z",
  "workspace": { "id": "ws_01KY0Z00000000000000000000", "name": "Northwind Ops" },
  "counts": {
    "documents": 128,
    "chunks": 3410,
    "entities": 743,
    "relations": 1289,
    "entitiesByType": { "system": 41, "metric": 22, "policy": 17 },
    "relationsByType": { "governed_by": 58, "owned_by": 44 },
    "byAuthority": { "machine_extracted": 601, "asserted": 92, "curated": 38, "certified": 12 },
    "byLifecycle": { "draft": 4, "pending_review": 2, "published": 724, "deprecated": 9, "superseded": 4 },
    "documentsByVerification": { "unverified": 96, "verified": 24, "certified": 6, "deprecated": 2 }
  },
  "hubConcepts": [
    {
      "id": "01KY0ZW3B8AVDR67NXSE96P81N",
      "name": "Order Platform",
      "type": "system",
      "summary": "Core order intake and fulfillment system.",
      "authority": "certified",
      "lifecycleStatus": "published",
      "version": 6,
      "inDegree": 31,
      "outDegree": 12,
      "mentionCount": 87,
      "lastReviewedAt": "2026-07-14T10:02:11.000Z",
      "ownerId": "usr_01KY0Z9M2E5Q4RS8T0V1WX2Y3Z"
    }
  ],
  "staleness": { "overdueReviews": 3, "neverReviewed": 640 }
}

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.

TableWhat one row isColumns a consumer cares about
entitiesOne 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_aliasesOne 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
relationsOne 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_evidenceThe 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
mentionsThe 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_relationsThe 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_typesOne 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_typesOne 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_layoutOne 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_revisionsOne 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, edge mentionCount, and the manifest's document and chunk counts. Without it, nothing breaks — you author the graph through POST /entities and POST /relations.
  • Warrant required for governance writes. The authority and lifecycle_status columns live on the Atlas entities row and Atlas serves them on every card and in the curation block, 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 are asserted, extraction is machine_extracted, everything is published — 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 the eli://workspace/manifest resource) for agents that speak that protocol instead of REST. Same server functions, different envelope.

Related reading

Entity graph explains the modelling decisions behind canonicalization and merges; Manifest covers the manifest as an agent bootstrap document; JavaScript & React SDK covers the transport, provider, and error types shared by every module.

Extending Atlas

Atlas exposes no contribution point of its own — the graph is read and written through the plugin data facade instead: 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.