Skip to Content

ActorCritic

from loomflow.architecture import ActorCritic

ActorCritic pairs two agents: an actor that writes an answer and a critic that scores it and lists problems. The actor revises until the critic’s score clears a threshold or the rounds run out. Use it when quality matters more than cost and a second opinion catches real mistakes, such as code review or security checks. Because actor and critic are separate Agents, you can give them different models so they don’t share the same blind spots.

For the conceptual page see ActorCritic.


Class signature

class ActorCritic: name: str = "actor-critic" def __init__( self, *, actor: Agent, critic: Agent, max_rounds: int = 3, approval_threshold: float = 0.9, critique_template: str | None = None, refine_template: str | None = None, worker_registry: dict[str, Any] | None = None, role_to_worker_id: dict[str, str] | None = None, ) -> None: ...

Constructor parameters

actor

TypeAgent
Defaultrequired

The Agent that generates and refines outputs. Typically your main production model.

critic

TypeAgent
Defaultrequired

The Agent that critiques outputs. Use a different model from the actor for blind-spot diversity (Anthropic for actor, OpenAI for critic, or vice versa). The critic must return structured JSON matching CriticOutput:

class CriticOutput(BaseModel): score: float # in [0.0, 1.0] issues: list[str] # specific problems to address summary: str # one-line assessment

The default critique_template instructs the critic to emit this shape; override only if you customize the schema. The JSON is found even when the critic wraps it in prose or a code fence, and a score written on a scale is normalised ("score": "7/10" reads as 0.7; see parse_score). If the reply has no JSON object, the whole reply becomes the single issue and the score is read from its text (Score: 9/10), or is 0.0 when there is none, so the loop keeps going instead of crashing. Actor and critic spend is charged to the coordinator’s budget.

max_rounds

Typeint
Default3

Maximum critique → refine rounds (after the initial actor generation). Each round is one critic call + one refine call. Must be >= 1.

approval_threshold

Typefloat
Default0.9

Critic score (in [0.0, 1.0]) at or above which the loop terminates. Must be in [0.0, 1.0]. Higher = stricter (more refinement rounds); lower = lenient.

critique_template

Typestr | None
DefaultNone (uses built-in default)

Override the prompt sent to the critic. Must instruct the model to return strict JSON {"score": 0..1, "issues": [...], "summary": "..."}. Use {prompt} and {output} for the original task and the output under review. The template is filled with str.format when it formats cleanly, so write literal braces as {{ / }}; when it doesn’t (a single-brace JSON example like the one above), only those placeholders are replaced and the rest is left as written.

refine_template

Typestr | None
DefaultNone (uses built-in default)

Override the prompt sent to the actor for refinement rounds. The default takes the current output + the critic’s issues as a bulleted list and asks for a revised output addressing them. Placeholders: {prompt}, {output}, {critique} (the critic’s summary) and {issues_bulleted}.

worker_registry / role_to_worker_id

Internal wiring set by Team.actor_critic when persistent_subagents=True (the default), so the actor and the critic each keep one conversation per user across rounds and runs. You normally don’t pass these yourself.


Methods

declared_workers

def declared_workers(self) -> dict[str, Agent]: return {"actor": self._actor, "critic": self._critic}

Both workers exposed for AgentGraph visualization and introspection.

run

  1. Round 0 (actor). After a budget check, actor.run(prompt) produces an initial output. If the actor interrupts itself (max_turns, budget), stop.
  2. Critique → refine loop (1..max_rounds):
    • Budget check. Stop or warn as needed.
    • Critic. critic.run(critique_prompt) returns CriticOutput. If score >= approval_threshold, stop.
    • Refine. actor.run(refine_prompt) produces a revised output, which replaces the current one.

Events: actor_critic.actor_started / actor_critic.actor_completed (for the first draft and each revision), actor_critic.critic_started, actor_critic.critique (score and issues), then actor_critic.approved or actor_critic.max_rounds_reached.


Cost model

Round 0: 1 actor run. Each subsequent round: 1 critic + 1 actor refine. For max_rounds=3 worst case: 1 + 3 × 2 = 7 agent runs (each agent run is itself N model calls under ReAct or whatever architecture the actor / critic uses).

Typical use case (code generation with security critic): 2–4× a single-actor ReAct run, depending on rounds.


Why a separate SelfRefine?

SelfRefine runs the critic and refiner as calls inside one agent. ActorCritic is worth its extra setup only when the actor and critic have different blind spots, which usually means different models. It needs two Agent instances; for same-model self-critique, use SelfRefine.


Example

from loomflow import Agent from loomflow.architecture import ActorCritic from loomflow.team import Team actor = Agent( "Generate the code.", model="claude-opus-4-7", ) critic = Agent( "Find bugs, security issues, and edge cases. Return strict JSON: " '{"score": 0..1, "issues": [...], "summary": "..."}', model="gpt-4o", # different model on purpose ) team = Team.actor_critic( actor=actor, critic=critic, max_rounds=3, approval_threshold=0.9, model="claude-opus-4-7", # the coordinator ) result = await team.run("Write a function that sanitizes user input.")

Or via the explicit Architecture form for nesting:

from loomflow import Agent from loomflow.architecture import ActorCritic agent = Agent( "Manage actor + critic.", model="claude-opus-4-7", architecture=ActorCritic( actor=actor, critic=critic, max_rounds=3, approval_threshold=0.9, ), )

Source

loomflow/architecture/actor_critic.py

Different models matter. The whole point is blind-spot diversity. Same-model actor and critic share the same blind spots, so the gain collapses. Use Anthropic for one and OpenAI for the other, or two different sizes (a large actor and a small, fast critic to save cost). Anything but identical.

Last updated on