> ## Documentation Index
> Fetch the complete documentation index at: https://conductorone-rohithkokku-cs-patch-1.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Agent classifiers reference

> Technical reference for agent classifiers: object model, rule and tool gate fields, CEL variables, enforcement surfaces, evaluation order, constraints, and troubleshooting.

<Warning>
  **Early access.** This feature is in early access, which means it's undergoing ongoing testing and development while we gather feedback, validate functionality, and improve outputs.
</Warning>

## Object model

| Object                 | What it is                                                                                            | Scope                         |
| :--------------------- | :---------------------------------------------------------------------------------------------------- | :---------------------------- |
| **Classifier**         | A named, ordered cascade of rules, plus the surface it may bind to                                    | Tenant; many per tenant       |
| **Classifier rule**    | One entry in a cascade. Stored inline on the classifier, not separately reusable                      | Per classifier                |
| **Classifier binding** | Attaches one classifier to one enforcement target. The only object that puts a classifier into effect | One per target                |
| **Tool gate**          | Standalone approval requirement: a filter plus a grant policy                                         | Tenant; reusable across rules |
| **Hook**               | Standalone interceptor that inspects, rewrites, or denies a payload                                   | Tenant; reusable across rules |

How they relate: a binding points a `(surface, target)` pair at one classifier. The classifier holds the cascade, and each rule in it references the gates and hooks it dispatches.

```
Classifier
 └─ rules[]  (ordered)
     ├─ tool gate ids
     ├─ pre-tool hook ids
     ├─ post-tool hook ids
     └─ pre-output hook ids
```

Rules live inline on the classifier because an ordered cascade is configuration, not a reusable unit. Tool gates and hooks are the reusable primitives — a rule references them by ID, and the same gate or hook can be selected by rules in different classifiers.

<Warning>
  A binding is the only thing that activates a classifier. Creating a classifier, authoring rules, and wiring up gates and hooks changes nothing about live traffic until a binding points at it.
</Warning>

## Enforcement surfaces

| Surface     | Governs                                    | Bind at                                                                                      | Available CEL variables                                                          |
| :---------- | :----------------------------------------- | :------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------- |
| **Agent**   | C1AI agents                                | **AI > Classifiers** → **Default C1AI Classifier policy**, or an agent's **Classifiers** tab | `ctx.private_data`, `ctx.untrusted_content`, `ctx.exfiltration`, `ctx.tool_name` |
| **Gateway** | External AI clients through the C1 Gateway | **AI > C1 Gateway > Settings** → **Default C1 Gateway Classifier policy**                    | `ctx.private_data`, `ctx.exfiltration`, `ctx.tool_name`                          |

**Applies to** is a two-way choice — **Agent** or **Gateway**. There is no option to target both.

<Warning>
  You may see a classifier whose surface displays as **Any surface**. That's the unset value, kept for classifiers carried over from the older single-policy model; it isn't offered when you create or edit one. A classifier in that state appears in **neither** binding selector, so it can't be bound from the admin UI at all — edit it and set **Applies to** to **Agent** or **Gateway** first.
</Warning>

Binding resolution on the agent surface tries the specific agent's binding first, then falls back to the tenant default agent binding. The gateway surface resolves a single tenant-wide binding — there is no per-client or per-connection gateway granularity.

<Note>
  `ctx.untrusted_content` is **not available on the Gateway surface**. The untrusted-content score comes from the judge, which runs inside a C1AI session; there is no equivalent for an external client. A gateway rule written against `ctx.untrusted_content` will not behave as intended.
</Note>

If no binding resolves for a surface — none configured, or it points at a deleted classifier — that surface falls back to an empty policy with no rules. It does **not** fall back to another surface's classifier.

## Classifier fields

