Intake
Content connectors
A knowledge base is only as good as what is in it, and most of what a team knows already lives somewhere else — Confluence spaces, SharePoint sites, Slack channels. Content connectors sync those sources into the same documents, chunks, and entity graph everything else runs on: read-only, incremental, resumable, and workspace-scoped under FORCE-RLS. These are distinct from data connectors, which run governed live SQL against operational databases — content connectors ingest documents, not rows.
One contract, four sources
Every source implements the same connector interface — the taxonomy is borrowed from Onyx (the MIT-licensed open-source connector suite), so governance, provenance, and citations are identical no matter where a document came from:
Source types
Resumable, incremental sync
Connectors — sync
One cross-queue advisory lock serializes every source-level operation. A crawl persists every document in a page before advancing that page's checkpoint; replay is idempotent by (source_id, external_id), and unchanged content skips re-embedding.
Downloads
Concepts
- Capabilities
- Connectors
- Sync
Keywords
- config + encrypted creds + sync_state
- or legacy ingest job
- workspaceId, sourceId
- DocBatch, Checkpoint>
- poll(start, end)
- normalize → Document
- persist inline
- contentHash changed?
- skip re-embed
- persist page checkpoint
- full · incremental · prune · permissions
- PostgreSQL advisory lock
- delta / CQL watermark / cursor
- saveDocument · chunks · extract · embed
- every document in page succeeded
- {markdown, contentHash, aclVisibility, aclPrincipals, …}
- markdown, contentHash, aclVisibility, aclPrincipals, …
- connector_source
- job.kind
- document/chunks
- revision/audit
- fullLoad(checkpoint)
- AsyncGenerator<
- full
- incremental
- vault
- yes
- no
Source and generation provenance
Status: current
Generated at: 2026-08-12T23:37:34.782Z
Source hash: a3c945d362ca7b0c4a5e2f37e46e87fbb9fffb87c7b2bc28c4bcf1587332d5e5
Metadata payload hash: 759e3c407a368f307d345007657b2896aba8d7f029b472bc9db7c99c03cc0732
Canonical appearance
src/app/(docs)/docs/capabilities/connectors/page.tsx:30 route /docs/capabilities/connectors
All appearances
canonical—src/app/(docs)/docs/capabilities/connectors/page.tsx:30route/docs/capabilities/connectors
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: connectors-sync-a3c945d3.json
The sync_state jsonb on each source is the resumability contract, modeled on Airbyte's STATE message: after every document in a yielded page is durably written, the runner checkpoints that page's position — a SharePoint deltaLink, a Confluence CQL watermark, per-channel Slack cursors, or an opaque checkpoint blob. If one document fails, the checkpoint does not advance; a retry replays any earlier successful upserts idempotently instead of skipping the failed record. Every run appends a sync_runs row — an append-only ledger of kind (full · incremental · prune · permissions), timing, and stats (docs seen / added / updated / tombstoned).
No overlapping syncs
(workspaceId, sourceId). It spans the complete full, incremental, prune, permissions, or legacy-ingest operation — including remote I/O — and therefore serializes different queues and worker replicas. The per-document ingest-document queue remains a retry-compatible legacy/explicit entry point; normal crawls persist inline so checkpoint success means content is already durable.Ingestion: upsert, dedup, tombstone
ingestConnectorDocument is the single write path, and it reuses the exact same primitives a hand-authored document uses — saveDocument, chunk persistence, entity extraction, and embedding — so a synced document is indistinguishable from one you typed:
- Upsert by identity — documents carry a
source_idandexternal_id, and a partial unique index on(workspace, source_id, external_id)(wheresource_idis not null) makes re-sync a dedup: the second sighting of a page updates the same document rather than duplicating it. Manual and vault documents (nullsource_id) are untouched by this constraint. - Content-hash change detection — each document records a
source_versionand content hash. If the hash is unchanged since the last sync, ingestion skips re-chunking, re-extraction, and re-embedding — the expensive work — so polling a large space every few minutes costs almost nothing when little changed. - Soft-tombstone on delete —
listIdsgives the prune job the authoritative id set; anything present locally but absent upstream is soft-tombstoned, removing it from retrieval without hard-deleting the record. The run incrementsdocsTombstonedonly after a delete succeeds, so partial failures remain visible instead of inflating the completion count. A page that reappears upstream un-tombstones cleanly. - Revision and audit continuity — connector create/update/reactivation and prune deletion append the same immutable
document_revisionssummaries and audit events used by manual edits. A reactivated row keeps its document identity and advances its version, preserving the end-to-end history instead of starting a second lineage.
A content change also notifies the semantic cache: ingestion calls the cache's cacheInvalidateOnDocChange hook, so a cached answer grounded in a page that a connector just re-synced is invalidated by the same event that updated the document.
Normalization and binary extractors
Native text and Slack mrkdwn normalize locally. Mammoth (.docx), Turndown (HTML), unpdf (PDF), and officeparser (other Office formats) are installed dependencies: PDF, DOCX, PPTX, and XLSX content extracts to markdown on the same normalization path every connector shares, and POST /api/w/{id}/import/binary accepts direct binary uploads. The imports stay availability-gated — a trimmed deployment that removes a package degrades to a clear, named skip — and a corrupt or unsupported file is reported as extraction failed rather than silently decoded, with a hard 30s extraction timeout as the backstop.
Credentials, egress & access
A source stores its secret as credential_enc — AES-256-GCM under the enc:v1 envelope, the same crypto the data connectors use. The API accepts a secret on create/update, stores only the ciphertext, and every read returns a masked placeholder plus a hasCredential flag; decryption happens only inside the sync runner at call time.
Connector HTTP uses the shared safe-egress client
Permission refreshes carry both acl_visibility and normalized acl_principals into each document:
- Confluencereads each page's read restrictions and records canonical email, account, group, and space principals. A page becomes
restrictedwhen the provider reports explicit user or group grants. - SharePoint reads Graph drive-item permissions and records email/user/group identities, inherited site/drive scopes, and organization-link tenant scope. The app itself uses
Sites.Selectedand requires explicit configured site/drive ids; site grants are a separate administrator action. - Retrieval enforces before ranking.SQL admits workspace-visible rows or restricted rows whose GIN-indexed principals intersect the authenticated actor's normalized principals. The actor's app user id and email are automatic. Provider user/group ids require a trusted identity/group mapping to add them; an unresolved restricted grant fails closed instead of becoming workspace-visible.
- Shared workspace API keys and named background/system jobs are explicit workspace-service authority and can read restricted workspace content. Delegated user keys resolve the owner's current actor principals and stay filtered.
Slack needs a per-customer internal app
conversations.history and conversations.replies were cut to roughly one request per minute and a handful of messages per page for new non-Marketplace apps — a shared third-party Slack app cannot sync at usable throughput. Each customer therefore installs a customer-owned internal app from a provided manifest and supplies its bot token; the sync respects the tier with cursor pagination and backoff. The setup steps are in Connect Confluence, SharePoint & Slack.Managing sources
Sources are created and monitored on the workspace Connectors page (/w/<workspace>/connectors) — add a source per type, watch sync status and the sync_runs history, trigger a manual sync, and pause or resume. Over the API:
GET/POST /api/v1/connectors— list (masked creds,data:read) and create (kb:write).PATCH/DELETE /api/v1/connectors/{id}— update config/creds or remove a source (kb:write).POST /api/v1/connectors/{id}/sync— enqueue a full or incremental sync (kb:write).
Research grounding
| Mechanism | Where it lives | Research grounding |
|---|---|---|
| fullLoad / poll / listIds / getPermissions | connectors/types.ts | Onyx (MIT) connector taxonomy — one contract across sources; permissions method optional for ACL-aware retrieval. |
| Persist-before-checkpoint (sync_state) | connectors/sync.ts | Airbyte RECORD-before-STATE ordering — every page's documents are durable before its state advances; retries replay idempotent upserts. |
| Cross-queue source serialization | connectors/sync.ts | A PostgreSQL session advisory lock keyed by workspace/source spans all sync verbs, queues, and replicas. |
| SharePoint driveItem delta | sources/sharepoint.ts | Microsoft Graph delta query + Sites.Selected — opaque deltaLink returns changes while explicit per-site grants bound application access. |
| Provider ACL expansion + retrieval filter | sources/* · authz/content-acl.ts | Remote read grants become normalized document principals; retrieval applies workspace visibility or actor-principal intersection before ranking. |
| Pinned safe HTTP egress | network/safe-fetch.ts | Validate and pin every DNS/redirect hop; provider-origin allowlists and credential stripping prevent redirect-based token forwarding. |
| Confluence CQL lastModified polling | sources/confluence.ts | Confluence CQL + v2 cursor pagination — poll by last-modified watermark. |
| Per-customer Slack internal app | sources/slack.ts | Slack 2025 non-Marketplace rate-limit cuts to conversations.history/replies — a shared app can't sustain throughput. |
| Availability-gated normalization | connectors/normalize.ts | MIT/Apache-only runtime policy — mammoth/turndown/unpdf/officeparser are optional dynamic imports; degrade to plain text, add no deps. |
| Content-hash upsert + revisioned tombstone | ingestConnectorDocument | Idempotent sync — skip re-embed on unchanged content; revision every change/reactivation/delete; count only successful tombstones. |
Next