Permission policies
Control which tools run automatically in Orca Agent Engine and which pause the session for your approval.
A permission policy decides whether a built-in or MCP tool runs immediately or waits for you to approve it. Policies govern the built-in sandbox toolset and tools from MCP servers. Custom tools execute in your application and bypass these policies so they can emit their own agent.custom_tool_use request. See Custom tools for what your application must handle. A guardrail can tighten a policy but never relax it.
Get your registry endpoint
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.
Policy types
| Policy | Behavior |
|---|---|
always_allow | The tool runs with no confirmation. |
always_ask | The session pauses and waits for your decision before the tool runs. |
Each toolset kind has its own default, chosen so that unfamiliar tools are not surprising:
| Toolset | Default when you set no policy |
|---|---|
agent_toolset | always_allow |
mcp_toolset | always_ask |
MCP toolsets default to asking because an MCP server can add tools after you declared it. A server that gains a delete_project tool next week should not start calling it unprompted.
A policy controls when an enabled tool runs. It is not a way to remove a tool - to keep a capability away from an agent, leave the toolset off the agent entirely.
always_deny appears in session transcripts as the outcome for tools the runtime refuses - for example, a tool from an MCP server the agent does not declare. It is not a value you can set: sending it in permission_policy returns 400, from schema validation rather than from a hand-written check, so the message names the failing path (for example tools.0.configs: Invalid input) rather than listing the two accepted values.
Set a policy
Policies live in the agent's tools array, so you set them when you create or update the agent. default_config.permission_policy sets the baseline for a whole toolset:
ork agent create \
--name "repo-assistant" \
--model claude-sonnet-4-6 \
--tool-json '{
"type": "agent_toolset",
"default_config": { "permission_policy": { "type": "always_ask" } }
}'configs overrides the default for individual tools. A common shape is to allow the toolset broadly but gate the one tool that can do arbitrary damage:
{
"type": "agent_toolset",
"default_config": { "permission_policy": { "type": "always_allow" } },
"configs": [
{ "name": "bash", "permission_policy": { "type": "always_ask" } }
]
}MCP toolsets take the same fields, with configs[].name set to a tool name the server reports. This trusts one server's tools without confirmation:
{
"mcp_servers": [
{ "name": "tickets", "type": "url", "url": "https://mcp.example.com/tickets" }
],
"tools": [
{ "type": "agent_toolset" },
{
"type": "mcp_toolset",
"mcp_server_name": "tickets",
"default_config": { "permission_policy": { "type": "always_allow" } }
}
]
}permission_policy set on an mcp_servers[] entry is silently stripped - the request succeeds and the policy has no effect. Policies belong on the matching mcp_toolset entry in tools.
How a policy is resolved
For each tool call, the runtime takes the first match:
- A
configsentry naming that exact tool. - For MCP tools, a policy on the toolset for that server.
- The toolset's
default_config. - The toolset default (
always_allowforagent_toolset,always_askformcp_toolset).
A tool from an MCP server the agent does not declare is refused outright.
Sessions pin the agent version they were created with, so changing a policy affects sessions created afterward. Running sessions keep the configuration they started with.
Answer a confirmation request
When the agent calls a tool governed by always_ask:
- The session emits
agent.tool_use(oragent.mcp_tool_usefor an MCP tool) carrying the call'sid,name, andinput. - The session emits
session.status_idlewithstop_reason.typeset torequires_action. Thestop_reason.event_idsarray holds the event IDs that are blocking. The session waits indefinitely. - You send a
user.tool_confirmationevent for each blocking ID, settingtool_use_idto that event's ID andresulttoallowordeny. Several confirmations can go in one request. - Once nothing is blocking, the session resumes. Allowed tools run. Denied tools do not, and the agent receives a result explaining the refusal, including your
deny_message.
This is run-time work, so it needs a session. sessionId and $SESSION_ID below are the id from creating a session against this agent. The tabs differ in where the blocking IDs come from: the TypeScript tab reads them off the streamed session.status_idle event, while the CLI and cURL tabs take $EVENT_ID as given, because pulling it out of the stream in shell is a separate step - see Events and streaming.
# Allow
ork agent sessions events send tool-confirmation \
--session "$SESSION_ID" \
--tool-use-id "$EVENT_ID" \
--decision allow
# Or deny, with a reason the agent can act on
ork agent sessions events send tool-confirmation \
--session "$SESSION_ID" \
--tool-use-id "$EVENT_ID" \
--decision deny \
--deny-message "Don't touch the production database. Use the staging copy."deny_message is only valid alongside result: "deny" - sending it with allow returns 400. Use it to tell the agent what to do instead, rather than leaving it to guess why the call failed.
See Events and streaming for the full event stream, and Monitor sessions for spotting sessions that have been waiting on a decision.
Custom tools bypass the policy map: default_config.permission_policy: always_ask and default_config.enabled: false do not gate them. Your application receives agent.custom_tool_use directly. colocated mode does not fully support permission policies yet; see the claude_code harness for how it treats them.
Custom tools
Custom tools are executed by your application: the agent emits agent.custom_tool_use and waits for your user.custom_tool_result, so you decide whether to run the tool as part of handling the call.
On claude_agent_sdk, custom tools are registered on the same in-process orca MCP server as the built-in toolset and resolve as mcp__orca__<name>. The harness nevertheless allows every declared custom tool through, before any exact-name or mcp__orca__* policy can create a generic user.tool_confirmation gate. That preserves the custom-tool callback lifecycle. Put approval logic in the application that handles agent.custom_tool_use.
Permissions
Policies are part of the agent resource, and tool confirmations are session events. Treat every Workspace API key as full access to its Workspace's resources, and separate access with Workspaces. See Control registry access.