Guardrails

July 18, 2026 · View on GitHub

Guardrails validate or modify agent input and output. They run before the agent sees a message (INPUT) or after the agent produces a response (OUTPUT).

There are several kinds, each producing a GuardrailDef:

  • RegexGuardrail.builder() — pattern matching (guardrailType="regex")
  • LLMGuardrail.builder() — LLM-judged policy (guardrailType="llm")
  • GuardrailDef.builder().func(...) — a custom Java function (guardrailType="custom")
  • Guardrail.of(name, func) — shorthand builder for a custom Java function
  • Guardrail.external(name) — reference an existing Conductor worker as a guardrail

Ownership and worker names

  • A guardrail with func(...) is local. AgentRuntime registers and starts its worker. Agent-level workers are named {agentName}_output_guardrail; tool-level workers are named {toolName}_output_guardrail. Multiple local guardrails at one scope run in declaration order.
  • Regex and LLM-as-judge guardrails have no Java function, so they are server-owned and do not start a Java worker.
  • Guardrail.external(name) is externally owned: start a worker for the named task yourself; the runtime deliberately does not duplicate it.

Tool-scoped local guardrails work on every tool type, including HTTP, API, MCP, human, media, RAG, and agent tools:

ToolDef guardedHttp = HttpTool.builder()
    .name("lookup_customer")
    .url("https://api.example.com/customers")
    .method("GET")
    .build()
    .withGuardrails(List.of(Guardrail.of("no_pii", content ->
        content.contains("ssn") ? GuardrailResult.fail("PII in tool output") : GuardrailResult.pass())
        .onFail(OnFail.RAISE)
        .build()));

Quick example

import org.conductoross.conductor.ai.guardrail.RegexGuardrail;
import org.conductoross.conductor.ai.guardrail.LLMGuardrail;
import org.conductoross.conductor.ai.enums.OnFail;
import org.conductoross.conductor.ai.enums.Position;

Agent agent = Agent.builder()
    .name("safe_agent")
    .model("anthropic/claude-sonnet-4-6")
    .guardrails(
        // Block output containing a phone-number pattern
        RegexGuardrail.builder()
            .name("no_phone_numbers")
            .position(Position.OUTPUT)
            .patterns("\\b\\d{3}[-.]?\\d{3}[-.]?\\d{4}\\b")
            .onFail(OnFail.RAISE)
            .build(),

        // Ask an LLM to enforce a policy; retry the turn if it fails
        LLMGuardrail.builder()
            .name("professional_tone")
            .position(Position.OUTPUT)
            .model("anthropic/claude-sonnet-4-6")
            .policy("The response must be professional and free of slang.")
            .onFail(OnFail.RETRY)
            .build())
    .build();

Guardrail types

Regex — RegexGuardrail

Match one or more patterns against the content.

RegexGuardrail.builder()
    .name("no_secrets")
    .position(Position.OUTPUT)
    .patterns("password", "secret", "api[_-]?key")   // varargs or List<String>
    .message("Output blocked: contained a secret")    // optional
    .onFail(OnFail.RAISE)
    .build();

LLM — LLMGuardrail

Ask a language model to evaluate the content against a policy.

LLMGuardrail.builder()
    .name("safe_content")
    .position(Position.OUTPUT)
    .model("anthropic/claude-sonnet-4-6")
    .policy("Reject any harmful, offensive, or unsafe content.")
    .onFail(OnFail.RAISE)
    .build();

Custom — GuardrailDef.builder().func(...)

Provide a Function<String, GuardrailResult> for full control. Runs as a local Conductor worker.

import org.conductoross.conductor.ai.model.GuardrailDef;
import org.conductoross.conductor.ai.model.GuardrailResult;

GuardrailDef.builder()
    .name("length_check")
    .position(Position.OUTPUT)
    .func(content -> {
        if (content.length() > 5000) {
            return GuardrailResult.fail("Response too long: " + content.length() + " chars");
        }
        return GuardrailResult.pass();
    })
    .onFail(OnFail.RAISE)
    .build();

Common builder options

MethodAvailable onDefaultDescription
name(String)allrequiredGuardrail ID.
position(Position)allOUTPUTINPUT or OUTPUT.
onFail(OnFail)allRAISEAction when the guardrail fails.
maxRetries(int)all3Retry budget when onFail == RETRY.
patterns(String...) / patterns(List<String>)RegexGuardrailRegex patterns to match.
mode(String)RegexGuardrail"block""block" fails on a match; "allow" fails when nothing matches.
message(String)RegexGuardrailCustom failure message.
model(String)LLMGuardrailJudge model, "provider/model".
policy(String)LLMGuardrailPolicy the content must satisfy.
func(Function<String,GuardrailResult>)GuardrailDefThe check, for custom guardrails.

Positions

ConstantWhen it runs
Position.INPUTBefore the user message reaches the agent's LLM
Position.OUTPUTAfter the agent produces a response, before it's returned

OnFail actions

ConstantEffect
OnFail.RAISETerminate the agent run with an error (default)
OnFail.RETRYRe-run the LLM turn, up to maxRetries times
OnFail.FIXReplace the output with the guardrail's fixed output (custom guardrails return it via GuardrailResult.fix(...)); falls back to RAISE if none is provided
OnFail.HUMANPause for human review (HITL)

GuardrailResult

Custom (func) guardrails return GuardrailResult:

GuardrailResult.pass()                         // guardrail passed
GuardrailResult.fail("reason")                 // guardrail failed
GuardrailResult.fix("rewritten output")        // provide a fixed replacement