Adding commercial guardrails to CrewAI agents
You add commercial guardrails to CrewAI agents by attaching a function guardrail to the task that produces outbound content: the function posts the task output to EvalLayer's /authorize endpoint and returns success with the approved text on allow or rewrite, or failure with the violation detail so the agent revises, while require_approval drafts are queued for a human. Pair it with the same check inside any send tool, and no crew output reaches a customer unchecked.
Why crews amplify commercial risk
CrewAI's model is role-based delegation: a researcher hands to an analyst, the analyst hands to a writer, the writer's output becomes an email, a proposal, a published post. That pipeline shape is the risk. Each agent treats the previous agent's output as ground truth, so one invented claim or over-generous concession early in the crew propagates downstream with growing confidence, and the last task ships it. In a single-agent loop a bad draft is one mistake; in a crew it is a supply chain.
CrewAI knows output quality is the weak point, which is why task guardrails exist: a validation step that runs after a task completes and before its output passes to the next task, with automatic retry when validation fails. The framework supports function-based guardrails (your Python, full control) and LLM-based ones (a judge described in natural language). What ships in the box is the mechanism. The commercial judgment, discount caps, spend authority, forbidden commitment types, approved claims, is the part you supply, and an agent grading its own crew's homework is not a control.
The interception point: a task guardrail on the outbound task
Attach the check where crew output becomes consequential: the task whose result will be sent, published, or executed.
import httpx
from crewai import Task
POLICY = {
"max_discount_pct": 10,
"commitment_authority_usd": 2500,
"forbidden_commitments": ["delivery_date", "indemnification"],
"approved_claims": ["iso27001_certified"],
"required_disclaimers": ["pricing_subject_to_change"],
"blocked_topics": [],
}
def commercial_guardrail(output):
r = httpx.post(
"https://api.evallayer.ai/authorize",
headers={"Authorization": "Bearer " + EVALLAYER_KEY},
json={"action_type": "send_email", "content": output.raw,
"context": {"crew": "sales_outreach"}, "policy": POLICY},
timeout=10,
)
data = r.json()
if data["decision"] == "allow":
return (True, output.raw)
if data["decision"] == "rewrite":
return (True, data["approved_rewrite"])
if data["decision"] == "require_approval":
queue_for_human(output.raw, data["authorization_id"])
return (False, "Exceeds authority, held for human approval: "
+ data["violations"][0]["detail"])
return (False, "Blocked by commercial policy: "
+ data["violations"][0]["detail"])
outreach_task = Task(
description="Draft the renewal offer email for the account.",
agent=writer,
guardrail=commercial_guardrail,
)
The return contract does the work. Success passes the approved text forward, which means a rewrite is substituted silently and the next task (or the send step) uses the compliant version. Failure sends your message back to the agent and CrewAI retries the task, so the violation detail becomes revision instructions, and the revised output hits the same guardrail again.
For tools that act directly, a send_email tool an agent can invoke mid-task, put the identical /authorize call inside the tool body before the side effect, or block it in a before-tool-call hook. CrewAI's hook surface can stop a tool execution outright; the tool-body check is the version-stable pattern.
Decision handling in crew terms
- allow: guardrail returns success with the original output; the crew proceeds.
- rewrite: guardrail returns success with
approved_rewrite; downstream tasks and the send step consume the compliant text. - require_approval: the draft plus
authorization_idgo to a human queue, and the guardrail returns failure so nothing sends automatically while review is pending. - block: guardrail returns failure with the reason; the agent revises or the crew ends without sending. Log the
authorization_ideither way.
Latency and placement
Guard the last mile, not the whole pipeline. Research and analysis tasks pass their output along unchecked; only tasks whose output leaves the crew get a guardrail. On those, deterministic policies add milliseconds and semantic checks one to two seconds, trivial next to the minutes a multi-agent crew already spends. The retry loop multiplies checks only when there are violations, which is exactly when you want it to.
What to log
Store every authorization_id with the kickoff ID and task name. Crews are the hardest agent systems to audit after the fact because responsibility is spread across roles; a per-task authorization record collapses that to a single question with a single answer: what did policy say about the thing that actually shipped?
Try it on one crew
Point the guardrail at /demo/authorize first (three free checks per day, no key) or watch the interactive demo catch planted violations. Decision semantics are specified in the authorization guide; policy design covers writing a policy object that matches what your humans are actually allowed to promise.