Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion factory/outer_loop/mutations.py
Original file line number Diff line number Diff line change
Expand Up @@ -599,9 +599,15 @@ def mutate_prompt(
try:
updated = node.model_copy(update={"prompt_template": new_prompt})
wf.nodes[node_id] = updated # type: ignore[assignment]
except Exception:
except Exception as exc:
log.warning("prompt_mutate_validation_failed", node=node_id, error=str(exc))
return None

# Persist in knob_values so the mutation survives Package.compile() round-trips
prompt_knob = f"_prompt_{node_id}"
wf.knob_values[prompt_knob] = new_prompt
wf.knob_expandable[prompt_knob] = f"Prompt for {node_id}"

record = MutationRecord(
operator=MutationType.PROMPT_MUTATE,
target_node=node_id,
Expand Down
29 changes: 28 additions & 1 deletion factory/workflow/package.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,14 +130,41 @@ def configure(self, **knob_values: str | float) -> Package:
return self.model_copy(update={"knobs": new_knobs})

def compile(self) -> Workflow:
"""Lower this package to a flat, mutable Workflow IR."""
"""Lower this package to a flat, mutable Workflow IR.

Prompt knobs (``_prompt_<node_id>`` in knob_values) are applied
back to node prompt_templates so that PROMPT_MUTATE mutations
survive compile() round-trips.
"""
wf = self.graph.model_copy(deep=True)
# Preserve _prompt_* entries from previous mutations before overwriting
saved_prompts = {
k: v for k, v in wf.knob_values.items()
if k.startswith("_prompt_")
}
saved_expandable = {
k: v for k, v in wf.knob_expandable.items()
if k.startswith("_prompt_")
}
if self.knobs:
wf.knob_values = {k.name: k.default for k in self.knobs}
wf.knob_bounds = {k.name: list(k.bounds) for k in self.knobs if k.bounds}
wf.knob_expandable = {
k.name: k.expansion_hint for k in self.knobs if k.expandable
}
wf.knob_values.update(saved_prompts)
wf.knob_expandable.update(saved_expandable)
for key, val in list(wf.knob_values.items()):
if key.startswith("_prompt_") and isinstance(val, str):
node_id = key[len("_prompt_"):]
node = wf.nodes.get(node_id)
if node and hasattr(node, "prompt_template"):
try:
wf.nodes[node_id] = node.model_copy(
update={"prompt_template": val}
)
except Exception:
pass
return wf


Expand Down
Loading