Module references
Module reference / Bring it in
Intake
- Atlas
Extract typed entities and relations with evidence.
- Lineage
Record source revisions and downstream provenance.
- Lens
Index source chunks for grounded retrieval.
Intake is the module that turns a Confluence site, a SharePoint drive, a Slack workspace, or a batch of staged markdown into deduplicated, incrementally re-synced documents rows — each one carrying where it came from and what permissions it had there.
What it does
A connector source is a governed, workspace-scoped record: a type, non-secret config, an encrypted credential, and a resumable checkpoint. You register sources through the API (or your own UI), then trigger a sync; the crawl runs server-side in a worker, page by page, persisting its checkpoint after every page so a crash resumes rather than restarts.
Ingestion is idempotent by construction: documents are keyed on (source_id, external_id), and a document whose sourceVersion is unchanged is skipped outright — no re-chunk, no re-embed. Intake is strictly read-only against the remote system: the connector interface exposes only fullLoad, poll, listIds, and an optional getPermissions. Nothing writes back to the source.
Intake connector execution
The HTTP surface only enqueues. The worker holds a per-source PostgreSQL advisory lock for the whole operation, so full, incremental, prune, and permissions work on one source can never interleave — across queues or replicas.
Downloads
Concepts
- Intake flow
- Modules
- Intake
Keywords
- keyed on source_id + external_id
- enqueue only · 202
- documents + chunks
- append-only stats
- Confluence · SharePoint · Slack
- POST /connectors/:id/sync
- worker · runConnectorSync
- per-source advisory lock
- fullLoad / poll / listIds
- markdown + contentHash + aclPrincipals
- checkpoint after every page
- markdown_upload
- sync_runs
- connector_sources.sync_state
- queue
- connector
- NormalizedDocument
Source and generation provenance
Status: current
Generated at: 2026-08-17T18:34:23.983Z
Source hash: 010a1cb78110aa28f45b2b818e38dfaec52d89133a13cd311002baf5fa69a79b
Metadata payload hash: e3e32783aa7f9b84b8fee71c7600042d808a364c724de4c5f14b4637fa263b93
Canonical appearance
src/app/(docs)/docs/modules/intake/page.tsx:51 route /docs/modules/intake
All appearances
canonical—src/app/(docs)/docs/modules/intake/page.tsx:51route/docs/modules/intake
No mirrored appearances.
Generation versions
App: eli-ai 0.1.0
Mermaid: 11.16.0 · Mermaid CLI: 11.16.0
Node: v26.3.1 · Yarn: 4.17.1
Renderer config hash: 68c10966fe84406ee626034d58bfabd555df9f65f691204b7c46db24038da101
Renderer theme hash: c80287a78d80ad63d27bd5ca348b2ef9a7e2f44da289e436be6484ea28a1b033
Adapter versions: diagramGenerator=2, drawioFlowchart=1, drawioGantt=1, drawioSequence=1, drawioState=1
Full sidecar JSON: intake-connector-execution-010a1cb7.json
Four source types
confluence, sharepoint, slack, and markdown_upload. The first three require a structured credential; markdown_upload is credential-less and reads files staged in its own config.documents array, flowing through the same normalize → chunk → persist path as a live connector.Use it standalone
Adopting Intake alone means: an API key, one package import, and a worker process that drains the sync queue. Nothing else in the product has to be configured.
Give the key the right scopes
Reads (
GET /api/v1/connectors,GET /api/v1/connectors/{id}) require scopedata:read. Everything that changes state — create, update, delete, and triggering a sync — requires scopekb:write. There is no connector-specific scope; the connector-specific granularity lives in capabilities.A workspace key (
eli_sk_) is checked on scope alone. A delegated user key (eli_uk_) is additionally narrowed against the owner's live capabilities:connectors:readfor the reads,connectors:writefor create/update/delete, andconnectors:executefor the sync trigger. A missing capability is a 403 witherror: "insufficient_capability".Import exactly one entry point
React:
@eli-ai/react/connectors. Headless:@eli-ai/client/connectors. Types only:@eli-ai/contracts/connectors. None of the three pulls in another module's client, contracts subpath, or components.Run a worker
The sync endpoint returns
202and stops there — it validates the source, checks it is not paused, and enqueues. If nothing is draining the queue, sources stay atlastSyncedAt: nullforever and nosync_runsrow is ever written. This is the one out-of-process dependency Intake genuinely has.
What Intake does NOT require
React components
@eli-ai/react/connectors exports exactly one component, EliConnectors, plus the types EliConnectorsProps, EliConnectorsLabels, and a re-export of ConnectorSyncKind. It is a client component ("use client") and reads its transport from EliProvider.
EliConnectors
Renders a titled surface containing a card grid — one card per connector source. Each card shows the source name (config.name when set, otherwise the type with underscores replaced by spaces), a status badge, a Last synced line, a credential badge, and the source's ACL-visibility badge. When canSync is on, each card also gets a kind picker (incremental / full) and a Sync button; the button is disabled while a sync is being queued, when the source is paused, and when the source has no stored credential. On success it refetches the list and calls onSyncQueued. It also renders its own loading, error, and empty states.
EliConnectors props
No emptyLabel prop
EliConnectors takes Omit<CommonSurfaceProps, "emptyLabel"> — the empty-state text is labels.empty, not a top-level prop. Passing emptyLabel is a type error.EliConnectorsLabels (all keys required on the full type; pass any subset)
What this component deliberately does not do
connectors.list() and connectors.sync(id, { kind }). Create/update/delete is your UI driven through onConfigure plus the HTTP API below; history comes from GET /api/v1/connectors/{id}. That is a deliberate boundary — credential entry is left to the host application.Headless, with your own markup
The same client the component uses is importable directly. Every method takes an optional EliCallOptions (workspaceId, signal, headers).
HTTP API
Six operations under /api/v1/connectors. Every one is workspace-scoped: the key resolves a workspace (optionally selected with the X-Workspace-Id header) and all reads and writes run beneath row-level security, so a source in another workspace is simply a 404. Errors use the shared { error, message } envelope, with an issues array added on body-validation failures.
/api/v1/connectorsscope data:read · capability connectors:readLists the workspace's connector sources, oldest first, credential-masked. No connection to the remote system is opened.
/api/v1/connectorsscope kb:write · capability connectors:writeRegisters a source and returns 201 with the credential-masked row. Creating a source does not start a crawl. The credential is JSON-serialized and encrypted at write; it is never returned by any read.
Request body
/api/v1/connectors/{id}scope data:read · capability connectors:readOne source plus its recent sync-run history, newest first (up to 50 runs). 404 when the id is unknown in this workspace.
/api/v1/connectors/{id}scope kb:write · capability connectors:writeEdits a source. At least one field is required (an empty body is a 400). A present credential is re-validated against the source's stored type and re-encrypted; omitting it keeps the stored secret. Returns the masked row.
Request body
/api/v1/connectors/{id}scope kb:write · capability connectors:writeRemoves the source and, by FK cascade, its sync_runs. Documents already ingested from it are retained and stay readable — their source_id simply dangles. Run a prune first if you want tombstones.
/api/v1/connectors/{id}/syncscope kb:write · capability connectors:executeEnqueues a crawl and returns 202. The body is optional — an empty body is parsed as an empty object. Enqueue only: the response says nothing about crawl progress, so poll GET /api/v1/connectors/{id} and read its syncRuns.
Request body
Two API-shape details worth pinning down
A paused source returns 409, coded invalid-config. The sync route maps the underlying connector error straight through, so the envelope is { "error": "invalid-config", "message": "Source <id> is paused" } at HTTP 409 — not a conflict code. Branch on the status, not the string.
Only full and incremental are reachable over HTTP. The data model and the worker both support prune (tombstone documents that vanished at the source) and permissions (re-read remote ACLs), and sync_runs.kind can hold all four — but the sync route's schema accepts only the first two, and SyncEnqueued.kind is typed to match. If you see a prune or permissions run in the history, it was enqueued server-side, not through this endpoint.
Data elements
Intake owns two tables and writes a defined set of columns on a third. All three are workspace-scoped under row-level security — every read and write goes through a workspace-bound transaction, so a cross-workspace id is invisible rather than forbidden.
| Table | One row is | Intake's relationship | RLS |
|---|---|---|---|
| connector_sources | One configured external source: type, config, encrypted credential, checkpoint, status. | Owns — created and updated only by this module. | Workspace-scoped |
| sync_runs | One append-only sync attempt, with per-run stats and a failure message. | Owns — written by the worker, read back through the detail endpoint. Cascades on source delete. | Workspace-scoped |
| documents | One knowledge document. Shared with the rest of the product. | Writes the provenance and ACL columns below, plus the ordinary content columns, on ingest. | Workspace-scoped |
| chunks | One retrievable slice of a document. | Written inline during ingest via the shared chunker — same path a manual save uses. | Workspace-scoped |
| audit_log | One recorded action. | Appends connector.create / connector.update / connector.delete / connector.ingest. Credential values are never written — an update records only the names of the changed fields. | Workspace-scoped |
Prune has a deliberate safety property
Feature map
Behind the six operations and the single component, the runtime divides into five feature areas. Each area names the concrete modules and tables that deliver it, and the same names reappear in the code map and the internals section below, so every behavioural claim on this page can be traced to the code that enforces it.
| Feature area | What it provides | Delivered by | Surfaces |
|---|---|---|---|
| Source registry and credential custody | Typed source records with per-type config validation; credentials serialized and encrypted at write, stripped from every read. | createSource / updateSource / maskSource in src/server/connectors/crud.ts; the connector_sources table | REST CRUD under /api/v1/connectors · EliConnectors cards · connectors SDK client |
| Sync execution and checkpointing | Enqueue-only trigger, queue-dispatched worker runners, one advisory lock per source, and a resumable checkpoint persisted after every crawled page. | triggerSync in crud.ts; runConnectorSync and withConnectorSourceLock in src/server/connectors/sync.ts; the queue in src/server/jobs/index.ts | POST /api/v1/connectors/{id}/sync (202) · background worker process |
| Normalization and deduplicated persistence | Per-type source adapters, binary and HTML normalization to markdown, deterministic chunking, and an upsert keyed on (source_id, external_id) that skips unchanged revisions. | Adapters in src/server/connectors/sources; src/server/connectors/normalize.ts; chunkMarkdown + persistChunks in src/server/ingest/chunking.ts | documents and chunks rows served by the document catalog and retrieval |
| ACL mirroring and prune lifecycle | Source-level ACL policy, per-document principals mirrored from the remote system, a permissions runner that re-reads remote ACLs, and outage-safe tombstoning. | runConnectorPermissions / runConnectorPrune in sync.ts; normalizeContentPrincipals in src/server/authz/content-acl.ts | acl_visibility / acl_principals columns, filtered in SQL by every retrieval read |
| Run observability and enrichment handoff | Append-only per-run stats, source status transitions, audit events, and non-fatal handoff of extraction and embedding work to queued jobs. | startSyncRun / finishSyncRun in sync.ts; the sync_runs and audit_log tables; enqueueExtract / enqueueEmbed in jobs | syncRuns on GET /api/v1/connectors/{id} · status badges in EliConnectors |
System architecture
The topology below names the real components: route directories, exported server functions, the job queue, and the tables each stage writes. Rectangles are API routes and services, cylinders are tables and queues, and stadium nodes are the external systems a crawl reads. Intake calls no AI model anywhere in this path — embeddings and entity extraction are downstream jobs owned by other modules.
Intake — system architecture
The HTTP surface writes connector_sources and enqueues; everything else happens in the worker, which holds the per-source advisory lock while adapters crawl, normalization produces markdown, and the keyed upsert lands documents, chunks, revisions, and audit rows.
Downloads
Concepts
- Intake architecture
- Sync execution
- Normalization pipeline
- External systems
- REST surface — src/app/api/v1/connectors
- Modules
- Intake
Keywords
- Connector CRUD routes (API)
- Sync trigger route (API)
- store: queue
- normalize.ts — binary and HTML to markdown (service)
- store: table
- enqueue extract + embed
- Worker dispatcher — (service)
- chunkMarkdown + persistChunks (service)
- SharePoint / Microsoft Graph
- withConnectorSourceLock — per-source advisory lock (service)
- persistConnectorDocument — keyed upsert (service)
- src/worker/index.ts
- runConnectorSync / runConnectorPrune / runConnectorPermissions (service)
- Confluence
- Slack
Source and generation provenance
Status: current
Generated at: 2026-08-17T18:34:28.173Z
Source hash: 08a0d58c95196f25a99720e1ef6c0c6e697d179154b06415942faa7a1332ca6a
Metadata payload hash: 70781767ea9e8b27d99f3b0cc832bcfc8411a9f1e1f3997405255eca404ee1b0
Canonical appearance
src/app/(docs)/docs/modules/intake/page.tsx:68 route /docs/modules/intake
All appearances
canonical—src/app/(docs)/docs/modules/intake/page.tsx:68route/docs/modules/intake
No mirrored appearances.
Generation versions
App: eli-ai 0.1.0
Mermaid: 11.16.0 · Mermaid CLI: 11.16.0
Node: v26.3.1 · Yarn: 4.17.1
Renderer config hash: 68c10966fe84406ee626034d58bfabd555df9f65f691204b7c46db24038da101
Renderer theme hash: c80287a78d80ad63d27bd5ca348b2ef9a7e2f44da289e436be6484ea28a1b033
Adapter versions: diagramGenerator=2, drawioFlowchart=1, drawioGantt=1, drawioSequence=1, drawioState=1
Full sidecar JSON: intake-system-architecture-08a0d58c.json
How to read the Intake system architecture
- Start at the two route shapes: CRUD routes touch only connector_sources; the sync route touches only the job queue. No HTTP request ever performs a crawl.
- Follow the worker lane: The worker dispatcher claims queued jobs and hands them to the runner functions, which acquire the per-source advisory lock before any remote I/O.
- Trace one document through the pipeline: An adapter emits a normalized document, normalize.ts flattens binaries and HTML to markdown, and persistConnectorDocument upserts it with its chunks, revision, and audit row.
- Note the dotted handoff: After a commit, the upsert enqueues extraction and embedding jobs back onto the same queue — non-fatal, so ingestion succeeds even with no consumer.
- Trust boundary
- Credentials decrypt only inside the worker runner, remote fetches run through an origin-allowlisted client, and every table write happens inside a workspace-scoped transaction under row-level security.
- Durable state
- connector_sources checkpoints, append-only sync_runs, deduplicated documents with provenance and ACL columns, chunks, document_revisions, and audit_log rows.
Failure paths
- No worker draining the queue leaves sources at lastSyncedAt null forever
- A remote outage mid-crawl fails the run after its checkpoint, so the retry resumes
- A paused source rejects the sync trigger with 409
- An unreadable credential fails the run before any remote call
Signals
- sync_runs stats per run: docsSeen, docsAdded, docsUpdated, docsTombstoned, pages
- connector_sources.status flipping to error after a failed run
- lastSyncedAt staleness per source
- audit_log connector.* event volume
Where the logic lives
The code map below is the module boundary in file terms. Every path is real; table names live in the notes because tables are defined in the schema module, not in files of their own.
| Component | Kind | Lives at | Notes |
|---|---|---|---|
| Connector CRUD routes | API | src/app/api/v1/connectors | List + create at the collection; get, patch, delete under the id segment. Reads guard on scope data:read, writes on kb:write. |
| Sync trigger route | API | src/app/api/v1/connectors/[id]/sync | Enqueue-only 202; capability connectors:execute; a paused source is a 409. |
| Source CRUD and credential envelope | service | src/server/connectors/crud.ts | AES-256-GCM encryption via src/lib/crypto.ts; every exported read returns hasCredential, never ciphertext. |
| Sync runners and advisory lock | service | src/server/connectors/sync.ts | runConnectorSync / runConnectorPrune / runConnectorPermissions plus persistConnectorDocument, the keyed upsert. |
| Source adapters | service | src/server/connectors/sources | fullLoad / poll / listIds / getPermissions per type; built by src/server/connectors/registry.ts. |
| Content normalization | service | src/server/connectors/normalize.ts | Availability-gated extractors for PDF, DOCX, Office formats, and HTML; a missing extractor degrades to a skip, never a crash. |
| Deterministic chunker | service | src/server/ingest/chunking.ts | Heading-aware splitting toward 1000 tokens, hard cap 1600; persistChunks reconciles rows by identity so valid chunk_embeddings survive. |
| Job queue | service | src/server/jobs/index.ts | pg-boss enqueue helpers for full / incremental / prune / permissions syncs plus extract, embed, and cache-invalidate jobs. |
| Worker dispatcher | service | src/worker/index.ts | The out-of-process consumer that dispatches queued jobs to the runner functions by name. |
| Ingestion tables | store | src/server/db/schema.ts | connector_sources, sync_runs, documents, chunks, document_revisions, audit_log — each with a tenant-isolation RLS policy. |
| Content-ACL predicate | service | src/server/authz/content-acl.ts | Normalizes principals on write and builds the SQL predicate that filters acl_visibility / acl_principals on read. |
| Guarded outbound fetch | service | src/server/network/safe-fetch.ts | Origin-allowlisted, size- and time-bounded HTTP client handed to every connector. |
| EliConnectors surface | UI | packages/react/src/connectors/index.tsx | The card grid documented above; issues exactly list() and sync(). |
| Connectors client | SDK | packages/client/src/connectors.ts | createConnectorsClient — list, get, create, update, remove, sync over the shared transport. |
| Wire contracts | SDK | packages/contracts/src/connectors.ts | ConnectorSource, ConnectorSourceDetail, SyncEnqueued, and the sync-kind unions. |
Primary runtime flow
The highest-value flow is a triggered sync: one HTTP call, then everything else in the worker. Message labels use the operation names from the HTTP API section above.
Intake — primary runtime flow
syncConnector returns 202 immediately; the worker acquires the source lock, decrypts the credential, crawls page by page with a checkpoint after each, and finishes by stamping the source and the run row. getConnector is how a caller observes progress.
Downloads
Concepts
- Intake runtime flow
- Modules
- Intake
Keywords
- routes (API)
- API consumer (SDK or curl)
- pg-boss job queue (store)
- sync_runs (store)
- connector_sources (store)
- persist the page checkpoint into sync_state
- upsert on (source_id, external_id), skip unchanged sourceVersion
- runConnectorSync (service)
- Remote source (external)
- documents + chunks (store)
- syncConnector with kind full or incremental
- triggerSync validates the source and enqueues
- worker dispatches the sync job
- acquire the per-source advisory lock
- startSyncRun appends an in-flight run row
- fullLoad or poll fetches one page
- normalized documents plus checkpoint
- mark status active and stamp lastSyncedAt
- finishSyncRun writes final stats
- getConnector reads the source and its syncRuns
- loadSourceForSync decrypts the stored credential
- /api/v1/connectors
Source and generation provenance
Status: current
Generated at: 2026-08-17T18:34:26.094Z
Source hash: c82b4a3473a2610cc04b9ba32436ae16062c524bbb1fbb1d01daf347b3711083
Metadata payload hash: 1a00847df07b591596c12d491477705ce29f32fd025249fd23091efcae1d0e4a
Canonical appearance
src/app/(docs)/docs/modules/intake/page.tsx:123 route /docs/modules/intake
All appearances
canonical—src/app/(docs)/docs/modules/intake/page.tsx:123route/docs/modules/intake
No mirrored appearances.
Generation versions
App: eli-ai 0.1.0
Mermaid: 11.16.0 · Mermaid CLI: 11.16.0
Node: v26.3.1 · Yarn: 4.17.1
Renderer config hash: 68c10966fe84406ee626034d58bfabd555df9f65f691204b7c46db24038da101
Renderer theme hash: c80287a78d80ad63d27bd5ca348b2ef9a7e2f44da289e436be6484ea28a1b033
Adapter versions: diagramGenerator=2, drawioFlowchart=1, drawioGantt=1, drawioSequence=1, drawioState=1
Full sidecar JSON: intake-primary-runtime-flow-c82b4a34.json
How to read the Intake primary runtime flow
- Watch the 202 boundary: The route validates the source, enqueues, and answers — crawl progress is never part of the HTTP response.
- Note where the credential appears: loadSourceForSync decrypts inside the worker runner, after the advisory lock is held; no route handler ever sees plaintext.
- Follow the page loop: Each page upserts its documents and then persists the checkpoint, so a crash between pages resumes from the last completed page.
- Check the terminal writes: Success stamps lastSyncedAt and resets status to active; failure records the error on the run row and flips the source to error before the job retries.
- Trust boundary
- Scope kb:write plus capability connectors:execute gate the trigger; the crawl itself runs with the source's own credential against an origin-allowlisted fetch.
- Durable state
- One sync_runs row per attempt, the updated sync_state checkpoint, and every upserted document with its chunks, revision, and audit row.
Failure paths
- Queue with no consumer: the 202 is honest but nothing ever runs
- Remote rate limiting or outage fails the run; the queue retries with the same checkpoint
- sourceVersion collisions never occur — unchanged documents are skipped, not rewritten
- Advisory-lock contention serializes overlapping triggers instead of interleaving them
Signals
- finished_at null on a sync_runs row marks an in-flight run
- pages and docsSeen growing across runs of the same source
- error text on failed runs
- status transitions on connector_sources
Internals and invariants
Source registry and credential custody
A source is created and edited through src/server/connectors/crud.ts, which validates non-secret config per type (a bad Confluence base URL or an empty SharePoint site list is a 400 at write time, not a failed crawl later), validates the credential shape against the stored type, and serializes the credential into an encrypted column. Reads go through a masking projection that replaces the ciphertext with a boolean.
- Invariant — Credential ciphertext never leaves the CRUD module unmasked: every exported read strips it to
hasCredential, and decryption happens only inloadSourceForSyncat crawl time, enforced insrc/server/connectors/crud.ts. - Invariant — Audit rows for source updates record only the names of the changed fields, never credential or config values, enforced in
src/server/connectors/crud.ts.
Sync execution and checkpointing
The trigger validates the source and enqueues; the worker claims the job and hands it to a runner. Every runner first takes a session-level PostgreSQL advisory lock keyed on the source, then crawls with the adapter, persisting the generator's checkpoint after each page. Remote I/O happens outside any database transaction — only the short bookkeeping writes open one.
- Invariant — All four run kinds against one source serialize under a single advisory lock that spans queues and worker replicas, enforced by
withConnectorSourceLockinsrc/server/connectors/sync.ts. - Invariant — The resumable checkpoint is persisted after every crawled page, so a crash resumes rather than restarts, enforced by the crawl driver in
src/server/connectors/sync.ts.
Normalization and deduplicated persistence
Adapters emit normalized documents; binary payloads flatten to markdown through availability-gated extractors. Persistence is a single upsert: frontmatter is injected, the document id is spliced in, content is hashed and chunked, and the row lands with its revision and audit entry in one workspace transaction. An existing live row with an unchanged source version is skipped before any of that work happens.
- Invariant — A live document whose sourceVersion matches is skipped outright — no rewrite, no re-chunk, no re-embed — enforced by
persistConnectorDocumentinsrc/server/connectors/sync.ts. - Invariant — Chunk rows reconcile by identity (document, normalized content hash, position), so existing chunk embeddings survive exactly when they are still valid, enforced by
persistChunksinsrc/server/ingest/chunking.ts. - Invariant — A plugin document transform may rewrite or deliberately skip a document, but a transform that throws or returns garbage is ignored and the last good version persists; identity keys are pinned by the host, enforced in
src/server/connectors/sync.ts.
ACL mirroring and prune lifecycle
Documents carry the visibility and principals their source reported. The permissions runner re-reads remote ACLs for every live document of a source; the prune runner enumerates the complete set of ids still present remotely and tombstones only what has verifiably vanished.
- Invariant — A content update never downgrades a restricted document, and a restricted source forces every document it ingests to restricted, enforced in the ACL merge of
src/server/connectors/sync.ts. - Invariant — A prune builds the complete present-id set before deleting anything; an enumeration failure aborts the run with zero tombstones, enforced by
runConnectorPruneinsrc/server/connectors/sync.ts.
Run observability and enrichment handoff
Every attempt writes a run row at start and finalizes it with stats and an error string on completion — success and failure alike — while the source row tracks status and last-synced time. After a successful upsert, extraction and embedding jobs are enqueued for downstream modules and a cache invalidation is signalled for changed content.
- Invariant — Every sync attempt leaves an append-only
sync_runsrow with its stats, written bystartSyncRun/finishSyncRuninsrc/server/connectors/sync.ts. - Invariant — Extraction and embedding handoffs are non-fatal: an enqueue failure logs a warning and never fails ingestion, enforced in
src/server/connectors/sync.ts.
For a self-hosting team the safe extension points are exactly the seams above: contribute a new source type through intakeConnectors (it resolves through the same buildConnector path in src/server/connectors/registry.ts and inherits the lock, checkpoints, and run rows), rewrite documents pre-persist with intakeDocumentTransforms, subscribe to the ingestion events on the plugin bus under src/server/plugins, or swap the vault body store in src/server/vault/store between its filesystem and Postgres implementations. The upsert key, the advisory lock, and the credential masking are load-bearing and not configurable.
How it composes
Intake is the first slice in the capability map, so most of what it gains from the others is downstream of the documents it produces. Two of those links are real dependencies for a specific feature; the rest are additive.
- Atlas — entities and relations. Every ingested or changed document enqueues an extraction job. That job is what turns connector text into graph entities, and the enqueue is explicitly non-fatal: if nothing consumes it, ingestion still succeeds and the documents and chunks are all still there. Genuinely needs Atlas: anything that reads the entity graph over connector content.
- Lens — retrieval and grounded answers. Chunks are persisted inline during ingest, so lexical retrieval over connector documents works as soon as a sync completes. Embeddings are a separate queued job, so the vector half of hybrid retrieval only covers connector documents once that job has run against a configured embedding provider. Retrieval is also where the ACL columns are enforced: the content-ACL predicate filters on
acl_visibilityandacl_principalsin SQL before ranking, so arestrictedsource stays restricted end to end. - Lineage — provenance. Intake records a document revision on every connector upsert, tagged with the connector activity and a
sourceId:externalIdnote. The lineage read joinsconnector_sourcesto classify a document's origin as connector, upload, or vault. Without Lineage you still have the raw columns; with it you get the assembled trail. - Warrant — governance. Connector documents are ordinary
documentsrows, so document verification and review SLAs apply to them exactly as they do to hand-authored ones. Nothing in Intake requires it. - Crucible and Ports. Purely additive: evals measure quality over whatever corpus exists, and agents and MCP read it. Neither is consulted during a sync.
Intake connectors are not Conduit connectors
/api/v1/connectors (this module) and /api/v1/data/connectors (Conduit) are different surfaces backed by different tables — connector_sources versus data_connectors. This module ingests content into the corpus; Conduit queries live systems at answer time and stores nothing in documents. They share the data:read scope on their list endpoints and the same encryption envelope for credentials, and nothing else. Adopting Intake does not adopt Conduit.Deeper reading: Content connectors for per-source operational detail (rate limits, credential scopes), Connect Confluence, SharePoint & Slack for a walkthrough, and the OpenAPI reference for the wire contract.
Extending Intake
intakeConnectors opens the formerly closed Record<ConnectorType, factory>, so a contributed type resolves through the same buildConnector path, checkpoints, advisory lock and sync_runs rows as the four built-ins — or rewrite a document before it is persisted with intakeDocumentTransforms, which runs after normalisation and before hashing. Intake also emits document.ingested, document.updated, document.deleted, connector.source.* and connector.sync.completed / failed onto the plugin event bus. See the extension model for the contract and the trust boundary, and Build a plugin for a worked example.