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

# Author custom guardrail rules with the deterministic DSL

> Write your own input/output guardrail rules in safety_config.custom_guardrail_rules — literal contains or regex matching with block, redact, or warn actions, evaluated deterministically before every agent turn.

# Author custom guardrail rules

The platform's fixed scanners — prompt injection, PII detection, harmful
content, blocked topics, sensitive words — cover the universal classes.
They cannot express tenant-specific rules like "never mention competitor X",
"flag reservation codes that match a carrier format", or "scrub internal
SKU prefixes before the reply reaches the customer."

Custom guardrail rules close that gap. You author them as a JSON array in
`safety_config.custom_guardrail_rules`, the same `safety_config` blob that
already carries `blocked_topics`, `sensitive_words`, and
`locale_pii_patterns`. Evaluation is **deterministic**: the same text and
the same rules produce the same violations every time — there is no LLM, no
clock, and no network in the matching path. That makes a custom rule
reproducible in a unit test and identical in production.

Custom rules are per-agent and separate from the central guardrail-policy
presets you may also have attached. A rule you write here applies to this
agent only.

The endpoint paths below are relative. Send them against
`https://api.orbit.devotel.io/api/v1`.

## Rule shape

Each rule in `custom_guardrail_rules` is a JSON object:

| Field       | Required | Semantics                                                                                                                                                                                                                                                                                   |
| ----------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`        | no       | Stable label for the violation entry and analytics. When absent the platform assigns `dsl_<n>` by position. Up to 64 characters.                                                                                                                                                            |
| `name`      | no       | Human-readable name shown in violation descriptions. Up to 64 characters.                                                                                                                                                                                                                   |
| `direction` | yes      | `input` scans the user message **before** it reaches the model; `output` scans the model reply **before** it is returned.                                                                                                                                                                   |
| `match`     | yes      | `contains` is a case-insensitive literal substring scan (regex metacharacters in the pattern are treated as literal text, so a `contains` rule is safe by construction). `regex` compiles the pattern as a JavaScript `RegExp` with the `i` flag for shape matching (ticket numbers, SKUs). |
| `pattern`   | yes      | The literal text or regex source, 1–200 characters after trim.                                                                                                                                                                                                                              |
| `action`    | yes      | `block`, `redact`, or `warn` (see below).                                                                                                                                                                                                                                                   |
| `severity`  | no       | `low`, `medium`, `high`, or `critical`. Defaults to `high` for `block`/`warn` actions, and to `low` for `redact`.                                                                                                                                                                           |

A fully annotated example:

```json theme={null}
{
  "safety_config": {
    "custom_guardrail_rules": [
      {
        "id": "competitor-mention",
        "name": "Competitor mention block",
        "direction": "input",
        "match": "contains",
        "pattern": "CompetitorCo",
        "action": "warn",
        "severity": "medium"
      }
    ]
  }
}
```

Up to 25 rules per agent; each pattern up to 200 characters; each `id` and
`name` up to 64 characters. Values outside those bounds are rejected at save
time with a 422.

## Semantics per action

* **block** — raises a violation and hands the decision to the same
  severity reducer the fixed scanners use. A `high` or `critical` severity
  stops the turn (the caller gets a refusal); `low` or `medium` records the
  violation without stopping the turn. Use this for rules that must prevent
  a category of input or output.
* **redact** — replaces every match in the text with the literal marker
  `[REDACTED]` before the turn proceeds, and emits a `low`-severity
  violation so the scrub is visible in the violation log. On `output` rules
  this happens before the reply is returned (or spoken on a voice agent);
  on `input` rules it scrubs the user message before the model sees it.
* **warn** — records the violation at the severity you set, never blocks.
  Use this for observe-only rollouts: measure how often a rule would fire,
  then flip `action` from `warn` to `block` once you are satisfied.

## Write path

Set `custom_guardrail_rules` on agent create or update, alongside the other
`safety_config` fields:

```bash theme={null}
curl -X PUT https://api.orbit.devotel.io/api/v1/agents/agent_abc123 \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "safety_config": {
      "custom_guardrail_rules": [
        {
          "id": "competitor-mention",
          "name": "Competitor mention block",
          "direction": "input",
          "match": "contains",
          "pattern": "CompetitorCo",
          "action": "warn"
        }
      ]
    }
  }'
```

Validation runs before the rules are persisted. A bad shape, an out-of-enum
`direction`/`match`/`action`/`severity`, or a regex that does not compile
returns a `422 VALIDATION_ERROR` naming the rule index and field, for
example `custom_guardrail_rules[0].pattern is not a valid regex`. Fix the
rule and resubmit.

Defense in depth: if a malformed rule ever reaches the runtime (for
example, JSON written through a path that bypassed the API validator), that
rule is skipped rather than throwing — a poisoned rule can never break an
agent turn.

## Evaluation order

Custom rules run at a defined point in each guardrail leg:

* **Input leg** — after the prompt-injection scanner, PII detection,
  blocked topics, and the encoding checks, and before the `passed`
  decision. A `contains` rule sees the same post-unicode-cleanup text the
  fixed scanners see.
* **Output leg** — after PII redaction, the content filter, and the
  citation check, and before the `passed` decision. A rule on `output`
  sees the post-redaction text, so a term a PII redactor already masked
  does not re-fire a custom rule.

On both legs the resulting violations fold into the normal pass/block
reducer and the violation-log feed, alongside the violations the fixed
scanners raise. Text length caps apply before evaluation: 50,000 characters
on input, 100,000 on output.

## Two worked rules

**Never mention a competitor (observe, then block).** Start in `warn` mode
so you can measure how often the rule fires without disrupting turns:

```json theme={null}
{
  "id": "competitor-telaxis",
  "name": "Competitor: Telaxis",
  "direction": "input",
  "match": "contains",
  "pattern": "Telaxis",
  "action": "warn",
  "severity": "medium"
}
```

Watch the violation feed in [guardrail analytics](/guides/guardrail-analytics-tuning).
When the firing rate looks right, flip `action` to `block` and (optionally)
`severity` to `high` so a mention stops the turn with a refusal instead of
just logging.

**Redact internal SKU prefixes (output, regex).** Internal prefixes such
as `SKU-` followed by digits should never reach the customer:

```json theme={null}
{
  "id": "sku-prefix-scrub",
  "name": "Internal SKU prefix scrub",
  "direction": "output",
  "match": "regex",
  "pattern": "SKU-[0-9]+",
  "action": "redact"
}
```

Every occurrence is replaced with `[REDACTED]` before the reply is
returned, and a low-severity violation records that the scrub fired. For a
shape match like this, prefer `regex` over `contains` so you match the
whole prefix-plus-digits form rather than a fixed literal.

## Related reading

* [Guardrail analytics tuning](/guides/guardrail-analytics-tuning) — read
  the violation feed and tune one rule at a time.
* [Guardrail effectiveness](/agents/guardrail-effectiveness) — the
  monitoring surface for firing rates across all guardrail checks.
* [Sensitive words guardrail](/agents/sensitive-words-guardrail) — the
  fixed literal-redaction guard that custom rules complement.
