> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sengol.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Guardrails

> Withhold, replace, or retry a response that fails evaluation at runtime.

Sengol has three ways to act on a response at runtime. Pick by what you
want to happen when a check fails.

| You want | Use | On failure |
| - | - | - |
| Patch the OpenAI, Anthropic or MCP client with no other code changes | `sengol.instrument()` | raises `BlockedError` |
| Wrap your own agent function and choose the failure behaviour | `GuardrailRuntime` | `block`, `fallback` or `retry` |
| Get scores and decide yourself | `InlineEvaluator` | returns the scores; withholds nothing |

All three run **deterministic evaluators only**, synchronously, on the hot
path. LLM judges belong in [`EvalSuite`](https://docs.sengol.io/journeys/ai-developer#3-add-an-llm-judge)
and the [CI gate](https://docs.sengol.io/guides/ci-gate.md).

## `sengol.instrument()`

```python theme={null}
sengol.configure(agent_id="fund-advisor", agent_version="1.0.0",
                 post_evaluators=[RequiredDisclosureEvaluator(["not financial advice"])])
sengol.instrument()
```

* Runs `pre_evaluators` on the prompt (default `[PIIEvaluator()]`). A CRITICAL
  failure blocks the call before it reaches the model.
* Runs `post_evaluators` on the response (default none). Any failure blocks.
* Raises `BlockedError`; read `exc.failure_mode`.
* Runs `observe_evaluators` (default none) after `post_evaluators` on every call
  that gets a response. They must be `DeterministicEvaluator` instances with a
  declared `version`. Their scores go on the call's record, never block, and
  never change `overall_passed`.
* `deployment_environment` (or `SENGOL_DEPLOYMENT_ENVIRONMENT`) labels every
  record the process signs. It must match `[a-z0-9_-]{1,40}`.
* Streams are buffered in full before the post-check runs.
* If the appliance cannot accept the record, the call is refused with
  `EVIDENCE_UNWRITABLE` (`on_evidence_failure="refuse"`, the default).

A full, runnable example is in the [AI developer
journey](https://docs.sengol.io/journeys/ai-developer.md#5-guard-live-calls).

## `GuardrailRuntime`

Wrap any function that takes a prompt and returns a response. The policy's
`on_failure` mode decides what the caller receives:

```python guard.py theme={null}
import asyncio

from sengol import GuardrailPolicy, GuardrailRuntime
from sengol.core.types import LLMEvalInput
from sengol.evaluators import PIIEvaluator
from sengol.governance.audit_store import NullAuditStore


def leaky_agent(prompt: str) -> str:
    return "Sure, email the advisor at jane.doe@example.com."


async def main():
    for policy in [
        GuardrailPolicy(on_failure="block", block_message="I can't share that."),
        GuardrailPolicy(on_failure="fallback", fallback_response="Please call 1-800-555-0100."),
        GuardrailPolicy(on_failure="retry", max_retries=1),
    ]:
        runtime = GuardrailRuntime(
            leaky_agent,
            policy,
            pre_evaluators=[],
            post_evaluators=[PIIEvaluator()],
            audit_store=NullAuditStore(),  # use ApiAuditSink in production
            tenant_id="local",
        )
        text, record = await runtime.call(
            LLMEvalInput(agent_id="fund-advisor", agent_version="1.0.0",
                         prompt="How do I reach my advisor?", response="")
        )
        outcome = record.eval_result.scores[-1].failure_mode
        print(f"{policy.on_failure:8} -> {text!r} ({outcome})")


asyncio.run(main())
```

```bash theme={null}
python guard.py
```

```text theme={null}
block    -> "I can't share that." (GUARDRAIL_BLOCKED)
fallback -> 'Please call 1-800-555-0100.' (FALLBACK_USED)
retry    -> 'Sure, email the advisor at jane.doe@example.com.' (RETRY_EXHAUSTED)
```

> [!WARNING]
>
> **`retry` does not withhold.** When every retry fails, it returns the last
> failing response. Use `block` or `fallback` when the user must never see a
> failing answer. `on_failure` has no default, so you always choose.

Notes:

* `response=""` is required on the input; the runtime fills it from your function.
* Pre-evaluators score the input exactly as given. `PIIEvaluator` reads the
  `response` field, so put it in `post_evaluators` to check the agent's answer.
* A pre-evaluator failure under `retry` is treated as `block`.
* Each call writes one record. Its last score, from `GuardrailRuntime`, names
  the outcome: `GUARDRAIL_BLOCKED`, `FALLBACK_USED`, `RETRY_EXHAUSTED`, or none
  when the response passed.

## `InlineEvaluator`

Scores only. It never changes the response:

```python inline.py theme={null}
from sengol.core.types import LLMEvalInput
from sengol.evaluators import InlineEvaluator, PIIEvaluator, ResponseLengthEvaluator
from sengol.governance.audit_store import NullAuditStore

inline = InlineEvaluator(NullAuditStore(), tenant_id="local")
scores = inline.run(
    [PIIEvaluator(), ResponseLengthEvaluator(max_length=200)],
    LLMEvalInput(agent_id="fund-advisor", agent_version="1.0.0",
                 prompt="Fee?", response="0.42% per year."),
)
print([(s.evaluator, s.passed) for s in scores])
```

```bash theme={null}
python inline.py
```

```text theme={null}
[('PIIEvaluator', True), ('ResponseLengthEvaluator', True)]
```

`InlineEvaluator` refuses LLM evaluators. Its audit write is fire-and-forget
on a running event loop; with no loop, as in this script, it is skipped. Use
`EvalSuite` or `GuardrailRuntime` when every verdict must be recorded.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.