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

# Permission System

> 精细控制 agent 可以执行哪些 tool、何时执行

## 概述

Permission system（`io.agentscope.core.permission`）拦截 agent 的每一次工具调用，给出三种决策之一：**允许（ALLOW）** 执行、**拒绝（DENY）** 执行，或者**询问用户（ASK）** 确认。

它把静态配置与动态运行时分析组合起来。三个组件共同决定结果：

* **Rules** —— 针对每个 tool 与命令的显式 allow / deny / ask 模式，最高优先级。规则有两种来源：在 `PermissionContextState` 中静态预配置，或在 ASK 提示中由用户接受**建议规则**而动态加入。建议规则由本次工具调用自动生成 —— 一旦接受，将来相同的调用便会被自动处理，不再询问。
* **Mode** —— 配置阶段设定的全局静态策略；决定所有不命中任何规则的调用的默认行为（例如 `EXPLORE` 让 agent 进入只读；`DONT_ASK` 静默拒绝未命中的调用）。
* **Built-in Checks** —— 由 tool 自身在运行时基于真实输入做的动态分析（在 `ToolBase#checkPermissions` 中实现）。这些是运行时检查而非预配置模式，因此**不可绕过**，不受 mode 或 rules 覆盖。

```mermaid theme={null}
sequenceDiagram
    participant LLM
    participant PS as Permission System
    participant Tool
    participant User

    LLM->>PS: Tool Call
    Note over PS: Built-in Checks · Rules · Mode

    alt ALLOW
        PS->>Tool: execute
        Tool->>LLM: result
    else DENY
        PS->>LLM: denied
    else ASK + Suggestions
        PS->>User: ASK + Suggestions
        alt User approves
            User->>Tool: allow
            Tool->>LLM: result
            User-->>PS: accept suggested rule
        else User denies
            User->>PS: deny
            PS->>LLM: denied
        end
    end
```

<Accordion title="详细决策流程">
  ```mermaid theme={null}
  flowchart TD
      A([Tool Call]) --> B{Deny Rules?}
      B -->|Match| DENY([DENY])
      B -->|No Match| C{Ask Rules?}
      C -->|Match| ASK1([ASK])
      C -->|No Match| D{Tool-Specific Checks}
      D -->|EXPLORE + write op| DENY
      D -->|Dangerous path| ASK2([ASK])
      D -->|Pass| E{Allow Rules?}
      E -->|Match| ALLOW([ALLOW])
      E -->|No Match| F{"ACCEPT_EDITS + safe file op?"}
      F -->|Yes| ALLOW
      F -->|No| G{"Read-only Bash command?"}
      G -->|Yes| ALLOW
      G -->|No| H{BYPASS mode?}
      H -->|Yes| ALLOW
      H -->|No| I{DONT_ASK mode?}
      I -->|Yes| DENY
      I -->|No| ASK3([ASK])
      ASK1 --> S[Generate Suggestions]
      ASK2 --> S
      ASK3 --> S
      S --> U{User Confirms?}
      U -->|Approve| ALLOW
      U -->|Deny| DENY
      U -->|Apply Rule| R[Update Context] --> ALLOW
      style DENY fill:#ff6b6b,color:#fff
      style ALLOW fill:#51cf66,color:#fff
      style ASK1 fill:#ffd43b,color:#333
      style ASK2 fill:#ffd43b,color:#333
      style ASK3 fill:#ffd43b,color:#333
  ```
</Accordion>

<Note>
  Deny 规则与危险路径检查是**不可绕过的** —— 即使在 `BYPASS` 模式下也照常生效。
</Note>

## Permission Mode

`PermissionMode` 枚举（`io.agentscope.core.permission.PermissionMode`）支持以下模式，分别适配不同的部署场景：

| Mode           | 行为                     | 适用场景        |
| -------------- | ---------------------- | ----------- |
| `DEFAULT`      | 所有操作都需要显式规则或用户确认       | 最安全，推荐默认值   |
| `ACCEPT_EDITS` | 自动放行工作目录内的文件操作         | 用户在场的活跃开发   |
| `EXPLORE`      | 只读：放行读、拒绝所有写与命令        | 代码探索、规划     |
| `BYPASS`       | 放行一切（deny / ask 规则仍生效） | 完全可信的沙箱     |
| `DONT_ASK`     | 把所有 ASK 转为 DENY        | 无人值守 / 计划任务 |

