Agent with Guardrails
%%{init: {'look': 'handDrawn', 'theme': 'base', 'themeVariables': {'primaryColor': '#eef2ff', 'primaryBorderColor': '#1e40af', 'primaryTextColor': '#1e293b', 'lineColor': '#1e3a8a', 'edgeLabelBackground': '#ffffff', 'clusterBkg': '#fbfcff', 'clusterBorder': '#2563eb', 'fontFamily': '-apple-system, system-ui, Segoe UI, Roboto, Helvetica, Arial, sans-serif', 'fontSize': '15px'}, 'flowchart': {'nodeSpacing': 50, 'rankSpacing': 58, 'padding': 14, 'htmlLabels': true, 'curve': 'basis'}}}%%
flowchart LR
Q(["Question"]) --> A("Agent answers")
A --> G{"Guardrails<br/>regex + custom check"}
G -. "fails · feedback goes back" .-> A
G == "passes" ==> O(["Answer"])Outcome: the agent's own output is checked before you ever see it, and a failed check sends the model back to try again with the reason attached.
How it works
- A
RegexGuardrailcosts nothing. It compiles to a ConductorINLINEtask and runs on the server — no Python process involved. - A
@guardrailfunction runs as a worker task, for checks a regex can't express. - Both live in the same durable retry loop.
on_fail=OnFail.RETRYappends the failure message to the conversation and regenerates. max_retriesbounds it. Without a cap, an agent that can't satisfy a rule loops until the workflow times out.
Prerequisites
A Conductor server with an LLM provider, and CONDUCTOR_SERVER_URL set. Install the SDK with python -m pip install conductor-python.
The agent
Save this as agent_guardrails.py:
"""Agent guardrails — a regex rule on the server plus a Python check, both retrying.
Derived from sdk/python-sdk/examples/agents/36_simple_agent_guardrails.py.
RegexGuardrail compiles to a Conductor INLINE task and runs on the server, so it
costs nothing and needs no worker. A @guardrail function compiles to a worker
task. Both sit inside the same durable retry loop: on failure the message is fed
back to the model and the answer is regenerated, up to max_retries.
"""
from conductor.ai.agents import (
Agent,
AgentRuntime,
Guardrail,
GuardrailResult,
OnFail,
RegexGuardrail,
guardrail,
)
MODEL = "openai/gpt-4o-mini"
# Runs on the server as an INLINE task — no Python process involved.
no_bullet_lists = RegexGuardrail(
patterns=[r"^\s*[-*]\s", r"^\s*\d+\.\s"],
mode="block",
name="no_lists",
message="Do not use bullet points or numbered lists. Write flowing prose instead.",
on_fail=OnFail.RETRY,
max_retries=3,
)
# Runs as a Conductor worker task.
@guardrail
def min_length(content: str) -> GuardrailResult:
"""Require at least 50 words."""
words = len(content.split())
if words < 50:
return GuardrailResult(
passed=False,
message=f"Only {words} words. Give a fuller answer of at least 50 words.",
)
return GuardrailResult(passed=True)
agent = Agent(
name="guarded_essay_writer",
model=MODEL,
instructions=(
"Answer the question in well-structured prose paragraphs. "
"Never use bullet points or numbered lists."
),
guardrails=[
no_bullet_lists,
Guardrail(min_length, on_fail=OnFail.RETRY),
],
)
if __name__ == "__main__":
with AgentRuntime() as runtime:
result = runtime.run(agent, "Explain why the sky is blue.")
result.print_result()
print("execution id:", result.execution_id)
Run it
The prompt asks for an explanation, the instructions forbid bullet points, and min_length demands at least 50 words. A verified run returned three prose paragraphs at 260 completion tokens — both guardrails passed on the first attempt, so no retry was needed.
Open Executions in the Conductor UI to see the guardrail tasks inside the agent's loop, each with its own pass/fail output.
The same example in other SDKs
The agent API is the same shape in every SDK. These are the upstream sources this recipe was derived from:
| SDK | Example |
|---|---|
| Python | 36_simple_agent_guardrails.py |
| Java | Example36SimpleAgentGuardrails.java |
| TypeScript | 36-simple-agent-guardrails.ts |
| C# | Program.cs |
Production notes
OnFailhas four modes:retry,raise,fix, andhuman— the last creates a durable approval point.- Prefer regex on the server for anything cheap. It rejects before you pay for a model call.
- Guardrails run on every response, so keep custom checks fast and side-effect free.
- A model-based guardrail can be talked around. Use it for tone and policy, not as a security control.
- Log the passes too. Failure-only logs can't tell you a check has stopped rejecting anything.