Overview
The permission system (io.agentscope.core.permission) intercepts every tool call the agent makes and produces one of three decisions: ALLOW, DENY, or ASK (request user confirmation).
It combines static configuration with dynamic runtime analysis. Three components together decide the outcome:
- Rules — explicit allow / deny / ask patterns per tool and command, with the highest priority. Rules come from two sources: static configuration in
PermissionContextState, or suggested rules added dynamically when the user accepts them at an ASK prompt. Suggested rules are auto-generated from the current invocation — once accepted, identical future calls are auto-handled without prompting. - Mode — a global static policy set at configuration time; decides the default behaviour for calls that match no rule (e.g.
EXPLOREmakes the agent read-only,DONT_ASKsilently denies anything not matching a rule). - Built-in Checks — runtime analysis performed by the tool itself based on the actual input (implemented in
ToolBase#checkPermissions). These are runtime checks rather than preconfigured patterns, so they are non-bypassable — they are not subject to mode or rules.
Detailed decision flow
Detailed decision flow
Deny rules and dangerous-path checks are non-bypassable — they apply even in
BYPASS mode.Permission Mode
ThePermissionMode enum (io.agentscope.core.permission.PermissionMode) supports the following modes:
Set the mode on the agent builder via
permissionContext(...):
- Initial config
- ACCEPT_EDITS with working dir
Permission Rule
PermissionRule (a record) maps a tool plus a specific call pattern to one of three behaviours: ALLOW, DENY, ASK.
Each rule has the fields below. When the engine evaluates a rule, it calls the tool’s matchRule() with the ruleContent and the actual input to decide whether the rule fires.
-
toolName·String· required — The tool name the rule applies to:todo_write(built-in) or any custom tool name. -
ruleContent·String | null· optional — Match pattern — semantics depend on the tool, interpreted by the tool’smatchRule().nullmeans the rule matches every invocation of that tool. -
behavior·PermissionBehavior· required —ALLOW,DENY,ASK, orPASSTHROUGH -
source·String· required — Origin of the rule:"userSettings","projectSettings","session","suggested", …
Configuring rules
At init time — pass rules throughPermissionContextState.builder():
ConfirmResult and the agent will write them into the engine:
agentscope-examples/documentation/.../tool/PermissionContextExample.java, hitl/PermissionHITLExample.java.
Built-in checks
Every tool implementscheckPermissions(toolInput, context) (on ToolBase) — a runtime check on the actual input that returns Mono<PermissionDecision>. These checks cannot be bypassed: they apply regardless of mode or rules.
PermissionDecision provides four static factories: allow(message) / deny(message) / ask(message) / passthrough(message). Returning PASSTHROUGH means “I’m not deciding — let the engine evaluate rules and mode.”
A custom tool can override checkPermissions() for its own logic:
Dangerous-path protection
TheToolBase dangerous-path list is maintained in ToolDangerousPathConstants. A custom tool can append more paths via the dangerousFiles / dangerousDirectories attributes on @Tool. Once matched, the path triggers ASK even in BYPASS mode.
HITL integration
When the permission engine returns an ASK decision for a tool call, the agent pauses instead of executing and returns a response withGenerateReason.PERMISSION_ASKING. The returned Msg contains the ToolUseBlocks in ASKING state. The caller extracts them, presents the pending operation to the user, and resumes the agent with ConfirmResult objects.
Interaction flow
- Configure ASK rules for tools that require human confirmation
- Agent pauses on ASK tools, returning
PERMISSION_ASKING - Extract
ToolUseBlocks (withASKINGstate) from the returnedMsgand show them to the user - Build
ConfirmResultobjects and attach them to the resume message via metadata
All tools denied
When the user denies all tool calls from a reasoning step in the confirmation UI, the agent continues to the next reasoning iteration by default — the model only sees “Permission denied by user” tool results, which often leads to unhelpful reasoning. To stop the agent in this scenario, wire up anonActing middleware that observes AllToolsDeniedEvent and emits a RequestStopEvent. After stopping, Msg.getGenerateReason() returns ALL_TOOLS_DENIED.
See Middleware — Stop agent when all tools are denied for the implementation.
Streaming mode
When usingstreamEvents(), you don’t need to extract ToolUseBlocks from the returned Msg — the event stream delivers a RequireUserConfirmEvent that carries the pending tool calls directly:
streamEvents(List.of(resumeMsg)), the stream includes a
UserConfirmResultEvent before the resumed tool execution. Use its replyId to associate
the accepted results with the earlier RequireUserConfirmEvent; the event contains only
the confirmations included in that resume call.
Comparison of the two modes:
Unattended mode
In CI or cron-job scenarios with no human operator, set the mode toDONT_ASK so that all ASK decisions degrade to DENY automatically:
agentscope-examples/documentation/.../hitl/PermissionHITLExample.java.
Common recipes
The examples below show how to configurepermissionContext for typical deployment scenarios. Each recipe combines a mode with a rule set tuned for one use case.
- Read-only exploration
- Unattended automation
- Block dangerous commands