| name | ag2-shell-tool |
| description | Give an AG2 beta `Agent` the ability to run shell commands. Covers `LocalShellTool` (client-side `subprocess`, works with any provider) and the provider-native `ShellTool` (Anthropic / OpenAI execution). Use when the user wants the Agent to execute commands, build/test code, manage files, or operate on a workspace. Always pair with sandboxing — `allowed`, `blocked`, `ignore`, or `readonly`. |
| license | Apache-2.0 |
Shell tools
When to use
Two distinct tools, both named "shell" — pick deliberately:
| Need | Use | Why |
|---|
| Works with any model provider; full control over what runs and where | LocalShellTool | Client-side subprocess. You own the sandbox. |
| Provider-managed sandbox (container, network policy) on Anthropic / OpenAI | ShellTool | Server-side execution. No local subprocess. |
LocalShellTool is the workhorse. Reach for it unless you specifically need provider-managed isolation and you're on Anthropic or OpenAI.
60-second recipe — LocalShellTool
from autogen.beta import Agent
from autogen.beta.config import AnthropicConfig
from autogen.beta.tools import LocalShellTool
agent = Agent(
"coder",
"You write and run Python code.",
config=AnthropicConfig(model="claude-sonnet-4-6"),
tools=[LocalShellTool()],
)
reply = await agent.ask("Write a hello world script and run it.")
print(await reply.content())
LocalShellTool is provider-agnostic — swap AnthropicConfig for OpenAIConfig(model="gpt-4.1"), GeminiConfig(model="gemini-2.5-pro"), etc. Make sure you've installed the matching ag2[<provider>] extra and set the matching env var (see ag2-quickstart → Prerequisites).
With no arguments, LocalShellTool creates a temporary working directory (prefixed ag2_shell_) and cleans it up when the process exits. Pass a path to use a specific directory:
from pathlib import Path
LocalShellTool("/tmp/my_project")
LocalShellTool(Path("/tmp/my_project"))
When a path is given, the directory is created if it does not exist and is not deleted on exit. Inspect the resolved working directory via tool.workdir.
Sandboxing (LocalShellEnvironment)
For anything beyond a throwaway demo, use LocalShellEnvironment and lock down what the agent can do. Filtering is applied in this order on every call:
allowed — if set, the command must match at least one prefix.
blocked — if set, the command must not match any prefix.
ignore — literal path tokens in the command are checked against gitignore-style patterns; matches return "Access denied: <path>".
- Execute via
subprocess.run.
from autogen.beta.tools import LocalShellTool
from autogen.beta.tools.shell import LocalShellEnvironment
sh = LocalShellTool(
LocalShellEnvironment(
path="/tmp/my_project",
allowed=["python", "uv run", "git"],
blocked=["rm -rf", "curl", "wget"],
ignore=["**/.env", "*.key", "secrets/**"],
timeout=30,
max_output=50_000,
)
)
Read-only mode
For inspection-only access (cat, head, tail, ls, grep, find, git log, git diff, git status, …):
from autogen.beta.tools import LocalShellTool
from autogen.beta.tools.shell import LocalShellEnvironment
sh = LocalShellTool(LocalShellEnvironment(path="/my/codebase", readonly=True))
Pass an explicit allowed=[...] to override the built-in read-only allowlist.
LocalShellEnvironment parameter reference
| Parameter | Default | Description |
|---|
path | None | Working dir. None → temp dir, deleted on exit |
cleanup | None | None → auto (True when path=None, False otherwise). Deletes path on process exit |
allowed | None | Whitelist of command prefixes. None → all commands allowed |
blocked | None | Blacklist of command prefixes |
ignore | None | Gitignore-style path patterns; matches block the command |
readonly | False | When True and allowed unset, restricts to a built-in read-only list |
env | None | Extra env vars merged into each command |
timeout | 60 | Per-command timeout in seconds (returns "Command timed out after Ns [exit code: 124]") |
max_output | 100_000 | Max characters returned (truncated output is suffixed [truncated: …]) |
Stateful multi-turn workspaces
Files persist in workdir across ask() calls, so the agent can build on prior work:
from autogen.beta.tools import LocalShellTool
from autogen.beta.tools.shell import LocalShellEnvironment
sh = LocalShellTool(LocalShellEnvironment(path="/tmp/counter_demo"))
agent = Agent("coder", "You manage files.", config=config, 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")
Provider-native ShellTool (Anthropic / OpenAI)
from autogen.beta.tools import ShellTool
agent = Agent("devops", config=AnthropicConfig(model="claude-sonnet-4-6"), tools=[ShellTool()])
OpenAI lets you configure the execution environment:
from autogen.beta.config import OpenAIResponsesConfig
from autogen.beta.tools import ShellTool
from autogen.beta.tools.builtin.shell import ContainerAutoEnvironment, NetworkPolicy
agent = Agent(
"devops",
config=OpenAIResponsesConfig(model="gpt-4.1"),
tools=[
ShellTool(
environment=ContainerAutoEnvironment(
network_policy=NetworkPolicy(allowed_domains=["pypi.org"]),
),
),
],
)
Environment options:
| Environment | Description |
|---|
ContainerAutoEnvironment | Provider-managed container with optional network policy |
ContainerReferenceEnvironment | Reference an existing container by ID |
ShellTool is not supported on Gemini — the request will raise UnsupportedToolError.
LocalShellTool vs ShellTool
| LocalShellTool | ShellTool |
|---|
| Execution | Client-side subprocess | Provider-side container |
| Provider support | Any provider | Anthropic, OpenAI |
| Environment control | Full (allowed, blocked, ignore, readonly, …) | Limited (provider-dependent) |
| Local FS access | Yes (you choose what's exposed) | No |
| Network control | Via blocked / allowed patterns | OpenAI: NetworkPolicy |
| Import | from autogen.beta.tools import LocalShellTool (env: from autogen.beta.tools.shell import LocalShellEnvironment) | from autogen.beta.tools import ShellTool |
Going deeper
website/docs/beta/tools/local_shell.mdx — full LocalShellTool reference, command-filtering semantics.
website/docs/beta/tools/builtin_tools.mdx#shell — provider-native ShellTool setup and environment configs.
- For human-approval gating before each shell call, layer
approval_required() middleware (see ag2-hitl).
Common pitfalls
- Forgetting sandboxing in production —
LocalShellTool() with no environment runs anything anywhere with a 60s timeout. Set allowed, blocked, or readonly for any non-trivial use.
ignore only checks literal paths in the command string — variable substitution, command substitution (`cat secrets.key`), and dynamic glob expansion are not inspected. Layer in blocked=["cat", "less"] if you also want to block readers.
- Trying to use
ShellTool on Gemini — unsupported, will raise. Use LocalShellTool instead.
- Using a hardcoded path that another process is also touching — multiple agents sharing
/tmp/my_project will race. Use tempfile.mkdtemp(prefix="...") for parallel runs.
- Expecting
ShellTool to access local files — it doesn't; it runs in the provider's container. Use LocalShellTool for anything on your filesystem.
- Trusting the LLM with shell access — even sandboxed, write
prompts that scope what's allowed and consider pairing with approval_required() for destructive operations.