llm/embeddings blocks in canonic.yaml into actual model calls. It gives the rest of canonic one interface to generation and one to embeddings regardless of provider, and it owns the offline/air-gapped guarantee. See Configuring an LLM for the four providers and their config shapes.
The compiler is never on this path. Compile/query is fully deterministic and LLM-free by construction; generation is used only for drafting during setup/ingest, never for computing an answer.
One interface, any backend
Every provider is reached through litellm behind one interface; local runtimes and hosted OpenAI-compatible endpoints (openai_compatible) differ from native hosted APIs (openai, anthropic, github_copilot) only in base_url/credential handling. litellm routes purely on the model-string prefix, so there’s no per-provider branch anywhere in canonic’s core logic.
Task-based model routing
Different work wants different models; cheap/local for high-volume drafting, a stronger model for judgment-heavy work. Three named tasks route independently:| Task | Used by |
|---|---|
draft | Ingestion’s builder: measure naming, grain/join proposals, knowledge-page prose. |
reconcile | Ingestion’s reconciliation step: conflict-resolution drafting. |
extract | Generic evidence-connector classification (used by canonic knowledge add and evidence connectors like Notion/web to classify fetched content). |
model. Resolution is deterministic; no silent model substitution: a failed call surfaces a structured error after bounded retries rather than quietly falling back to a different model.
Air-gapped mode: enforced, not advisory
air_gapped: true is localhost-only: the tightest guarantee. This is defense-in-depth, the same posture as read-only for the database:
- Load-time validation. Every configured endpoint must resolve to a local/private address. A public endpoint in config is a hard error at load; the daemon refuses to start misconfigured.
- Call-time enforcement. Any attempted call to a non-allowlisted host is blocked before the request leaves the process, aborting with
AIR_GAPPED_VIOLATION. - No telemetry, no remote secrets. Opt-in telemetry is forced off under air-gapped, and a
*_refpointing at a remote secret service is rejected.
EgressPolicy) backs both the load-time check and the call-time guard, so there’s one source of truth for “what counts as local.”
Local embeddings
Powers knowledge search’s vector arm: an optionalcanonic[embeddings] add-on backed by sentence-transformers, not bundled by default.
Keys are bring-your-own
Keys are never literal incanonic.yaml: api_key_ref points at env:, keyring:, or a local file, resolved only at call time and never logged or written to the event log. Local endpoints typically need no key at all.