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 atool. prefix:
"<leaf>.instruction","<leaf>.demos","<leaf>.model","<leaf>.context","<leaf>.code""tool.<name>.desc","tool.<name>.code"
"drafter.instruction" or "tool.search.desc". Program::param_id(path) resolves a path to its id; after load everything speaks ids.
The Overlay API
AnOverlay 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 (thelet binding in your module body), a dot, and the slot name.
2. Bake
bake returns a new program with the overlay’s values folded in as the defaults.
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
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 bothbake 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::runreads 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
