Course Content
CrewAI Multi-Agents
9 sections · 53 lessons
How do you implement review, critique, or validation agents?
What you need to know
Layer 1: guardrails on the task
A guardrail is a check that runs on a task's output before it is passed on. CrewAI supports two kinds, and you can mix them in a guardrails list:
1from typing import Any2from crewai import Task, TaskOutput34def has_ticket_id(result: TaskOutput) -> tuple[bool, Any]:5 if "TKT-" not in result.raw:6 return (False, "Reply must quote the ticket ID, e.g. TKT-48213.")7 return (True, result.raw)89reply = Task(10 description="Draft a reply to the customer for ticket {ticket_id}",11 expected_output="A polite reply under 150 words",12 agent=support_writer,13 guardrails=[14 has_ticket_id, # code check15 "The reply must not promise a refund or a date", # LLM check16 ],17 guardrail_max_retries=2, # default is 318)A function guardrail returns (True, value) to pass (the value can be a cleaned version) or (False, message) to fail. A string guardrail is checked by an LLM. On failure, the message goes back to the agent, which tries again with that feedback. This is cheaper and more precise than a whole critic agent.
Layer 2: a critic agent
Some checks need judgement: "Is the tone right for an angry premium customer?" For those, add a reviewer:
1class Review(BaseModel):2 verdict: str # "pass" or "revise"3 issues: list[str] # each quotes the problem text45critic = Agent(role="Support QA Reviewer",6 goal="Find policy, tone and accuracy problems. Never rewrite.",7 backstory="You quote the exact sentence that is wrong.",8 tools=[], allow_delegation=False, llm="openai/gpt-4.1")910review = Task(description="Review the draft reply against the refund policy.",11 expected_output="Verdict and specific issues",12 agent=critic, context=[reply], output_pydantic=Review)Three rules make a critic useful:
- A different agent, ideally a different model. A model that reviews its own text tends to agree with it.
- Structured findings. "Looks good overall" cannot be acted on; a list of quoted issues can.
- A cap on rounds. A critic asked to find problems always finds some. Allow at most two revision rounds, then send it to a person.
The revision loop
Loops belong in a Flow, where the counter is ordinary state:
1@router(review_draft)2def decide(self):3 if self.state.verdict == "pass":4 return "send"5 if self.state.rounds >= 2:6 return "human"7 self.state.rounds += 18 return "revise"For sign-off by a person, current Flows have @human_feedback, which pauses the flow and routes on the reviewer's answer.
A real-life example
A telecom company runs a customer-support escalation crew for complaints that reach level 2. A writer agent drafts the reply; the reply goes out only after review.
In the first week, 11% of drafts promised "refund in 3 days", which is against policy. The team added the string guardrail "must not promise a refund or a date"; those promises dropped to under 1%, at the cost of about one retry per 12 tickets. The critic agent then catches tone problems, for example a cheerful reply to a customer whose service was down for 4 days. Drafts that fail review twice go to a human agent's queue, about 3% of tickets.
Follow-up questions to expect
- "Guardrail or critic agent — which first?" — Guardrail first. It is cheaper, deterministic for code checks, and retries with a precise message. Add a critic only for problems code cannot see.
- "What happens when retries run out?" — The task raises an error. Catch it in the Flow and route to a fallback or a person; never let a failed draft go out silently.
- "Is
max_retriesstill the setting?" — It is deprecated onTask; useguardrail_max_retries.