Primitive spec
Korell
The bulk-import primitive. Korell is the substrate for moving entities into Foundation from external sources — tenant onboarding from a legacy system, periodic CSV pulls, one-time migrations — without each primitive growing its own ad-hoc importers. Upload a file (or a whole ZIP export of your old system), let the AI propose the mapping, preview the dry run, and import.
What it owns
Korell owns the lifecycle of a data-import job. An ImportTask is a reusable definition (source + target + mapping); an ImportRun is one execution of that task with a status, counts, and any per-row failure detail. The engine drives the run; source adapters read from the outside world; target adapters write into a primitive's normal write surface. Korell also owns the upload store the files land in (presigned S3 uploads, never proxied through the application) and the cross-import identity map that keeps multi-file imports linked.
Korell does not bypass primitive validation. Targets are thin adapters over each primitive's existing controllers (or service-layer write APIs) — the same rules a single-item REST call would hit also apply to a 100,000-row import.
Concepts
- ImportTask
- A reusable import definition. Carries the source descriptor (where to read from), the target primitive + entity (where to write to), and a field-mapping document.
- ImportRun
- One execution of an
ImportTask. Tracks status (pending,running,succeeded,failed,partial), row counts, and structured per-row error detail. - Source adapter
- The "read" side.
CsvSourceReader, JSON (inline and file), a dependency-freeXlsxSourceReader, andS3ArchiveEntrySourceReader— run an import straight from one entry inside an uploaded ZIP. Each source produces a stream of normalised rows for the engine to consume. - Upload store & archive inspector
- Files land via presigned S3 upload (
korell.create_upload_url/korell.inspect_upload) — the application never proxies the bytes. TheKorellArchiveInspectorenumerates a ZIP's entries so a whole legacy-system export becomes a set of importable files in one upload. - AI import planning
- The "Beth figures out the import" core.
korell.propose_import_planreads the uploaded files against a typed target field catalogue and proposes per-file targets + field mappings; the operator reviews and edits per file before anything runs. - Target adapter
- The "write" side. A target is a thin wrapper over a primitive's existing controllers or write services, each publishing its typed
TargetFieldcatalogue. Shipping today:TerminusPersonImportTarget,TerminusEmployeeImportTarget,TrantorResourceImportTarget,HardinProductImportTarget. Adding a new importable entity is a target adapter, not a new endpoint. - ImportRefMap
- The cross-import identity map. When file two references file one's IDs (customers → their bookings), the
ImportRefMapremaps source IDs to the entities an earlier run created, so multi-file imports stay linked across runs. - KorellEngine
- The orchestrator. Resolves the source + target adapters from an
ImportTask, opens anImportRun, streams rows through the mapping, and writes to the target with idempotency keys derived from the source row identity. A dry-run preview (korell.preview_import) exercises the same path without writing.
API surface
Korell's surface is published as typed capabilities in the platform capability registry — invoked by the Demerzel import wizard, by Beth over MCP, or through the generic capability invoke path, and gated to tenant operators by the DATA_IMPORT permission. There are no /korell/v1/ REST routes (and no Korell tag in the OpenAPI reference); the capabilities below are the API.
| Capability | Purpose |
|---|---|
korell.create_upload_url / korell.inspect_upload | Presigned S3 upload for a source file; inspect what landed (headers, sheets, archive entries). |
korell.inspect_archive | Enumerate the entries of an uploaded ZIP so each can become its own import. |
korell.propose_import_plan | AI-proposed per-file targets + field mappings against the target field catalogue. |
korell.create_import_task / korell.list_import_tasks / korell.get_import_task | Author and inspect reusable ImportTask definitions. |
korell.preview_import | Dry-run a task — full validation, row counts, and per-row errors with nothing written. |
korell.run_import_task | Open an ImportRun and execute the import. |
korell.list_import_runs / korell.get_import_run | Inspect run status, counts, and per-row failure detail. |
korell.list_import_targets | The target field catalogue — what can be imported, with typed fields. |
How it fits with the rest
flowchart LR
Up[Uploaded CSV / XLSX / ZIP] --> Src[Source adapter]
AI[AI plan proposer] -. proposes mapping .-> Task[ImportTask]
Src --> Ko(Korell engine)
Task -. defines .-> Ko
Ko --> Tgt[Target adapter]
Tgt --> Te[Terminus]
Tgt --> Ha[Hardin]
Tgt --> T[Trantor]
Tgt --> Other[any primitive]
Korell writes to other primitives through their normal surfaces, so anything that ships in Foundation can be imported from elsewhere by adding a target adapter. Demerzel is the operator surface: an import wizard with per-file target + mapping editing and an inline run-in-chat card — or skip the wizard and drag a data export onto Beth's chat, and she proposes the plan herself via korell.propose_import_plan.