9.5 TerminalTool: Instant File System Access
In previous chapters, we introduced MemoryTool and RAGTool, which provide conversational memory and knowledge retrieval capabilities respectively. However, in many practical scenarios, agents need instant access and exploration of the file system—viewing log files, analyzing codebase structure, retrieving configuration files, etc. This is where TerminalTool comes in.
TerminalTool provides agents with secure command-line execution capability, supporting common file system and text processing commands, while ensuring system security through multi-layer security mechanisms. This design implements the "Just-in-time (JIT) context" concept mentioned in Section 9.2.2—agents don't need to preload all files, but explore and retrieve on demand.
9.5.1 Design Philosophy and Security Mechanisms
(1) Why do we need TerminalTool?
When building long-horizon agents, we often encounter the following scenarios:
Scenario 1: Codebase Exploration
A development assistant needs to help users understand the structure of a large codebase:
# Traditional approach: Pre-index all files (high cost, may be outdated)
rag_tool.add_document("./project/**/*.py") # Time-consuming, occupies large storage
# TerminalTool approach: Instant exploration
terminal.run({"command": "find . -name '*.py' -type f"}) # Fast, real-time
terminal.run({"command": "grep -r 'class UserService' ."}) # Precise location
terminal.run({"command": "head -n 50 src/services/user.py"}) # View on demandScenario 2: Log File Analysis
An operations assistant needs to analyze application logs:
# Check log file size
terminal.run({"command": "ls -lh /var/log/app.log"})
# View latest error logs
terminal.run({"command": "tail -n 100 /var/log/app.log | grep ERROR"})
# Count error type distribution
terminal.run({"command": "grep ERROR /var/log/app.log | cut -d':' -f3 | sort | uniq -c"})Scenario 3: Data File Preview
A data analysis assistant needs to quickly understand the structure of data files:
# View first few lines of CSV file
terminal.run({"command": "head -n 5 data/sales.csv"})
# Count lines
terminal.run({"command": "wc -l data/*.csv"})
# View column names
terminal.run({"command": "head -n 1 data/sales.csv | tr ',' '\n'"})The common characteristic of these scenarios is: need real-time, lightweight file system access, rather than pre-indexing and vectorization. TerminalTool is designed precisely for this "exploratory" workflow.
(2) Security Mechanism Detailed Explanation
Allowing agents to execute commands is a powerful but dangerous capability. TerminalTool ensures system security through multi-layer security mechanisms:
First Layer: Command Whitelist
Only allow safe read-only commands, completely prohibit any operations that may modify the system:
ALLOWED_COMMANDS = {
# File listing and information
'ls', 'dir', 'tree',
# File content viewing
'cat', 'head', 'tail', 'less', 'more',
# File search
'find', 'grep', 'egrep', 'fgrep',
# Text processing
'wc', 'sort', 'uniq', 'cut', 'awk', 'sed',
# Directory operations
'pwd', 'cd',
# File information
'file', 'stat', 'du', 'df',
# Others
'echo', 'which', 'whereis',
}If the agent attempts to execute commands outside the whitelist, it will be immediately rejected:
terminal.run({"command": "rm -rf /"})
# ❌ Command not allowed: rm
# Allowed commands: cat, cd, cut, dir, du, ...Second Layer: Working Directory Restriction (Sandbox)
TerminalTool can only access the specified working directory and its subdirectories, cannot access other parts of the system:
# Specify working directory during initialization
terminal = TerminalTool(workspace="./project")
# Allowed: Access files within working directory
terminal.run({"command": "cat ./src/main.py"}) # ✅
# Prohibited: Access files outside working directory
terminal.run({"command": "cat /etc/passwd"}) # ❌ Not allowed to access paths outside working directory
# Prohibited: Escape through ..
terminal.run({"command": "cd ../../../etc"}) # ❌ Not allowed to access paths outside working directoryThis sandbox mechanism ensures that even if the agent's behavior is abnormal, it cannot affect other parts of the system.
Third Layer: Timeout Control
Each command has an execution time limit to prevent infinite loops or resource exhaustion:
terminal = TerminalTool(
workspace="./project",
timeout=30 # 30 second timeout
)
# If command execution exceeds 30 seconds
terminal.run({"command": "find / -name '*.log'"})
# ❌ Command execution timeout (exceeded 30 seconds)Fourth Layer: Output Size Limit
Limit the size of command output to prevent memory overflow:
terminal = TerminalTool(
workspace="./project",
max_output_size=10 * 1024 * 1024 # 10MB
)
# If output exceeds 10MB
terminal.run({"command": "cat huge_file.log"})
# ... (first 10MB of content) ...
# ⚠️ Output truncated (exceeded 10485760 bytes)Through these four layers of security mechanisms, TerminalTool provides powerful capabilities while maximizing system security.
9.5.2 Core Functionality Detailed Explanation
The implementation of TerminalTool focuses on two core functions: command execution and directory navigation.
(1) Command Execution
The core _execute_command method is responsible for actually executing commands:
def _execute_command(self, command: str) -> str:
"""Execute command"""
try:
# Execute command in current directory
result = subprocess.run(
command,
shell=True,
cwd=str(self.current_dir), # Execute in current working directory
capture_output=True,
text=True,
timeout=self.timeout,
env=os.environ.copy()
)
# Merge standard output and standard error
output = result.stdout
if result.stderr:
output += f"\n[stderr]\n{result.stderr}"
# Check output size
if len(output) > self.max_output_size:
output = output[:self.max_output_size]
output += f"\n\n⚠️ Output truncated (exceeded {self.max_output_size} bytes)"
# Add return code information
if result.returncode != 0:
output = f"⚠️ Command return code: {result.returncode}\n\n{output}"
return output if output else "✅ Command executed successfully (no output)"
except subprocess.TimeoutExpired:
return f"❌ Command execution timeout (exceeded {self.timeout} seconds)"
except Exception as e:
return f"❌ Command execution failed: {e}"Key points of this implementation:
- Current directory awareness: Use
cwdparameter to execute commands in the correct directory - Error handling: Capture and merge standard error, provide complete diagnostic information
- Return code check: Non-zero return codes are marked as warnings
- Fault-tolerant design: Timeouts and exceptions are handled properly, won't cause agent to crash
(2) Directory Navigation
Special handling of the cd command supports agent navigation in the file system:
def _handle_cd(self, parts: List[str]) -> str:
"""Handle cd command"""
if not self.allow_cd:
return "❌ cd command is disabled"
if len(parts) < 2:
# cd without parameters, return current directory
return f"Current directory: {self.current_dir}"
target_dir = parts[1]
# Handle relative path
if target_dir == "..":
new_dir = self.current_dir.parent
elif target_dir == ".":
new_dir = self.current_dir
elif target_dir == "~":
new_dir = self.workspace
else:
new_dir = (self.current_dir / target_dir).resolve()
# Check if within working directory
try:
new_dir.relative_to(self.workspace)
except ValueError:
return f"❌ Not allowed to access paths outside working directory: {new_dir}"
# Check if directory exists
if not new_dir.exists():
return f"❌ Directory does not exist: {new_dir}"
if not new_dir.is_dir():
return f"❌ Not a directory: {new_dir}"
# Update current directory
self.current_dir = new_dir
return f"✅ Switched to directory: {self.current_dir}"This design supports agents in multi-step file system exploration:
# Step 1: View project structure
terminal.run({"command": "ls -la"})
# Step 2: Enter source code directory
terminal.run({"command": "cd src"})
# Step 3: Find specific files
terminal.run({"command": "find . -name '*service*.py'"})
# Step 4: View file content
terminal.run({"command": "cat user_service.py"})9.5.3 Typical Usage Patterns
TerminalTool supports various common file system operation patterns.
(1) Exploratory Navigation
Agents can explore codebases step by step like human developers:
from hello_agents.tools import TerminalTool
terminal = TerminalTool(workspace="./my_project")
# Step 1: View project root directory
print(terminal.run({"command": "ls -la"}))
"""
total 24
drwxr-xr-x 6 user staff 192 Jan 19 16:00 .
drwxr-xr-x 5 user staff 160 Jan 19 15:30 ..
-rw-r--r-- 1 user staff 1234 Jan 19 15:30 README.md
drwxr-xr-x 4 user staff 128 Jan 19 15:30 src
drwxr-xr-x 3 user staff 96 Jan 19 15:30 tests
-rw-r--r-- 1 user staff 456 Jan 19 15:30 requirements.txt
"""
# Step 2: View source code directory structure
terminal.run({"command": "cd src"})
print(terminal.run({"command": "tree"}))
# Step 3: Search for specific patterns
print(terminal.run({"command": "grep -r 'def process' ."}))(2) Data File Analysis
Quickly understand the structure and content of data files:
terminal = TerminalTool(workspace="./data")
# View first few lines of CSV file
print(terminal.run({"command": "head -n 5 sales_2024.csv"}))
"""
date,product,quantity,revenue
2024-01-01,Widget A,150,4500.00
2024-01-01,Widget B,200,8000.00
2024-01-02,Widget A,180,5400.00
2024-01-02,Widget C,120,3600.00
"""
# Count total lines
print(terminal.run({"command": "wc -l *.csv"}))
"""
10234 sales_2024.csv
8567 sales_2023.csv
18801 total
"""
# Extract and count product categories
print(terminal.run({"command": "tail -n +2 sales_2024.csv | cut -d',' -f2 | sort | uniq -c"}))
"""
3456 Widget A
4123 Widget B
2655 Widget C
"""(3) Log File Analysis
Real-time analysis of application logs, quickly locate issues:
terminal = TerminalTool(workspace="/var/log")
# View latest error logs
print(terminal.run({"command": "tail -n 50 app.log | grep ERROR"}))
# Count error type distribution
print(terminal.run({"command": "grep ERROR app.log | awk '{print $4}' | sort | uniq -c | sort -rn"}))
"""
245 DatabaseConnectionError
123 TimeoutException
67 ValidationError
34 AuthenticationError
"""
# Find logs for specific time period
print(terminal.run({"command": "grep '2024-01-19 15:' app.log | tail -n 20"}))(4) Codebase Analysis
Assist code review and understanding:
terminal = TerminalTool(workspace="./codebase")
# Count lines of code
print(terminal.run({"command": "find . -name '*.py' -exec wc -l {} + | tail -n 1"}))
# Find all TODO comments
print(terminal.run({"command": "grep -rn 'TODO' --include='*.py'"}))
# Find definition of specific function
print(terminal.run({"command": "grep -rn 'def process_data' --include='*.py'"}))
# View function implementation
print(terminal.run({"command": "sed -n '/def process_data/,/^def /p' src/processor.py | head -n -1"}))9.5.4 Collaboration with Other Tools
The true power of TerminalTool lies in its collaborative use with MemoryTool, NoteTool, and ContextBuilder.
(1) Collaboration with MemoryTool
Information discovered by TerminalTool can be stored in the memory system:
# Use TerminalTool to discover project structure
structure = terminal.run({"command": "tree -L 2 src"})
# Store in semantic memory
memory_tool.run({
"action": "add",
"content": f"Project structure:\n{structure}",
"memory_type": "semantic",
"importance": 0.8,
"metadata": {"type": "project_structure"}
})(2) Collaboration with NoteTool
Important discoveries can be recorded as structured notes:
# Discover a performance bottleneck
log_analysis = terminal.run({"command": "grep 'slow query' app.log | tail -n 10"})
# Record as blocker note
note_tool.run({
"action": "create",
"title": "Database Slow Query Issue",
"content": f"## Problem Description\nFound multiple slow queries affecting system performance\n\n## Log Analysis\n```\n{log_analysis}\n```\n\n## Next Steps\n1. Analyze slow query SQL\n2. Add indexes\n3. Optimize query logic",
"note_type": "blocker",
"tags": ["performance", "database"]
})(3) Collaboration with ContextBuilder
TerminalTool output can be part of the context:
# Explore codebase
code_structure = terminal.run({"command": "ls -R src"})
recent_changes = terminal.run({"command": "git log --oneline -10"})
# Convert to ContextPacket
from hello_agents.context import ContextPacket
from datetime import datetime
packets = [
ContextPacket(
content=f"Codebase structure:\n{code_structure}",
timestamp=datetime.now(),
token_count=len(code_structure) // 4,
relevance_score=0.7,
metadata={"type": "code_structure", "source": "terminal"}
),
ContextPacket(
content=f"Recent commits:\n{recent_changes}",
timestamp=datetime.now(),
token_count=len(recent_changes) // 4,
relevance_score=0.8,
metadata={"type": "git_history", "source": "terminal"}
)
]
# Include this information when building context
context = context_builder.build(
user_query="How to refactor the user service module?",
custom_packets=packets
)