Shell Agent

Shell command execution tools for AI agents.

python
from praisonaiagents import Agent
from praisonaiagents import execute_command

agent = Agent(
    name="Shell Assistant",
    instructions="Run approved shell commands and report output.",
    tools=[execute_command],
)
agent.start("Show disk usage for the current project directory")

Run long-lived commands without blocking the turn:

python
from praisonaiagents import Agent
from praisonaiagents import execute_command, get_command_output, stop_command

agent = Agent(
    name="Dev Runner",
    instructions=(
        "Start the dev server in the background, tail its output to confirm "
        "it's up, run a quick smoke test against it, then stop it."
    ),
    tools=[execute_command, get_command_output, stop_command],
)
agent.start("Launch `npm run dev`, confirm it serves on :3000, then shut it down.")

The user requests a system check; the agent runs bounded shell commands and returns stdout safely.

Shell tools let AI agents execute commands, manage processes, and monitor system resources.

Diagram

Input

Shell Agent

Shell Tools

Output

Diagram
Shell AgentAgentUserShell AgentAgentUserRequestProcess requestResultResponse

Shell Tools

Use Shell Tools to execute shell commands with AI agents.

  1. Install Dependencies

    First, install the required package:

    bash
    pip install praisonaiagents
  2. Import Components

    Import the necessary components:

    python
    from praisonaiagents import Agent, Task, AgentTeam
    from praisonaiagents import execute_command, list_processes, kill_process, get_system_info
  3. Create Agent

    Create a shell command agent:

    python
    shell_agent = Agent(
        name="ShellCommander",
        role="Shell Command Specialist",
        goal="Execute shell commands efficiently and safely.",
        backstory="Expert in command-line operations and automation.",
        tools=[execute_command, list_processes, kill_process, get_system_info],
        reflection=False
    )
  4. Define Task

    Define the shell task:

    python
    shell_task = Task(
        description="List and organize files in the current directory.",
        expected_output="Organized file structure with detailed listing.",
        agent=shell_agent,
        name="file_organization"
    )
  5. Run Agent

    Initialize and run the agent:

    python
    agents = AgentTeam(
        agents=[shell_agent],
        tasks=[shell_task],
        process="sequential"
    )
    agents.start()

Understanding Shell Tools

What are Shell Tools?

Shell Tools provide command-line capabilities for AI agents: – Command execution – Process management – Output handling – Error handling – Environment management

Key Components

python
from praisonaiagents import Agent, Task, AgentTeam
from praisonaiagents import execute_command, list_processes, kill_process, get_system_info

# Create shell agent
shell_agent = Agent(
    name="CommandExecutor",
    role="Shell Command Specialist",
    goal="Execute shell commands efficiently and safely.",
    backstory="Expert in command-line operations and scripting.",
    tools=[execute_command, list_processes, kill_process, get_system_info],
    reflection=False
)

# Define shell task
shell_task = Task(
    description="Clean up temporary files and organize downloads.",
    expected_output="Cleaned and organized file system.",
    agent=shell_agent,
    name="system_cleanup"
)

# Run agent
agents = AgentTeam(
    agents=[shell_agent],
    tasks=[shell_task],
    process="sequential"
)
agents.start()

Advanced Shell Management with Multiple Agents

python
# Create command agent
command_agent = Agent(
    name="Commander",
    role="Command Executor",
    goal="Execute shell commands systematically.",
    tools=[execute_command, list_processes, kill_process, get_system_info],
    reflection=False
)

# Create monitoring agent
monitor_agent = Agent(
    name="Monitor",
    role="Process Monitor",
    goal="Monitor and manage running processes.",
    backstory="Expert in system monitoring and process control.",
    reflection=False
)

# Define tasks
command_task = Task(
    description="Execute system maintenance commands.",
    agent=command_agent,
    name="system_maintenance"
)

monitor_task = Task(
    description="Monitor system resources and processes.",
    agent=monitor_agent,
    name="process_monitoring"
)

# Run agents
agents = AgentTeam(
    agents=[command_agent, monitor_agent],
    tasks=[command_task, monitor_task],
    process="sequential"
)
agents.start()

Available Functions

python
from praisonaiagents import execute_command
from praisonaiagents import list_processes
from praisonaiagents import kill_process
from praisonaiagents import get_system_info
from praisonaiagents import get_command_output
from praisonaiagents import stop_command

