> ## Documentation Index
> Fetch the complete documentation index at: https://docs.veadk.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Bash Toolset

<Note>
  `BashToolset` is unreleased and is not included in PyPI 1.1.13. Use the verified source revision below to try it. Agent examples require model configuration; direct file reads do not need model credentials.
</Note>

```bash lines theme={null}
python -m pip install "veadk-python @ git+https://github.com/volcengine/veadk-python.git@adcdfdcc6a5a213b249a8caad435b939c01df7f6"
```

## Overview

`BashToolset` is a read-only filesystem toolset providing ten familiar shell commands for reading, without starting a shell process or accepting command-line options. All tools are implemented using the Python standard library and safely read files and directories within the configured permission scope.

| Tool | Description |
| :- | :- |
| `cat` | Read the contents of a text file |
| `pwd` | Return the working directory |
| `ls` | List children of a directory |
| `find` | Recursively find files or directories by filename glob |
| `grep` | Search file contents for literal text, returning matching lines |
| `head` | Read the first lines of a file |
| `tail` | Read the last lines of a file |
| `wc` | Count newlines, words, and bytes in a file |
| `stat` | Return file or directory type, size, permissions, and modification time |
| `diff` | Compare two text files and output a unified diff |

Import path: `from veadk.tools.builtin_tools.bash_toolset import BashToolset`

<Note>
  `BashToolset` requires Linux or macOS. All tools are read-only — no writes, deletions, or command execution.
</Note>

## When to use

Use `BashToolset` when the agent needs to read files in the working directory, browse directory structures, search file contents, or compare file differences. Unlike the [code sandbox](/productions/veadk/preview/en/components/tools/code-sandbox), `BashToolset` executes within the local process without depending on a remote sandbox, making it suitable for scenarios that require fast, safe access to local files.

<Warning>
  `BashToolset` provides application-level access control, not an OS-level sandbox. The host must control directory renames, hard links, and mounts inside the allowed roots to prevent permission bypass. Timeouts cancel the caller and cooperatively stop the read worker, but a blocked filesystem call cannot be forcibly interrupted by a Python thread.
</Warning>

## Usage example

```python title="bash_toolset_agent.py" lines theme={null}
import asyncio
from pathlib import Path

from veadk import Agent, Runner
from veadk.memory.short_term_memory import ShortTermMemory
from veadk.tools.builtin_tools.bash_toolset import BashToolset

workspace = Path("reader_demo").resolve()
workspace.mkdir(exist_ok=True)
(workspace / "main.py").write_text("print(2 + 3)\n", encoding="utf-8")

toolset = BashToolset(
    working_directory=workspace,
    allowed_tools=["cat", "find", "grep", "ls"],
)

agent = Agent(
    name="code_reader_agent",
    model_name="doubao-seed-2-1-pro-260628",
    description="An agent that reads and searches local files.",
    instruction="Use the provided read-only tools to read and search files in the working directory and answer the user's questions.",
    tools=[toolset],
)

runner = Runner(agent=agent, short_term_memory=ShortTermMemory())


async def main():
    response = await runner.run("Read main.py and summarise its functionality")
    print(response)


if __name__ == "__main__":
    asyncio.run(main())
```

## Parameters

### Constructor parameters

```python lines theme={null}
BashToolset(
    working_directory=None,
    allowed_directories=None,
    allowed_tools=None,
    exclude_patterns=None,
    timeout=10.0,
    max_output_bytes=32_768,
    max_read_bytes=8 * 1024 * 1024,
    max_entries=10_000,
)
```

| Parameter | Type | Default | Description |
| :- | :- | :- | :- |
| `working_directory` | `str \| Path \| None` | `None` (current directory) | Base directory for relative paths. Must already exist. |
| `allowed_directories` | `Sequence[str \| Path] \| None` | `None` (working directory) | Accessible root directories. Relative paths are resolved against the working directory. |
| `allowed_tools` | `Sequence[str] \| None` | `None` (all ten tools) | Enabled tool names. An empty sequence disables every tool. Unknown tool names raise an error. |
| `exclude_patterns` | `Sequence[str] \| None` | `None` (common credential files) | Case-sensitive glob patterns to exclude. Matched against names and root-relative paths (including ancestors, to exclude whole directories). `None` uses default exclusions; `[]` explicitly clears them. |
| `timeout` | `float` | `10.0` | Maximum caller wait in seconds, shared by an entire invocation. Must be a finite positive number. |
| `max_output_bytes` | `int` | `32768` | Maximum UTF-8 bytes returned in the output field. Must be a positive integer. |
| `max_read_bytes` | `int` | `8388608` (8 MB) | Maximum total input bytes read by one invocation. Must be a positive integer. |
| `max_entries` | `int` | `10000` | Maximum directory entries visited by one invocation. Must be a positive integer. |

<Note>
  Default exclusions are: `.env`, `.env.*`, `.ssh`, `.aws`, `*.pem`, `*.key`. Pass an empty list `[]` to explicitly clear all exclusions, making every file within the permission scope readable.
</Note>

Permission checks apply to both the requested path and its resolved symlink target. Recursive operations skip symlinks, excluded entries, and special files (such as named pipes).

### Return format

All tools return a dictionary with the following fields:

| Field | Type | Description |
| :- | :- | :- |
| `output` | `str` | Tool output. May be partial when an error or resource limit interrupts a request. |
| `error` | `str \| None` | Error message. `None` on success. |
| `truncated` | `bool` | Whether the output was truncated due to exceeding `max_output_bytes`. |

## Tool reference

### cat

Read the contents of a text file at the given path.

| Parameter | Type | Default | Description |
| :- | :- | :- | :- |
| `path` | `str` | — | File path, absolute or relative to the working directory. |