| Field           | Notes                                                                                                                      |
| :-------------- | :------------------------------------------------------------------------------------------------------------------------- |
| **Name**        | Required. Up to 128 characters.                                                                                            |
| **Description** | Optional. Up to 1024 characters.                                                                                           |
| **Applies to**  | **Agent** or **Gateway**. Determines which variables rules can read, and which binding selector the classifier appears in. |
| **Managed by**  | **Tenant** for classifiers you author — freely editable. **System** marks a C1-curated classifier, which is read-only.     |

## Classifier rule fields

| Field                | Notes                                                                                      |
| :------------------- | :----------------------------------------------------------------------------------------- |
| **Name**             | Required. Up to 128 characters.                                                            |
| **Description**      | Optional. Up to 1024 characters.                                                           |
| **Condition (CEL)**  | Optional. Empty matches **every** tool call. Up to 32,768 bytes.                           |
| **Action**           | **Block** or **Evaluate hooks**.                                                           |
| **Pre-tool hooks**   | Hooks evaluated before the tool call. Hidden when Action is Block.                         |
| **Post-tool hooks**  | Hooks evaluated after the tool call. Hidden when Action is Block.                          |
| **Pre-output hooks** | Hooks evaluated before the assistant's response is delivered. Hidden when Action is Block. |
| **Tool gates**       | Gates that must grant before the call executes. Hidden when Action is Block.               |
| **Mode**             | **Enforce**, **Observe**, or **Disabled**.                                                 |
| **Deny reason**      | Optional. Up to 256 characters. Shown to the caller on denial.                             |

Only hooks and tool gates with **Managed by classifiers** enabled are selectable in the hook and gate pickers, and each picker only offers hooks whose event matches it.

<Note>
  Audit records identify a matched rule by its **ID**, not its name. Renaming a rule doesn't change what earlier records point at — but it also means a rule's name isn't what you search on when tracing a decision.
</Note>

### Actions

| Action             | Behavior                                                               |
| :----------------- | :--------------------------------------------------------------------- |
| **Block**          | Deny the call immediately. Tool gates and hooks are skipped entirely.  |
| **Evaluate hooks** | Run the selected tool gates and hooks; their outcome decides the call. |

### Modes

| Mode         | On a match                                                                                                                                                                                                                        |
| :----------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Enforce**  | The outcome applies and cascade evaluation stops.                                                                                                                                                                                 |
| **Observe**  | Logs that the rule *would have fired*, then **falls through to the next rule**. Nothing is blocked. The first Observe match is recorded as an informational audit event marked *would have fired* if no later Enforce rule fires. |
| **Disabled** | Skipped entirely. Unset behaves as disabled.                                                                                                                                                                                      |

## CEL variables

| Variable                | Type   | Surface    | Notes                                    |
| :---------------------- | :----- | :--------- | :--------------------------------------- |
| `ctx.private_data`      | level  | Both       | Sensitivity of data in play              |
| `ctx.untrusted_content` | level  | Agent only | Prompt-injection risk, from the judge    |
| `ctx.exfiltration`      | level  | Both       | Capability of this call to move data out |
| `ctx.tool_name`         | string | Both       | The tool being called                    |

Levels are `LOW`, `MEDIUM`, and `HIGH`, ordered — they compare as 0, 1, and 2 respectively, so `>=` works as you'd expect and the numeric form is equivalent.

Examples:

```
ctx.untrusted_content == HIGH
  && ctx.exfiltration == HIGH

ctx.private_data >= MEDIUM
  && ctx.exfiltration >= MEDIUM

ctx.private_data == HIGH
  && ctx.tool_name.startsWith("salesforce_")
```

<Note>
  A rule whose CEL expression fails to evaluate is **skipped**, and the cascade continues to the next rule. It does not abort evaluation and does not deny the call — so a catch-all rule lower in the cascade still fires. This is the opposite of hook filter behavior, where a failed filter denies the call.
</Note>

## Evaluation order on a tool call

