Shell Agent
Shell command execution tools for AI agents.
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:
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.
Shell Tools
Use Shell Tools to execute shell commands with AI agents.
- Install Dependencies
First, install the required package:
bash pip install praisonaiagents - 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 - 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 ) - 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" ) - 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
Create specialized shell agents:
Agent(tools=[execute_command, list_processes, kill_process, get_system_info])Define shell tasks:
Task(description="shell_operation")Sequential or parallel processing:
process="sequential"Customize shell operations:
timeout=30from 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
# 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
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_commandFunction 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
_KEYor containing aTOKEN,SECRET,PASSWORD,PASSWD,CREDENTIAL,CREDENTIALSorAPIKEYsegment are removed from the subprocess env and from$VARexpansion. See Credential Environment Variables.
| Parameter | Type | Default | Description |
|---|---|---|---|
command | str | — | Command to execute (split with shlex, no shell). |
cwd | Optional[str] | None | Working directory. |
timeout | int | 30 | Maximum execution time in seconds. Ignored when run_in_background=True. |
env | Optional[Dict[str, str]] | None | Extra environment variables. Values passed here are always forwarded, even names that match the credential pattern. |
max_output_size | int | 10000 | Byte/char budget for the inline preview. |
spill | bool | True | Save over-budget output to an artifact instead of dropping the middle. |
spill_dir | Optional[str] | None | Directory for artifacts (overrides PRAISONAI_TOOL_OUTPUT_DIR). |
run_in_background | bool | False | Start 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:
# 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.
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)# 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_outRefused 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:
| Command | Refused? | Reason | ||||
|---|---|---|---|---|---|---|
echo written > redir.txt | Yes (>) | unquoted redirect | ||||
echo a >> log.txt | Yes (>>) | unquoted redirect | ||||
cd .. && pwd | Yes (&&) | unquoted chain | ||||
| `false \ | \ | echo fallback` | Yes (`\ | \ | `) | unquoted chain |
| `ls \ | head` | Yes (`\ | `) | unquoted pipe | ||
echo a; echo b | Yes (;) | 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’` | No | single-quoted, literal | |||
echo '$(whoami)' | No | single-quoted, literal | ||||
git commit -m "fix: a > b" | No | > inside double quotes is inert | ||||
echo first\ntouch proof.txt | Yes (\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:
{
"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:
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:
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.
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.
| Variable | Stripped? | Why |
|---|---|---|
OPENAI_API_KEY | yes | ends with _KEY |
GATEWAY_AUTH_TOKEN, GH_TOKEN | yes | TOKEN segment |
AWS_SECRET_ACCESS_KEY | yes | SECRET segment |
DB_PASSWORD | yes | PASSWORD segment |
SERVICE_APIKEY | yes | APIKEY segment |
PATH, HOME, LANG, TMPDIR, VIRTUAL_ENV, SSH_AUTH_SOCK | no | operational |
$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.
Pass a secret into one command (preferred):
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):
# Comma-separated, exact variable names
export PRAISONAI_SHELL_ENV_ALLOWLIST=GITHUB_TOKEN,SLACK_BOT_TOKENAllowlisted 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.
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.
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.
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.
| Parameter | Type | Default | Description |
|---|---|---|---|
process_id | str | — | The process_id returned by execute_command. |
tail | int | 200 | Most-recent lines to return (read bounded to the last 64 KB). |
{
"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.
| Parameter | Type | Default | Description |
|---|---|---|---|
process_id | str | — | The process_id returned by execute_command. |
{"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
# 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
# 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
# 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
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
- System Monitoring:
# 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)- Process Management:
# 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']}")- Command Execution:
# 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:
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:
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
# 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
Non-blocking execute_command for dev servers, watchers, long test suites
Build your own agent tools
Tools OverviewBrowse PraisonAI tool documentation
Tool Output SpillSave large command output to a retrievable artifact
Protected PathsGuard sensitive files from tool writes