### pwd

Returns the working directory of the toolset. Takes no parameters.

### ls

List children of a directory without following symlinks.

| Parameter | Type | Default | Description |
| :- | :- | :- | :- |
| `path` | `str` | `"."` | Directory to list. |
| `show_hidden` | `bool` | `False` | Include dotfiles except those excluded by permissions. |

### find

Recursively find files or directories by filename glob without following symlinks.

| Parameter | Type | Default | Description |
| :- | :- | :- | :- |
| `path` | `str` | `"."` | Directory to search. |
| `pattern` | `str` | `"*"` | Filename glob, e.g. `*.py`. |
| `file_type` | `str` | `"all"` | Filter type: `all`, `file`, or `directory`. |
| `max_depth` | `int` | `20` | Search depth from 0 to 100. `1` lists direct children only. |

### grep

Search file contents for literal text and return matching lines with path and line number. Does not support regular expressions.

| Parameter | Type | Default | Description |
| :- | :- | :- | :- |
| `pattern` | `str` | — | Literal text to find. |
| `path` | `str` | `"."` | File or directory to search. |
| `recursive` | `bool` | `True` | Search descendant directories when the path is a directory. |
| `ignore_case` | `bool` | `False` | Use Unicode case-insensitive matching. |
| `file_pattern` | `str` | `"*"` | Filename glob used when searching a directory. |
| `max_depth` | `int` | `20` | Maximum recursive directory depth from 0 to 100. |

### head

Read the first lines of a text file.

| Parameter | Type | Default | Description |
| :- | :- | :- | :- |
| `path` | `str` | — | File to read. |
| `lines` | `int` | `10` | Number of lines, from 0 to 10000. |

### tail

Read the last lines of a text file.

| Parameter | Type | Default | Description |
| :- | :- | :- | :- |
| `path` | `str` | — | File to read. |
| `lines` | `int` | `10` | Number of lines, from 0 to 10000. |

### wc

Count newline characters, whitespace-separated words, and bytes in a file. Returns a JSON result.

| Parameter | Type | Default | Description |
| :- | :- | :- | :- |
| `path` | `str` | — | File to count. Subject to the `max_read_bytes` limit. |

### stat

Return file or directory type, size, permissions, and modification time as a JSON result.

| Parameter | Type | Default | Description |
| :- | :- | :- | :- |
| `path` | `str` | — | File or directory to inspect. |

### diff

Compare two text files using unified diff format. Each file is limited to 2000 lines.

| Parameter | Type | Default | Description |
| :- | :- | :- | :- |
| `path_a` | `str` | — | Original file path. |
| `path_b` | `str` | — | Changed file path. |
| `context_lines` | `int` | `3` | Surrounding unchanged lines to include, from 0 to 100. |

## Configuring multiple allowed directories

`BashToolset` supports multiple allowed root directories, enabling the agent to read files across directories:

```python title="bash_toolset_multi_root.py" lines theme={null}
import asyncio
from pathlib import Path

from veadk import Agent, Runner
from veadk.memory.short_term_memory import ShortTermMemory
from veadk.tools.builtin_tools.bash_toolset import BashToolset

project = Path("reader_demo/project").resolve()
docs = Path("reader_demo/docs").resolve()
(project / "src").mkdir(parents=True, exist_ok=True)
docs.mkdir(parents=True, exist_ok=True)
(project / "src/main.py").write_text("print(2 + 3)\n", encoding="utf-8")
(docs / "design.md").write_text("The program adds two numbers\n", encoding="utf-8")

toolset = BashToolset(
    working_directory=project,
    allowed_directories=[project, docs],
    allowed_tools=["cat", "ls", "find", "grep"],
    timeout=30.0,
    max_output_bytes=65536,
)

agent = Agent(
    name="multi_root_agent",
    model_name="doubao-seed-2-1-pro-260628",
    description="An agent that reads files across multiple directories.",
    instruction="Read files from the code and docs directories and answer the user's questions.",
    tools=[toolset],
)

runner = Runner(agent=agent, short_term_memory=ShortTermMemory())


async def main():
    response = await runner.run(f"Compare src/main.py with {docs / 'design.md'}")
    print(response)


if __name__ == "__main__":
    asyncio.run(main())
```

## Security notes

<Warning>
  * Permissions on `BashToolset` are fixed at creation time. Modifying the lists passed to the constructor after creation does not affect the toolset.
  * Application-level access control does not replace an OS-level sandbox. The host must control directory renames, hard links, and mounts inside the allowed roots.
  * Timeouts cancel the caller and cooperatively stop the read worker; a blocked filesystem call cannot be forcibly interrupted by a Python thread.
</Warning>

## Local verification without a model

This example creates a text file in a temporary directory, reads and prints `hello`, then removes the temporary directory. The application prepares the file; the reading tool does not modify it.

```python lines theme={null}
import asyncio
from pathlib import Path
from tempfile import TemporaryDirectory
from veadk.tools.builtin_tools.bash_toolset import BashToolset

async def main():
    with TemporaryDirectory() as directory:
        Path(directory, "sample.txt").write_text("hello\n", encoding="utf-8")
        toolset = BashToolset(working_directory=directory, allowed_tools=["cat"])
        try:
            tool = (await toolset.get_tools())[0]
            result = await tool.run_async(args={"path": "sample.txt"}, tool_context=None)
            assert result["error"] is None
            print(result["output"])
        finally:
            await toolset.close()

asyncio.run(main())
```

Relative paths always resolve against `working_directory`, including with multiple allowed roots. Use an absolute path for another allowed directory. Custom `exclude_patterns` replace the defaults rather than merging with them. When `error` is present, nonempty `output` may be only a partial result.
