Skip to content

ant_ai.tools.builtins.shell_tool

ShellTool pydantic-model

Bases: Tool

Execute shell commands in the agent workspace.

Example
from ant_ai.agent import Agent
from ant_ai.tools.builtins.shell_tool import ShellTool

shell = ShellTool(
    cwd="/workspace",
    timeout=30,
    blocked_commands=["rm\s+-rf\b", "shutdown", "reboot"],
)
agent = Agent(tools=[shell], ...)

The agent can now call:

- ShellTool_run(command="python3 main.py")
- ShellTool_run(command="pytest tests/ -q")
Notes

Commands are filtered before execution against allowed_commands (allowlist) and blocked_commands (denylist), both expressed as regex patterns. Policy violations, timeouts, and runtime errors are returned as a dict with a non-zero returncode and the reason in stderr, so the agent receives a recoverable error rather than an exception.

Show JSON schema:
{
  "description": "Execute shell commands in the agent workspace.\n\nExample:\n    ```python\n    from ant_ai.agent import Agent\n    from ant_ai.tools.builtins.shell_tool import ShellTool\n\n    shell = ShellTool(\n        cwd=\"/workspace\",\n        timeout=30,\n        blocked_commands=[\"rm\\s+-rf\\b\", \"shutdown\", \"reboot\"],\n    )\n    agent = Agent(tools=[shell], ...)\n    ```\n\n    The agent can now call:\n\n        - ShellTool_run(command=\"python3 main.py\")\n        - ShellTool_run(command=\"pytest tests/ -q\")\n\nNotes:\n    Commands are filtered before execution against `allowed_commands` (allowlist)\n    and `blocked_commands` (denylist), both expressed as regex patterns.\n    Policy violations, timeouts, and runtime errors are returned as a dict\n    with a non-zero `returncode` and the reason in `stderr`, so the agent\n    receives a recoverable error rather than an exception.",
  "properties": {
    "name": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Tool name.",
      "title": "Name"
    },
    "description": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "Tool description. Is used by the LLM to decide whether to call or not the specific tool.",
      "title": "Description"
    },
    "parameters": {
      "anyOf": [
        {
          "additionalProperties": true,
          "type": "object"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "description": "The parameters needed by the tool. This is a self-constructed field.",
      "title": "Parameters"
    },
    "cwd": {
      "default": "/workspace",
      "title": "Cwd",
      "type": "string"
    },
    "timeout": {
      "default": 60,
      "title": "Timeout",
      "type": "integer"
    },
    "allowed_commands": {
      "anyOf": [
        {
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "title": "Allowed Commands"
    },
    "blocked_commands": {
      "anyOf": [
        {
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        {
          "type": "null"
        }
      ],
      "default": null,
      "title": "Blocked Commands"
    }
  },
  "title": "ShellTool",
  "type": "object"
}

Fields:

  • name (str | None)
  • description (str | None)
  • parameters (dict[str, Any] | None)
  • __namespace_methods__ (list[str])
  • cwd (str)
  • timeout (int)
  • allowed_commands (list[str] | None)
  • blocked_commands (list[str] | None)

Validators:

  • _set_defaults
Source code in src/ant_ai/tools/builtins/shell_tool.py
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
class ShellTool(Tool):
    """
    Execute shell commands in the agent workspace.

    Example:
        ```python
        from ant_ai.agent import Agent
        from ant_ai.tools.builtins.shell_tool import ShellTool

        shell = ShellTool(
            cwd="/workspace",
            timeout=30,
            blocked_commands=["rm\\s+-rf\\b", "shutdown", "reboot"],
        )
        agent = Agent(tools=[shell], ...)
        ```

        The agent can now call:

            - ShellTool_run(command="python3 main.py")
            - ShellTool_run(command="pytest tests/ -q")

    Notes:
        Commands are filtered before execution against `allowed_commands` (allowlist)
        and `blocked_commands` (denylist), both expressed as regex patterns.
        Policy violations, timeouts, and runtime errors are returned as a dict
        with a non-zero `returncode` and the reason in `stderr`, so the agent
        receives a recoverable error rather than an exception.
    """

    cwd: str = "/workspace"
    timeout: int = 60
    allowed_commands: list[str] | None = None
    blocked_commands: list[str] | None = None

    def run(self, command: str) -> dict:
        """Execute a shell command in the workspace. Prefer running scripts by filename (e.g. `python3 hello.py`) over passing file contents inline.

        Args:
            command: The shell command to execute.

        Returns:
            A dict with keys: stdout, stderr, returncode.
        """
        if self.allowed_commands:
            allowed: Pattern[str] = re.compile(
                "|".join(f"(?:{p})" for p in self.allowed_commands)
            )
            if not allowed.search(command):
                return {
                    "stdout": "",
                    "stderr": "Command not allowed by policy",
                    "returncode": -1,
                }

        if self.blocked_commands:
            blocked: Pattern[str] = re.compile(
                "|".join(f"(?:{p})" for p in self.blocked_commands)
            )
            if blocked.search(command):
                return {
                    "stdout": "",
                    "stderr": "Command is blocked by policy",
                    "returncode": -1,
                }

        try:
            result: CompletedProcess[str] = subprocess.run(
                command,
                cwd=self.cwd,
                capture_output=True,
                text=True,
                timeout=self.timeout,
                shell=True,
            )
            return {
                "stdout": result.stdout,
                "stderr": result.stderr,
                "returncode": result.returncode,
            }
        except subprocess.TimeoutExpired:
            return {
                "stdout": "",
                "stderr": f"Command timed out after {self.timeout} seconds",
                "returncode": -1,
            }
        except FileNotFoundError:
            return {
                "stdout": "",
                "stderr": f"Working directory not found: {self.cwd}",
                "returncode": -1,
            }
        except Exception as e:
            return {
                "stdout": "",
                "stderr": str(e),
                "returncode": -1,
            }

run

run(command: str) -> dict

Execute a shell command in the workspace. Prefer running scripts by filename (e.g. python3 hello.py) over passing file contents inline.

Parameters:

Name Type Description Default
command str

The shell command to execute.

required

Returns:

Type Description
dict

A dict with keys: stdout, stderr, returncode.

Source code in src/ant_ai/tools/builtins/shell_tool.py
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
def run(self, command: str) -> dict:
    """Execute a shell command in the workspace. Prefer running scripts by filename (e.g. `python3 hello.py`) over passing file contents inline.

    Args:
        command: The shell command to execute.

    Returns:
        A dict with keys: stdout, stderr, returncode.
    """
    if self.allowed_commands:
        allowed: Pattern[str] = re.compile(
            "|".join(f"(?:{p})" for p in self.allowed_commands)
        )
        if not allowed.search(command):
            return {
                "stdout": "",
                "stderr": "Command not allowed by policy",
                "returncode": -1,
            }

    if self.blocked_commands:
        blocked: Pattern[str] = re.compile(
            "|".join(f"(?:{p})" for p in self.blocked_commands)
        )
        if blocked.search(command):
            return {
                "stdout": "",
                "stderr": "Command is blocked by policy",
                "returncode": -1,
            }

    try:
        result: CompletedProcess[str] = subprocess.run(
            command,
            cwd=self.cwd,
            capture_output=True,
            text=True,
            timeout=self.timeout,
            shell=True,
        )
        return {
            "stdout": result.stdout,
            "stderr": result.stderr,
            "returncode": result.returncode,
        }
    except subprocess.TimeoutExpired:
        return {
            "stdout": "",
            "stderr": f"Command timed out after {self.timeout} seconds",
            "returncode": -1,
        }
    except FileNotFoundError:
        return {
            "stdout": "",
            "stderr": f"Working directory not found: {self.cwd}",
            "returncode": -1,
        }
    except Exception as e:
        return {
            "stdout": "",
            "stderr": str(e),
            "returncode": -1,
        }