Skip to main content
The Program is the in-memory IR every authoring lane produces: a #[module] function and a parsed .dsrs file both end at this one value. It is the compiled form of a pipeline: everything the interpreter, the optimizer, and the serializer need, in one place. This page lists what a program holds, the nine node kinds, the parameter (overlay) surface, and how a winning candidate is baked into a new program.

What a Program holds

In plain words: the shape of the pipeline, the signatures, the models, the tools, the tunable values, the allowed capabilities, and a fingerprint hash. meta.program_hash is a stable hash over the canonical printed text minus the lineage block, so JSON and text loads of the same program agree on it. Overlays, traces, and state artifacts reference it. Nodes form a tree: one parent, one use. Fan-in happens through field references, never shared nodes. Leaf nodes (Predict, AgentLoop, Hole) carry a mandatory, program-unique name; that name is also the trace component name and the parameter path prefix. Containers are anonymous.

The nine node kinds

Every node’s binding (or out/join/carry) is a list of field-level wires: a destination field name fed from a port (Input for $.field, Out for node.field, Carried for ^field, or a JSON literal).

Tunable values (params)

Every mutable thing in a program is a named, addressable slot; a candidate is an overlay read through at render time, never a mutation of the program.

Kinds

Path naming rule

Slots are addressed by canonical string paths. Node-owned slots use the leaf name as prefix; tool-owned slots use a tool. prefix:
  • "<leaf>.instruction", "<leaf>.demos", "<leaf>.model", "<leaf>.context", "<leaf>.code"
  • "tool.<name>.desc", "tool.<name>.code"
For example "drafter.instruction" or "tool.search.desc". Program::param_id(path) resolves a path to its id; after load everything speaks ids.

The Overlay API

An Overlay is one candidate: a dense set of parameter values layered over a fixed program. The interpreter reads through it at render time, so many candidates can be evaluated concurrently over one shared program. Typed slot handles come from Program::slot_of::<Kind>(path), which returns None when the path is unknown or the slot has a different kind.

Baking a candidate

bake(overlay, note) promotes a candidate into a new program value: every overlay entry becomes the corresponding slot’s default, the lineage is stamped (the caller’s note, plus parent set to the old program hash and overlay set to the overlay hash, both hex), and the program hash is recomputed. The original program is untouched. Failures are BakeError::Overlay (stale base or kind mismatch) or BakeError::Invalid (the baked program failed validation). The workflow below turns a winning overlay, from an optimizer or from hand tuning, into a new self-contained .dsrs file.

1. Build an overlay against the program

An overlay is created for one specific program. Address each value by its parameter path: the leaf name (the let binding in your module body), a dot, and the slot name.
You can also set demos (worked examples the model sees before your input):

2. Bake

bake returns a new program with the overlay’s values folded in as the defaults.
Lineage is a note stored in the file that says where this version came from: which optimizer produced it, on what data, at what cost, and on what date. bake fills in parent (the hash of the program you baked from) and overlay (the hash of the candidate you promoted), so leave those as None.

3. Print the new version

The new file carries the baked values as its own defaults, plus a lineage { ... } block. It runs exactly like the old program plus the overlay, and you can check and serve it with the CLI like any other program.

The base-hash safety rules

An overlay refuses to apply to a different program than it was made for. This is on purpose. Every overlay remembers the hash of its base program, and both bake and the runtime check it, so you cannot accidentally promote tuning results from one version of a pipeline onto another. The same rule has a second effect worth knowing: baking changes the program’s hash. Overlays minted against the old program do not apply to the baked one. Candidates are re-minted against the new skeleton by design, so every round of tuning starts from a clean, known base.

Common mistakes

Wrong parameter path. The path is <binding>.<slot>, using the let binding name from the module body, not the step function name. If your module says let drafter = draft(...).await?;, the path is drafter.instruction. Reusing an old overlay after baking. It will be refused because the base hash changed. Build a fresh overlay against the baked program. Filling in parent or overlay yourself. bake overwrites both with the real hashes. Anything you put there is discarded.

See also

  • The .dsrs file: the canonical text form a program prints to
  • Runtime: loading and running a program, and how Interpreter::run reads through an overlay
  • Optimizer engine: where candidates and overlays come from during optimization, and how checkpointing saves them
  • CLI: checking and serving the baked file