# Template 04 — Permission Policy Decision Matrix

Permission policies control whether each built-in agent tool runs automatically or pauses and waits for your approval. There are exactly two policies:

- **`always_allow`** — The tool executes immediately, no confirmation needed. The session never pauses.
- **`always_ask`** — Before executing, the session emits a `session.status_idle` event with `stop_reason: requires_action`. Your code must respond with a `user.tool_confirmation` event (with `result: "allow"` or `result: "deny"`) before the session resumes.

Getting this wrong in either direction hurts you: `always_allow` on a powerful tool means an agent can overwrite files or run shell commands without any human checkpoint. `always_ask` on every tool turns your autonomous agent into a manual approval queue.

Use this matrix to make a deliberate decision for each tool in each deployment context.

---

## How to Use This Template

1. Identify your risk context from the three columns: **Personal Sandbox**, **Internal Team**, or **Customer-Facing**.
2. Read the recommendation for each tool and the reason behind it.
3. Override any recommendation based on your specific agent's purpose.
4. Copy the JSON block at the end, fill in your decisions, and include it in your agent definition.

---

## The Decision Matrix

### Risk Contexts Defined

| Context | Description | Example |
|---|---|---|
| **Personal Sandbox** | You are the only user. You trust the agent completely and want maximum autonomy. Mistakes are recoverable. | Developer testing a new agent on their own machine with test data. |
| **Internal Team** | Agents run on behalf of employees within your organization. Mistakes can affect real business data but stay internal. | A content agent that publishes to an internal CMS, or a data agent that writes to shared spreadsheets. |
| **Customer-Facing** | Agents run on behalf of or interact with external customers. Mistakes are visible externally, may be irreversible, and carry reputational or financial risk. | A support agent that emails customers, a developer agent that commits to a client's repository. |

---

### `bash` — Execute shell commands

| Context | Recommended policy | Reason |
|---|---|---|
| Personal Sandbox | `always_allow` | You trust the execution environment and want fast iteration. |
| Internal Team | `always_ask` | Shell commands can delete files, install packages, or make system changes that affect other team members. A one-time approval per unusual command is worth the safety check. |
| Customer-Facing | `always_ask` | Mandatory. Shell execution with customer data or credentials in scope requires explicit human oversight. Consider disabling `bash` entirely if it is not required. |

---

### `read` — Read a file from the local filesystem

| Context | Recommended policy | Reason |
|---|---|---|
| Personal Sandbox | `always_allow` | Reading files is low-risk and required for most tasks. |
| Internal Team | `always_allow` | File reads are non-destructive. Approve freely; restrict sensitive paths in your system prompt instead. |
| Customer-Facing | `always_allow` | Reads are non-destructive. However, ensure your system prompt constrains which paths the agent is allowed to read (e.g., `/workspace/` only). |

---

### `write` — Write a file to the local filesystem

| Context | Recommended policy | Reason |
|---|---|---|
| Personal Sandbox | `always_allow` | Writing files within the container is recoverable. |
| Internal Team | `always_allow` for output paths; consider `always_ask` if the agent can write to shared mounts | Within the session container, writes are isolated. If files are exported to shared storage, apply more scrutiny. |
| Customer-Facing | `always_allow` for container-internal paths; `always_ask` if writing to mounted external storage | Container writes are session-scoped and isolated. Any write that exits the container (via an MCP tool or custom tool) should be gated separately. |

---

### `edit` — Perform string replacement in a file

| Context | Recommended policy | Reason |
|---|---|---|
| Personal Sandbox | `always_allow` | In-place edits during development; mistakes are easy to undo. |
| Internal Team | `always_allow` | Edits are within the isolated session container. Pair with `write: always_allow`. |
| Customer-Facing | `always_allow` | Same as `write`; scoped to the container. If the edited file will be exported to a customer system via a custom tool, gate the export step instead. |

---

### `glob` — Find files by pattern

| Context | Recommended policy | Reason |
|---|---|---|
| Personal Sandbox | `always_allow` | File enumeration has no side effects. |
| Internal Team | `always_allow` | Read-only operation; no data modification risk. |
| Customer-Facing | `always_allow` | Read-only; no risk. |

---

### `grep` — Search file contents with regex

| Context | Recommended policy | Reason |
|---|---|---|
| Personal Sandbox | `always_allow` | Text search has no side effects. |
| Internal Team | `always_allow` | Read-only operation. |
| Customer-Facing | `always_allow` | Read-only. Ensure the agent's access to the filesystem is already constrained by your environment config. |

---

### `web_fetch` — Fetch a URL's content

| Context | Recommended policy | Reason |
|---|---|---|
| Personal Sandbox | `always_allow` | Fetching public URLs is low-risk in development. |
| Internal Team | `always_allow` | Acceptable if URLs are public. Consider `always_ask` if the agent might fetch internal intranet URLs that contain sensitive data. |
| Customer-Facing | `always_allow` | Acceptable for public URLs. Use `limited` networking in your environment config to restrict which hosts the container can reach — that is a stronger control than the permission policy for network access. |

---

### `web_search` — Search the web

| Context | Recommended policy | Reason |
|---|---|---|
| Personal Sandbox | `always_allow` | Searches are read-only and low-risk. |
| Internal Team | `always_allow` | Searches are read-only. Consider `always_ask` if search queries could contain confidential business information you don't want logged externally. |
| Customer-Facing | `always_allow` | Searches are read-only. Ensure your system prompt prevents the agent from including customer PII in search queries. |

