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

# Guardrails

> Declare what the assistant refuses, escalates, or withholds, and know exactly what happens when a rule fires.

Guardrails are policy rules in the instance config that the platform enforces on every turn, outside the model's own instructions. A **refusal domain** stops a request before the assistant reads anything. An **escalation rule** lets the assistant answer and marks the turn for a person. An **output check** withholds an answer that matches a pattern. Every decision is streamed as a `guardrail` frame and recorded.

## How it works

1. A new `message` arrives. If the config has refusal domains or escalation rules, a separate model call (the classifier) judges the message against them. It reads the message plus, by default, the two most recent prior exchanges on the conversation, so a short follow-up is judged in context. It runs before any model round.
2. If a refusal domain fired, the turn ends with a fixed refusal. Nothing is looked up. An escalation rule that fired on the same message is not recorded.
3. If an escalation rule fired, the turn continues and ends with `stop_reason: "escalated"`, unless an output check or the output-token limit ends it with its own stop reason.
4. If the config has output checks, the assistant's prose from every model round is held back and checked before it is streamed. A match withholds it.

If the classifier call fails or its reply cannot be read as a verdict, the turn fails with `500 guardrail_evaluation_failed` and does not run. Guardrail policy never passes silently. The classifier's model usage is metered to your org with the turn.

Resumes (`tool_results`, `confirmations`) are not classified again; the message that started the turn was.

## Rule families

All three families share one list of unique rule ids. Every rule must name at least one eval scenario id in `scenarios`, the scenario that proves the rule works. A rule without one fails validation.

| Family             | Required fields                         | Fields that must be empty           |
| ------------------ | --------------------------------------- | ----------------------------------- |
| `refusal_domains`  | `id`, `description`, `scenarios`        | `condition`, `route`, `check`       |
| `escalation_rules` | `id`, `condition`, `route`, `scenarios` | `description`, `check`              |
| `output_checks`    | `id`, `check`, `scenarios`              | `description`, `condition`, `route` |

```json theme={null}
{
  "guardrails": {
    "refusal_domains": [
      {
        "id": "no-legal-advice",
        "description": "Requests for legal advice or interpretation of contract law.",
        "scenarios": ["refuses legal advice"]
      }
    ],
    "escalation_rules": [
      {
        "id": "billing-dispute",
        "condition": "The user disputes a charge or asks to file a complaint.",
        "route": "billing-desk",
        "scenarios": ["escalates a billing dispute"]
      }
    ],
    "output_checks": [
      {
        "id": "no-full-card-numbers",
        "check": "\\b\\d{12,19}\\b",
        "scenarios": ["masks card numbers"]
      }
    ]
  }
}
```

`route` is a label you choose for where escalated conversations go. The platform stores it with the rule and does not route on it; your application decides what to do with an escalated turn.

## What happens when a rule fires

| Family                                           | Frames                                                                                        | Answer                                                                                    | `stop_reason`                                                    | Recorded decision        |
| ------------------------------------------------ | --------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | ------------------------ |
| Refusal domain                                   | `guardrail` with `decision: "refused"` per fired rule, then one `text_delta`                  | Fixed text, stored as the assistant message                                               | `refusal`                                                        | `refused`                |
| Escalation rule                                  | `guardrail` with `decision: "escalated"` per fired rule, right after `conversation`           | The assistant answers normally                                                            | `escalated` on the segment that finishes the turn with an answer | `escalated`              |
| Output check on the final answer                 | `guardrail` with `decision: "output_check_triggered"` per matched rule, then one `text_delta` | Replaced by "The response was withheld: it failed this assistant's output policy checks." | `output_check_triggered`                                         | `output_check_triggered` |
| Output check on prose written between tool calls | `guardrail` with `decision: "output_check_triggered"`                                         | That prose is dropped from the stream and the transcript; the round's tool calls continue | Unchanged                                                        | `output_check_triggered` |

The refusal text is fixed, not written by the model. It names each fired rule by id and description, says the check ran before anything was read, and offers the ways forward:

```text theme={null}
I did not run that request. It matched a refusal policy configured for this workspace: "no-legal-advice" (Requests for legal advice or interpretation of contract law). That check runs before I read anything, so nothing was looked up.

If you were asking about this rather than asking me to do it, say so and I will answer from what is on file. If you do need the action itself, it has to go through someone with permission to perform it, and I can prepare everything up to that point. If you think this policy should not apply here, it is workspace configuration and can be changed.
```

## Output checks

* `check` is a regular expression in Go (RE2) syntax, matched case-insensitively, with `.` matching newlines.
* When an instance has output checks, assistant prose is not streamed as it is written. Each round's prose is sent as one `text_delta` after it passes. Expect answers to arrive in larger pieces.
* A pattern that does not compile fails every turn with `500 guardrail_config_invalid` before anything streams. Validation checks only that `check` is non-empty, so test new patterns with an eval run before you publish.

## Where decisions are recorded

Every decision is stored as a guardrail event with the turn id, the rule id, the decision, and the config hash that was in force. Guardrail events appear in the [conversation export](/conversations/export-and-retention) and are kept when retention deletes conversation content.

## Test your guardrails

Write eval scenarios whose rubrics use `must_refuse` or `must_escalate` with your rule ids, and `final_must_not_contain` for output checks. Put them in the `gate` suite so the publish gate runs them. A regression blocks a publish only when the latest released version has a recorded gate run to compare against, a scenario newly fails or a new scenario fails, the candidate config sets `evals.on_regression: "block"`, and the request carries no waiver. The first gate run records a baseline and passes. See [the eval gate rules](/conversations/versions-and-publish#the-eval-gate) and [Evaluation](/conversations/evaluation).

<CardGroup cols={2}>
  <Card title="Evaluation" href="/conversations/evaluation">
    Scenarios and rubrics that prove each rule.
  </Card>

  <Card title="Turns and streaming" href="/conversations/turns-and-streaming">
    The `guardrail` frame and stop reasons.
  </Card>
</CardGroup>
