SelfRefine
from loomflow.architecture import SelfRefineSelfRefine improves an answer by having the model critique its own
draft and then revise it, for a few rounds. Use it when a first draft
is usually close but benefits from a review pass (writing, summaries,
code explanations). By default the same model writes, critiques and
revises. Based on Madaan et al. 2023, Self-Refine: Iterative
Refinement with Self-Feedback.
For the conceptual page see Self-Refine.
Class signature
class SelfRefine:
name: str = "self-refine"
def __init__(
self,
*,
base: Architecture | None = None,
max_rounds: int = 3,
critic_prompt: str | None = None,
refiner_prompt: str | None = None,
stop_phrase: str = "no issues",
critic_model: str | Model | None = None,
refiner_model: str | Model | None = None,
) -> None: ...Constructor parameters
base
| Type | Architecture | None |
| Default | None (resolves to ReAct()) |
The architecture used for round 0 (the initial draft). Defaults to
ReAct. Pass another architecture to compose them:
SelfRefine(base=PlanAndExecute(...)) drafts with PlanAndExecute,
then refines with the critic. See
Recursive composition.
max_rounds
| Type | int |
| Default | 3 |
Maximum refinement rounds (after the initial generation). Each round
is one critic call + one refiner call. Must be >= 1.
critic_prompt
| Type | str | None |
| Default | None (uses built-in default) |
Override the critic’s system prompt. The default reviews the current
output and produces a critique; if the critique is stop_phrase
(the whole reply or its first line), the loop terminates.
refiner_prompt
| Type | str | None |
| Default | None (uses built-in default) |
Override the refiner’s system prompt. The default takes the current output + the critique and produces a revised output. The refiner’s message also carries every earlier output and critique of the run (Madaan et al., Eq. 4).
stop_phrase
| Type | str |
| Default | "no issues" |
The reply the critic gives when it considers the output good enough.
The loop stops only when the critique is this phrase (the whole
reply or its first line, ignoring case, markdown and punctuation),
not when it merely mentions it. Customize for non-English critics or different
output rubrics. Termination is keyword-based; for numeric-score
gating, use ActorCritic.
critic_model / refiner_model
| Type | str | Model | None |
| Default | None (use the agent’s model) |
Use a different model for the critique or the revision, for example
a cheap model to critique while the draft uses the agent’s main
model. Accepts a model string or a Model instance; role models are
not wrapped with the agent’s retry policy. A model string is resolved
with the agent’s secrets= backend, like the main model.
Methods
declared_workers
Returns {}. SelfRefine itself has no sub-agents.
run
- Round 0 (draft). Run
base.run(session, deps, prompt)to produce an initial output. If the base interrupts itself (max_turns, budget), stop without refining, since refining a partial result would waste tokens. - Refinement rounds (1..max_rounds):
- Budget check. Stop or warn as needed.
- Critic. One model call against the current output.
Terminate if the critique is
stop_phrase(whole reply or first line). - Refiner. One model call producing a revised output, given the current output and critique plus every earlier pair. Replaces the current output.
Cost model
For an N-round refinement: base + 2 × (rounds_actually_run) LLM
calls. A base=ReAct() with max_rounds=3 is roughly 2–3× a single
ReAct run.
When to use ActorCritic instead
When the same model has the same blind spots as critic and generator,
gains plateau quickly. For asymmetric blind-spot coverage (different
prompts, ideally different models), use
ActorCritic. Actor and
critic are distinct Agent instances, possibly on different model
providers.
Example
from loomflow import Agent
from loomflow.architecture import SelfRefine
agent = Agent(
"Write a tight 3-paragraph product brief.",
model="claude-opus-4-7",
architecture=SelfRefine(max_rounds=3),
)
result = await agent.run("Brief about Acme Corp.")Or by string:
agent = Agent("...", model="claude-opus-4-7", architecture="self-refine")Wrapping a planner:
from loomflow.architecture import PlanAndExecute, SelfRefine
agent = Agent(
"...",
model="claude-opus-4-7",
architecture=SelfRefine(
base=PlanAndExecute(max_steps=5),
max_rounds=2,
),
)Source
loomflow/architecture/self_refine.py
Threshold via stop phrase, not score. SelfRefine’s critic emits
free-form text; termination is keyword-based. For numeric-score
gating (score ≥ threshold), use
ActorCritic which returns
structured JSON {score, issues, summary}.