---

## Summary Table

| Tool | Personal Sandbox | Internal Team | Customer-Facing |
|---|---|---|---|
| `bash` | `always_allow` | `always_ask` | `always_ask` |
| `read` | `always_allow` | `always_allow` | `always_allow` |
| `write` | `always_allow` | `always_allow` | `always_allow` |
| `edit` | `always_allow` | `always_allow` | `always_allow` |
| `glob` | `always_allow` | `always_allow` | `always_allow` |
| `grep` | `always_allow` | `always_allow` | `always_allow` |
| `web_fetch` | `always_allow` | `always_allow` | `always_allow` |
| `web_search` | `always_allow` | `always_allow` | `always_allow` |

**Observation:** For most built-in tools, `always_allow` is appropriate because the tools operate within the isolated session container. The one tool that consistently warrants `always_ask` in production is `bash`, because shell execution has a much broader blast radius.

**The real guardrails are elsewhere:** Networking restrictions (`limited` mode in your environment config) and your tools whitelist (disabling tools you don't need) provide stronger, more structural protection than `always_ask` permission policies. Use the permission policy primarily as a human checkpoint on high-risk irreversible actions.

---

## Fill-in-the-Blank JSON Block

Copy this into your agent definition's `tools` array. Replace each `"[FILL IN]"` with `"always_allow"` or `"always_ask"`.

```json
{
  "type": "agent_toolset_20260401",
  "default_config": {
    "enabled": false,
    "permission_policy": { "type": "[FILL IN: always_allow or always_ask — your default]" }
  },
  "configs": [
    {
      "name": "bash",
      "enabled": true,
      "permission_policy": { "type": "[FILL IN]" }
    },
    {
      "name": "read",
      "enabled": true,
      "permission_policy": { "type": "[FILL IN]" }
    },
    {
      "name": "write",
      "enabled": true,
      "permission_policy": { "type": "[FILL IN]" }
    },
    {
      "name": "edit",
      "enabled": true,
      "permission_policy": { "type": "[FILL IN]" }
    },
    {
      "name": "glob",
      "enabled": true,
      "permission_policy": { "type": "[FILL IN]" }
    },
    {
      "name": "grep",
      "enabled": true,
      "permission_policy": { "type": "[FILL IN]" }
    },
    {
      "name": "web_fetch",
      "enabled": true,
      "permission_policy": { "type": "[FILL IN]" }
    },
    {
      "name": "web_search",
      "enabled": true,
      "permission_policy": { "type": "[FILL IN]" }
    }
  ]
}
```

**Note:** Setting `enabled: false` for a tool in your whitelist entirely prevents the agent from using it — there is no permission policy to configure for a disabled tool. Only include tools you actually need.

---

## How to Handle `always_ask` in Your Code

When a tool is set to `always_ask`, your event loop must respond to the `requires_action` stop reason. Here is the minimum pattern:

```python
with client.beta.sessions.events.stream(session.id) as stream:
    for event in stream:
        if event.type == "session.status_idle" and event.stop_reason:
            if event.stop_reason.type == "requires_action":
                for event_id in event.stop_reason.event_ids:
                    # Your approval logic here.
                    # To approve: result="allow"
                    # To deny: result="deny", add deny_message="explanation"
                    client.beta.sessions.events.send(
                        session.id,
                        events=[
                            {
                                "type": "user.tool_confirmation",
                                "tool_use_id": event_id,
                                "result": "allow",
                            }
                        ],
                    )
            elif event.stop_reason.type == "end_turn":
                break
```

---

## Worked Example — Internal Blog Publishing Agent

*Fictional business: Calloway Creative, a boutique agency. Their content director, Nneka, is deploying an agent that drafts and publishes posts to their internal CMS via a custom tool.*

**Risk context:** Internal Team

**Decisions:**

| Tool | Enabled? | Policy | Reasoning |
|---|---|---|---|
| `bash` | Yes | `always_ask` | The agent occasionally needs to compress images; Nneka wants to approve each shell command. |
| `read` | Yes | `always_allow` | Reads draft files; no risk. |
| `write` | Yes | `always_allow` | Writes to the container workspace; output review happens before the custom publish tool fires. |
| `edit` | Yes | `always_allow` | In-place draft edits; non-destructive. |
| `glob` | Yes | `always_allow` | File enumeration; no side effects. |
| `grep` | No | N/A | Not needed for this workflow. |
| `web_fetch` | Yes | `always_allow` | Fetches reference URLs for fact-checking. |
| `web_search` | Yes | `always_allow` | Research phase; read-only. |

**The actual publication step** uses a custom tool (`publish_to_cms`). Custom tools are NOT governed by permission policies — when the agent invokes `publish_to_cms`, Nneka's application code receives an `agent.custom_tool_use` event and she has full control over whether to execute it.

```json
{
  "type": "agent_toolset_20260401",
  "default_config": {
    "enabled": false,
    "permission_policy": { "type": "always_allow" }
  },
  "configs": [
    { "name": "bash",       "enabled": true,  "permission_policy": { "type": "always_ask" } },
    { "name": "read",       "enabled": true },
    { "name": "write",      "enabled": true },
    { "name": "edit",       "enabled": true },
    { "name": "glob",       "enabled": true },
    { "name": "web_fetch",  "enabled": true },
    { "name": "web_search", "enabled": true }
  ]
}
```
