Skip to main content

Crate warble

Crate warble 

Source
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.8");

§Invariants

These hold across the whole workspace, not just this crate — they matter to you as an API consumer too, not only to contributors:

  1. If you write your own back-end that consumes the IR compile returns, drive it off the three enum fields — realization_kind, outcome.kind, trigger.kind — never off a component’s id or verb string. Any enum value your back-end can’t handle must be a hard error, not a best-effort guess or a silent no-op.
  2. Depending on warble alone pulls in no I/O and no wren dependency. compile never touches a filesystem or network — you build the ContextLoader yourself 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.
  3. 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: true with a when guard whose guard is one of on_failure / on_flag / on_missing and whose target names what it’s checking — never an arbitrary predicate. If you’re writing a dispatcher, that means you must either realize when or loud-fail on it, not silently treat it as a no-op. IR growth is additive (a new optional field), never a new mechanism.
  4. The IR compile emits never names a specific mechanism (no cron, slack, subagent, …). If you’re writing a dispatcher, those names only show up later, at the capability layer via realize-via — so the same IR is valid input to any runtime’s back-end, including ones this crate has never heard of. See capability-model.md.
  5. This crate builds exactly one native capability: blast_radius (semantic lineage — see blast-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 from core.

Structs§

AssetDecl
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.
BindingFile
The root of context/binding.yml: which context a profile binds against.
CompileError
A compile failure with a human-facing message. Every compile-time check is loud-fail: on the first violation compile returns one of these rather than emitting a silently-wrong IR (see ir-schema.md §checks).
ComponentFile
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).
DimensionInfo
A dimension the semantic layer can group/slice by — a cube dimension or a selectable model column. is_temporal marks a DATE/TIMESTAMP-typed dimension (satisfies has_time_dimension).
Effect
What a component’s steps produce for the host to act on: any render blocks plus the outcome classification.
ExternalContext
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) or overridable, its scope, and an optional threshold.
GuardrailPatch
A profile-level override to a mounted component’s guardrail. Only locked is patchable today; patching a guardrail whose component default is locked: true is always a compile error, regardless of what the patch requests.
HostAnalysis
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.
HostConsumers
The counts the IR’s lineage.consumers block reports. Which node kinds count as consumers is the host’s call, so the host supplies the totals rather than Warble deriving them.
HostImpact
One seed’s impact, as the host computed it.
LineageEdge
A directed dependency edge, oriented upstream → downstream: from is the thing depended on, to is the dependent that would break if from changed. A producer’s closure follows them forward.
LineageGraph
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).
LineageNode
A node in the lineage DAG. id is 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_steps entry: a single LLM call — its tier, its prompt reference, and the artifact-flow (consumes/produces) linking it to other steps.
MetricInfo
A queryable metric — either a declared cube measure (whose additivity is determinable) or an implicit numeric column projected as a metric so that has_metric holds on cube-less projects. declared is the line between the two; additivity is Some only when declared.
ModelInfo
A model in the semantic layer, narrowed to what the precondition vocabulary needs.
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 optional default) or sourced from context (source). Exactly one of bind/source is expected to be present per the schema, but both are optional here since this is a Deserialize-only view.
PreparedContext
A ContextLoader built from a host-supplied snapshot rather than from a semantic layer this process knows how to read.
ProfileComponentMount
One components: entry: which component to mount, plus the config/binding/tier-override/ guardrail patches layered on top of that component’s own defaults.
ProfileConfig
The profile’s global config block — settings that apply to the profile as a whole.
ProfileContext
Where the bound context lives, relative to the profile.
ProfileFile
The root of profile.yml: which components are mounted, the context this profile binds against, and profile-level config defaults.
RankedSeverity
A severity as the host ranked it. Warble orders by rank and never interprets name: 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.
RawSourceContext
A ContextLoader backed 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 make source_introspectable / raw_docs_readable evaluable instead of unanswerable.
RawSources
The raw file contents of a raw source, read by the host. from_sources operates purely over these bytes (WASM-friendly); the native read_raw_dir convenience fills them from disk.
RenderBlock
A render block entry, authored either as a bare type name (shorthand, no fields) or as a mapping with an explicit fields schema. Always normalized to {type, fields} in the IR.
SlotContents
Slot-variant file contents, read by the host and handed to crate::compile — the slot counterpart of the step_contents map, and for the same reason: core is sans-IO, so every authored file is read by the caller and passed in.
SlotDecl
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.
LineageKind
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). Query and Dashboard are 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.
PreparedContextError
Why a prepared-context document could not be read.

Constants§

PREPARED_CONTEXT_VERSION
The wire-format version of a prepared-context document.

Traits§

ContextLoader
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 lineage and the impact analysis over it.

Functions§

compile
Resolves a Warble project into its IR JSON document.
prepared_document_from
Render any ContextLoader as a prepared-context document.
read_raw_dir
Read a raw-source directory into RawSources (native host only; the pure RawSourceContext::from_sources path is what a WASM host feeds directly). Returns None if the directory has no schema.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.