Skip to main content
When a guardrail blocks a run, PraisonAI returns a safe fixed string — never None, never the validator’s reason.

Quick Start

1

Add an input guardrail

2

Get the fixed string back

reply is always a str, so reply.strip() never crashes.
3

Read the reason for your logs

Before PraisonAI PR #6258, an Agent built without name= failed every output guardrail internally, so the blocked response fired even for valid answers. If old logs show blocks on a nameless agent with no matching last_guardrail_error reason, upgrade — or pass name="..." on older releases.

How It Works

The agent checks the prompt first; a rejected prompt never reaches the LLM. The same fixed string is returned when an output guardrail exhausts its retries, so callers handle one shape for every block.

Before / after PR #6104


Telling a block from a normal reply

The blocked string is identical for input and output blocks, so compare against it rather than parsing it.
Call _guardrail_blocked_message() once at startup only — it also marks any in-flight run as error, so don’t call it per turn.
last_guardrail_error is not cleared on the next successful turn. Use the reply == BLOCKED comparison to decide whether a turn was blocked, and last_guardrail_error only to read why.

Common Patterns

Pattern A — friendly message in a chat UI

Pattern B — log the reason, keep the reply opaque

PraisonAI keeps the reason out of the reply because LLM validators often quote the rejected content back.

Pattern C — async parity

Pattern D — streaming

Join the chunks and compare — don’t search for "[Input blocked", which is no longer emitted.

Best Practices

The reason can echo the rejected prompt back and defeat the guardrail. Show your own message instead.
Log it, alert on it, count it — don’t display it.
Compare it to the BLOCKED value you captured at startup; don’t parse or translate it.
A sync validate_input that does blocking I/O delays every turn. See Per-Tool Guardrails for the async offload story on tool and output guardrails.

Learned “always allow” rules survive concurrent writes

Concurrency-safe since PR #6104. PolicyConfig.save_learned_rule now reads, dedupes and appends under a per-path lock using utils.atomic_io.update_json. Multiple processes or threads saving “always allow” decisions no longer drop each other’s rules; dedupe behaviour is preserved (saving the same resource twice is a no-op, and the first action wins).

Guardrails

Input and output validation for agents

Apply Guardrail

Run a guardrail on any text directly

Per-Tool Guardrails

Validate individual tool calls

last_guardrail_error

SDK reference