How Deep Agents Implements Shell Command Execution: Inside FilesystemMiddleware
This article dissects Deep Agents' shell command execution via the FilesystemMiddleware's execute tool, detailing its parameter validation, backend resolution through SandboxBackendProtocol, LocalShellBackend's subprocess-based implementation, BaseSandbox's abstract interface for isolated sandboxes, and a practical example using a local workspace.
1. The execute Tool
The FilesystemMiddleware creates an execute_tool that implements shell command execution. The tool accepts two parameters:
command: Annotated[str, "Shell command to execute in the sandbox environment."]
timeout: Annotated[int | None, "Optional timeout in seconds for this command. Overrides the default timeout. Use for long-running commands."] = NoneExecution Flow
Parameter pre-validation
timeout <= 0 → return Error
timeout > max_execute_timeout → return Error
_get_backend(runtime)
If callable → factory call to get instance
Otherwise → use stored backend directly
_supports_execution(resolved_backend)
CompositeBackend → check .default
Others → isinstance(SandboxBackendProtocol)
If False → return "Error: Execution not available"
execute_accepts_timeout(type(executable))
Uses inspect.signature() to check if timeout parameter exists
If False and timeout provided → return Error
Backend execution
Sync: executable.execute(command, ...)
Async: await executable.aexecute(command, ...)
Exception handling
NotImplementedError → "Error: Execution not available."
ValueError → "Error: Invalid parameter."
Output formatting
result.output
[Command succeeded/failed with exit code N]
[Output was truncated due to size limits]
Key logic: execution capability is encapsulated in the backend, which must implement SandboxBackendProtocol. The backend internally enforces a maximum output limit.
2. FilesystemMiddleware Hooks
2.1 wrap_model_call: System Prompt Injection
The execute tool's prompt is appended only when backend_supports_execution is true:
## Execute Tool `execute`
You have access to an `execute` tool for running shell commands in a sandboxed environment.
Use this tool to run commands, scripts, tests, builds, and other shell operations.
- execute: run a shell command in the sandbox (returns output and exit code)2.2 wrap_tool_call: Offloading Large Outputs to Filesystem
Oversized execute tool outputs are offloaded to /large_tool_results/{sanitized_id}.
3. SandboxBackendProtocol Implementation
SandboxBackendProtocolspecifies id and execute interfaces. Deep Agents core provides a non-isolated LocalShellBackend and an abstract sandbox base class BaseSandbox. Full isolated sandbox backends (daytona, modal, runloop) live in the partners directory.
class SandboxBackendProtocol(BackendProtocol):
@property
def id(self) -> str:
"""Unique identifier for the backend instance"""
raise NotImplementedError
def execute(self, command: str, *, timeout: int | None = None) -> ExecuteResponse:
"""Synchronously execute shell command"""
raise NotImplementedError
async def aexecute(self, command: str, *, timeout: int | None = None) -> ExecuteResponse:
"""Async execute (default wraps sync execute via asyncio.to_thread)"""
if timeout is not None and execute_accepts_timeout(type(self)):
return await asyncio.to_thread(self.execute, command, timeout=timeout)
return await asyncio.to_thread(self.execute, command)3.1 LocalShellBackend
LocalShellBackendinherits from FilesystemBackend and runs commands locally via subprocess:
result = subprocess.run(
command,
check=False,
shell=True, # Intentional: designed for LLM-controlled shell execution
capture_output=True,
text=True,
timeout=effective_timeout,
env=self._env,
cwd=str(self.cwd), # Use the root_dir from FilesystemBackend
)It has no sandbox isolation and must be used with HumanInTheLoopMiddleware.
Output handling:
stdout appended directly
stderr lines prefixed with [stderr] then appended
No output → return empty string
Exceeds max_output_bytes → truncate + append truncation notice
Non-zero exit code → append "Exit code: N" to output end
Timeout handling: Returns "Error: Command timed out after {effective_timeout} seconds..."
Exception handling: Returns "Error executing command ({type(e).__name__}): {e}"
3.2 BaseSandbox Abstract Base Class
BaseSandboxis an abstract base for partner implementations. External sandboxes (containers, VMs, remote) expose only an execute interface; all filesystem operations are implemented as Python scripts executed via that interface:
ls_info() → execute("python3 -c '...os.scandir...'")
read() → execute("python3 -c '...open(file_path)...'")
write() → execute("python3 -c '...' <<'EOF'\n{payload}\nEOF")
edit() → execute("python3 -c '...' <<'EOF'\n{payload}\nEOF")
glob_info() → execute("python3 -c '...glob.glob...'")
grep_raw() → execute("grep -rHnF ...") BaseSandbox defines four abstract interfaces:
class BaseSandbox(SandboxBackendProtocol, ABC):
@abstractmethod
def execute(self, command: str, *, timeout: int | None = None) -> ExecuteResponse:
"""Execute command in sandbox, return ExecuteResponse"""
@property
@abstractmethod
def id(self) -> str:
"""Backend unique identifier"""
@abstractmethod
def upload_files(self, files: list[tuple[str, bytes]]) -> list[FileUploadResponse]:
"""Upload files to sandbox"""
@abstractmethod
def download_files(self, paths: list[str]) -> list[FileDownloadResponse]:
"""Download files from sandbox"""The last two are binary file transfer interfaces and are developer APIs, not LLM tools.
4. Simple Example
SYSTEM_PROMPT = """You are a coding assistant with access to a local shell workspace.
You can write files and run shell commands using the available tools:
- write_file / edit_file: create or modify files
- execute: run shell commands (python, bash, etc.)
- read_file / ls / glob / grep: explore files
Work in /workspace. When asked to run code, write the script first then execute it."""
# Workspace directory for this session
workspace = tempfile.mkdtemp()
# Backend with shell execution support (implements SandboxBackendProtocol)
backend = LocalShellBackend(root_dir=workspace, virtual_mode=True, inherit_env=True)
# Model
model = init_chat_model(model="deepseek:deepseek-chat", temperature=0.0)
# Create the agent
agent = create_deep_agent(
model=model,
system_prompt=SYSTEM_PROMPT,
backend=backend,
)Given a task like "calculate 43 * 52", the agent uses the execute tool to run a Python script. Detailed interaction trace: 34b1a642-e58b-4218-a26e-e70532d37467 (https://smith.langchain.com/public/34b1a642-e58b-4218-a26e-e70532d37467/r).
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
