Skip to main content
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. Contact the C1 Support team if you’d like to try it out or share feedback.

Object model

How they relate: a binding points a (surface, target) pair at one guardrail. The guardrail holds the cascade, and each rule in it references the gates and hooks it dispatches.
Rules live inline on the guardrail 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 guardrails.
A binding is the only thing that activates a guardrail. Creating a guardrail, authoring rules, and wiring up gates and hooks changes nothing about live traffic until a binding points at it.

Enforcement surfaces

Applies to is a two-way choice — Agent or Gateway. There is no option to target both.
You may see a guardrail whose surface displays as Any surface. That’s the unset value, kept for guardrails carried over from the older single-policy model; it isn’t offered when you create or edit one. A guardrail 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.
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.
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.
If no binding resolves for a surface — none configured, or it points at a deleted guardrail — that surface falls back to an empty policy with no rules. It does not fall back to another surface’s guardrail.

Guardrail fields

Guardrail rule fields

Only hooks and tool gates with Managed by guardrails enabled are selectable in the hook and gate pickers, and each picker only offers hooks whose event matches it.
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.

Actions

Modes

CEL variables

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:
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.

Evaluation order on a tool call

  1. Resolve the binding for the surface, and walk the guardrail’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 guardrails off. Only the first matching gate 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 guardrails 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 guardrail-curated gates or hooks — unmanaged gates and hooks still apply. A guardrail 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.
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.
Pre-output hooks are not part of this walk. See 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.
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.

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

ctx.tool_name is unset here.
ctx.untrusted_class (pre-output hook filter) and ctx.untrusted_content (guardrail 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.

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.

Tool gate fields

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 guardrails off, so an unmanaged gate with a lower priority can win ahead of one your rule explicitly selected.
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.

Gate filter variables

A tool gate filter is evaluated against a different variable set than a guardrail rule — it sees the call’s identity and target rather than the risk axes: ctx.tool_kind and ctx.caller_kind are separate vocabularies — the values are not interchangeable: 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.
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.

The untrusted content judge

There is no model selection, threshold, or per-agent judge configuration.
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.

Constraints

Troubleshooting

My guardrail isn’t doing anything

Check, in order:
  1. Is it bound? An unbound guardrail is evaluated on zero calls. Check Default C1AI Guardrail policy (agent) or Default C1 Gateway Guardrail policy (gateway), and any per-agent override that might be shadowing the default.
  2. Does the surface match? An Agent guardrail 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 guardrail 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 guardrails 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 guardrails 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 guardrail 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.