可以在创建 agent 时通过 `permissionContext(...)` 设置 mode：

<Tabs>
  <Tab title="初始化时配置">
    ```java theme={null}
    import io.agentscope.core.ReActAgent;
    import io.agentscope.core.permission.PermissionContextState;
    import io.agentscope.core.permission.PermissionMode;

    PermissionContextState permCtx =
            PermissionContextState.builder()
                    .mode(PermissionMode.DEFAULT)
                    .build();

    ReActAgent agent =
            ReActAgent.builder()
                    .name("my_agent")
                    .sysPrompt("...")
                    .model(model)
                    .permissionContext(permCtx)
                    .build();
    ```
  </Tab>

  <Tab title="ACCEPT_EDITS 配合工作目录">
    ```java theme={null}
    import io.agentscope.core.permission.AdditionalWorkingDirectory;
    import io.agentscope.core.permission.PermissionContextState;
    import io.agentscope.core.permission.PermissionMode;

    PermissionContextState permCtx =
            PermissionContextState.builder()
                    .mode(PermissionMode.ACCEPT_EDITS)
                    .addWorkingDirectory(
                            "/my/project",
                            new AdditionalWorkingDirectory("/my/project", "userSettings"))
                    .build();
    ```
  </Tab>
</Tabs>

## Permission Rule

`PermissionRule`（record）把某个 tool 与具体的调用模式映射到三种行为之一：`ALLOW`、`DENY`、`ASK`。

每条规则由下述字段组成。当权限引擎评估一条规则时，它会用 `ruleContent` 与实际调用入参调用该 tool 的 `matchRule()` 方法，判断规则是否命中。

* **`toolName` · `String` · *required*** — 规则适用的 tool 名：内置 `todo_write`，或任意自定义 tool 名。

* **`ruleContent` · `String | null` · *optional*** — 匹配模式 —— 语义随 `toolName` 变化，由该 tool 的 `matchRule()` 方法解释。`null` 表示对该 tool 的所有调用均匹配。

* **`behavior` · `PermissionBehavior` · *required*** — `ALLOW`、`DENY`、`ASK` 或 `PASSTHROUGH`

* **`source` · `String` · *required*** — 规则来源：`"userSettings"`、`"projectSettings"`、`"session"`、`"suggested"` 等。

### 配置规则

**初始化时** —— 通过 `PermissionContextState.builder()` 把规则传入：

```java theme={null}
import io.agentscope.core.permission.PermissionBehavior;
import io.agentscope.core.permission.PermissionContextState;
import io.agentscope.core.permission.PermissionMode;
import io.agentscope.core.permission.PermissionRule;

PermissionContextState permCtx =
        PermissionContextState.builder()
                .mode(PermissionMode.DEFAULT)
                .addAllowRule(
                        "safe_read",
                        new PermissionRule(
                                "safe_read", null, PermissionBehavior.ALLOW, "userSettings"))
                .addAskRule(
                        "dangerous_delete",
                        new PermissionRule(
                                "dangerous_delete",
                                null,
                                PermissionBehavior.ASK,
                                "userSettings"))
                .addDenyRule(
                        "drop_table",
                        new PermissionRule(
                                "drop_table", null, PermissionBehavior.DENY, "userSettings"))
                .build();
```

**运行时通过建议规则** —— 当权限系统返回 ASK 时，会基于本次调用自动生成建议规则。把已接受的规则附在 `ConfirmResult.acceptedRules` 中回传，agent 会自动写入引擎：

```java theme={null}
import io.agentscope.core.event.ConfirmResult;

// ASK 决策中包含基于本次调用生成的 suggestedRules（位于 ToolUseBlock 上）。
// 接受建议时，把它放入结果即可：
ConfirmResult result =
        new ConfirmResult(
                /* confirmed = */ true,
                /* toolCall  = */ toolCall,
                /* rules     = */ toolCall.getSuggestedRules());
```

