Architecture
cairn is built around a strict one-way dependency: a domain-neutral engine plus packs that carry all domain knowledge. The engine never imports a pack.
Three layers
Section titled “Three layers”| Layer | What it holds | Example |
|---|---|---|
| Pack | intent — templates, skills, policies, and descriptors for a domain | the training pack’s finetune template |
| Profile | technology choices — which provider backs each capability ([profile.bindings]) | bind storage.put to S3 or to local disk |
| Environment | glue — secrets, endpoints, and per-deployment env vars | OPENAI_API_KEY, GPU/backend URLs |
A pack declares what it needs (a capability); a profile decides which provider satisfies it; the environment supplies the credentials. The same pack runs against different backends by changing the profile, with no code change. See Profiles & environment.
Pack kinds and tiers
Section titled “Pack kinds and tiers”Packs are classified on two axes:
- kind —
domain(a multi-step workflow for a domain, e.g. fine-tuning or incident response),operator(executes a workflow node — a GPU job, an eval, a registry push),service_provider(backs a cross-cutting service like memory, RAG, redaction, artifact storage, or audit), orknowledge(ships a governed RAG corpus, nothing else). - trust tier —
builtin(engine substrate) orofficial(first-party add-ons); community/private/local packs install as separate wheels.
On disk this is kind-first: operators live under
operators/{builtins,official}/, domain packs under packs/. An operator is
referenced everywhere by its stable capability name — never by its tier or
path — so moving it between tiers is a metadata change, not a code change. Any
operator that satisfies the contract inherits the budget, policy gate, and
audit automatically.
Engine subsystems
Section titled “Engine subsystems”- engine — template schema, compiler, and runner. Templates come in three
flow types:
prompt,agent_loop, andstate_machine. - dispatch — realtime and background lanes with per-lane concurrency, deduplication by trigger fingerprint, the cron scheduler, the bus bridge, and the pipeline bridge that chains governed runs into pipelines.
- guards — citation validation and capped self-critique.
- approval — gate resolution (operator ∪ profile ∪ policy ∪ binding —
strictness only), the durable
interrupt()pause, and a TTL reaper for stale gates. - actions — the executor registry plus in-process and durable handlers.
- runtime — checkpointer tiers (
memory/sqlite/postgres) and the runtimes:inprocess(default),dbos(single-node durable, no server), andtemporal(durable workflows, dedicated workers, remote/GPU queue). - storage — run store tiers (in-memory / SQLite / Postgres via
OBS_STORAGE), plus the config-family stores (policies, schedules, connections, workflow config, presets, pipelines) on the same backend. - llm / obs — the LLM gateway chokepoint and the cost / events / redaction / audit seams.
- pack — the pack contract, the entry-point loader, the registry, and profiles.
Surfaces
Section titled “Surfaces”One FastAPI server (cairn-server, default port 9090) exposes everything:
webhook and schedule triggers, the run/approval/pipeline API with SSE
streaming, the console SPA at /console, an
OpenAI-compatible chat endpoint at /v1/chat/completions, and the
/ask agent. The CLI drives the same server over HTTP
(cairn remote ...) or runs templates locally in-process (cairn run).
Every flat route can also be addressed per-workspace as
/workspaces/{id}/... — tenancy is part of the path.
Request lifecycle
Section titled “Request lifecycle”flowchart LR T[Trigger: webhook / schedule / manual / pipeline / bus event] --> SK[Skill match] SK --> TMPL[Template compiled to a graph] TMPL --> RUN[Run: LLM gateway + operators] RUN --> G[Guards: citation validation + budget] G --> POL[Policy gate] POL -->|allow_auto| EX[Executor] POL -->|require_hitl| HITL[Human approval - durable pause] HITL --> EX EX --> STORE[Run + events + provenance stored, audit hash-chained]
A trigger is matched to a skill, which binds it to a template. The
template compiles to a graph and runs through the LLM gateway with the pack’s
operators. Guards verify the output and the budget is enforced
(BudgetExceeded stops overspend). The policy gate decides whether an action
may run automatically or needs human approval — an approval pause is a durable
interrupt(): the run parks as awaiting_approval and resumes from its
checkpoint on POST /runs/{id}/resume. The run, its events, its provenance
(validated inputs, schema hash, resolved operators), and the hash-chained
audit record are persisted.
Pipelines add one layer above: a bundle declares steps, each step is a full governed run, and the pipeline bridge chains them through gates, schedules, and events — see Pipelines & bundles.
Further reading
Section titled “Further reading”The internal decision record — architecture decisions (ADRs), the live
roadmap, and probe-backed status — lives in the engine repository’s docs/
tree. This site documents the shipped, externally-facing surface only.