Overlay mutates parameter values over a fixed skeleton; an Edit mutates the skeleton itself — add a reasoning field, swap a Predict for an AgentLoop, wrap a flaky step in a Retry, remove a step. Edits are plain serde values — inspectable, diffable, replayable — and are only ever applied through Program::edited, which is pure: it clones the arenas, applies the edits in order, re-runs the same load-time validation the builder and loader use, and seals a new content hash. A program value is never mutated in place, so every hash-bound artifact (overlays, traces, caches) minted against the parent stays coherent.
All items are exported from dspy_rs::ir: Edit, EditKind, SwapTarget, EditError, ApplyError, migrate_overlay.
The edits
An optimizer’s structural proposal is data, not code — it can be logged, replayed against the same parent, and diffed.SwapTarget is the target kind of SwapLeaf: Agent { tools, stop, budget } or Predict.
Program::edited
edits in order to a clone of self and returns the sealed, validated result. The child gets a new content hash and lineage.parent set to the parent’s hash — exactly like Program::bake; the other provenance fields are left empty for the optimizer to fill (an edit is not an optimization run record).
Behavior worth knowing:
- NodeIds are positional handles against the parent. Within one
edited()batch, ids stay stable (swaps happen in place, removals only detach); dead nodes, signatures, and params are garbage-collected once at the end. Ids in the child may therefore differ from the parent — re-locate leaves by name (Program::leaf_id(name), leaf names are program-unique and survive edits) and params by path. - Batch validation. Edits are validated as a sequence: intermediate states may be inconsistent (remove a producer, then its consumer); only the final program must pass validation. Apply-time errors cover what is checkable locally; everything data-flow shaped is deliberately left to the load-time validator, so the edit layer and the loader can never disagree.
- Identity is preserved.
edited(&[])returns a program with the parent’s hash — only lineage differs, and lineage is outside the hash preimage. Signatures that were already unreferenced in the parent are kept; only newly orphaned ones are collected. - CoT re-sugars. When the prepended field is exactly the
cotreasoning field on aPredict, the augmented signature copy keeps the base name so the canonical printer re-sugars it ascot <Sig>; otherwise it gets a fresh unique name (<Sig>_<field>).
Errors
ApplyError is the locally-checkable failure set: StaleNode, WrongKind (e.g. SetStop on a Predict), DuplicateField, UnknownTool, ToolCapsExceedProgram (a tool’s caps exceed the program ceiling), ToolAlreadyDeclared, ToolNotDeclared, NotInSeq (only Seq steps can be removed), Unparented.
legal_edits: the proposer menu
EditKind descriptors suitable for prompting an LLM proposer:
The menu is purely structural — data-flow legality (whether a removal orphans a downstream binding) is still
validate()’s call, surfaced by edited. A stale id yields an empty menu.
This is exactly how the shipped Structural optimizer proposes edits: it serializes the menu, has a reflection LM choose one entry, applies the choice through edited, and gates the child against the parent on a shared minibatch.
migrate_overlay: carrying tuned values across an edit
migrate_overlay carries value-level progress across the structural change: for every entry in the overlay, it re-mints the entry against the child when the child has a slot at the same path and kind whose owning leaf/tool still has a carrying signature — inputs identical (names and types, in order) and every parent output present in the child’s outputs. Outputs may widen: that is what lets instruction and demos survive AugmentSig (demo rows still map onto the base fields; the new field is simply absent from the row). ModelRef entries are re-minted by model name, not ordinal. ToolSet entries are re-minted by tool name and intersected with what the child’s agent still declares — partial survival carries the selection forward; a selection with no survivors is dropped. Entries that no longer fit are dropped; a base-mismatched overlay yields an empty result.
See also
- Program and nodes: the value half — params,
Overlay, andbake - Structural: the shipped optimizer over this calculus — LM-guided edit choice,
migrate_overlay, minibatch gating - Optimizer engine: how candidates are evaluated; a structural optimizer proposes
Edits where a prompt optimizer proposes overlays - Runtime: loading and running the edited program
- The .dsrs file: the canonical text the child prints to