完整可运行示例：`agentscope-examples/documentation/.../tool/PermissionContextExample.java`、`hitl/PermissionHITLExample.java`。

## Built-in Checks

每个 tool 都实现了一个 `checkPermissions(toolInput, context)` 方法（位于 `ToolBase`），在运行时基于真实调用入参执行检查，返回 `Mono<PermissionDecision>`。这些检查不可绕过 —— 无论 mode 或 rules 是什么，它们都生效。

`PermissionDecision` 提供四个静态构造方法：`allow(message)` / `deny(message)` / `ask(message)` / `passthrough(message)`。返回 `PASSTHROUGH` 表示「我不强加判断，交给引擎按 rules / mode 评估」。

自定义 tool 可以重写 `checkPermissions()` 实现自己的检查逻辑：

```java theme={null}
import io.agentscope.core.permission.PermissionDecision;
import io.agentscope.core.tool.ToolBase;
import io.agentscope.core.tool.ToolExecutionContext;
import java.util.Map;
import reactor.core.publisher.Mono;

public class MyTool extends ToolBase {

    public MyTool() {
        super(
                ToolBase.builder()
                        .name("MyTool")
                        .description("...")
                        .readOnly(false));
    }

    @Override
    public Mono<PermissionDecision> checkPermissions(
            Map<String, Object> toolInput, ToolExecutionContext context) {
        Object target = toolInput.get("target");

        // 自定义安全检查：阻止操作生产资源
        if (target instanceof String s && s.startsWith("prod-")) {
            return Mono.just(
                    PermissionDecision.ask("Operation targets production resource: " + s));
        }

        // 返回 PASSTHROUGH 让引擎继续按 rules / mode 评估
        return Mono.just(PermissionDecision.passthrough("default"));
    }
}
```

### 危险路径保护

`ToolBase` 内置的危险路径列表通过 `ToolDangerousPathConstants` 维护，自定义 tool 可以在 `@Tool` 注解上追加 `dangerousFiles` / `dangerousDirectories` 把额外路径并入受保护集合。命中后即使在 `BYPASS` 模式下也会强制 ASK。

| 类别       | 默认受保护示例                                                    |
| -------- | ---------------------------------------------------------- |
| Shell 配置 | `.bashrc`、`.zshrc`、`.bash_profile`、`.profile`              |
| Git 配置   | `.gitconfig`、`.gitmodules`                                 |
| SSH      | `.ssh/config`、`.ssh/authorized_keys`、`id_rsa`、`id_ed25519` |
| 凭证       | `.env`、`.env.local`、`.npmrc`、`.pypirc`、`.aws/credentials`  |
| 目录       | `.git/`、`.ssh/`、`.aws/`、`.kube/`                           |

## 结合 HITL

当权限引擎对某个工具调用返回 ASK 决策时，agent 不会直接执行，而是暂停并返回一个 `GenerateReason.PERMISSION_ASKING` 的响应。返回的 `Msg` 中包含处于 `ASKING` 状态的 `ToolUseBlock`，调用方据此向用户展示待确认的操作，收集决策后通过 `ConfirmResult` 恢复 agent。

### 交互流程

1. 配置 ASK 规则，标记需要人工确认的工具
2. Agent 遇到 ASK 工具时暂停，返回 `PERMISSION_ASKING`
3. 从返回的 `Msg` 中提取 `ToolUseBlock`（状态为 `ASKING`），向用户展示
4. 构建 `ConfirmResult`，附在新消息的 metadata 中恢复 agent