1. **Resolve the binding** for the surface, and walk the classifier's rules top to bottom, first match wins.
2. If the matched rule's action is **Block** → deny. **Gates and hooks never run.**
3. Otherwise run **tool gates**: the gates the rule selected, plus every gate with **Managed by classifiers** off. Only the [first matching gate](#only-the-first-matching-gate-runs) runs. A gate that requests approval or denies short-circuits here — **hooks and the tool never run.**
4. Run **pre-tool hooks**: those the rule selected, plus every pre-tool hook with **Managed by classifiers** off. A denial blocks the call.
5. **The tool executes.**
6. Run **post-tool hooks**, same union rule. A denial blocks the result from reaching the caller.

If no rule matches, the call proceeds with no classifier-curated gates or hooks — unmanaged gates and hooks still apply.

A classifier with no rules, or one whose every rule is **Disabled**, matches nothing. At call time it is indistinguishable from an unbound surface: bound-but-empty and not-bound-at-all behave identically.

<Warning>
  **A call that matches no rule writes no `guardrail_decision` event.** Only a matched rule produces a durable record. So in the system log you cannot tell *"traffic reached this surface and matched nothing"* apart from *"no traffic reached this surface at all"* — both look like silence. Keep that in mind when a rule you expected to fire shows no records.
</Warning>

Pre-output hooks are not part of this walk. See [Pre-output hooks](#pre-output-hooks).

## Pre-output hooks

Pre-output hooks run when a chunk of the assistant's generated response is about to be delivered. No tool call is involved, and the stage differs from the tool-call stages in four ways that each have a distinct failure mode.

### It is a separate cascade walk with an eligibility test

The cascade is walked again for the outgoing response, but a rule is only **eligible** to win if both of these hold:

* its action is **Evaluate hooks**, and
* its **Pre-output hooks** list is non-empty.

An ineligible rule is skipped and the walk **continues to the next rule**, even if its condition matched. Two consequences:

* A **Block**-action rule can never win on this path. It curates no hook lists, so it is structurally excluded — a Block rule does not block outgoing responses.
* An **Evaluate hooks** rule that curates only tool gates or pre/post-tool hooks is also skipped here, so a later rule can win the response even though the earlier rule won the tool call.

Mode works exactly as it does on the tool-call path: **Disabled** and unset are skipped, **Observe** logs a would-have-fired and falls through, **Enforce** can win. If no rule is eligible, the response is delivered unfiltered.

### Only two built-in patterns run here

**Block output** and **Link filter**. Every other built-in pattern is restricted to the tool-call events, and C1 rejects an incompatible event when you save the hook.

<Warning>
  **Custom function hooks cannot run on this stage.** Saving a function hook with the Pre-output event is rejected. The stage is also fail-closed, so a function hook that did reach it would withhold every matching response rather than being ignored.
</Warning>

### The filter variables are different, and one name is a trap

| Variable              | Values                                    |
| :-------------------- | :---------------------------------------- |
| `ctx.untrusted_class` | The strings `"LOW"`, `"MEDIUM"`, `"HIGH"` |
| `ctx.surface`         | `"slack"` or `"web"`                      |

`ctx.tool_name` is **unset** here.

<Warning>
  `ctx.untrusted_class` (pre-output hook filter) and `ctx.untrusted_content` (classifier rule) are the same underlying risk score exposed under different names *and* different types — an uppercase string versus an ordered level. A hook filter needs `ctx.untrusted_class == "HIGH"`; a rule needs `ctx.untrusted_content == HIGH`. Writing the rule form in a hook filter fails to evaluate, and a failed hook filter **denies** — so the mistake withholds responses rather than erroring visibly.
</Warning>

### A Block output hook with no surfaces is inert

An empty surface list means no surface at all, not all of them. Select **Slack**, **Web**, or both, or the hook never fires. See [Tool call hooks](/product/admin/tool-call-hooks#built-in-patterns).

## Tool gate fields

| Field                       | Notes                                                                                                                                                                                                                    |
| :-------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Name**                    | Required. 1–100 characters.                                                                                                                                                                                              |
| **Description**             | Optional. Up to 2048 characters.                                                                                                                                                                                         |
| **Grant policy**            | Required. Determines who approves and how.                                                                                                                                                                               |
| **Filter (CEL expression)** | Optional. See the variables below. Empty matches every call the gate sees, apart from the exclusions below.                                                                                                              |
| **Exclusions**              | Two checkboxes, both on by default: **Exclude task result read-backs** and **Exclude built-in tools**. Each adds a conjunct to the saved filter without altering the condition you typed. See [Exclusions](#exclusions). |
| **Priority**                | 0–1000. Lower matches first.                                                                                                                                                                                             |
| **Enabled**                 | Default on.                                                                                                                                                                                                              |
| **Managed by classifiers**  | Default **off** — the gate always evaluates. On — only when a rule selects it.                                                                                                                                           |

### Exclusions

| Exclusion                          | Default | What it does                                                                                                                                                                                                            |
| :--------------------------------- | :------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Exclude task result read-backs** | On      | Leaves `c1_task_service_get` ungated, so an agent can read the result of an action it was already approved for.                                                                                                         |
| **Exclude built-in tools**         | On      | Leaves the agent's own object-search, code-mode, and file-workspace tools ungated. A connector tool that happens to share one of those names is still gated — the exemption checks the tool's kind as well as its name. |

Each active exclusion is combined with your typed condition using `&&` when the gate is saved. Turning an exclusion off brings those calls back under the gate; it doesn't change what you typed in the filter field.

### Only the first matching gate runs

A rule can select several tool gates, but they are not all applied. The gates are evaluated in order and **the first one whose filter matches wins** — its grant policy is the one that decides the call, and no further gate is consulted.

The order is:

1. Enabled gates first
2. Then ascending **Priority** (lower first)
3. Then oldest first

The set evaluated is the gates the matched rule selected **plus** every gate with **Managed by classifiers** off, so an unmanaged gate with a lower priority can win ahead of one your rule explicitly selected.

<Warning>
  This makes **Priority** load-bearing whenever more than one gate could match the same call. Two gates with different grant policies and overlapping filters will not both apply — the call is approved under whichever gate sorts first, and the other is silently skipped. If you need a stricter approver to take precedence, give that gate the lower priority number.
</Warning>

### Gate filter variables

A tool gate filter is evaluated against a different variable set than a classifier rule — it sees the call's identity and target rather than the risk axes:

| Variable                 | Notes                                         |
| :----------------------- | :-------------------------------------------- |
| `ctx.tool_name`          | The tool being called                         |
| `ctx.tool_kind`          | What kind of tool it is — see below           |
| `ctx.tool_input`         | The call's input arguments                    |
| `ctx.app_id`             | Target app                                    |
| `ctx.connector_id`       | Target connector                              |
| `ctx.app_entitlement_id` | Target entitlement                            |
| `ctx.caller_kind`        | What kind of caller made the call — see below |
| `ctx.principal_user_id`  | The acting user                               |
| `ctx.agent_id`           | The acting agent                              |

`ctx.tool_kind` and `ctx.caller_kind` are **separate vocabularies** — the values are not interchangeable:

| Variable          | Values                                                                                 |
| :---------------- | :------------------------------------------------------------------------------------- |
| `ctx.tool_kind`   | `builtin`, `connector`, `c1_derived`, `unspecified`                                    |
| `ctx.caller_kind` | `c1aw`, `claw_builtin`, `code_mode`, `connector_manager`, `mcp_gateway`, `unspecified` |

Both fall back to `unspecified` for a value C1 doesn't recognize, so a filter written as an equality test against a specific kind won't match an unrecognized one.

<Note>
  The in-product helper text on the filter field mentions only `ctx.tool_name` and `ctx.tool_input` — the two you'll use most. The rest are available.
</Note>

## The untrusted content judge

| Property                                       | Detail                                                                                           |
| :--------------------------------------------- | :----------------------------------------------------------------------------------------------- |
| What it does                                   | Scores agent input and tool output for prompt-injection risk on every turn                       |
| Produces                                       | `ctx.untrusted_content`, as `LOW`, `MEDIUM`, or `HIGH`                                           |
| Default                                        | On                                                                                               |
| Configuration                                  | A single on/off toggle at **AI > Classifiers > Settings**                                        |
| When off                                       | The dimension **always scores as low risk** and the judge never runs                             |
| When it can't be reached at the start of a run | C1 seeds a cautious **MEDIUM** untrusted-content floor rather than treating the turn as low risk |
| When a mid-run rescore fails                   | The existing floor is kept; no new score is synthesized                                          |

There is no model selection, threshold, or per-agent judge configuration.

<Warning>
  Disabling the judge does not disable rules that read `ctx.untrusted_content` — it makes them silently stop matching, because the value is pinned to low risk. Disable those rules explicitly rather than relying on the judge being off.
</Warning>

## Constraints

| Constraint                         | Detail                                                                                           |
| :--------------------------------- | :----------------------------------------------------------------------------------------------- |
| Rule evaluation                    | First match wins, top to bottom                                                                  |
| Rule with empty condition          | Matches every call                                                                               |
| Rule with a failing CEL expression | Skipped; cascade continues                                                                       |
| Hook failure mode                  | Fail-closed — error, timeout, or filter failure denies the call                                  |
| Custom function hook timeout       | 60 seconds                                                                                       |
| Gate filter variables              | Nine — see [Gate filter variables](#gate-filter-variables). Distinct from the rule variable set. |
| Gate ordering                      | Enabled first, then priority ascending, then oldest first                                        |
| Priority range                     | 0–1000 for gates and hooks; lower first                                                          |
| Tenant defaults                    | No classifier, binding, or rule is created for a new tenant                                      |

## Troubleshooting

### My classifier isn't doing anything

Check, in order:

1. **Is it bound?** An unbound classifier is evaluated on zero calls. Check **Default C1AI Classifier policy** (agent) or **Default C1 Gateway Classifier policy** (gateway), and any per-agent override that might be shadowing the default.
2. **Does the surface match?** An Agent classifier won't appear in the gateway selector, and vice versa.
3. **Are the rules in Observe?** Observe logs and falls through by design.
4. **Are the rules Disabled?** An unset mode behaves as disabled.
5. **Is an earlier rule matching first?** A broad rule above a narrow one makes the narrow one unreachable.

### A rule that reads `ctx.untrusted_content` never matches

Either the classifier is bound to the **Gateway** surface, where that variable isn't available, or the **untrusted content judge** is turned off, which pins the value to low risk.

### A hook or tool gate I created isn't selectable in a rule

Its **Managed by classifiers** toggle is off. Turn it on from the hook or gate form, or from the toggle column in the table.

### A hook fires even though no rule selects it

That's the documented behavior for **Managed by classifiers** off: the hook always evaluates. Turn the toggle on to bring it under rule control.

### Calls are being denied and I can't tell which layer did it

Distinguish the three denial shapes:

* A **Block**-action rule denies with your configured **Deny reason**.
* A **tool gate** doesn't deny — it pauses the call behind an approval request. A denial only follows if the request is denied.
* A **hook** denies with its own reason, or fail-closed because it errored, timed out, or its filter failed to evaluate. Check the recorded hook status: `ERROR`, `TIMEOUT`, and `FILTER_ERROR` mean the hook is broken, not that it matched.

### An Observe rule shows nothing in the logs

Observe records a match, and only a match. A call that matches no rule writes no event at all — so "traffic arrived and matched nothing" and "no traffic arrived" produce identical silence, and you can't tell them apart from the log. Check both: widen the condition, and independently confirm traffic is reaching the bound surface.

Also confirm the classifier is bound, and that your severity filter isn't excluding informational records.

### A rule matches calls I expected it to ignore

An empty **Condition (CEL)** matches every tool call. Check that the condition saved, and that a broader rule above this one isn't the one actually matching — evaluation stops at the first match in **Enforce** mode.
