Skip to content

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.

LayerWhat it holdsExample
Packintent — templates, skills, policies, and descriptors for a domainthe training pack’s finetune template
Profiletechnology choices — which provider backs each capability ([profile.bindings])bind storage.put to S3 or to local disk
Environmentglue — secrets, endpoints, and per-deployment env varsOPENAI_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.

Packs are classified on two axes:

  • kinddomain (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), or knowledge (ships a governed RAG corpus, nothing else).
  • trust tierbuiltin (engine substrate) or official (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 — template schema, compiler, and runner. Templates come in three flow types: prompt, agent_loop, and state_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), and temporal (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.

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.

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.

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.