Skip to content

Sandbox Shell Tool#

SandboxShellTool lets an agent run shell commands inside an environment you choose — a local subprocess, a Docker container, a Daytona or Tenki sandbox, or any custom backend. Unlike the provider-hosted ShellTool (which runs server-side, and only on OpenAI), it executes client-side, so it works with any model provider.

The design has two orthogonal pieces:

  • The environment decides where commands run and carries all backend config (image, env vars, network, timeout, …).
  • The tool decides the agent-facing policy (allowed / blocked / ignore / readonly).

The same environment can back both a SandboxShellTool and a SandboxCodeTool.

Quick Start#

from ag2 import Agent
from ag2.config import AnthropicConfig
from ag2.tools import SandboxShellTool

agent = Agent(
    "coder",
    "You write and run Python code.",
    config=AnthropicConfig(model="claude-sonnet-5"),
    tools=[SandboxShellTool()],
)

reply = await agent.ask("Write a hello world script and run it.")
print(await reply.content())

With no arguments, SandboxShellTool uses a LocalEnvironment with a temporary working directory that is cleaned up when the process exits.

Choosing an Environment#

The first argument is the environment. Pass a LocalEnvironment, DockerEnvironment, DaytonaEnvironment, or TenkiEnvironment:

from ag2.tools import SandboxShellTool, LocalEnvironment
from ag2.extensions.docker import DockerEnvironment
from ag2.extensions.daytona import DaytonaEnvironment
from ag2.extensions.tenki import TenkiEnvironment

# Local subprocess in a specific directory
sh = SandboxShellTool(LocalEnvironment("/tmp/my_project"))

# Docker container — configure the backend once
sh = SandboxShellTool(DockerEnvironment(image="python:3.12-slim", network_mode="none"))

# Daytona hosted sandbox
sh = SandboxShellTool(DaytonaEnvironment(image="python:3.12"))

# Tenki hosted sandbox
sh = SandboxShellTool(TenkiEnvironment())

A LocalEnvironment directory is created automatically if it does not exist, and is not deleted on exit when an explicit path is given.

Command Filtering#

Filtering policy lives on the tool, not the environment:

1
2
3
4
5
6
7
8
from ag2.tools import SandboxShellTool, LocalEnvironment

sh = SandboxShellTool(
    LocalEnvironment("/tmp/my_project"),
    allowed=["python", "uv run", "git"],
    blocked=["rm -rf", "curl", "wget"],
    ignore=["**/.env", "*.key", "secrets/**"],
)

Tool Parameters#

Parameter Default Description
environment None Backend: LocalEnvironment / DockerEnvironment / DaytonaEnvironment / TenkiEnvironment. None → LocalEnvironment()
allowed None Whitelist of command prefixes, matched word by word. Setting it switches on restricted mode. None → all commands allowed
blocked None Blacklist of command prefixes. None → nothing blocked
ignore None Gitignore-style path patterns. Commands referencing matching paths return "Access denied: <path>"
readonly False When True and allowed is not set, restricts to a built-in list of commands that cannot write files or run programs (cat, ls, grep, …) in restricted mode

LocalEnvironment Parameters#

Parameter Default Description
path None Working directory. None → temporary dir prefixed ag2_sandbox_, deleted on exit
cleanup None None → auto (True when path=None, False otherwise)
timeout 60 Per-command timeout in seconds. Returns an exit code 124 result on expiry
max_output 100_000 Maximum characters in the returned output; truncated output gets a [truncated: …] suffix
env_vars None Environment variables merged into every command

Filter Order#

Filtering is applied in this order on every run_shell_command(command) call:

  1. allowed — if set, the command must match at least one prefix and use no shell syntax (pipes, redirects, chaining). Otherwise: "Command not allowed: <cmd>".
  2. blocked — if set, the command must not match any prefix. Otherwise: "Command not allowed: <cmd>".
  3. ignore — literal file paths parsed from the command string are resolved and checked against the patterns. On match: "Access denied: <path>".
  4. Execute — the command runs in the environment: through sh -c, or as a plain argv in restricted mode.

