Skip to main content

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. EXPLORE makes the agent read-only, DONT_ASK silently 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.
Deny rules and dangerous-path checks are non-bypassable — they apply even in BYPASS mode.

Permission Mode

The PermissionMode enum (io.agentscope.core.permission.PermissionMode) supports the following modes: Set the mode on the agent builder via permissionContext(...):

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’s matchRule(). null means the rule matches every invocation of that tool.
  • behavior · PermissionBehavior · requiredALLOW, DENY, ASK, or PASSTHROUGH
  • source · String · required — Origin of the rule: "userSettings", "projectSettings", "session", "suggested", …

Configuring rules

At init time — pass rules through PermissionContextState.builder():
At runtime via suggested rules — when the permission system returns ASK, it auto-generates suggested rules based on the current invocation. Pass the accepted rules in ConfirmResult and the agent will write them into the engine:
Runnable examples: agentscope-examples/documentation/.../tool/PermissionContextExample.java, hitl/PermissionHITLExample.java.

Built-in checks

Every tool implements checkPermissions(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

The ToolBase 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 with GenerateReason.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

  1. Configure ASK rules for tools that require human confirmation
  2. Agent pauses on ASK tools, returning PERMISSION_ASKING
  3. Extract ToolUseBlocks (with ASKING state) from the returned Msg and show them to the user
  4. Build ConfirmResult objects 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 an onActing 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 using streamEvents(), you don’t need to extract ToolUseBlocks from the returned Msg — the event stream delivers a RequireUserConfirmEvent that carries the pending tool calls directly:
If the resume is sent with 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 to DONT_ASK so that all ASK decisions degrade to DENY automatically:
Full runnable example: agentscope-examples/documentation/.../hitl/PermissionHITLExample.java.

Common recipes

The examples below show how to configure permissionContext for typical deployment scenarios. Each recipe combines a mode with a rule set tuned for one use case.