AgentTeam(guardrails=…) is not wired yet (PraisonAI #4004) — set guardrails=… on each Agent(...) in the team instead.Quick Start
1
Simple Usage
Pass a validation function to an agent:
Since PraisonAI PR #3944, callable guardrails reliably return the agent’s answer. On releases before commit
edab0de (2026-08-15), a duplicate TaskOutput class made the guardrail path silently coerce successful calls into failures and agent.start(...) returned None after the configured max_retries. If you saw None from a callable-guardrail agent on an earlier build, upgrade — no code change is required.2
With Configuration
Use
GuardrailConfig for LLM-based validation with retry settings:Input-side validation
Any guardrail that exposesvalidate_input(content, **kwargs) -> (bool, str) is called before the LLM dispatch on both chat() and achat(). When it returns (False, …) the call short-circuits and returns None — no LLM cost, no tool dispatch — while plain callable and plain string guardrails stay output-only (string guardrails are marked output-only to avoid an extra synchronous LLM call per turn).
Input-side validation runs on both
chat() / start() and achat() / astart(), and it fails closed — an exception inside validate_input blocks the prompt.
Since PraisonAI PR #3908, input-side validation is wired into both the sync and async agent paths. Earlier releases defined
validate_input but never called it, so a guardrail meant to block a prompt silently let it through.Class-based Guardrails (Object Protocol)
Any Python object exposingvalidate_input(content, **kwargs), validate_output(content, **kwargs), or validate_tool_call(tool_name, arguments, **kwargs) is accepted directly by Agent(guardrails=...). Pass a single instance, or a list of instances, and the SDK wraps them in a GuardrailChain for you.
Since PraisonAI PR #4122 (2026-08-20), class-based guardrails are wrapped into a
GuardrailChain automatically. On earlier releases a bare instance (or list of instances) fell through every dispatch branch and the agent was constructed with no guardrail — no exception, no warning. If you tried the pattern before and saw content sail through unfiltered, upgrade past commit c904372 and no code change is needed.Fail-loud on unsupported guardrail values
Values that cannot be turned into any enforceable validator raiseTypeError at Agent(...) construction, instead of silently disabling enforcement.
Which Validator Should I Use?
Pick a validator strategy based on how you need to check the output.How It Works
Guardrails work identically in sync (.start(), .chat()) and async (.astart(), .achat()) execution paths.
Validator Model
LLM-based guardrails run on the auxiliarysmall_model when it is configured — the agent’s primary model still handles user-facing work.
- An explicit
llm_instanceon the guardrail (e.g. a fully configured LLM object withapi_key/base_url) always wins — never rerouted. - Otherwise, a bare primary model-name string is passed through
get_small_model(primary_model=<primary>, fallback=<primary>). - When
small_modelis unset, the primary model is used — behaviour is byte-identical to earlier releases.
Agent(guardrails=...) and Task(guardrail=...) when the guardrail is a natural-language string. Callable validators are not affected — they run in-process without an LLM.
See Configuration File → Cheap auxiliary model for internal calls for the full resolver order.
Since PraisonAI PR #3632, the guardrail’s in-flow validation path (
validate_input / validate_output / validate_tool_call) recognises the SDK’s LLM.get_response(prompt=..., verbose=False, markdown=False, stream=False) interface directly. Guardrails configured as a string model name or a bare LLM(model=...) instance now validate through the documented protocol methods. If you were carrying a workaround that wrapped the LLM to expose complete / invoke / __call__, you no longer need it.Fail-closed guarantees
An LLM guardrail with no configured LLM — or an LLM of an unsupported type — blocks the output rather than silently allowing it. The guardrail is a security gate; a gate that can’t run must not open.Ambiguous validator replies also fail closed
When the validator LLM returns a reply that is neitherPASS nor FAIL: <reason> — markdown wrappers, refusals, or a reasoning preamble — the guardrail blocks the output with the reason "Guardrail validation unclear: <reply>" instead of silently passing it through. Both entry points (__call__ and _llm_validate) apply this rule.
Guardrail validation unclear: after upgrading to spot outputs that the older permissive behaviour would have let through.
A
GuardrailChain built only from LLM guardrails inherits the same fail-closed behaviour. A missing LLM in any member of the chain blocks the output — not just when a bare LLMGuardrail runs on its own. Since PraisonAI PR #3877 a chain is a valid Agent(guardrails=...) value; earlier releases silently dropped it to None and validated nothing.Streaming bypass
Token-level streaming (iter_stream() / start(stream=True)) yields tokens as the model produces them — there is no full response to validate until streaming completes. Output guardrails do not apply to streamed responses, and the SDK logs a warning as soon as streaming begins on an agent with a guardrail attached:
iter_stream() and start(stream=True) share the same generator internally, so the bypass is loud on both surfaces.
Use chat() (or the non-streaming path of start() / astart()) when you need guardrail-validated output. See Streaming → Streaming and guardrails for the trade-off.
Tool-Call Validation (validate_tool_call)
When a guardrail object exposes validate_tool_call(tool_name, arguments, **kwargs) -> (bool, dict), it is consulted before every tool call. A return of (False, ...) blocks the tool call.
Since PraisonAI PR #4122 (2026-08-20),
validate_tool_call is wired for guardrail objects and GuardrailChains. On earlier releases the check-site read an attribute that was never assigned, so validate_tool_call was unreachable dead code and every tool call went through. String and LLM-string guardrails (guardrails="Be polite") are excluded from this wiring on purpose — running an extra LLM call before every tool call would be a hot-path regression.Configuration Options
GuardrailConfig SDK Reference
Full parameter reference for GuardrailConfig
Since PraisonAI PR #4020,
[defaults.guardrails] in .praisonai/config.toml is honoured — an Agent(...) with no explicit guardrails= now picks up the config-wide safety net. Earlier releases silently ignored it. See Guardrail safety net for the whole project.Composing Guardrails with GuardrailChain
GuardrailChain composes several guardrails into one and is directly usable as Agent(guardrails=chain) — it takes one positional argument and returns a (bool, task_output) tuple, matching the Agent guardrail signature.
Each guardrail can expose three validation entry points, all called through the chain:
Any object with matching method names satisfies
GuardrailProtocol — it is duck-typed, so a guardrail only needs the methods it actually uses.
Since PraisonAI PR #4122, you can also pass such an object directly to Agent(guardrails=...) without constructing a GuardrailChain yourself. See Class-based Guardrails above.
Policy-string guardrails (
guardrails=["policy:strict"]) are enforced on the agent path — a policy that denies still blocks the run.Common Patterns
Tool Policy Enforcement (replaces policy strings)
To allow or deny tool calls, attach aPolicyEngine via the policy parameter — not guardrails=.
Function-Based Validation
This callable pattern also silently returned
None before PraisonAI PR #3944. Upgrade past commit edab0de (2026-08-15) and it returns the agent’s answer — no code change is required.Natural Language Validation
LLM guardrail validation uses your configured
small_model when the guardrail is a natural-language string (no explicit LLM instance passed). See Auxiliary / Small Model Resolution.Chaining Guardrails
Chain multiple guardrails so an output must pass every check before being accepted.on_fail behaviour as a single guardrail. On success the chain passes your original TaskOutput object straight through, so downstream steps keep the structured result rather than a coerced string.
Multi-Agent with Guardrails
Best Practices
Write specific, measurable criteria
Write specific, measurable criteria
Vague guardrails like “be good” are hard to enforce. Use concrete criteria: “must be between 100 and 200 words” or “must contain a JSON array”.
Use function validators for structured data
Use function validators for structured data
When validating JSON, code, or data formats, use a function validator. LLM validators are slower and better suited for qualitative criteria like tone or completeness.
Return helpful error messages on failure
Return helpful error messages on failure
The
(False, "reason") message is passed back to the agent as feedback. Make it actionable — tell the agent exactly what to fix.Set max_retries conservatively
Set max_retries conservatively
Start with
max_retries=2. Increasing retries adds latency and cost. If the agent fails repeatedly, the validator criteria or instructions may need refinement.Related
Policy Engine
Allow/deny tool calls with
Agent(policy=...) — replaces policy stringsApproval
Add human-in-the-loop approval steps
Hooks
Intercept and modify agent behavior at lifecycle points
BEFORE_LLM on async
BEFORE_LLM / AFTER_LLM now fire on achat() tooGateway Self-Lifecycle Guard
Block agent commands that would stop or restart this gateway
Configure
small_model to route guardrail validation to a cheap modelWhy streaming bypasses guardrails, and how to opt in to validation

