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

# Permission policies

> Control which tool calls run, ask, or deny.

Permission policies control what happens when an Agent wants to call a tool. Built-in and MCP tool calls are evaluated as `allow`, `ask`, or `deny`; client-side custom tools always pause so your application can execute them and return the result.

## Runtime behavior

When a tool call is projected into the event stream:

| Event                   | Meaning                         | Key fields                                                       |
| ----------------------- | ------------------------------- | ---------------------------------------------------------------- |
| `agent.tool_use`        | Built-in tool call              | `id`, `name`, `input`, `evaluated_permission`                    |
| `agent.mcp_tool_use`    | MCP tool call                   | `id`, `name`, `input`, `mcp_server_name`, `evaluated_permission` |
| `agent.custom_tool_use` | Client-side custom tool request | `id`, `name`, `input`                                            |

`evaluated_permission` can be:

| Value   | Behavior                                               |
| ------- | ------------------------------------------------------ |
| `allow` | The platform executes the tool directly                |
| `ask`   | The turn pauses and waits for `user.tool_confirmation` |
| `deny`  | The platform returns a denied tool result to the Agent |

Custom tools do not support `permission_policy`; they are executed by your client and resolved with `user.custom_tool_result`.

## Configure policies on an agent

Configure built-in and MCP tool permissions inside the Agent `tools` array:

```json theme={null}
{
  "tools": [
    {
      "type": "agent_toolset_20260401",
      "enabled_tools": ["Bash", "Read", "Write"],
      "configs": [
        {"name": "Read", "permission_policy": {"type": "always_allow"}},
        {"name": "Write", "permission_policy": {"type": "always_ask"}},
        {"name": "Bash", "permission_policy": {"type": "always_deny"}}
      ]
    },
    {
      "type": "mcp_toolset",
      "mcp_server_name": "weather-service",
      "configs": [
        {"name": "get_forecast", "permission_policy": {"type": "always_ask"}}
      ]
    }
  ]
}
```

| Location                              | Applies to     | Description                                                                                                                                                 |
| ------------------------------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tools[].configs[].permission_policy` | One named tool | Per-tool override. For built-in tools, `name` is a built-in tool name such as `Read`; for MCP tools, `name` is the raw tool name exposed by that MCP server |
| `tools[].configs[].enabled`           | One named tool | Set to `false` to disable and deny that tool. When using `enabled_tools`, keep disabled tools out of that allowlist                                         |

`permission_policy.type` values:

| Value          | Runtime result                                                                |
| -------------- | ----------------------------------------------------------------------------- |
| `always_allow` | `evaluated_permission: "allow"`                                               |
| `always_ask`   | `evaluated_permission: "ask"` and the turn waits for `user.tool_confirmation` |
| `always_deny`  | `evaluated_permission: "deny"`                                                |

## Pending action flow

When a tool call needs human or client input:

1. The stream emits `agent.tool_use` or `agent.custom_tool_use`.
2. The stream emits `session.status_idle` with `stop_reason.type: "requires_action"`.
3. `stop_reason.event_ids` lists the event IDs that need a response.
4. Your client sends a response event to `POST /api/v1/cloud/sessions/{session_id}/events`.
5. The Agent continues the same turn.

```json theme={null}
{
  "type": "session.status_idle",
  "status": "idle",
  "stop_reason": {
    "type": "requires_action",
    "event_ids": ["evt_01JZ6Q3FB6SG8F7J1M2N"]
  }
}
```

Pending actions do not automatically time out. They remain pending until your client resolves them or the session/turn is cancelled.

## Confirm a tool call

Use the `id` of the `agent.tool_use` event as `tool_use_id`. This is an `evt_...` event ID, not the model provider's internal tool-use ID.

<Tabs>
  <Tab title="Approve">
    ```bash theme={null}
    curl -X POST https://api.qoder.com/api/v1/cloud/sessions/sess_abc123/events \
      -H "Authorization: Bearer $QODER_PAT" \
      -H "Content-Type: application/json" \
      -d '{
        "events": [
          {
            "type": "user.tool_confirmation",
            "tool_use_id": "evt_01JZ6Q3FB6SG8F7J1M2N",
            "result": "allow"
          }
        ]
      }'
    ```
  </Tab>

  <Tab title="Deny">
    ```bash theme={null}
    curl -X POST https://api.qoder.com/api/v1/cloud/sessions/sess_abc123/events \
      -H "Authorization: Bearer $QODER_PAT" \
      -H "Content-Type: application/json" \
      -d '{
        "events": [
          {
            "type": "user.tool_confirmation",
            "tool_use_id": "evt_01JZ6Q3FB6SG8F7J1M2N",
            "result": "deny",
            "deny_message": "Only inspect files; do not delete them."
          }
        ]
      }'
    ```
  </Tab>
</Tabs>

## Complete a custom tool

Custom tools are configured on the Agent with `type: "custom"`; see [Agent Tools](/cloud-agents/tools). When the Agent requests one, execute it in your application and respond with the `agent.custom_tool_use` event ID:

```bash theme={null}
curl -X POST https://api.qoder.com/api/v1/cloud/sessions/sess_abc123/events \
  -H "Authorization: Bearer $QODER_PAT" \
  -H "Content-Type: application/json" \
  -d '{
    "events": [
      {
        "type": "user.custom_tool_result",
        "custom_tool_use_id": "evt_01JZ6R1V9Z8K2M3N4P5Q",
        "content": [{"type": "text", "text": "Order status: shipped"}]
      }
    ]
  }'
```

The `content` value can be a string, one text block, or an array of text blocks. The returned event is stored with content-block shape.

## FAQ

**Q: Can one turn require multiple responses?** A: Yes. `stop_reason.event_ids` may contain multiple event IDs; respond to each pending tool request.

**Q: Do pending actions time out?** A: No. They stay pending until resolved or until the session/turn is cancelled.

**Q: Can custom tools use `permission_policy`?** A: No. Custom tools are client-side, so the client is responsible for executing or refusing them.

## Next steps

<CardGroup cols={2}>
  <Card title="Tools" icon="wrench" href="/cloud-agents/tools">
    Equip your agent with built-in, MCP, and custom tools.
  </Card>

  <Card title="Session event stream" icon="wave-pulse" href="/cloud-agents/events-stream">
    Stream agent thinking, messages, and tool calls over SSE.
  </Card>

  <Card title="Start a session" icon="play" href="/cloud-agents/sessions">
    Run an agent against an environment.
  </Card>

  <Card title="Agent setup" icon="user-gear" href="/cloud-agents/define-agent">
    Review Agent configuration.
  </Card>
</CardGroup>
