Guardrails catch bad outputs inside the run, before they reach users or the next task.
A guardrail checks a task's output before the task counts as done. If the check fails, the feedback goes back to the agent, which tries again, up to guardrail_max_retries times (default 3). If it still fails, the task raises an error.
Function guardrails are plain Python. The function receives the TaskOutput and returns a pair (a tuple):
(True, value)when the output is acceptable. The value becomes the task's result, so you can also clean it up here.(False, "message")when it is not. Write the message as an instruction, because the agent reads it.
def under_200_words(output):
n = len(output.raw.split())
return (True, output.raw) if n <= 200 else (False, f"Too long ({n} words); cut to 200.")Text guardrails are written in plain language, for example guardrail="Every claim must cite a source URL.". CrewAI asks the agent's LLM to judge the output against that rule. They are flexible but cost an extra model call and can be wrong, so prefer functions for anything you can check in code.
guardrails=[...] takes a list mixing both kinds. They run in order, each receiving the previous result. If guardrails is set, guardrail is ignored.
Good uses: length and format limits, required sections, banned words, checking that numbers add up, and verifying a structured output's values. Guardrails are one layer of safety, not all of it: still limit tools and permissions.
Example
from crewai import Agent, Crew, Task
from crewai.tasks.task_output import TaskOutput
def has_three_bullets(output: TaskOutput):
bullets = [l for l in output.raw.splitlines() if l.strip().startswith("-")]
if len(bullets) == 3:
return (True, output.raw.strip())
return (False, f"Found {len(bullets)} bullet lines; return exactly 3 lines starting with '- '.")
writer = Agent(role="Summarizer", goal="Summarize articles",
backstory="You write tight bullet summaries.")
task = Task(
description="Summarize this article: {text}",
expected_output="Exactly 3 bullets starting with '- '.",
agent=writer,
guardrails=[has_three_bullets, "No bullet may contain opinions or speculation."],
guardrail_max_retries=2,
)
result = Crew(agents=[writer], tasks=[task]).kickoff(inputs={"text": "..."})
print(result.raw)Remember
- Guardrails return
(True, value)to accept or(False, feedback)to retry - Function guardrails are cheap and exact; text guardrails use an LLM judge
guardrail_max_retries(default 3) bounds the retries
Watch out for
- Returning a bare boolean from a function guardrail instead of the (bool, value) tuple.