> ## Documentation Index
> Fetch the complete documentation index at: https://docs.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 shell commands, reading files, calling APIs. Each tool exposes itself to the LLM as a JSON Schema, and the agent invokes it through a unified streaming interface.

AgentScope organizes tool-related building blocks under three concepts:

* **Tool** — any class that satisfies the `ToolBase` interface, including the built-ins shipped with AgentScope and the `FunctionTool` / `MCPTool` adapters that wrap plain functions or MCP-server tools.
* **Toolkit** — the container that registers tools, MCP clients, and skills, exposes their JSON schemas to the model, and dispatches each tool call to the right tool object.
* **Tool Group** — a named bundle of tools, MCP clients, and skills that can be activated or deactivated as a unit. The agent toggles groups at runtime via the built-in meta tool to keep its context focused.

```python theme={null}
from agentscope.tool import Toolkit, Bash, Read, Write, Edit

toolkit = Toolkit(
    tools=[Bash(), Read(), Write(), Edit()],
)
```

A `Toolkit` created with `tools` alone exposes those tools in the special `"basic"` group, which is always active. Adding `mcps`, `skills_or_loaders`, or extra `tool_groups` extends what the agent can reach — see the sections below.

## Python Tool

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 custom tools.

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

| Attribute             | Type          | Description                                                                                                          |
| --------------------- | ------------- | -------------------------------------------------------------------------------------------------------------------- |
| `name`                | `str`         | The tool name presented to the agent                                                                                 |
| `description`         | `str`         | Agent-oriented description of what the tool does                                                                     |
| `input_schema`        | `dict`        | JSON Schema defining the tool's parameters                                                                           |
| `is_concurrency_safe` | `bool`        | Whether the tool is safe to call in parallel                                                                         |
| `is_read_only`        | `bool`        | Whether the tool only reads data without side effects                                                                |
| `is_external_tool`    | `bool`        | If `True`, execution is delegated externally (see [Define External Execution Tool](#define-external-execution-tool)) |
| `is_state_injected`   | `bool`        | If `True`, the agent state is injected via the `_agent_state` argument                                               |
| `is_mcp`              | `bool`        | Whether the tool comes from an MCP server                                                                            |
| `mcp_name`            | `str \| None` | The MCP server name when `is_mcp` is `True`                                                                          |

Methods that hook into execution and the permission system:

| Method                                   | Required | Description                                                                                                                                                                                                                                                       |
| ---------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `check_permissions(tool_input, context)` | Yes      | Runtime permission check before execution; returns `PermissionDecision`                                                                                                                                                                                           |
| `check_read_only(tool_input)`            | Optional | Per-invocation read-only check; returns `bool`. Defaults to `self.is_read_only`. Override when read-only-ness depends on the input (e.g. `Bash` — `ls` is read-only, `rm` is not). Used by the permission engine to decide auto-allow in EXPLORE / ACCEPT\_EDITS. |
| `match_rule(rule_content, tool_input)`   | Optional | Custom rule-matching logic for the permission system; returns `bool`                                                                                                                                                                                              |
| `generate_suggestions(tool_input)`       | Optional | Generate suggested permission rules from a tool call; returns `list[PermissionRule]`                                                                                                                                                                              |
| `call(**kwargs)`                         | Yes\*    | The tool's execution logic — the subclass override point. Returns `ToolChunk` or `AsyncGenerator[ToolChunk, None]`. Not required for external execution tools (`is_external_tool = True`).                                                                        |
| `__call__(**kwargs)`                     | —        | Framework dispatch entry point. Runs the middleware chain (if any), then delegates to `call()`. Do not override.                                                                                                                                                  |

### Use Built-in Tools

AgentScope ships a set of ready-to-use tools covering common agent operations. Instantiate them and pass into `Toolkit(tools=[...])`:

| Tool         | Description                                    | Read-only |
| ------------ | ---------------------------------------------- | --------- |
| `Bash`       | Execute shell commands                         | No        |
| `Read`       | Read file contents with line numbers           | Yes       |
| `Write`      | Create or overwrite files                      | No        |
| `Edit`       | Perform exact string replacements in files     | No        |
| `Glob`       | Find files by glob pattern                     | Yes       |
| `Grep`       | Search file contents using ripgrep             | Yes       |
| `TaskCreate` | Create a structured task for progress tracking | No        |
| `TaskGet`    | Retrieve task details by ID                    | Yes       |
| `TaskList`   | List all tasks and their status                | Yes       |
| `TaskUpdate` | Update task status or metadata                 | No        |

<Note>
  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 [Manage Tools Agentically](#manage-tools-agentically) and [Skill](#skill).
</Note>

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

| Pattern        | Matches                         | Does not match |
| -------------- | ------------------------------- | -------------- |
| `npm run:*`    | `npm run build`, `npm run test` | `npm install`  |
| `git commit:*` | `git commit -m "fix"`           | `git push`     |
| `rm:*`         | `rm file.txt`, `rm -rf /tmp/x`  | `ls`           |

`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:

```python theme={null}
from agentscope.tool import Bash

bash = Bash(
    additional_dangerous_files=[".secrets"],
    additional_dangerous_directories=[".credentials"],
)
```

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

| Tool    | Operation                       | Key behavior                                                                                            |
| ------- | ------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `Read`  | Read file contents              | Returns content with line numbers; supports offset/limit for large files; results cached in agent state |
| `Write` | Create or overwrite a file      | Fails if the file exists but has not been read first                                                    |
| `Edit`  | Replace exact strings in a file | Fails if `old_string` is not found or is not unique (unless `replace_all=True`); requires prior read    |

`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](/versions/2.0.3/en/building-blocks/permission-system#safety-check-contract) 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`).

`match_rule()` — all three tools use `fnmatch` glob matching against the `file_path` argument:

| Pattern       | Matches                   |
| ------------- | ------------------------- |
| `src/**`      | Any file under `src/`     |
| `src/**/*.py` | Python files under `src/` |
| `config.json` | Exact file match          |

`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](/versions/2.0.3/en/building-blocks/plan) for the full task lifecycle, the storage model, and how to seed or customize tasks programmatically.

### Create Custom Tool

To create a custom tool, subclass `ToolBase`, declare its schema, and implement `check_permissions` and `call`:

```python theme={null}
from agentscope.tool import ToolBase, ToolChunk
from agentscope.permission import (
    PermissionContext, PermissionDecision, PermissionBehavior,
)
from agentscope.message import TextBlock

class WebSearch(ToolBase):
    name = "WebSearch"
    description = "Search the web for information on a given query."
    input_schema = {
        "type": "object",
        "properties": {
            "query": {
                "type": "string",
                "description": "The search query.",
            },
        },
        "required": ["query"],
    }
    is_concurrency_safe = True
    is_read_only = True

    async def check_permissions(
        self, tool_input: dict, context: PermissionContext,
    ) -> PermissionDecision:
        return PermissionDecision(
            behavior=PermissionBehavior.ALLOW,
            message="Web search is read-only.",
        )

    async def call(self, query: str) -> ToolChunk:
        results = await do_search(query)
        return ToolChunk(content=[TextBlock(text=results)])
```

<Note>
  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](/versions/2.0.3/en/building-blocks/permission-system#safety-check-contract) for per-mode handling.
</Note>

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

```python theme={null}
from agentscope.tool import FunctionTool, Toolkit

def get_weather(city: str, unit: str = "celsius") -> str:
    """Get the current weather for a city.

    Args:
        city: The city name to look up.
        unit: Temperature unit, either "celsius" or "fahrenheit".
    """
    return f"The weather in {city} is 22°{unit[0].upper()}"

toolkit = Toolkit(tools=[FunctionTool(get_weather)])
```

`FunctionTool` accepts overrides when the auto-extracted defaults are not what you want:

| Argument              | Type          | Description                                                             |
| --------------------- | ------------- | ----------------------------------------------------------------------- |
| `func`                | `Callable`    | The Python function to wrap                                             |
| `name`                | `str \| None` | Override the tool name (defaults to `func.__name__`)                    |
| `description`         | `str \| None` | Override the description (defaults to the docstring)                    |
| `is_concurrency_safe` | `bool`        | Whether parallel calls are safe (default `True`)                        |
| `is_read_only`        | `bool`        | Whether the function has side effects (default `False`)                 |
| `is_state_injected`   | `bool`        | Whether the agent state is injected as `_agent_state` (default `False`) |

<Note>
  Wrapped functions default to `ASK` permission behavior — the user must explicitly allow each call. Subclass `ToolBase` directly when you need custom permission logic.
</Note>

### 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](/versions/2.0.3/en/building-blocks/agent) workflow, where certain actions require human approval or manual execution.

To create an external execution tool, set `is_external_tool = True`. There is no need to implement `call`:

```python theme={null}
from agentscope.tool import ToolBase
from agentscope.permission import (
    PermissionContext, PermissionDecision, PermissionBehavior,
)

class HumanApproval(ToolBase):
    name = "HumanApproval"
    description = "Request human approval for a sensitive operation."
    input_schema = {
        "type": "object",
        "properties": {
            "action": {"type": "string", "description": "The action requiring approval."},
            "reason": {"type": "string", "description": "Why this action needs approval."},
        },
        "required": ["action", "reason"],
    }
    is_concurrency_safe = True
    is_read_only = False
    is_external_tool = True

    async def check_permissions(
        self, tool_input: dict, context: PermissionContext,
    ) -> PermissionDecision:
        return PermissionDecision(
            behavior=PermissionBehavior.ALLOW,
            message="External tool dispatch is always allowed.",
        )
```

### 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`:

| Parameter      | Type                                             | Description                                                                                                                                                           |
| -------------- | ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tool`         | `ToolBase`                                       | The tool instance being invoked                                                                                                                                       |
| `input_kwargs` | `dict[str, Any]`                                 | Input arguments for this invocation. Pass (possibly modified) arguments to `next_handler` to control what the inner layers receive                                    |
| `next_handler` | `Callable[..., AsyncGenerator[ToolChunk, None]]` | Call as `next_handler(**input_kwargs)` to continue to the next layer. Always returns an async generator regardless of whether the underlying tool is streaming or not |

#### 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()`.

```
middlewares[0].on_tool_call
  └─ middlewares[1].on_tool_call
       └─ ... → tool.call()
```

#### Attach Middleware

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

```python theme={null}
from agentscope.tool import Bash

bash = Bash(middlewares=[LoggingMiddleware(), MetricsMiddleware()])
# Execution order: LoggingMiddleware → MetricsMiddleware → Bash.call()
```

#### Example

A logging middleware that prints before and after each invocation, and a retry middleware that re-attempts on failure:

```python theme={null}
from typing import AsyncGenerator, Any, Callable
from agentscope.tool import ToolMiddlewareBase, ToolBase, ToolChunk, Bash


class LoggingMiddleware(ToolMiddlewareBase):
    async def on_tool_call(
        self,
        tool: ToolBase,
        input_kwargs: dict[str, Any],
        next_handler: Callable[..., AsyncGenerator[ToolChunk, None]],
    ) -> AsyncGenerator[ToolChunk, None]:
        print(f"→ Calling {tool.name} with {input_kwargs}")
        async for chunk in next_handler(**input_kwargs):
            yield chunk
        print(f"✓ {tool.name} finished")


class RetryMiddleware(ToolMiddlewareBase):
    def __init__(self, max_attempts: int = 3):
        self.max_attempts = max_attempts

    async def on_tool_call(
        self,
        tool: ToolBase,
        input_kwargs: dict[str, Any],
        next_handler: Callable[..., AsyncGenerator[ToolChunk, None]],
    ) -> AsyncGenerator[ToolChunk, None]:
        for attempt in range(1, self.max_attempts + 1):
            try:
                async for chunk in next_handler(**input_kwargs):
                    yield chunk
                return
            except Exception as e:
                if attempt == self.max_attempts:
                    raise
                print(f"Attempt {attempt} failed: {e}, retrying…")


bash = Bash(middlewares=[LoggingMiddleware(), RetryMiddleware(max_attempts=3)])
```

<Note>
  **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 — permission decisions, the tool-call event, or the surrounding ReAct round. See [Middleware](/versions/2.0.3/en/building-blocks/middleware) for the full agent-level hook reference.
</Note>

## MCP

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

Two connection modes are supported:

* **Stateful** (STDIO or HTTP) — persistent session with explicit `connect()` / `close()` lifecycle
* **Stateless** (HTTP only) — ephemeral session created per tool call, no lifecycle management needed

MCP tools are namespaced as `mcp__{server_name}__{tool_name}` to avoid name collisions, and tools annotated with `readOnlyHint` are recognized as read-only by the permission system (auto-allowed in EXPLORE and ACCEPT\_EDITS modes; in DEFAULT they still ASK unless an allow rule matches).

### Register MCP Tool

Build one or more `MCPClient` instances and pass them to `Toolkit(mcps=[...])`. Stateful clients must be connected before the toolkit is constructed.

<CodeGroup>
  ```python Stateful (STDIO) theme={null}
  from agentscope.mcp import MCPClient, StdioMCPConfig
  from agentscope.tool import Toolkit

  client = MCPClient(
      name="filesystem",
      is_stateful=True,
      mcp_config=StdioMCPConfig(
          command="mcp-server-filesystem",
          args=["--root", "/my/project"],
      ),
  )

  await client.connect()

  toolkit = Toolkit(mcps=[client])
  ```

  ```python Stateful (HTTP) theme={null}
  from agentscope.mcp import MCPClient, HttpMCPConfig
  from agentscope.tool import Toolkit

  client = MCPClient(
      name="weather",
      is_stateful=True,
      mcp_config=HttpMCPConfig(
          url="https://api.weather.com/mcp",
          headers={"Authorization": "Bearer xxx"},
      ),
  )

  await client.connect()

  toolkit = Toolkit(mcps=[client])
  ```

  ```python Stateless (HTTP) theme={null}
  from agentscope.mcp import MCPClient, HttpMCPConfig
  from agentscope.tool import Toolkit

  client = MCPClient(
      name="search",
      is_stateful=False,
      mcp_config=HttpMCPConfig(url="https://api.search.com/mcp"),
  )

  toolkit = Toolkit(mcps=[client])
  ```
</CodeGroup>

To expose only a subset of an MCP server's tools, set `enable_tools` or `disable_tools` on the client itself:

```python theme={null}
client = MCPClient(
    name="search",
    is_stateful=False,
    mcp_config=HttpMCPConfig(url="https://api.search.com/mcp"),
    enable_tools=["web_search", "image_search"],
)
```

If you need to invoke MCP tools outside a `Toolkit`, call `await client.list_tools()` to retrieve a list of `MCPTool` adapters and use them like any other `ToolBase` instance.

## Skill

Skills are markdown-based instruction sets that extend agent 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 callable directly. The agent uses the auto-registered `Skill` viewer tool to read a skill's instructions, then follows those instructions using its existing tools.

### Register Skill

Pass skill sources to the `Toolkit` constructor through `skills_or_loaders`. Each entry can be a directory path string, a `Skill` object, or a `SkillLoaderBase` subclass:

<CodeGroup>
  ```python Directory path (simple) theme={null}
  from agentscope.tool import Toolkit

  toolkit = Toolkit(
      skills_or_loaders=["/path/to/skills"],
  )
  ```

  ```python LocalSkillLoader (with subdirectory scanning) theme={null}
  from agentscope.tool import Toolkit
  from agentscope.skill import LocalSkillLoader

  loader = LocalSkillLoader(
      directory="/path/to/skills",
      scan_subdir=True,
  )

  toolkit = Toolkit(skills_or_loaders=[loader])
  ```
</CodeGroup>

### How Skill Works

When a `Toolkit` is constructed with skills, the registration and lookup flow runs in two phases.

At initialization:

* The toolkit scans every registered skill source and collects each skill's name, description, and directory.
* It auto-registers the built-in `Skill` viewer tool.
* It composes a system-prompt fragment listing the available skills (name and description only) and instructing the agent to invoke the `Skill` viewer to read the full content.

At runtime:

* The agent picks a skill by name and calls the `Skill` viewer.
* The viewer reads the corresponding `SKILL.md` and returns its full markdown.
* The agent follows those instructions using its already-equipped tools.

<Note>
  Skills are not tools — the agent cannot call a skill directly. It must first use the `Skill` viewer to read the instructions, then execute the steps described within using its other tools.
</Note>

## Manage Tools Agentically

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

### Define Tool Group

A `ToolGroup` is a named bundle of tools, MCP clients, and skills. Pass groups to `Toolkit(tool_groups=[...])`. The reserved `"basic"` group is created automatically from the constructor's top-level `tools`, `mcps`, and `skills_or_loaders` arguments and is always active.

```python theme={null}
from agentscope.tool import Toolkit, ToolGroup, Bash, Read, Write, Edit

toolkit = Toolkit(
    tools=[Bash(), Read(), Write(), Edit()],
    tool_groups=[
        ToolGroup(
            name="database",
            description="Tools for database operations.",
            instructions="Always wrap mutations in a transaction.",
            tools=[db_query_tool, db_migrate_tool],
        ),
        ToolGroup(
            name="deployment",
            description="Tools for deploying services.",
            instructions="Confirm the target environment before deploying.",
            tools=[deploy_tool, rollback_tool],
        ),
    ],
)
```

`ToolGroup` accepts the same `tools`, `mcps`, and `skills_or_loaders` arguments as the toolkit, plus a `description` shown to the agent in the meta tool schema and an optional `instructions` string returned when the group is activated.

### Use Meta Tool

When at least one non-basic tool group exists, `Toolkit` auto-registers `reset_tools` and exposes its schema to the agent. Each non-basic group becomes a boolean field on that schema, and the agent calls the meta tool with the desired final state.

The behavior at runtime:

* Tools in the `"basic"` group are always exposed; they are never affected by the meta tool.
* Each call to `reset_tools` overwrites the activated set — any non-basic group not explicitly set to `True` becomes inactive, regardless of its previous state.
* For each group transitioning to active, its `instructions` (when provided) are concatenated and returned in the meta tool's response, telling the agent how to use that group properly.
* Tools from inactive groups are hidden from the agent's tool schema, freeing context space for the active set.

<Warning>
  The meta tool input represents the **final state** of all groups, not incremental changes. Any group not explicitly set to `True` will be deactivated, regardless of its previous state.
</Warning>

## Further Reading

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

  <Card title="Permission System" icon="shield" href="/versions/2.0.3/en/building-blocks/permission-system">
    Fine-grained control over which tools can execute and when
  </Card>

  <Card title="Middleware" icon="layer-group" href="/versions/2.0.3/en/building-blocks/middleware">
    Intercept agent lifecycle hooks — reply, reasoning, model calls, and more
  </Card>

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