```java theme={null}
import io.agentscope.core.event.ConfirmResult;
import io.agentscope.core.message.GenerateReason;
import io.agentscope.core.message.Msg;
import io.agentscope.core.message.MsgRole;
import io.agentscope.core.message.ToolCallState;
import io.agentscope.core.message.ToolUseBlock;
import io.agentscope.core.message.UserMessage;
import io.agentscope.core.permission.PermissionBehavior;
import io.agentscope.core.permission.PermissionContextState;
import io.agentscope.core.permission.PermissionMode;
import io.agentscope.core.permission.PermissionRule;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

// 1. 配置权限：safe_read 自动放行，dangerous_delete 需要确认
PermissionContextState permCtx =
        PermissionContextState.builder()
                .mode(PermissionMode.DEFAULT)
                .addAllowRule(
                        "safe_read",
                        new PermissionRule(
                                "safe_read", null, PermissionBehavior.ALLOW, "policy"))
                .addAskRule(
                        "dangerous_delete",
                        new PermissionRule(
                                "dangerous_delete", null, PermissionBehavior.ASK, "policy"))
                .build();

ReActAgent agent =
        ReActAgent.builder()
                .name("GuardedAgent")
                .sysPrompt("...")
                .model(model)
                .toolkit(toolkit)
                .permissionContext(permCtx)
                .build();

// 2. 调用 agent
Msg result = agent.call(new UserMessage("Delete /tmp/important.txt")).block();

// 3. 检查是否需要用户确认
if (result != null && result.getGenerateReason() == GenerateReason.PERMISSION_ASKING) {
    // 从返回的 Msg 中提取待确认的 ToolUseBlock
    List<ToolUseBlock> askingTools =
            result.getContent().stream()
                    .filter(b -> b instanceof ToolUseBlock)
                    .map(ToolUseBlock.class::cast)
                    .filter(t -> t.getState() == ToolCallState.ASKING)
                    .toList();

    // 向用户展示
    askingTools.forEach(t -> System.out.println("Pending: " + t.getName() + " " + t.getInput()));

    // 4. 收集用户决策，构建 ConfirmResult 恢复 agent
    boolean approved = askUser();
    List<ConfirmResult> confirmResults =
            askingTools.stream()
                    .map(t -> new ConfirmResult(approved, t))
                    .toList();

    Map<String, Object> meta = new HashMap<>();
    meta.put(Msg.METADATA_CONFIRM_RESULTS, confirmResults);
    Msg resumeMsg =
            Msg.builder()
                    .name("user")
                    .role(MsgRole.USER)
                    .textContent(approved ? "approved" : "denied")
                    .metadata(meta)
                    .build();

    Msg finalResult = agent.call(List.of(resumeMsg)).block();
}
```

### 全部工具被拒绝

当用户在确认界面拒绝了本轮推理产出的**全部**工具调用时，agent 默认会继续下一轮推理 —— 此时模型只能看到 "Permission denied by user" 的工具结果，容易产生无效推理。

如果需要在这种场景下停止 agent，可以装备一个 `onActing` middleware 观察 `AllToolsDeniedEvent` 并发出 `RequestStopEvent`。停止后 `Msg.getGenerateReason()` 返回 `ALL_TOOLS_DENIED`。