Function Details

execute_command(command, cwd=None, timeout=30, env=None, max_output_size=10000, spill=True, spill_dir=None, run_in_background=False)

Safely executes shell commands:

  • Timeout protection
  • Output capture
  • Dangerous command blocking (PR #2062) — rm, mkfs, dd, shutdown, etc.
  • Environment and working directory control
  • Large output spill — over-budget output is saved to a retrievable artifact
  • Shell syntax refusal (PR #4968) — redirects, pipes, chains, substitutions and newlines are refused with a named-token error rather than silently mangled
  • Credential env scrubbing (PR #5987) — names ending in _KEY or containing a TOKEN, SECRET, PASSWORD, PASSWD, CREDENTIAL, CREDENTIALS or APIKEY segment are removed from the subprocess env and from $VAR expansion. See Credential Environment Variables.
ParameterTypeDefaultDescription
commandstr—Command to execute (split with shlex, no shell).
cwdOptional[str]NoneWorking directory.
timeoutint30Maximum execution time in seconds. Ignored when run_in_background=True.
envOptional[Dict[str, str]]NoneExtra environment variables. Values passed here are always forwarded, even names that match the credential pattern.
max_output_sizeint10000Byte/char budget for the inline preview.
spillboolTrueSave over-budget output to an artifact instead of dropping the middle.
spill_dirOptional[str]NoneDirectory for artifacts (overrides PRAISONAI_TOOL_OUTPUT_DIR).
run_in_backgroundboolFalseStart the command and return a process_id immediately instead of waiting. timeout is ignored. See Non-Blocking Background Commands.

On timeout or interrupt, partial output is kept. Anything the command printed before the cut-off (including text without a trailing newline) stays in stdout/stderr and goes through the normal max_output_size + spill handling. The reason (Command timed out after N seconds / Command interrupted by user) is appended on its own line to stderr. The result includes timed_out: True on timeout and interrupted: True on user cancel.

Dangerous Command Protection

By default, commands whose base name is in DANGEROUS_COMMANDS are blocked:

python
# Returns: success=False, exit_code=-1
# error: "Command 'rm' is in DANGEROUS_COMMANDS; pass allow_dangerous=True to override."

The check uses shlex.split + os.path.basename, so rm and /usr/bin/rm are both classified as rm. Unparseable commands fall through to later validation.

python
from praisonaiagents import Agent, execute_command
from functools import partial

agent = Agent(
    name="SafeShellAgent",
    instructions="Run shell commands safely; refuse destructive operations.",
    tools=[execute_command],
)

agent.start("List files in current directory")  # works
agent.start("Delete /tmp/test")                 # blocked — rm in pipeline

# Explicit override (use sparingly; combine with approval)
risky_exec = partial(execute_command, allow_dangerous=True)
Diagram

no

yes

User

Agent

execute_command

DANGEROUS_COMMANDS?

Run command

Safe error response

python
# Basic command execution
result = execute_command("ls -la")

# Advanced execution (shell=False — deprecated param omitted)
result = execute_command(
    "python script.py",
    cwd="/path/to/scripts",
    timeout=60,
    env={"PYTHONPATH": "/custom/path"},
)

# Returns: Dict[str, Union[str, int, bool]]
# Example output:
# {
#     'stdout': 'command output...',
#     'stderr': 'error output if any',
#     'exit_code': 0,
#     'success': True,
#     'execution_time': 0.123,
#     # Only present on overflow with spill=True:
#     'stdout_artifact': '/tmp/praisonai_tool_output/stdout_xxxxx.txt',
#     'stderr_artifact': '/tmp/praisonai_tool_output/stderr_xxxxx.txt'
# }

# On timeout — output printed before the cut-off is kept:
# {
#     'stdout': 'step1 ok\n',
#     'stderr': 'warn: x\nCommand timed out after 2 seconds',
#     'exit_code': -1,
#     'success': False,
#     'timed_out': True,
#     'execution_time': 2,
# }

# On /stop interrupt — same shape with interrupted=True instead of timed_out

Refused Shell Syntax

execute_command refuses shell metacharacters it cannot honour so agents get a clear error instead of a silent no-op.

Because commands run with shell=False, a redirect like > is never interpreted — it becomes a literal argument. Before PR #4968, execute_command("echo written > redir.txt") returned success=True with stdout="written > redir.txt\n" and no file, so the agent was told its redirect had succeeded with nothing to correct on. Now the tool refuses the command and names the offending token, so the agent can run the steps separately instead.

Tokens refused when they appear outside quotes:

CommandRefused?Reason
echo written > redir.txtYes (>)unquoted redirect
echo a >> log.txtYes (>>)unquoted redirect
cd .. && pwdYes (&&)unquoted chain
`false \\echo fallback`Yes (`\\`)unquoted chain
`ls \head`Yes (`\`)unquoted pipe
echo a; echo bYes (;)unquoted separator
echo $(whoami)Yes ($()unquoted substitution
echo "$(whoami)"Yes ($()POSIX shells evaluate $(...) inside double quotes
`echo "whoami"`Yes (` “)same for backticks
`echo ‘a \b’`Nosingle-quoted, literal
echo '$(whoami)'Nosingle-quoted, literal
git commit -m "fix: a > b"No> inside double quotes is inert
echo first\ntouch proof.txtYes (\n)newline separates commands like ;

The \r carriage return is also treated as a separator.

When refused, the result names the token and reports failure:

python
{
    "error": "Shell syntax '>' is not supported by this executor, which runs commands directly (shell=False) so untrusted input cannot reach a shell. Run the steps separately, or use a tool that provides a real shell inside a sandbox.",
    "stdout": "",
    "stderr": "",
    "exit_code": 1,
    "success": False,
}

An agent that tries a redirect is refused, then writes the file with write_file instead:

python
from praisonaiagents import Agent, execute_command
from praisonaiagents.tools import write_file

agent = Agent(
    name="ShellAssistant",
    instructions="Run shell commands. If a redirect or pipe is refused, use write_file or run the steps separately.",
    tools=[execute_command, write_file],
)

# The agent tries: execute_command("echo hello > out.txt")
#   → refused with "Shell syntax '>' is not supported by this executor..."
# The agent recovers by calling write_file("out.txt", "hello") instead.
agent.start("Save 'hello' to out.txt")

Users seeing this for the first time need to know what to do instead:

Diagram

yes

no

Need a redirect / pipe / chain?

execute_command

Shell syntax outside quotes?

Refused: named-token error

Split into separate execute_command calls

Use file_tools: read_file / write_file

Use a sandboxed shell tool

Run command

Credential Environment Variables

Each subprocess runs with a scrubbed copy of os.environ, so a model-driven env or echo $OPENAI_API_KEY cannot read LLM keys, bot tokens, or gateway secrets back into the chat.

python
from praisonaiagents import Agent, execute_command

agent = Agent(
    name="Shell Assistant",
    instructions="Run shell commands and report their output.",
    tools=[execute_command],
)

# OPENAI_API_KEY is set in the parent env, but any command the agent runs
# (e.g. `env`) sees no OPENAI_API_KEY — PATH, HOME and VIRTUAL_ENV are intact.
agent.start("Dump environment variables for debugging")

A variable is stripped when its uppercased name ends with _KEY, or any underscore-separated segment is TOKEN, SECRET, PASSWORD, PASSWD, CREDENTIAL, CREDENTIALS, or APIKEY.

VariableStripped?Why
OPENAI_API_KEYyesends with _KEY
GATEWAY_AUTH_TOKEN, GH_TOKENyesTOKEN segment
AWS_SECRET_ACCESS_KEYyesSECRET segment
DB_PASSWORDyesPASSWORD segment
SERVICE_APIKEYyesAPIKEY segment
PATH, HOME, LANG, TMPDIR, VIRTUAL_ENV, SSH_AUTH_SOCKnooperational

$VAR expansion in command arguments and cwd reads the same scrubbed env, so a stripped name is left unexpanded — echo $OPENAI_API_KEY prints the literal text $OPENAI_API_KEY, not the key. Names passed via env= are expanded, so cwd="$WORK_DIR" with env={"WORK_DIR": "/srv/app"} works. On Windows, %VAR% is handled the same way with case-insensitive names.

Diagram

yes

no

os.environ

Credential
name?

Drop from child env

Keep in child env

PRAISONAI_SHELL_ENV_ALLOWLIST

env= param

Subprocess env

Not available to command

Pass a secret into one command (preferred):

python
import os
from praisonaiagents import execute_command

execute_command(
    "gh pr list",
    env={"GITHUB_TOKEN": os.environ["GITHUB_TOKEN"]},
)

Allow a secret for every command (operator opt-in):

bash
# Comma-separated, exact variable names
export PRAISONAI_SHELL_ENV_ALLOWLIST=GITHUB_TOKEN,SLACK_BOT_TOKEN

Allowlisted names are kept in the child env and substituted during $VAR expansion.

Non-Blocking Background Commands

Set run_in_background=True to start dev servers, watchers, and long builds without blocking the turn.

Diagram

execute_command
run_in_background=True

process_id

get_command_output

stop_command

status + exit_code + output

python
from praisonaiagents import execute_command, get_command_output, stop_command

h = execute_command("npm run dev", run_in_background=True)
# {"success": True, "process_id": "3f9a1c2b", "pid": 4821, "status": "running",
#  "log_file": "/tmp/praisonai-bg-xxxxxx.log"}

print(get_command_output(h["process_id"], tail=20))
# {"status": "running", "exit_code": None, "output": "...last lines..."}

stop_command(h["process_id"])
# {"success": True, "status": "exited", "exit_code": -15}

On POSIX, stop_command sends SIGTERM to the process group, then SIGKILL after 5 seconds. On Windows it uses psutil or taskkill /F /T.

Diagram

Yes

No

Need to run a command?

Finishes within 30 seconds?

Foreground execute_command

run_in_background=True
get_command_output + stop_command

See Background Shell Execution for patterns and limits.

Large Output Handling

When output exceeds max_output_size, the full buffer is saved to a disk artifact and the preview keeps a bounded head/tail plus a pointer to that file.

<head bytes...>
...[52,318 chars / 894 lines truncated in preview]...
Full output saved to: /tmp/praisonai_tool_output/stdout_xxxxx.txt
Use read_file/grep on that path to inspect the omitted region (do NOT re-run the command through head/tail).
<tail bytes...>

The agent reads the omitted region straight from the artifact path.

python
from praisonaiagents.tools import read_file

read_file("/tmp/praisonai_tool_output/stdout_xxxxx.txt")

Set spill=False to keep the legacy middle-truncated preview with no persistence. See Tool Output Spill for the full mechanism.

get_command_output(process_id, tail=200)

Returns status and the latest output of a background command.

ParameterTypeDefaultDescription
process_idstr—The process_id returned by execute_command.
tailint200Most-recent lines to return (read bounded to the last 64 KB).
python
{
    "success": True,       # True while running or on clean exit
    "process_id": "3f9a1c2b",
    "pid": 4821,
    "status": "running",   # or "exited"
    "exit_code": None,     # int once exited
    "output": "last N lines...",
    "log_file": "/tmp/praisonai-bg-xxxxxx.log",
}

stop_command(process_id)

Stops a background command and its child processes.

ParameterTypeDefaultDescription
process_idstr—The process_id returned by execute_command.
python
{"success": True, "process_id": "3f9a1c2b", "status": "exited", "exit_code": -15}

An unknown id returns {"success": False, "error": "No background command with process_id '...'"}.

list_processes()

Lists running system processes:

  • Process details
  • Resource usage
  • User information
  • Performance metrics
python
# Get list of running processes
processes = list_processes()

# Sort by CPU usage
cpu_intensive = sorted(
    processes,
    key=lambda x: x['cpu_percent'],
    reverse=True
)[:5]

# Returns: List[Dict[str, Union[int, str, float]]]
# Example output:
# [
#     {
#         'pid': 1234,
#         'name': 'python',
#         'username': 'user',
#         'memory_percent': 2.5,
#         'cpu_percent': 15.3
#     },
#     ...
# ]

kill_process(pid: int, force: bool = False)

Terminates system processes:

  • Graceful termination
  • Force kill option
  • Error handling
  • Access control
python
# Graceful termination
result = kill_process(1234)

# Force kill
result = kill_process(
    pid=1234,
    force=True  # Use SIGKILL
)

# Returns: Dict[str, Union[bool, str]]
# Example output:
# {
#     'success': True,
#     'message': 'Process 1234 killed successfully'
# }
# or
# {
#     'success': False,
#     'message': 'Access denied to kill process 1234'
# }

get_system_info()

Retrieves system information:

  • CPU statistics
  • Memory usage
  • Disk space
  • Platform details
  • Boot time
python
# Get system information
info = get_system_info()

# Access specific metrics
print(f"CPU Usage: {info['cpu']['percent']}%")
print(f"Memory Free: {info['memory']['free']} bytes")

# Returns: Dict[str, Union[float, int, str, Dict]]
# Example output:
# {
#     'cpu': {
#         'percent': 45.2,
#         'cores': 8,
#         'physical_cores': 4
#     },
#     'memory': {
#         'total': 16000000000,
#         'available': 8000000000,
#         'percent': 50.0,
#         'used': 8000000000,
#         'free': 4000000000
#     },
#     'disk': {
#         'total': 500000000000,
#         'used': 250000000000,
#         'free': 250000000000,
#         'percent': 50.0
#     },
#     'boot_time': 1641544800,
#     'platform': 'Darwin'
# }

Example Agent Configuration

python
from praisonaiagents import Agent
from praisonaiagents import (
    execute_command, list_processes,
    kill_process, get_system_info
)

agent = Agent(
    name="SystemManager",
    description="An agent that manages system processes and executes commands",
    tools=[
        execute_command, list_processes,
        kill_process, get_system_info
    ]
)

Dependencies

The shell tools require the following package:

  • psutil: For system and process information

This will be automatically installed when needed.

Error Handling

All functions include comprehensive error handling:

  • Command execution errors
  • Process access errors
  • Permission errors
  • Timeout errors
  • Resource errors

Errors are handled consistently:

  • Success cases return expected data type
  • Error cases return error details in result
  • All errors are logged for debugging

Common Use Cases

  1. System Monitoring:
python
# Monitor system resources
info = get_system_info()
if info['cpu']['percent'] > 90:
    print("High CPU usage detected!")
if info['memory']['percent'] > 80:
    print("Low memory warning!")

# List resource-intensive processes
processes = sorted(
    list_processes(),
    key=lambda x: x['memory_percent'],
    reverse=True
)[:5]
print("Top memory users:", processes)
  1. Process Management:
python
# Find and kill zombie processes
for process in list_processes():
    if process['name'] == 'zombie_process':
        result = kill_process(
            process['pid'],
            force=True
        )
        print(f"Kill result: {result['message']}")
  1. Command Execution:
python
# Run maintenance commands one at a time — call the binary directly, no shell syntax
commands = [
    "apt-get update",
    "apt-get upgrade -y",
    "apt-get autoremove -y",
]
for cmd in commands:
    result = execute_command(cmd, timeout=300)
    if result['success']:
        print(f"Command succeeded: {cmd}")
    else:
        print(f"Command failed: {result['stderr']}")

Chaining these with && or piping with | would be refused; run each command as its own execute_command call, as shown.

Best Practices

Agent Configuration

Configure agents with clear shell focus:

python
Agent(
    name="ShellManager",
    role="Command Line Specialist",
    goal="Execute shell commands safely and efficiently",
    tools=[execute_command, list_processes, kill_process, get_system_info]
)
Task Definition

Define specific shell operations:

python
Task(
    description="Clean up system temp files and optimize storage",
    expected_output="Optimized system storage"
)
Always stop background commands you start

A dev server left running at the end of a turn is reaped by the interpreter’s atexit hook, but relying on that silently is bad hygiene. For every execute_command(..., run_in_background=True) call, write the matching stop_command(process_id) into the agent’s instructions.

Common Patterns

Shell Command Pipeline

python
# Command agent
commander = Agent(
    name="Commander",
    role="Shell Commander",
    tools=[execute_command]
)

# Monitor agent
monitor = Agent(
    name="Monitor",
    role="Process Monitor"
)

# Define tasks
command_task = Task(
    description="Execute maintenance commands",
    agent=commander
)

monitor_task = Task(
    description="Monitor command execution",
    agent=monitor
)

# Run workflow
agents = AgentTeam(
    agents=[commander, monitor],
    tasks=[command_task, monitor_task]
)
agents.start()

Related