Expand description
Warble front-end compiler (warble): parses a Warble project (profile + components + context
binding), merges component defaults with profile overrides, runs the loud-fail compile checks,
and emits the language-neutral IR JSON that any back-end dispatcher consumes.
§Architecture
profile + components + context ──► warble compile ──► IR JSON ──► warble dispatch ──► native agent
(declarative YAML + prompts) (front-end, Rust) (the seam) (per-target back-end)This crate is the first arrow: it owns the authoring types (ProfileFile, ComponentFile,
and friends), ContextLoader (the host’s semantic-layer probe), and compile, which
resolves a parsed project into IR. Everything downstream of the IR — the claude-code-cli and
vercel back-ends (both fold into the warble binary), plus the separate TS
claude-agent-sdk back-end — depends only on the IR schema, never on this crate’s Rust types.
See authoring.md for the authoring-side contract and
ir-schema.md for the emitted shape.
§Sans-IO
This crate performs no filesystem or network access. compile takes already-parsed
authoring values, the raw step markdown, and a ContextLoader the caller has already built
from the bound semantic layer — it never reads a path itself. The example below is exactly
this: it deserializes YAML strings in-memory and calls compile directly, no disk involved.
Keeping the core pure is what lets the same crate target native, WASM, and language bindings
unchanged; the native host adapter lives in the warble-cli crate.
§Example
A minimal project — one component, one step — compiled against an empty in-memory
ContextLoader. Both the profile and the component are parsed from YAML exactly as a host
would read them off disk; only the reading itself is out of scope for this sans-IO crate.
use warble::{compile, BindingFile, ComponentFile, ProfileFile, SlotContents};
let profile: ProfileFile = serde_yaml::from_str(concat!(
"profile: demo\n",
"context:\n",
" project: ./context/binding.yml\n",
"components:\n",
" - use: hello\n",
))?;
let binding: BindingFile = serde_yaml::from_str("kind: external\nproject: ./warehouse\n")?;
let component: ComponentFile = serde_yaml::from_str(concat!(
"id: hello\n",
"verb: greet\n",
"type: analytical\n",
"realization_kind: skill\n",
"binding_mode: runtime_selected\n",
"llm_steps:\n",
" - name: say_hello\n",
" tier: cheap\n",
" prompt_ref: steps/say_hello.md\n",
"trigger:\n",
" kind: one_shot\n",
"guardrails:\n",
" - name: read_only_execution\n",
" locked: true\n",
"effect:\n",
" render_blocks: []\n",
" outcome:\n",
" kind: none\n",
))?;
let mut components = HashMap::new();
components.insert(component.id.clone(), component);
let mut steps = HashMap::new();
steps.insert("say_hello".to_string(), "Say hello to {{project_name}}.".to_string());
let mut step_contents = HashMap::new();
step_contents.insert("hello".to_string(), steps);
let ir = compile(
&profile,
&components,
&binding.project,
&EmptyContext(LineageGraph::default()),
&step_contents,
&SlotContents::default(),
)?;
assert_eq!(ir["warble_ir_version"], "0.9");§Invariants
These hold across the whole workspace, not just this crate — they matter to you as an API consumer too, not only to contributors:
- If you write your own back-end that consumes the IR
compilereturns, drive it off the three enum fields —realization_kind,outcome.kind,trigger.kind— never off a component’sidorverbstring. Any enum value your back-end can’t handle must be a hard error, not a best-effort guess or a silent no-op. - Depending on
warblealone pulls in no I/O and nowrendependency.compilenever touches a filesystem or network — you build theContextLoaderyourself and hand it in — and this crate has no dependency on any semantic-format library. That’s what lets you embed it in a native binary, WASM, or another language’s bindings without inheriting a dependency graph you didn’t ask for. Nothing in this workspace links such a library any more: a host reads its own format and hands the projection over as a prepared-context document. - No DSL in the composition layer — there’s no boolean algebra, expression language, or
imperative logic in the profile/IR schema. The one form of conditionality that does exist
is a closed vocabulary: a step can set
conditional: truewith awhenguard whoseguardis one ofon_failure/on_flag/on_missingand whosetargetnames what it’s checking — never an arbitrary predicate. If you’re writing a dispatcher, that means you must either realizewhenor loud-fail on it, not silently treat it as a no-op. IR growth is additive (a new optional field), never a new mechanism. - The IR
compileemits never names a specific mechanism (nocron,slack,subagent, …). If you’re writing a dispatcher, those names only show up later, at the capability layer viarealize-via— so the same IR is valid input to any runtime’s back-end, including ones this crate has never heard of. Seecapability-model.md. - This crate builds exactly one native capability:
blast_radius(semantic lineage — seeblast-radius.md). If you’re checking what a target back-end can support natively versus needs to borrow from its runtime, know that everything else — human approval, VCS/rollback, scheduling, subagent dispatch, schema introspection — is expected to come from the runtime (realize-via), not fromcore.
Structs§
- Asset
Decl - One
assets:entry: a file this component needs present on disk at run time — a themed template, a stylesheet, a whole scaffold tree — that is never read into the IR. - Binding
File - The root of
context/binding.yml: which context a profile binds against. - Compile
Error - A compile failure with a human-facing message. Every compile-time check is
loud-fail: on the first violation
compilereturns one of these rather than emitting a silently-wrong IR (seeir-schema.md§checks). - Component
File - The root of a
component.yml: identity, anatomy (type/realization_kind/trigger), the LLM steps that realize it, and everything that gates or governs how it runs (guardrails, preconditions, capabilities). - Dimension
Info - A dimension the semantic layer can group/slice by — a cube dimension or a selectable model
column.
is_temporalmarks a DATE/TIMESTAMP-typed dimension (satisfieshas_time_dimension). - Effect
- What a component’s steps produce for the host to act on: any render blocks plus the outcome classification.
- External
Context - A binding whose semantic layer is not reachable from here — held by a service that answers the questions itself, or simply not pulled onto this machine.
- Guardrail
- A guardrail attached to a component: whether it is
locked(unpatchable by a mounting profile) oroverridable, its scope, and an optional threshold. - Guardrail
Patch - A profile-level override to a mounted component’s guardrail. Only
lockedis patchable today; patching a guardrail whose component default islocked: trueis always a compile error, regardless of what the patch requests. - Host
Analysis - The host’s own analysis of the layer it resolved, carried on the wire so Warble can use results it does not derive. Absent entirely when the host analysed nothing — which is not the same as an analysis that found nothing.
- Host
Consumers - The counts the IR’s
lineage.consumersblock reports. Which node kinds count as consumers is the host’s call, so the host supplies the totals rather than Warble deriving them. - Host
Impact - One seed’s impact, as the host computed it.
- Lineage
Edge - A directed dependency edge, oriented upstream → downstream:
fromis the thing depended on,tois the dependent that would break iffromchanged. A producer’s closure follows them forward. - Lineage
Graph - The semantic lineage DAG, as the layer’s owner built it (from structural refs: relationships,
cube base objects, column expressions, view statements). Warble does not traverse it — the
downstream closure arrives already computed, in
HostAnalysis— and reads it only to check that every edge endpoint is a declared node (Self::is_resolvable). - Lineage
Node - A node in the lineage DAG.
idis a stable, adapter-assigned identifier (e.g.model:orders,column:orders.amount,metric:revenue.total_revenue); it is what an impact analysis is keyed by. - LlmStep
- One
llm_stepsentry: a single LLM call — its tier, its prompt reference, and the artifact-flow (consumes/produces) linking it to other steps. - Metric
Info - A queryable metric — either a declared cube measure (whose additivity is determinable) or an
implicit numeric column projected as a metric so that
has_metricholds on cube-less projects.declaredis the line between the two;additivityisSomeonly whendeclared. - Model
Info - A model in the semantic layer, narrowed to what the precondition vocabulary needs.
- NoContext
- No context was authored. It answers no semantic probe and never claims parse success.
- Outcome
- The component’s outcome classification (
kind:none/assertion/mutation/dispatch) and the outcome-kind-specific fields that go with it. - Param
- A component parameter, either bound at dispatch time (
bind, with an optionaldefault) or sourced from context (source). Exactly one ofbind/sourceis expected to be present per the schema, but both are optional here since this is a Deserialize-only view. - Prepared
Context - A
ContextLoaderbuilt from a host-supplied snapshot rather than from a semantic layer this process knows how to read. - Profile
Component Mount - One
components:entry: which component to mount, plus the config/binding/tier-override/ guardrail patches layered on top of that component’s own defaults. - Profile
Config - The profile’s global
configblock — settings that apply to the profile as a whole. - Profile
Context - Where the bound context lives, relative to the profile.
- Profile
File - The root of
profile.yml: which components are mounted, the context this profile binds against, and profile-level config defaults. - Ranked
Severity - A severity as the host ranked it. Warble orders by
rankand never interpretsname: deciding that a silently shifted metric outranks a broken model is a statement about what those objects mean, which belongs to whoever owns the semantic format. - RawSource
Context - A
ContextLoaderbacked by a raw (pre-MDL) source. Carries no metrics/dimensions/models/lineage — that is what a CONSTITUTIVE component’s output produces — but answers the two raw-shape probes that makesource_introspectable/raw_docs_readableevaluable instead of unanswerable. - RawSources
- The raw file contents of a raw source, read by the host.
from_sourcesoperates purely over these bytes (WASM-friendly); the nativeread_raw_dirconvenience fills them from disk. - Render
Block - A render block entry, authored either as a bare type name (shorthand, no fields) or as a
mapping with an explicit
fieldsschema. Always normalized to{type, fields}in the IR. - Slot
Contents - Slot-variant file contents, read by the host and handed to
crate::compile— the slot counterpart of thestep_contentsmap, and for the same reason: core is sans-IO, so every authored file is read by the caller and passed in. - Slot
Decl - One
slots:entry: a named position in prompt text, a set of alternative wordings for it, and optionally a condition that removes it entirely. - Trigger
- How this component starts (
kind:one_shot/scheduled/event).
Enums§
- Additivity
- Whether a metric’s aggregation is additive across the dimensions a decomposition would drill along. Inferred by an adapter from the metric’s underlying aggregation; it is not a field any current MDL carries.
- Lineage
Kind - The kind of a node in the semantic lineage DAG. Ordered coarse→fine along the dependency flow
raw → models → relationships → metrics/dimensions → views → consumers(capability-model §7.1).QueryandDashboardare consumer nodes — artifacts outside the semantic layer (a confirmed saved query, a dashboard spec) that depend on it; they are always sinks, never upstream of a semantic node. - Prepared
Context Error - Why a prepared-context document could not be read.
Constants§
- PREPARED_
CONTEXT_ VERSION - The wire-format version of a prepared-context document.
Traits§
- Context
Loader - Host-injected semantic-layer access. Pure on the core side (operates over an already-loaded
adapter); the host owns all I/O. One accessor per capability the compiler needs to probe the
bound Context for, plus
lineageand the impact analysis over it.
Functions§
- compile
- Resolves a Warble project into its IR JSON document.
- prepared_
document_ from - Render any
ContextLoaderas a prepared-context document. - read_
raw_ dir - Read a raw-source directory into
RawSources(native host only; the pureRawSourceContext::from_sourcespath is what a WASM host feeds directly). ReturnsNoneif the directory has noschema.json— i.e. it is not a raw source — so the caller can fall through to another adapter or an unparseable context. - verify_
context_ preconditions - Validate resolved runtime predicates against a host-owned context snapshot.