Note

ignore checks only literal path tokens in the command string. Paths computed dynamically inside the shell (variable substitution, command substitution, glob expansion) are not inspected.

Read-Only Mode#

Use readonly=True to let the agent inspect files without modifying anything:

1
2
3
from ag2.tools import SandboxShellTool, LocalEnvironment

sh = SandboxShellTool(LocalEnvironment("/my/codebase"), readonly=True)

This restricts commands to cat, head, tail, ls, grep, wc, diff, stat, and a few others: only commands that have no option to write a file or run another program.

find, file and git are not in the list, because each can write or run programs through its arguments (find -exec, file -C, git diff --output, and programs named in .git/config). To give a read-only agent git log or find, list them in allowed. An explicit allowed replaces the built-in set, and what its commands can do is then your call:

sh = SandboxShellTool(LocalEnvironment("/my/codebase"), allowed=["cat", "ls", "grep", "git log", "git diff"])

For real isolation, use a DockerEnvironment, DaytonaEnvironment, or TenkiEnvironment.

Restricted Mode#

Setting allowed or readonly=True switches on restricted mode. The command is split into words once, the same way a POSIX shell quotes them, and that exact argv is checked and then run without a shell. Nothing can expand after the check, so none of these work:

  • pipes, redirects and chaining (|, >, <, ;, &, &&, ||, newlines), rejected with a clear error;
  • globs: ls *.py passes *.py to ls literally, so use grep -r --include='*.py' or ls instead;
  • variables, ~, brace expansion and command substitution;
  • shell builtins: echo and pwd run the programs found on PATH.

Without allowed and readonly, commands run through sh -c with full shell syntax.

Accessing the Working Directory#

SandboxShellTool exposes the resolved working directory via the workdir property:

sh = SandboxShellTool(LocalEnvironment("/tmp/my_project"))
print(sh.workdir)  # PosixPath('/tmp/my_project')

Stateful Multi-Turn Conversations#

Because files persist in workdir across ask() calls, the agent can build on prior work in a chained conversation:

from ag2 import Agent
from ag2.config import AnthropicConfig
from ag2.tools import SandboxShellTool, LocalEnvironment

sh = SandboxShellTool(LocalEnvironment("/tmp/counter_demo"))
agent = Agent("coder", "You manage files.", config=AnthropicConfig(model="claude-sonnet-5"), tools=[sh])

reply1 = await agent.ask("Create counter.txt with value 0")
reply2 = await reply1.ask("Increment the counter by 1")
reply3 = await reply2.ask("Read the counter and tell me the value")

Warning

A LocalEnvironment gives the agent direct access to your filesystem and the ability to run arbitrary commands. Always set allowed, blocked, or readonly when exposing it to untrusted prompts, or use a DockerEnvironment, DaytonaEnvironment, or TenkiEnvironment for real isolation.

SandboxShellTool vs ShellTool vs AnthropicBashTool#

SandboxShellTool ShellTool AnthropicBashTool
Execution Client-side, in your environment Provider-side, in OpenAI's container Client-side, in your environment
Provider support Any provider OpenAI only Anthropic only
Tool definition AG2's run_shell_command function OpenAI's hosted shell Anthropic's bash_20250124
Environment control Full (allowed, blocked, ignore, backend choice) Limited (provider-dependent) Full — same options as SandboxShellTool
Import ag2.tools.SandboxShellTool ag2.tools.ShellTool ag2.tools.AnthropicBashTool

AnthropicBashTool exists because Anthropic ships its own bash tool definition but does not run it. It reuses everything on this page for execution, so reach for it when you want Claude to use the tool it was trained on; reach for SandboxShellTool when the same agent code has to run against several providers. See Anthropic Bash.