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

# Rules

> Define what your AI agents can and cannot do with policy rules evaluated in-process.

## How Rules Work

Rules define what actions your agents are permitted to perform. Every tool call hooked by the in-process SDK is evaluated against your rule set. Rules are compiled into a bundle that SDKs download and cache locally, so evaluation happens in-process with no network round-trip on the hot path — and the SDK runs the **same evaluator** over the same rules as the backend, so a local decision always matches what the server would decide.

Each rule is a policy written in a Rego subset. When a rule matches a tool call, it produces one of four decisions:

| Decision            | Effect                                                                              |
| ------------------- | ----------------------------------------------------------------------------------- |
| `permit`            | The tool call proceeds                                                              |
| `deny`              | The tool call is blocked; the agent receives the block message as the tool's output |
| `approval_required` | The call pauses for a [human decision](/rules/action/hitl)                          |
| `mask`              | The call proceeds with the rule's named arguments redacted first                    |

<Note>A `mask` rule whose masking directives cannot be resolved is downgraded to `deny` — a mask that would mask nothing never runs the tool with raw arguments.</Note>

***

## What you start with

You do not start from an empty rule set. Every organization is seeded a **curated catalog of 35 default rules** covering secrets and credentials, payment-card data, PII, funds transfers, destructive writes, privileged admin actions, outbound communications, and more. Each catalog rule grants access on a need-to-know matrix: the agent's **business function** (is this data or action part of its job?) crossed with its **trust tier** (`tier1` highest, `tier2` standard, `tier3` restricted) — full access for trusted agents that need it, human approval or masking in the middle, denial for agents with no need. Business functions are AI-assigned from observed traffic (and pinnable by you); trust tiers are set by you on the Agents page.

When **no rule matches at all**, the tenant-wide **no-coverage default** decides, per operation type (`read`, `write`, `delete`, `admin`), each set to `approve`, `ask`, or `deny`. The platform default is `approve` for all four — **no default disruption**, even in `enforce` mode: an uncovered action proceeds untouched until you tighten it. Configure this under **Settings**, or via `PUT /allow/settings` (`no_coverage_defaults`). Evaluation errors always fail closed to deny, and an agent **confirmed in `enforce`** that has lost its bundle stays fail-closed — but a **never-confirmed** agent cold-starts in `monitor` (monitor-until-confirmed) and blocks nothing.

***

## Authoring rules

The dashboard rule editor (**Harness → Rules**, then **+ New rule**) gives you three surfaces on the same rule:

* **Natural language** — describe the rule in plain English in the editor's chat. The compiler reads your existing rules for context, drafts the policy source, validates it against the live evaluation engine, and simulates it before anything is saved.
* **Visual condition builder** — compose condition branches and decisions as a graph; the editor serializes it to the same policy source.
* **Simulate panel** — replay a real captured event through the exact production evaluation chain, and run an aggregate simulation showing the outcome distribution the rule would produce over your recent traffic.

