Permission System
概述
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 覆盖。
详细决策流程
注意
Deny 规则与危险路径检查是不可绕过的 —— 即使在 BYPASS 模式下也照常生效。
Permission Mode
PermissionMode 枚举(io.agentscope.core.permission.PermissionMode)支持以下模式,分别适配不同的部署场景:
| Mode | 行为 | 适用场景 |
|---|
DEFAULT | 所有操作都需要显式规则或用户确认 | 最安全,推荐默认值 |
ACCEPT_EDITS | 自动放行工作目录内的文件操作 | 用户在场的活跃开发 |
EXPLORE | 只读:放行读、拒绝所有写与命令 | 代码探索、规划 |
BYPASS | 放行一切(deny / ask 规则仍生效) | 完全可信的沙箱 |
DONT_ASK | 把所有 ASK 转为 DENY | 无人值守 / 计划任务 |
可以在创建 agent 时通过 permissionContext(...) 设置 mode:
初始化时配置
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();
ACCEPT_EDITS 配合工作目录
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();
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() 把规则传入:
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 会自动写入引擎:
import io.agentscope.core.event.ConfirmResult;
ConfirmResult result =
new ConfirmResult(
true,
toolCall,
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() 实现自己的检查逻辑:
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));
}
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。
交互流程
配置 ASK 规则,标记需要人工确认的工具
Agent 遇到 ASK 工具时暂停,返回 PERMISSION_ASKING
从返回的 Msg 中提取 ToolUseBlock(状态为 ASKING),向用户展示
构建 ConfirmResult,附在新消息的 metadata 中恢复 agent
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;
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();
Msg result = agent.call(new UserMessage("Delete /tmp/important.txt")).block();
if (result != null && result.getGenerateReason() == GenerateReason.PERMISSION_ASKING) {
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()));
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。
Streaming 模式
使用 streamEvents() 时,不需要从返回的 Msg 提取 ToolUseBlock —— 通过事件流直接获得 RequireUserConfirmEvent,它携带了待确认的工具调用列表:
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) {
List<ToolUseBlock> pending = confirmEvent.getToolCalls();
pending.forEach(t ->
System.out.println("Pending: " + t.getName() + " " + t.getInput()));
}
})
.blockLast();
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:
PermissionContextState headless =
PermissionContextState.builder()
.mode(PermissionMode.DONT_ASK)
.addAllowRule(
"safe_read",
new PermissionRule(
"safe_read", null, PermissionBehavior.ALLOW, "policy"))
.build();
完整可运行示例:agentscope-examples/documentation/.../hitl/PermissionHITLExample.java。
常见配方
下面的示例展示了如何为常见部署场景配置 permissionContext。每个配方把一种 mode 与一组规则结合,匹配特定的使用场景。
只读探索
PermissionContextState explore =
PermissionContextState.builder()
.mode(PermissionMode.EXPLORE)
.build();
ReActAgent explorer =
ReActAgent.builder()
.name("explorer")
.sysPrompt("...")
.model(model)
.permissionContext(explore)
.build();
无人值守自动化
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();
阻止危险命令
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();