Skip to main content
A Python tool is any object satisfying the ToolBase interface. AgentScope ships built-in tools for common operations and exposes the same interface for developers to build their own:

ToolBase Interface

ToolBase is the abstract base class every tool satisfies. The tables below list its attributes and methods. Attributes that describe the tool to the agent and the runtime: Methods that hook into execution and the permission system:

Use Built-in Tools

AgentScope ships a set of ready-to-use tools covering common agent operations. Instantiate them and pass into Toolkit(tools=[...]):
Two more tools, the reset_tools meta tool and the Skill viewer, are auto-registered by Toolkit whenever extra tool groups or skills exist. Developers do not instantiate them directly. See Meta Tool and Skill.

Glob and Grep

Glob searches file names, and Grep searches file contents. When creating these tools directly, set the keyword-only cwd constructor argument to choose their default search directory. For example, these tools search ./project when a call omits path:
Search directory
With cwd configured, path="." searches that directory and path="src" searches its src subdirectory. Absolute search paths keep their meaning. A relative cwd is resolved against the backend’s current directory. Omitting cwd preserves the backend’s existing path behavior, which uses the Python process’s current directory for standalone local tools. LocalWorkspace.list_tools() configures both search tools with the workspace’s workdir automatically.

Bash

The Bash tool executes shell commands and returns stdout/stderr. It implements every optional interface method to provide fine-grained permission control. check_permissions() runs a layered safety analysis on the command string:
  1. Injection risk detection: flags dynamic shell structures ($(...), backticks, process substitution) that cannot be statically analyzed → ASK
  2. Read-only command detection: auto-allows safe commands (git status, ls, cat, grep, docker ps, etc.), including compound commands where every subcommand is read-only → ALLOW
  3. Dangerous command patterns: detects destructive operations (e.g. chmod 777, mkfs) → ASK
  4. Sed constraint check: blocks in-place sed -i against dangerous files → ASK
  5. Dangerous path protection: checks if the command operates on sensitive config files (.bashrc, .ssh/, .env) → ASK
  6. Dangerous removal detection: catches rm / rmdir targeting critical system paths (/, ~, /usr) → ASK
  7. ACCEPT_EDITS mode: auto-allows filesystem commands (mkdir, touch, rm, rmdir, mv, cp, sed) only when every target path resolves inside a configured working directory. A command that touches any path outside the working set (e.g. cp /etc/hosts /tmp/x) falls through to PASSTHROUGH instead of auto-allowing.
check_read_only() returns True for any command identified by the read-only detector above (step 2), and False otherwise. The permission engine uses it to decide auto-allow in EXPLORE / ACCEPT_EDITS without re-running the full safety analysis. match_rule() uses prefix-based wildcard matching against the command string: generate_suggestions() extracts the command prefix (first two tokens) and proposes a prefix rule. For example, git commit -m "fix bug" produces the suggestion git commit:*. The constructor accepts optional extra entries for the dangerous-path lists:

PowerShell

The PowerShell tool is the Windows counterpart of Bash. LocalWorkspace.list_tools() returns it in place of Bash when the host is Windows, so the same agent code works on both platforms:
Its execution model differs from Bash in a few ways:
check_permissions() returns ASK for every PowerShell command. The static command analysis that lets Bash auto-allow read-only commands is not yet implemented for PowerShell, so no command is classified as safe. This is a regular ASK that allow rules and BYPASS mode can still override, and generate_suggestions() deliberately proposes no rules to keep the permission boundary conservative.

File Tools (Read, Write, Edit)

The file tools enforce a read-before-write rule: Write and Edit require the target file to have been read via Read first. This prevents blind overwrites and ensures the agent always operates on current content. check_permissions(): Write and Edit share the same permission logic:
  1. Dangerous path protection: operations on sensitive files (.bashrc, .env, .ssh/) return a bypass-immune ASK (bypass_immune=True), so allow rules cannot silently authorize them. The ASK is still skipped in BYPASS mode (which opts out of safety prompts by design) and converted to DENY in DONT_ASK mode. See the permission system docs for the full contract.
  2. ACCEPT_EDITS mode: auto-allows operations on files within configured working directories
  3. PASSTHROUGH: falls through to the permission engine for rule matching
Read is read-only and always returns PASSTHROUGH (the engine handles EXPLORE-mode and ACCEPT_EDITS-mode auto-allow via check_read_only). Read picks its return format based on the file type:
  • Text files are paginated with offset / limit and returned as TextBlocks with line numbers;
  • Image types the model supports are returned as base64 DataBlocks;
  • PDFs are returned as DataBlocks when the model supports application/pdf, otherwise their text is extracted locally. Beyond 10 pages, a range such as pages="1-5" is required, and one call reads at most 20 pages.