具体实现参见 [Middleware — 全部工具被拒绝时停止 agent](/v2/zh/docs/building-blocks/middleware#全部工具被拒绝时停止-agent)。

### Streaming 模式

使用 `streamEvents()` 时，不需要从返回的 `Msg` 提取 `ToolUseBlock` —— 通过事件流直接获得 `RequireUserConfirmEvent`，它携带了待确认的工具调用列表：

```java theme={null}
import io.agentscope.core.event.AgentEvent;
import io.agentscope.core.event.ConfirmResult;
import io.agentscope.core.event.RequireUserConfirmEvent;
import io.agentscope.core.message.Msg;
import io.agentscope.core.message.MsgRole;
import io.agentscope.core.message.ToolUseBlock;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

// 订阅事件流
agent.streamEvents(List.of(new UserMessage("Delete /tmp/important.txt")))
        .doOnNext(event -> {
            if (event instanceof RequireUserConfirmEvent confirmEvent) {
                // 直接从事件中获取待确认的 ToolUseBlocks
                List<ToolUseBlock> pending = confirmEvent.getToolCalls();
                pending.forEach(t ->
                        System.out.println("Pending: " + t.getName() + " " + t.getInput()));

                // 收集用户决策后，在下一次 call 时恢复
                // （存储 pending 列表，在后续 call 时使用）
            }
        })
        .blockLast();

// 恢复方式与 blocking API 相同：构建 ConfirmResult 附在 metadata 中
List<ConfirmResult> confirmResults =
        pendingTools.stream()
                .map(t -> new ConfirmResult(true, t))
                .toList();
Map<String, Object> meta = new HashMap<>();
meta.put(Msg.METADATA_CONFIRM_RESULTS, confirmResults);
Msg resumeMsg =
        Msg.builder()
                .name("user")
                .role(MsgRole.USER)
                .textContent("approved")
                .metadata(meta)
                .build();
agent.call(List.of(resumeMsg)).block();
```

如果使用 `streamEvents(List.of(resumeMsg))` 发起恢复，事件流会在恢复执行工具之前包含
`UserConfirmResultEvent`。使用它的 `replyId` 将本次接受的确认结果关联到之前的
`RequireUserConfirmEvent`；该事件只包含本次恢复消息携带的确认结果。

两种模式的区别：

|         | Blocking `call()`                                        | Streaming `streamEvents()`                      |
| ------- | -------------------------------------------------------- | ----------------------------------------------- |
| 获取待确认工具 | 从返回的 `Msg.getContent()` 中筛选 `ToolUseBlock`（状态为 `ASKING`） | 从 `RequireUserConfirmEvent.getToolCalls()` 直接获取 |
| 恢复方式    | 相同：构建 `ConfirmResult` 附在 metadata 中发起新的 `call()`         | 相同                                              |
| 适用场景    | REST API、简单同步服务                                          | WebSocket、SSE、实时 UI                             |

### 无人值守模式

在 CI 或定时任务等无人值守场景下，把 mode 设为 `DONT_ASK`，所有 ASK 决策会自动降级为 DENY：

```java theme={null}
PermissionContextState headless =
        PermissionContextState.builder()
                .mode(PermissionMode.DONT_ASK)
                .addAllowRule(
                        "safe_read",
                        new PermissionRule(
                                "safe_read", null, PermissionBehavior.ALLOW, "policy"))
                .build();
// ASK 规则命中时自动拒绝，不会阻塞等待
```

完整可运行示例：`agentscope-examples/documentation/.../hitl/PermissionHITLExample.java`。

## 常见配方

下面的示例展示了如何为常见部署场景配置 `permissionContext`。每个配方把一种 mode 与一组规则结合，匹配特定的使用场景。

<Tabs>
  <Tab title="只读探索">
    ```java theme={null}
    // EXPLORE 模式：agent 可以自由调用只读工具，所有写工具会被自动拒绝。
    PermissionContextState explore =
            PermissionContextState.builder()
                    .mode(PermissionMode.EXPLORE)
                    .build();

    ReActAgent explorer =
            ReActAgent.builder()
                    .name("explorer")
                    .sysPrompt("...")
                    .model(model)
                    .permissionContext(explore)
                    .build();
    ```
  </Tab>

  <Tab title="无人值守自动化">
    ```java theme={null}
    import io.agentscope.core.permission.PermissionBehavior;
    import io.agentscope.core.permission.PermissionRule;

    PermissionContextState ci =
            PermissionContextState.builder()
                    .mode(PermissionMode.DONT_ASK)
                    .addAllowRule(
                            "deploy",
                            new PermissionRule(
                                    "deploy", "staging", PermissionBehavior.ALLOW, "project"))
                    .addAllowRule(
                            "git_commit",
                            new PermissionRule(
                                    "git_commit", null, PermissionBehavior.ALLOW, "project"))
                    .build();

    ReActAgent ciAgent =
            ReActAgent.builder()
                    .name("ci_agent")
                    .sysPrompt("...")
                    .model(model)
                    .permissionContext(ci)
                    .build();
    // 只有显式放行的命令会执行；其余调用被静默拒绝
    ```
  </Tab>

  <Tab title="阻止危险命令">
    ```java theme={null}
    PermissionContextState bypassWithDeny =
            PermissionContextState.builder()
                    .mode(PermissionMode.BYPASS)
                    .addDenyRule(
                            "drop_table",
                            new PermissionRule(
                                    "drop_table", null, PermissionBehavior.DENY, "userSettings"))
                    .addDenyRule(
                            "force_push",
                            new PermissionRule(
                                    "force_push", null, PermissionBehavior.DENY, "userSettings"))
                    .build();
    // 除显式拒绝的工具外，其余均放行（deny 规则不可绕过）
    ```
  </Tab>
</Tabs>