Programmatic management uses the [REST API](/rules/action/api-reference#rules); `POST /allow/rules/compile` is the natural-language compiler as an endpoint.

### Rule fields

| Field              | Type      | Description                                                                              |
| ------------------ | --------- | ---------------------------------------------------------------------------------------- |
| `name`             | `string`  | Rule name, unique per organization                                                       |
| `description`      | `string`  | Optional longer description                                                              |
| `rego_source`      | `string`  | The policy source (required) — see the condition language below                          |
| `natural_language` | `string`  | Optional plain-English intent the policy was compiled from                               |
| `priority`         | `number`  | Higher priority is evaluated first. Default: `0`                                         |
| `enabled`          | `boolean` | Whether the rule is active. Disabled rules are excluded from the bundle. Default: `true` |

### Example rule

```rego theme={null}
package allow

# Refunds of $1,000 or more require human approval; smaller ones proceed.
default decision = "deny"

decision = "approval_required" if {
  input.action == "issue_refund"
  input.context.issue_refund.amount >= 1000
}

decision = "permit" if {
  input.action == "issue_refund"
  input.context.issue_refund.amount < 1000
}
```

Condition lines inside a block are **AND**ed; write multiple blocks for **OR**. The `default` declaration is inert in the evaluation cascade — when none of a rule's condition blocks fire, the rule simply doesn't match and evaluation moves on to the next rule (see below).

***

## The condition language

Conditions match against fields of the evaluated event:

| Input                           | Meaning                                                                                                                                                                                                                                                                                                                                 |
| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `input.action`                  | The tool name (e.g. `issue_refund`)                                                                                                                                                                                                                                                                                                     |
| `input.target_app`              | The target application or resource the call is directed at                                                                                                                                                                                                                                                                              |
| `input.context.<tool>.<arg>`    | The tool-call arguments, nested under the tool name                                                                                                                                                                                                                                                                                     |
| `input.agent.trust_tier`        | The agent's operator-assigned trust tier (`tier1` \| `tier2` \| `tier3`)                                                                                                                                                                                                                                                                |
| `input.agent.business_function` | The agent's business function (e.g. `finance_accounting`, `hr`, `engineering`)                                                                                                                                                                                                                                                          |
| `input.normalized.*`            | Canonical normalized event fields — e.g. `input.normalized.action_class` (a taxonomy of 46 action classes such as `funds_transfer`, `record_delete`, `credential_reset`, inferred by the AI schema mapper from the tool once it is learned) and `input.normalized.write.amount` {/* truth:count id=action-class-vocabulary value=46 */} |

Supported condition forms:

| Form                               | Example                                                                     |
| ---------------------------------- | --------------------------------------------------------------------------- |
| Equality                           | `input.action == "issue_refund"`                                            |
| Inequality                         | `input.agent.trust_tier != "tier1"`                                         |
| Negation                           | `not input.agent.trust_tier == "tier1"`                                     |
| Set membership                     | `input.normalized.action_class in ["funds_transfer", "funds_disbursement"]` |
| Array includes                     | `"third_party" in input.agent.categories`                                   |
| String prefix / suffix / substring | `startswith(input.action, "delete_")`, `endswith(...)`, `contains(...)`     |
| Regular expression                 | `regex.match("^prod-", input.target_app)`                                   |
| Numeric comparison                 | `input.context.issue_refund.amount >= 1000`                                 |
| Count                              | `count(input.context.bulk_export.record_ids) > 100`                         |

<Note>Undefined fields fail closed. A condition referencing a field absent from the event is not satisfied — for `!=` and `not` forms too, matching real OPA semantics. Prefer `not x == "..."` over `!=` when you want the *absence* of an attribute to land in the protective branch. An unrecognized condition line parses as always-false, so a rule containing one can never match.</Note>

***

## Rule Evaluation Flow

1. The engine sorts the cached bundle by `priority` **descending** — the highest number is evaluated first.
2. For each rule, the policy body is evaluated against the event. The **first rule with a firing condition block wins** and its decision applies.
3. A rule whose blocks don't fire is skipped — its `default` declaration never decides; the cascade continues.
4. Two blocks of the same rule that fire with **conflicting** decisions fail closed to `deny`.
5. If no rule matches anywhere, the per-operation-type **no-coverage default** resolves the action (`approve` by default — see [uncovered scenarios](/rules/action/hitl#uncovered-actions-and-coverage-gaps)).

<Note>Uncovered actions are **permitted** by default, not denied, even in `enforce` mode — governance comes from the seeded default catalog and the rules you add, and you can tighten any operation type to `ask` or `deny` in Settings. What fails closed is errors: an evaluation error, or an agent **confirmed in `enforce`** that has lost its bundle, denies. A never-confirmed agent cold-starts in `monitor` (monitor-until-confirmed) and blocks nothing.</Note>

### Priority guidelines

| Priority range | Suggested use                                             |
| -------------- | --------------------------------------------------------- |
| `100+`         | Override rules — specific denies that must always win     |
| `61–99`        | Your custom rules, evaluated ahead of the default catalog |
| `15–60`        | Where the seeded default catalog ships                    |
| `0–14`         | Broad catch-all rules                                     |

***

## The interference gate

A rule that blocks half your real traffic is a bug, not a policy. Rule authoring in the dashboard — both the natural-language compiler and direct saves — simulates every new or edited rule against a sample of your recent traffic and **rejects any rule whose combined deny + approval share exceeds 5%** (a save over the limit fails with `422 INTERFERENCE_THRESHOLD_EXCEEDED`). Masking doesn't count toward the limit — a masked call still proceeds.

The same bar applies after saving: a daily background check disables any enabled rule inhibiting more than 5% of its recent evaluations (once it has a statistically meaningful sample), records why in the audit log and the rule's revision history, and leaves it for you to tighten and re-enable. Opt out per organization with the `auto_disable_high_interference` setting.

Every rule change — create, update, enable, disable, restore, delete — is recorded in the rule's **revision history** with the acting user and a full snapshot, so the editor can show contributors over time and restore any prior version.

***

## Rule Sync

The SDK fetches one compiled bundle from `GET /rules/bundle` — it carries your enabled rules (both action and retrieval facets), the agent's mode, the server-assigned agent attributes, and the no-coverage settings — and refreshes it in the background roughly every 5 seconds.

Sync uses `ETag` / `If-None-Match`: when nothing changed, the server answers `304 Not Modified` and no data is transferred. The bundle `version` is a SHA-256 hash over the serialized payload — rules, the agent's mode and attributes, and the no-coverage settings — so flipping an agent to enforce, or retiering it, propagates on the next refresh just like a rule edit.

There is no server-side fallback on the decision path. With a `VISIQ_API_KEY` the harness reaches the managed SaaS backend (`https://api.visiqlabs.com` by default) and confirms a bundle automatically, so monitor-until-confirmed is only the brief pre-first-bundle window (or the genuinely-no-key case). Until that first bundle loads, an agent **confirmed in `enforce`** fails closed and denies, while a **never-confirmed** agent cold-starts in `monitor` and blocks nothing. `awaitBundleReady()` is called automatically before the first evaluation so a warm start resolves locally.

<Note>The action-facet-only `GET /allow/rules/bundle` remains available and is documented in the [API Reference](/rules/action/api-reference#get-allowrulesbundle).</Note>

***

## Managing Rules

* **Dashboard**: **Harness → Rules**. Use **+ New rule** to open the editor — natural-language chat, visual builder, and Simulate panel in one place. The list shows each rule's priority, operations, and share of recent traffic it inhibits.
* **REST API**: the [Rules endpoints](/rules/action/api-reference#rules) — programmatic rule management, CI/CD pipelines, and bulk imports.

<Tip>Rules can also govern both facets at once: a single rule authored in the dashboard can span action and retrieval, evaluated from the same bundle by the same in-process engine. See [Retrieval Governance](/rules/retrieval/introduction) for the retrieval side.</Tip>