Pass the input types from the model card straight to Read so the tool only returns multimodal content the downstream model can handle:
match_rule(): all three tools use fnmatch glob matching against the file_path argument: generate_suggestions() proposes a glob covering the parent directory. For example, editing /project/src/main.py produces the suggestion src/**.

Plan Tools (TaskCreate, TaskGet, TaskList, TaskUpdate)

The plan tools give the agent a structured task list it can append to, query, and update through normal tool calls. They share a single store on agent.state.tasks_context, are state-injected, and always pass permission checks: the agent treats them as free-cost coordination primitives for breaking complex work into trackable steps. See Plan for the full task lifecycle, the storage model, and how to seed or customize tasks programmatically.

Ask the User

When an agent needs a decision from the person it is working for — which of two implementations to take, whether a draft is heading the right way, a preference the instructions never spelled out — AskUser lets it put the question in front of them instead of guessing. The tool carries no execution logic of its own: its is_external_tool is True, so it only declares what to ask. Presenting the question and collecting the answer is up to whatever drives the agent — a web frontend, a terminal UI, any custom shell — and once the interaction is over the answer comes back as an ExternalExecutionResultEvent before the reply continues. One call carries up to four questions, each with two to four options. Beyond its label, an option has a description for the trade-off it represents, and a single-select question’s options may each carry a preview for comparing concrete artifacts side by side: a code snippet, a config, a mockup. An “Other” free-text answer is always offered alongside the options, so the agent should not add one of its own. Permission checking passes AskUser straight through — the call is answered by the user in the first place, so a confirmation in front of it would only be a prompt about a prompt. A full round takes three steps:
1

Equip the tool

Put it in a Toolkit like any other built-in tool, and the agent decides for itself when to ask:
Equip AskUser
2

Catch it outside and render it

Calling the tool emits a RequireExternalExecutionEvent and pauses the reply. The driver catches that event on the stream, reads this call’s questions and options out of tool_calls, and presents them in its own interface: a web frontend renders a set of cards, a terminal UI a keyboard-selectable list. Alongside the options the tool supplied, the interface must offer an “Other” free-text field, because the tool has already promised the user one. For how each field maps onto interface elements, see human-in-the-loop.
3

Return the answers

Once the user has answered, wrap the result in a ToolResultBlock and hand it back inside an ExternalExecutionResultEvent. output is the text the model reads; metadata carries the same answers shaped by AskUserMetadata, for programs that branch on the choice:
Return the answers

Switch Tool Backend

The Bash, PowerShell, Grep, Glob, Read, Write, and Edit tools in AgentScope support backend switching: delegating their execution to different runtime environments such as the local filesystem, a Docker container, an E2B sandbox, and so on. A backend is selected via the backend argument. Backend instances are obtained from a Workspace, which defaults to the local environment. See Workspace for more details.

Create Custom Tool

To create a custom tool, subclass ToolBase, declare its schema, and implement check_permissions and call:
Two extension hooks worth knowing about when writing custom tools with safety logic:
  • check_read_only(tool_input): override when whether an invocation modifies state depends on the input (like Bash: ls is read-only, rm is not). Defaults to returning the static is_read_only attribute. The permission engine calls it before deciding EXPLORE / ACCEPT_EDITS auto-allow.
  • PermissionDecision(..., bypass_immune=True): set on a returned ASK to mark it as a safety check that allow rules cannot silence (e.g. a DeployTool flagging prod-* targets). See the safety check contract for per-mode handling.

Wrap Function as Tool

For lightweight cases that don’t justify a full subclass, wrap a plain Python function with the FunctionTool adapter. It auto-extracts the tool name from func.__name__, the description from the function docstring, and the input schema from type hints.
FunctionTool accepts overrides when the auto-extracted defaults are not what you want: Pass a Pydantic model directly when the arguments need enums, numeric ranges or nested structures:
FunctionTool also resolves the deferred annotations produced by from __future__ import annotations. When automatic inference cannot express the constraints you need, prefer input_schema.
Wrapped functions default to ASK permission behavior: the user must explicitly allow each call. Subclass ToolBase directly when you need custom permission logic.

Define External Execution Tool

An external execution tool delegates its actual execution outside the agent runtime, typically to a human operator or an external system. When the agent calls one, it emits a RequireExternalExecutionEvent and pauses until the result is delivered via ExternalExecutionResultEvent. This pattern underlies the human-in-the-loop workflow, where certain actions require human approval or manual execution. The built-in AskUser is one such tool. To create your own, set is_external_tool = True. There is no need to implement call:

Tool Middleware

Tool middleware attaches onion-style hooks directly to a tool instance. Every time that tool is invoked, whether by an agent or called directly, the registered middlewares fire in order, wrap the execution, and can observe or transform both inputs and outputs. This is separate from agent-level middleware (MiddlewareBase): on_acting in agent middleware wraps the entire tool-call slot inside the ReAct loop (including permission checks and event emission), while ToolMiddlewareBase hooks only inside the tool’s own call() execution chain and fires even when the tool is called outside any agent.

ToolMiddlewareBase Interface

Subclass ToolMiddlewareBase and implement the single abstract async-generator method on_tool_call:

Execution Model

  • The first registered middleware is the outermost layer: its pre-logic runs first, its post-logic runs last.
  • next_handler(**input_kwargs) always returns AsyncGenerator[ToolChunk, None]. Streaming and non-streaming tools are unified, so a middleware never needs to handle the two shapes separately.
  • The innermost layer calls the tool’s own call().

Attach Middleware

Pass a list of middleware instances to the tool constructor via the middlewares argument:

Example

A logging middleware that prints before and after each invocation, and a retry middleware that re-attempts on failure:
Tool middleware vs. agent middleware: use ToolMiddlewareBase for cross-cutting concerns that belong to the tool itself (logging, metrics, retry). Use MiddlewareBase.on_acting when you need access to the broader agent context, such as permission decisions, the tool-call event, or the surrounding ReAct round. See Middleware for the full agent-level hook reference.