> ## 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.

# Tool

> Define, register, and manage the capabilities an agent can call

## Overview

Tools are how an agent acts on the world — running business operations, calling APIs, reading and writing data. Each tool exposes itself to the LLM as a JSON Schema, and the agent invokes it through a unified interface.

AgentScope organizes tool-related building blocks under three concepts:

* **Tool** — any object implementing the `AgentTool` contract (typically by extending `ToolBase`) or any plain class whose methods are annotated with `@Tool`. Java refers to the latter as *reflective function tools* — `Toolkit#registerTool(Object)` registers them by reflection automatically.
* **Toolkit** — the container that registers tools, MCP clients, and skills, exposes their JSON schemas to the model, and dispatches each tool call to the matching tool object.
* **Tool Group** — a named bundle of tools / MCP clients / skills that can be activated or deactivated as a unit. The agent uses a built-in meta tool to switch groups at runtime, keeping the context focused.

```java theme={null}
import io.agentscope.core.tool.Toolkit;
import io.agentscope.core.tool.builtin.TodoTools;

Toolkit toolkit = new Toolkit();
toolkit.registerTool(new TodoTools());
toolkit.registerTool(new MyCustomTools());
```

When you only call `registerTool(Object)`, every `@Tool` method on the registered object joins the reserved `"basic"` group — always active. Add MCP clients, tool groups, or skills to extend the agent further — see the sections below.

## Java tools

A Java tool is any object satisfying the `AgentTool` contract. AgentScope ships an abstract base class `ToolBase` for declaring tools with explicit parameter schemas, plus a reflective adapter that wraps plain methods into tools.

### AgentTool / ToolBase contract

`ToolBase` is the abstract `AgentTool` implementation. The table below lists its properties and methods.

Properties exposed to the agent and runtime:

