Normandy.Guardrails
(normandy v0.6.1)
View Source
Runs a list of Normandy.Guardrails.Guard modules against a value.
Guardrails sit between schema validation (shape) and resilience (transport) and express content-level constraints — length limits, forbidden phrases, regex allow/deny lists, required output fields, and so on.
Each entry in the guard list is either a bare module atom or a
{module, opts} tuple. Guards run in order and short-circuit on the first
failure: guardrails model blocking policy, not aggregate reporting.
Example
iex> alias Normandy.Guardrails
iex> alias Normandy.Guardrails.Builtins.MaxLength
iex> Guardrails.run([{MaxLength, limit: 5}], "too long")
{:error, [%{guard: MaxLength, path: [], message: _, constraint: :max_length}]}
iex> Guardrails.run([{MaxLength, limit: 100}], "ok")
{:ok, "ok"}Attach a guard list to an agent by setting :input_guardrails or
:output_guardrails on Normandy.Agents.BaseAgentConfig, or via the
guardrails/2 macro in Normandy.DSL.Agent.
Streaming
Input guardrails run on streaming entry points (BaseAgent.stream_response/3,
BaseAgent.stream_with_tools/3) before the LLM call — violations raise the
same as non-streaming paths.
Output guardrails run on streaming in one of two modes, configured per agent:
:accumulate(default) — guards run on the concatenated assistant text (text blocks from the streamed response, joined) after the stream completes. Log-and-continue on violation; content has already reached the caller. This differs from the non-streaming path, which guards the validated schema struct — streaming never produces one, so field-targeting guards (e.g.{ForbiddenSubstrings, field: :summary}) do not apply in streaming mode. Use bare string guards (no:field) for streaming output.:incremental— guards run every:output_guardrails_chunk_sizebytes of accumulated assistant text. On violation, the stream is halted, a:guardrail_violationevent is emitted to the caller callback, any partial tool-use block is dropped, and the returned response carries:guardrail_violations. Follows NVIDIA NeMo Guardrails' chunking approach.
RequiredFields is terminal-only and does not apply to either streaming
mode (no schema struct to inspect).
Both modes populate the :guardrail_violations field on the returned
response (list, empty on pass) and emit the
[:normandy, :agent, :guardrail, :violation] telemetry event with metadata
streaming: true and mode: :accumulate | :incremental.
Telemetry
A [:normandy, :agent, :guardrail, :violation] event is emitted per
violation batch. One event per final-response emission, not per user turn —
with enable_json_retry plus retries, a single turn can emit multiple
output-guardrail events. Metadata includes the matched :term / :pattern
on the violation payload; redact downstream if your block-list is sensitive.
Summary
Functions
Runs the guard list against value.
Types
Functions
@spec run([spec()], term()) :: {:ok, term()} | {:error, [Normandy.Guardrails.Guard.violation()]}
Runs the guard list against value.
Returns {:ok, value} if every guard passes, or {:error, [violation]} as
soon as one fails. An empty guard list is always {:ok, value}.