Guardrails
Create, attach, update, and retire guardrails in Orca Agent Engine at session, agent, Workspace, or organization scope.
A guardrail is a registry resource that holds one rule, the phases the rule runs at, and the scope it applies to. The rule is either a builtin from the catalog, configured with parameters, or a CEL expression. Govern agents explains how verdicts combine across rules.
A guardrail with workspace scope, the default, applies to every session in the Workspace as soon
as you create it. Depending on the runtime, a rule it cannot enforce stops the session from
starting or goes unenforced - see Enforcement. Create a rule
with explicit scope and attach it to one test session first.
Registry endpoint
Examples on this page target your registry endpoint - the deployment host root, with no path
suffix. For CLI, set ORCA_REGISTRY_URL and exactly one of ORCA_ACCESS_TOKEN (Bearer) or
ORCA_API_KEY (x-api-key). For TypeScript SDK, set ORCA_BASE_URL / ORCA_API_KEY (Bearer).
To find the endpoint, see Connect to the registry.
Guardrail configuration fields
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | 1 to 200 characters. Appears in some denial reasons. Names do not have to be unique. |
rule | object | Yes | A builtin, {"kind": "builtin", "builtin": "<type>", "params": {...}}, or an expression, {"kind": "expression", "expression": "<CEL>", "on_false": "ask" | "deny", "reason": "..."}. |
phases | array | For expression rules | Where the rule runs: request, tool_call, or tool_result. A builtin defaults to its catalog phases, and you can send a subset of them that some runtime evaluates. |
scope | string | No | workspace (the default) applies to every session in the Workspace. explicit applies only where an agent or session lists the guardrail's ID. organization is written on the admin listener. |
enabled | boolean | No | Defaults to true. false stops enforcement and keeps every reference in place. |
description | string | No | Up to 1024 characters. |
metadata | object | No | Up to 16 string pairs. Keys are 1 to 64 characters and values up to 512. On update, a null value removes the key. |
A response adds id (with the grd_ prefix), type: "guardrail", archived_at, created_at, and
updated_at. The registry stores params exactly as written and applies catalog defaults when the
rule runs.
The registry checks the whole rule when you write it: the builtin name, each parameter's name, type,
and range, phases the builtin does not run at, phases no runtime evaluates, and CEL syntax. A rule
that fails returns 400 describing the first check it failed, for example
phases: this rule does not fire at request; it fires at tool_call.
List guardrail types
The catalog lists every builtin with its phases, verdicts, whether it keeps state, and a JSON Schema for its parameters:
ork guardrails list-types -o jsonThe response lists 22 types. This excerpt shows one:
{
"data": [
{
"name": "block_tools",
"title": "Block tools",
"description": "Denies the named tools outright.",
"phases": ["tool_call"],
"stateful": false,
"verdicts": ["deny"],
"paramsSchema": {
"type": "object",
"properties": {
"tools": {
"type": "array",
"items": { "type": "string" },
"description": "Tool names or glob patterns to deny.",
"minItems": 1
},
"reason": {
"type": "string",
"description": "Message returned to the agent in place of the tool result."
}
},
"required": ["tools"],
"additionalProperties": false
}
}
]
}Catalog entries use camelCase keys. A stateful type adds stateScope, and some types add
allowedScopes or requireAtLeastOneOf. Builtin guardrails
describes each type.
Create a guardrail
This rule denies the built-in shell tool, mcp__orca__bash, for every agent or session that lists
it:
guardrail=$(ork guardrails create --config-json '{
"name": "block-shell",
"scope": "explicit",
"rule": {
"kind": "builtin",
"builtin": "block_tools",
"params": { "tools": ["mcp__orca__bash"] }
}
}' -o json)
GUARDRAIL_ID=$(jq -r '.id' <<< "$guardrail")ork guardrails create also reads the same request object from a file with --file. The response
fills in the builtin's phases:
{
"id": "grd_01H8...",
"type": "guardrail",
"name": "block-shell",
"description": "",
"enabled": true,
"phases": ["tool_call"],
"scope": "explicit",
"rule": {
"kind": "builtin",
"builtin": "block_tools",
"params": { "tools": ["mcp__orca__bash"] }
},
"metadata": {},
"archived_at": null,
"created_at": "2026-09-29T17:00:00.000Z",
"updated_at": "2026-09-29T17:00:00.000Z"
}Attach a guardrail
An explicit guardrail applies only where an agent or a session lists its ID in guardrail_ids.
workspace and organization guardrails already apply everywhere in their scope.
Attach to an agent
Every session pinned to the resulting agent version enforces the rule, including actions taken by
the subagents that agent dispatches. The CLI has no flag for guardrail_ids, so use the API or an
SDK:
const agent = await orca.agents.create(
{
name: 'review-assistant',
model: 'claude-sonnet-4-6',
guardrail_ids: [guardrail.id],
},
{ headers: { 'orca-beta': 'guardrails' } },
);from orca import Orca
client = Orca()
agent = client.agents.create(
name="review-assistant",
model="claude-sonnet-4-6",
guardrail_ids=[guardrail.id],
extra_headers={"orca-beta": "guardrails"},
)The Python example assumes guardrail came from client.guardrails.create(...), which takes the
same fields as the TypeScript call. The Go SDK takes GuardrailIDs on AgentNewParams; see
the Go SDK guide.
- The
orca-betaheader only affects the response. The registry storesguardrail_idswith or without it, but returns the field only to requests that send the header. See beta headers. None of the SDKs adds it for you. - Every ID must name a guardrail the Workspace can see that is not archived, or the request
returns
400. An agent can list up to 64 guardrails. - Changing
guardrail_idscreates a new agent version. On update, omitting the field keeps the current list, andnullor[]clears it. Existing sessions keep the version they were created with.
Attach to a session
A session can list guardrails in an agent_with_overrides reference. They add to the agent's own
guardrails and cannot remove any. Set them when you create the session; a session's guardrails
cannot change afterward. $AGENT_ID / agent.id comes from the previous example, and
$ENVIRONMENT_ID / environmentId is the id of an
environment:
ork agent sessions create \
--environment-id "$ENVIRONMENT_ID" \
--agent-json "$(jq -cn --arg agent "$AGENT_ID" --arg grd "$GUARDRAIL_ID" \
'{type: "agent_with_overrides", id: $agent, guardrail_ids: [$grd]}')" \
-o jsonList and retrieve guardrails
A Workspace credential sees the Workspace's own guardrails and its organization's guardrails. It can read organization guardrails but not change them. Archived guardrails are left out unless you ask for them:
ork guardrails list --limit 100 -o json
ork guardrails get "$GUARDRAIL_ID" -o jsonA list returns up to 100 guardrails per page, newest first, with data and next_page. Pass
next_page back as page for the next page; the TypeScript iterator does this for you. Add
--include-archived or include_archived=true to include archived guardrails.
Update a guardrail
An update changes only the fields you send and keeps the guardrail's ID. A new rule replaces the
old one in full. The registry checks the merged rule, phases, and scope again, so switching to a
builtin that runs at different phases requires sending phases too. The stored phases are checked
as well: a rule stored with a phase no runtime evaluates, such as deny_pii_in_llm_request created
without phases, rejects every update until you also send phases. This update pauses the rule:
ork guardrails update "$GUARDRAIL_ID" --config-json '{"enabled": false}' -o jsonSet enabled back to true to resume enforcement. The update route is POST, not PATCH.
When changes take effect
A running session picks up a created, updated, disabled, or archived guardrail at its next user message, when its runtime restarts with the new set. A turn already in progress keeps the rules it started with. AI Gateway caches the rules it evaluates; see Policy guardrails for its refresh behavior.
Archive or delete a guardrail
Archiving hides a guardrail from lists and stops enforcing it everywhere, and it keeps the record.
It is not rejected while agents or sessions reference the guardrail: running sessions skip archived
and deleted IDs without an error. An agent that still lists the ID cannot be updated, though: an
update that keeps the stored guardrail_ids returns 400 with unknown or archived guardrail_ids,
so send guardrail_ids without the archived ID. There is no unarchive operation, so pause a rule
with enabled: false if you may need it again:
ork guardrails archive "$GUARDRAIL_ID" -o jsonDeleting removes the guardrail permanently. It returns 409 with
guardrail is referenced by one or more agents while the current version of any agent in the
Workspace lists the guardrail, so remove it from those agents first. Earlier agent versions and
sessions are not checked; they skip the deleted ID. The CLI cannot change guardrail_ids, so this
step uses the API or an SDK. It removes the reference added earlier on this page:
await orca.agents.update(agent.id, { guardrail_ids: [] });Then delete the guardrail:
ork guardrails delete "$GUARDRAIL_ID" -o json{ "id": "grd_01H8...", "type": "guardrail_deleted" }Organization guardrails
An organization guardrail applies to every session in every Workspace of the organization.
Workspace credentials can read it but not create, change, or delete it; those requests return 403
with organization-scoped guardrails are managed by the organization. Organization administrators
manage these rules on the registry's admin listener, the same listener that serves
operator model prices.
You set $ORCA_ADMIN_URL to that listener's host root and $ORCA_ADMIN_API_KEY to an organization admin API key with org:admin.
This rule denies user messages that contain personal data across the organization. It keeps only
the request phase, which every runtime enforces:
curl -fsS "$ORCA_ADMIN_URL/v1/organizations/guardrails" \
-H "x-api-key: $ORCA_ADMIN_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"name": "org-deny-personal-data",
"phases": ["request"],
"rule": { "kind": "builtin", "builtin": "deny_pii_in_llm_request" }
}'The admin routes differ from the Workspace routes:
| Operation | Admin listener route | Difference |
|---|---|---|
| Create | POST /v1/organizations/guardrails | Omit scope or set it to organization. |
| List | GET /v1/organizations/guardrails | limit defaults to 20 and allows up to 1000. Pages with after_id or before_id and returns has_more, first_id, and last_id. |
| Retrieve | GET /v1/organizations/guardrails/{id} | Returns organization guardrails only. |
| Update | PATCH /v1/organizations/guardrails/{id} | scope cannot change. |
| Delete | DELETE /v1/organizations/guardrails/{id} | Returns 409 while an agent anywhere in the organization lists the guardrail. |
The admin listener has no archive route; set enabled to false to pause a rule. Its errors are a
plain {"error": "<message>"} object rather than the registry's usual error envelope.
Permissions
| Operation | Permission |
|---|---|
| List guardrail types; list and retrieve guardrails, including the organization's | A Workspace credential |
Create, update, archive, or delete explicit and workspace guardrails | A Workspace credential |
Create, update, or delete organization guardrails | An organization admin API key with org:admin, on the admin listener |
See Control registry access for how to separate access with Workspaces.
What's next
Overview
Control which actions agents in Orca Agent Engine may take, with per-tool permission policies and guardrails that inspect context and apply across agents.
Builtin guardrails
The catalog of builtin guardrail types in Orca Agent Engine - what each type checks, its parameters, and the phases and verdicts it supports.