| Method | Type | Description |
| - | - | - |
| `getName()` | `String` | The tool name shown to the agent |
| `getDescription()` | `String` | Description shown to the agent |
| `getParameters()` | `Map<String, Object>` | JSON Schema describing the parameters |
| `isConcurrencySafe()` | `boolean` | Can the tool be called concurrently? |
| `isReadOnly()` | `boolean` | Is the tool read-only / side-effect-free? |
| `isExternalTool()` | `boolean` | When `true`, execution is delegated externally (see [external execution](#external-execution-tools)) |
| `isStateInjected()` | `boolean` | When `true`, the framework injects `AgentState` as an extra parameter |
| `isMcp()` | `boolean` | Did the tool come from an MCP server? |
| `getMcpName()` | `String` | The MCP server name when `isMcp()` is `true` |

Methods that integrate with the execution flow and the permission system:

| Method | Required | Description |
| - | - | - |
| `checkPermissions(toolInput, context)` | yes | Runtime permission check before execution; returns `Mono<PermissionDecision>` |
| `matchRule(ruleContent, toolInput)` | optional | Custom rule matcher for the permission system; returns `boolean` |
| `generateSuggestions(toolInput)` | optional | Generate suggested rules from the current invocation; returns `List<PermissionRule>` |
| `callAsync(param)` | optional | Tool execution; returns `Mono<ToolResultBlock>`. External tools do not implement this. |

### Built-in tools

AgentScope currently ships these built-in tools:

| Tool | Description | Read-only |
| - | - | - |
| `TodoTools.todoWrite` | Maintain a structured task list for the current session (full-list-replace semantics) | no |

Usage:

```java theme={null}
Toolkit toolkit = new Toolkit();
toolkit.registerTool(new io.agentscope.core.tool.builtin.TodoTools());
```

<Note>
  The `Toolkit` automatically registers the `reset_tools` meta tool and the `load_skill_through_path` skill viewer tool when extra tool groups or skills are present — you don't need to instantiate them manually. See [self-managed tools](#self-managed-tools) and [Skill](#skill).
</Note>

### Custom tools (annotation-based)

The lightest-weight way: annotate plain methods with `@Tool` and `@ToolParam`, then call `Toolkit#registerTool(Object)`. The framework derives the JSON schema from Java types and the `description` for the agent.

```java theme={null}
import io.agentscope.core.tool.Tool;
import io.agentscope.core.tool.ToolParam;
import io.agentscope.core.tool.Toolkit;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;

public class SimpleTools {

    @Tool(
            name = "get_current_time",
            description = "Returns the current time in a given IANA timezone.",
            readOnly = true,
            concurrencySafe = true)
    public String getCurrentTime(
            @ToolParam(name = "timezone", description = "IANA timezone, e.g. Asia/Shanghai")
                    String timezone) {
        return LocalDateTime.now(ZoneId.of(timezone))
                .format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
    }
}

Toolkit toolkit = new Toolkit();
toolkit.registerTool(new SimpleTools());
```

Common `@Tool` attributes:

| Attribute | Type | Description |
| - | - | - |
| `name` | `String` | Tool name (defaults to the method name) |
| `description` | `String` | Description shown to the agent |
| `readOnly` | `boolean` | Whether the tool is read-only (default `false`) |
| `concurrencySafe` | `boolean` | Whether the tool is safe for concurrent calls (default `true`) |
| `stateInjected` | `boolean` | Inject `AgentState` as an extra parameter (default `false`) |
| `dangerousFiles` / `dangerousDirectories` | `String[]` | Append custom dangerous paths |
| `converter` | `Class<? extends ToolResultConverter>` | Custom conversion of return values into `ToolResultBlock` |

#### Explicit result states

Return `ToolResultBlock.success(text)` for a successful call and `ToolResultBlock.error(message)` for a failure. Both are supported directly and inside `Mono<ToolResultBlock>`. Do not infer failure from text prefixes. `ToolResultBlock.text(text)` defaults to `RUNNING`; use `withState(...)` to set a terminal state when preserving an existing JSON or multiline output.

Built-in tools now return structured results. Direct callers should inspect `getState()` and read the text blocks from `getOutput()`; `Toolkit` registration is unchanged.

### Custom tools (extending `ToolBase`)

When you need a custom permission policy, external execution, or a more complex schema, extend `ToolBase`:

```java theme={null}
import io.agentscope.core.message.TextBlock;
import io.agentscope.core.message.ToolResultBlock;
import io.agentscope.core.permission.PermissionBehavior;
import io.agentscope.core.permission.PermissionContextState;
import io.agentscope.core.permission.PermissionDecision;
import io.agentscope.core.tool.ToolBase;
import io.agentscope.core.tool.ToolCallParam;
import java.util.List;
import java.util.Map;
import reactor.core.publisher.Mono;

public class WebSearchTool extends ToolBase {

    public WebSearchTool() {
        super(
                ToolBase.builder()
                        .name("WebSearch")
                        .description("Search the web for information on a given query.")
                        .inputSchema(Map.of(
                                "type", "object",
                                "properties", Map.of(
                                        "query", Map.of(
                                                "type", "string",
                                                "description", "The search query.")),
                                "required", List.of("query")))
                        .readOnly(true)
                        .concurrencySafe(true));
    }

    @Override
    public Mono<PermissionDecision> checkPermissions(
            Map<String, Object> toolInput, PermissionContextState context) {
        return Mono.just(PermissionDecision.allow("Web search is read-only."));
    }

    @Override
    public Mono<ToolResultBlock> callAsync(ToolCallParam param) {
        String query = (String) param.getInput().get("query");
        return doSearchAsync(query)
                .map(text ->
                        ToolResultBlock.builder()
                                .id(param.getId())
                                .name(getName())
                                .output(List.of(TextBlock.builder().text(text).build()))
                                .build());
    }
}
```

### External execution tools

External-execution tools delegate the actual work outside the agent runtime — typically to a human operator or an external system. The agent emits `RequireExternalExecutionEvent` and pauses. When the next call feeds back matching `ToolResultBlock`s, the agent emits `ExternalExecutionResultEvent` with the same `replyId` before continuing.

This pattern is the foundation of [human-in-the-loop](/v2/en/docs/building-blocks/agent#human-in-the-loop) flows — some actions need human approval or human execution.

To create an external tool, set `externalTool` to `true` and skip implementing `callAsync`:

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

public class HumanApprovalTool extends ToolBase {

    public HumanApprovalTool() {
        super(
                ToolBase.builder()
                        .name("HumanApproval")
                        .description("Request human approval for a sensitive operation.")
                        .inputSchema(Map.of(
                                "type", "object",
                                "properties", Map.of(
                                        "action", Map.of("type", "string"),
                                        "reason", Map.of("type", "string")),
                                "required", List.of("action", "reason")))
                        .readOnly(false)
                        .concurrencySafe(true)
                        .externalTool(true));
    }

    @Override
    public Mono<PermissionDecision> checkPermissions(
            Map<String, Object> toolInput, PermissionContextState context) {
        return Mono.just(PermissionDecision.allow("External tool dispatch is always allowed."));
    }
}
```

Runnable examples: `agentscope-examples/documentation/.../tool/ToolBaseExample.java`, `tool/ToolExecutionContextExample.java`.

## Receiving context

The [`RuntimeContext`](/v2/en/docs/building-blocks/agent#runtimecontext-per-call-context) passed to `agent.call(msgs, runtimeContext)` is forwarded to every tool invocation in that reply. Tools can read it in two ways: annotation-based tools through automatic injection, and `ToolBase.callAsync` through `ToolCallParam`.

### Automatic injection (`@Tool` methods)

Inside a `@Tool` method, any parameter **without `@ToolParam`** is treated as framework-injected. The resolution order:

| Parameter type | Source |
| - | - |
| `ToolEmitter` | Streaming emitter and tool call context (no-op when none configured) |
| `Agent` | The current agent instance |
| `AgentState` | The per-session state for the current call (via `RuntimeContext.getAgentState()`) |
| `RuntimeContext` | The current per-call context |
| `ToolExecutionContext` | `runtimeContext.asToolExecutionContext()` (compatibility shim, deprecated) |
| Any other user POJO type | `runtimeContext.get(ParamType.class)` — i.e. an object the caller registered via `RuntimeContext.builder().put(ParamType.class, value)` |

An injected `ToolEmitter` exposes the current tool call ID, which can be used to correlate progress with frontend state or another shared store:

```java theme={null}
@Tool(name = "run_task", description = "Run a long-running task")
public String runTask(ToolEmitter emitter) {
    String toolCallId = emitter.getToolCallId();
    if (toolCallId != null) {
        progressByToolCall.put(toolCallId, "running");
    }
    return "done";
}
```

`getToolCallId()` returns `null` when the emitter has no tool call context, such as a no-op or custom emitter.

"User POJO" means: no `@ToolParam`, not primitive, not `ContentBlock` / `Msg`, not under `java.*` / `javax.*`. Every other parameter (those with `@ToolParam`, or that fall outside the above types) is read from the LLM-supplied JSON by name.

```java theme={null}
import io.agentscope.core.tool.Tool;
import io.agentscope.core.tool.ToolParam;

public record UserContext(String username, String locale) {}

public class PersonalizedTools {

    @Tool(name = "greet", description = "Greet the user with a custom greeting")
    public String greet(
            @ToolParam(name = "greeting", description = "Greeting word, e.g. 'Hello'")
                    String greeting,                  // ← supplied by the model
            UserContext userCtx) {                    // ← injected by the framework
        return greeting + ", " + (userCtx == null ? "unknown" : userCtx.username()) + "!";
    }
}
```

The caller registers the POJO by type once; every `call` then routes the matching instance to any tool that asks for it:

```java theme={null}
RuntimeContext ctx =
        RuntimeContext.builder()
                .put(UserContext.class, new UserContext("alice", "en"))
                .userId("alice")
                .build();

agent.call(List.of(new UserMessage("Greet me.")), ctx).block();
```

The model never sees `userCtx` — it is not part of the tool's JSON schema. Full example: `agentscope-examples/documentation/.../tool/ToolExecutionContextExample.java`.

### Accessing context in `ToolBase.callAsync`

Tools that extend `ToolBase` read context through `ToolCallParam`:

```java theme={null}
import io.agentscope.core.agent.RuntimeContext;
import io.agentscope.core.tool.ToolBase;
import io.agentscope.core.tool.ToolCallParam;
import reactor.core.publisher.Mono;

public class TenantAwareTool extends ToolBase {

    public TenantAwareTool() {
        super(/* builder ... */);
    }

    @Override
    public Mono<io.agentscope.core.message.ToolResultBlock> callAsync(ToolCallParam param) {
        RuntimeContext rc = param.getRuntimeContext();
        String tenantId = rc != null ? rc.getUserId() : null;
        TenantConfig cfg = rc != null ? rc.get(TenantConfig.class) : null;
        // ... apply tenantId / cfg ...
    }
}
```

`ToolCallParam` also exposes `getAgent()`, `getInput()`, `getEmitter()`, `getToolUseBlock()`, and the deprecated `getContext()`. Prefer `getRuntimeContext()` in new code.

### Coordinating between hooks and tools

The `RuntimeContext` string layer (`put(String, Object)` / `get(String)`) is a short-lived channel between middleware and tools during a single `call` — a middleware can write at `onActing`/`onReasoning` and a tool that injects a `RuntimeContext` parameter reads it. The instance is unbound from the agent (along with all hooks) when the call finishes.

## MCP

AgentScope integrates with the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/), letting the agent talk to any MCP-compatible tool provider. The framework handles protocol negotiation, tool discovery, and result conversion.

Three transports are supported:

* **STDIO** — local process via stdin/stdout
* **SSE / Streamable HTTP** — remote HTTP long-connection

MCP tools are exposed in the toolkit under the namespace `mcp__{server_name}__{tool_name}` to avoid collisions; tools marked `readOnlyHint` are auto-allowed by the permission system.

### Registering MCP tools

Use `McpClientBuilder` to build an `McpClientWrapper`, then register it on the `Toolkit`:

<Tabs>
  <Tab title="STDIO">
    ```java theme={null}
    import io.agentscope.core.tool.Toolkit;
    import io.agentscope.core.tool.mcp.McpClientBuilder;
    import io.agentscope.core.tool.mcp.McpClientWrapper;

    McpClientWrapper filesystem =
            McpClientBuilder.create("filesystem")
                    .stdioTransport("mcp-server-filesystem", "--root", "/my/project")
                    .buildAsync()
                    .block();

    Toolkit toolkit = new Toolkit();
    toolkit.registerMcpClient(filesystem).block();
    ```
  </Tab>

  <Tab title="Streamable HTTP">
    ```java theme={null}
    import io.agentscope.core.tool.Toolkit;
    import io.agentscope.core.tool.mcp.McpClientBuilder;
    import io.agentscope.core.tool.mcp.McpClientWrapper;

    McpClientWrapper weather =
            McpClientBuilder.create("weather")
                    .streamableHttpTransport("https://api.weather.com/mcp")
                    .header("Authorization", "Bearer xxx")
                    .buildAsync()
                    .block();

    Toolkit toolkit = new Toolkit();
    toolkit.registerMcpClient(weather).block();
    ```
  </Tab>

  <Tab title="SSE">
    ```java theme={null}
    import io.agentscope.core.tool.mcp.McpClientBuilder;
    import io.agentscope.core.tool.mcp.McpClientWrapper;

    McpClientWrapper search =
            McpClientBuilder.create("search")
                    .sseTransport("https://api.search.com/mcp/sse")
                    .buildAsync()
                    .block();

    Toolkit toolkit = new Toolkit();
    toolkit.registerMcpClient(search).block();
    ```
  </Tab>
</Tabs>

Runnable examples: `agentscope-examples/documentation/.../mcp/McpStdioExample.java`, `mcp/McpSseExample.java`, `mcp/McpStreamableHttpExample.java`.

## Skill

Skills are markdown-based instruction sets that extend an agent's capabilities without writing new tool code. Each skill is a directory containing a `SKILL.md` file with frontmatter metadata and detailed instructions.

Unlike tools, skills are not directly callable. The agent reads skill instructions through an auto-registered viewer tool named `load_skill_through_path`, then carries them out using whatever tools it already has.

### Registering skills

Attach one or more `AgentSkillRepository` directly via `ReActAgent.builder().skillRepository(...)`. At `build()` time the builder auto-installs `DynamicSkillMiddleware`, which rebuilds the skill prompt and tool groups from the configured sources on every `call()`:

```java theme={null}
import io.agentscope.core.ReActAgent;
import io.agentscope.core.skill.repository.FileSystemSkillRepository;
import java.nio.file.Paths;

ReActAgent agent =
        ReActAgent.builder()
                .name("SkillCreator")
                .sysPrompt("...")
                .model(model)
                .skillRepository(new FileSystemSkillRepository(Paths.get("/path/to/skills"), false))
                .build();
```

Multiple `skillRepository(...)` calls append in order (low → high priority); when two repositories expose a skill with the same name, the later entry wins. Use `skillRepositories(List<AgentSkillRepository>)` to replace the list.

Reference implementations: `agentscope-examples/documentation/.../skill/AgentSkillExample.java`, `skill/SkillWithToolGroupExample.java`.

### How skills work

When skills are present, the `Toolkit` performs a two-phase setup.

Initialisation:

* The toolkit scans every registered skill source and collects each skill's name, description, and directory.
* It auto-registers the built-in viewer tool `load_skill_through_path` (implemented in `io.agentscope.core.skill.SkillToolFactory`) as an ungrouped, always-visible tool.
* It assembles a system-prompt fragment listing the available skills (names + descriptions) and instructing the agent to read full content via `load_skill_through_path`.

At runtime, the agent invokes the viewer with two required arguments:

| Parameter | Type | Description |
| - | - | - |
| `skillId` | `string` (enum of registered skill IDs) | The skill to load. |
| `path` | `string` | Use `"SKILL.md"` to fetch the skill's markdown instructions, or an exact resource path declared by the skill such as `"references/guide.md"` or `"scripts/run.py"`. Do not pass `"."`, `"./"`, a directory, or an absolute path. |

Example tool call payload:

```json theme={null}
{
  "name": "load_skill_through_path",
  "input": { "skillId": "pdf-extractor", "path": "SKILL.md" }
}
```

Each successful call has two effects:

1. Returns the requested content (the `SKILL.md` markdown, or the named resource file).
2. **Activates the skill** — its associated tool group is enabled in the `Toolkit`, so any tools bundled with the skill become callable for the rest of the turn. If the requested `path` does not exist, the viewer returns an error that lists the available resource paths (with `SKILL.md` first) so the agent can retry.

<Note>
  A skill is not a tool — the agent cannot call it directly. The agent must read the instructions via `load_skill_through_path` first, then act on them with other tools.
</Note>

### Skill script execution: configuring shell tools

Skills only provide instructions — actual execution relies on the tools the agent already has. If a skill's instructions involve running scripts (e.g. `scripts/run.py`), the agent needs shell access:

* **`ReActAgent`** — register `ShellCommandTool` in the toolkit:

```java theme={null}
import io.agentscope.core.tool.Toolkit;
import io.agentscope.core.tool.coding.ShellCommandTool;
import io.agentscope.core.tool.file.ReadFileTool;
import io.agentscope.core.tool.file.WriteFileTool;

Toolkit toolkit = new Toolkit();
toolkit.registerTool(new ShellCommandTool());
toolkit.registerTool(new ReadFileTool("/path/to/base/dir"));
toolkit.registerTool(new WriteFileTool("/path/to/base/dir"));

ReActAgent agent =
        ReActAgent.builder()
                .name("SkillAgent")
                .sysPrompt("...")
                .model(model)
                .toolkit(toolkit)
                .skillRepository(skillRepo)
                .build();
```

* **`HarnessAgent`** — the harness module ships workspace-aware shell and file tools (`execute`, `read_file`, `write_file`, etc.) out of the box; no extra registration needed.

### Skill + ToolGroup: on-demand tool disclosure

`SkillToolGroup` binds a group of tools to a skill name — the group activates automatically when the agent loads that skill, and stays hidden from the model's schema otherwise, reducing context noise.

```java theme={null}
import io.agentscope.core.ReActAgent;
import io.agentscope.core.tool.Toolkit;

Toolkit toolkit = new Toolkit();

// 1. Create a tool group bound to a skill (initially inactive)
toolkit.createSkillToolGroup(
        "analysis-tools",                // group name
        "Data analysis tools",           // description
        false,                           // initially inactive
        "data-analysis");                // bound skill name

// 2. Register tools into that group
toolkit.registration()
        .tool(new AnalysisTools())
        .group("analysis-tools")
        .apply();

// 3. Build the agent with meta tool for model-driven group switching
ReActAgent agent =
        ReActAgent.builder()
                .name("AnalysisAgent")
                .sysPrompt("...")
                .model(model)
                .toolkit(toolkit)
                .skillRepository(skillRepo)
                .enableMetaTool(true)
                .build();
```

When the agent loads the `data-analysis` skill via `load_skill_through_path`, the `analysis-tools` group activates and its tools become immediately available. With `enableMetaTool(true)`, the model can also manage group activation via `reset_tools`.

Reference implementation: `agentscope-examples/documentation/.../skill/SkillWithToolGroupExample.java`.

## Self-managed tools

The built-in **meta tool** (`reset_tools`) lets the agent self-manage which tool groups are active at runtime, keeping the context focused — only the tools relevant to the current task are exposed to the model.

### Defining tool groups

`ToolGroup` is a named bundle of tools / MCP clients / skills. Register the group on the `Toolkit` and turn on the meta tool through the builder:

```java theme={null}
import io.agentscope.core.ReActAgent;
import io.agentscope.core.tool.Toolkit;
import io.agentscope.core.tool.ToolGroup;
import io.agentscope.core.tool.ToolGroupScope;

Toolkit toolkit = new Toolkit();
toolkit.registerTool(new BasicTools());

ToolGroup database =
        ToolGroup.builder()
                .name("database")
                .description("Tools for database operations.")
                .scope(ToolGroupScope.SESSION)
                .active(false)
                .build();
database.addTool("db_query");
database.addTool("db_migrate");
toolkit.registerTool(new DatabaseTools());
toolkit.registerToolGroup(database);

ToolGroup deployment =
        ToolGroup.builder()
                .name("deployment")
                .description("Tools for deploying services.")
                .scope(ToolGroupScope.SESSION)
                .active(false)
                .build();
deployment.addTool("deploy");
deployment.addTool("rollback");
toolkit.registerTool(new DeploymentTools());
toolkit.registerToolGroup(deployment);

ReActAgent agent =
        ReActAgent.builder()
                .name("router")
                .toolkit(toolkit)
                .enableMetaTool(true)
                .build();
```

`ToolGroup` is built with `ToolGroup.builder()`: a name, a description, a scope (`ToolGroupScope`), and an initial active flag. The reserved name `"basic"` is auto-populated by `Toolkit#registerTool(Object)` and is always active.

### Using the meta tool

Whenever there's at least one non-basic tool group and `enableMetaTool(true)` is on, the `Toolkit` auto-registers `reset_tools` and exposes its schema to the agent. Each non-basic group becomes a boolean field; calling the meta tool declares the desired final state.

Runtime behavior:

* Tools in the `"basic"` group are always exposed; the meta tool does not touch them.
* Each `reset_tools` call **wholly overwrites** the active set — any non-basic group not explicitly set to `true` is deactivated, regardless of its previous state.
* For each group that just became active, its description and (if provided) instructions are spliced into the meta tool's return value, telling the agent how to use it correctly.
* Tools in inactive groups do not appear in the agent's tool schema, leaving more context for the active toolset.

<Warning>
  The meta tool's input represents the **final state** of all groups, not a delta. Any group not explicitly set to `true` is deactivated regardless of previous state.
</Warning>

## Further reading

<CardGroup cols={2}>
  <Card title="Agent" href="/v2/en/docs/building-blocks/agent">
    How agents orchestrate tool calls in the ReAct loop
  </Card>

  <Card title="Permission System" href="/v2/en/docs/building-blocks/permission-system">
    Fine-grained control over which tools execute and when
  </Card>

  <Card title="Middleware" href="/v2/en/docs/building-blocks/middleware">
    Use onion middlewares to intercept and rewrite tool calls
  </Card>

  <Card title="Human-in-the-Loop" href="/v2/en/docs/building-blocks/agent#human-in-the-loop">
    External execution tools and approval workflows
  </Card>
</CardGroup>

## Request-scoped tools and session isolation

Agent construction copies tool registries and registration metadata. Existing tool instances remain shared by reference, so custom tools must support concurrent use. Hook and knowledge tools registered while building one agent do not overwrite another agent's registrations.

Use `io.agentscope.core.tool.ToolRequestConfig` instead of mutating a shared Toolkit during a call:

```java theme={null}
ToolRequestConfig tools = new ToolRequestConfig(
        Map.of(schema.getName(), new SchemaOnlyTool(schema)),
        ToolMergeMode.MERGE_EXTERNAL_PRIORITY);
RuntimeContext ctx = RuntimeContext.builder()
        .userId(userId).sessionId(sessionId)
        .toolRequestConfig(tools).build();
agent.streamEvents(messages, ctx).subscribe(this::handleEvent);
```

External tools contain schemas only. Execution suspends for the caller to execute the tool and submit its result. External tools override backend tools with the same name. `EXTERNAL_ONLY` hides all backend tools, even with an empty external list or when Toolkit disallows deletion: composing a request view does not delete registrations. `AGENT_ONLY` rejects nonempty external tool maps.

`Toolkit.callTool` and `callTools` also read configuration from their explicit RuntimeContext. `getTool(name)` inspects the underlying registry; use `getTool(name, config)` for the request view. Activation groups live in the session's `ToolContextState`; streaming tool callbacks belong to each invocation.

Repositories with user- or session-dependent visibility implement `io.agentscope.core.skill.repository.RuntimeContextSkillRepository` in Core. Both Core and Harness pass the current context to the repository. Dynamic Skill views are cached by content signature. Concurrent cache misses share one completed materialization; rebuilding an evicted view uses a fresh directory so active executions keep their files. Cache eviction bounds retained boxes, not disk usage: generated directories remain until JVM shutdown. Callers own cleanup of explicitly supplied working directories; do not remove resources while executions still reference them.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.