deepagents 0.3.8__tar.gz → 0.3.10__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. deepagents-0.3.10/PKG-INFO +76 -0
  2. deepagents-0.3.10/README.md +45 -0
  3. {deepagents-0.3.8 → deepagents-0.3.10}/deepagents/__init__.py +3 -1
  4. deepagents-0.3.10/deepagents/_version.py +3 -0
  5. {deepagents-0.3.8 → deepagents-0.3.10}/deepagents/backends/__init__.py +2 -0
  6. {deepagents-0.3.8 → deepagents-0.3.10}/deepagents/backends/composite.py +2 -2
  7. {deepagents-0.3.8 → deepagents-0.3.10}/deepagents/backends/filesystem.py +13 -21
  8. deepagents-0.3.10/deepagents/backends/local_shell.py +305 -0
  9. deepagents-0.3.10/deepagents/backends/sandbox.py +767 -0
  10. {deepagents-0.3.8 → deepagents-0.3.10}/deepagents/backends/utils.py +69 -24
  11. {deepagents-0.3.8 → deepagents-0.3.10}/deepagents/middleware/filesystem.py +482 -522
  12. {deepagents-0.3.8 → deepagents-0.3.10}/deepagents/middleware/skills.py +1 -1
  13. {deepagents-0.3.8 → deepagents-0.3.10}/deepagents/middleware/subagents.py +23 -9
  14. {deepagents-0.3.8 → deepagents-0.3.10}/deepagents/middleware/summarization.py +9 -4
  15. deepagents-0.3.10/deepagents/py.typed +0 -0
  16. deepagents-0.3.10/deepagents.egg-info/PKG-INFO +76 -0
  17. {deepagents-0.3.8 → deepagents-0.3.10}/deepagents.egg-info/SOURCES.txt +3 -0
  18. deepagents-0.3.10/deepagents.egg-info/requires.txt +5 -0
  19. {deepagents-0.3.8 → deepagents-0.3.10}/pyproject.toml +37 -28
  20. deepagents-0.3.8/PKG-INFO +0 -527
  21. deepagents-0.3.8/README.md +0 -508
  22. deepagents-0.3.8/deepagents/backends/sandbox.py +0 -360
  23. deepagents-0.3.8/deepagents.egg-info/PKG-INFO +0 -527
  24. deepagents-0.3.8/deepagents.egg-info/requires.txt +0 -5
  25. {deepagents-0.3.8 → deepagents-0.3.10}/deepagents/backends/protocol.py +0 -0
  26. {deepagents-0.3.8 → deepagents-0.3.10}/deepagents/backends/state.py +0 -0
  27. {deepagents-0.3.8 → deepagents-0.3.10}/deepagents/backends/store.py +0 -0
  28. {deepagents-0.3.8 → deepagents-0.3.10}/deepagents/graph.py +0 -0
  29. {deepagents-0.3.8 → deepagents-0.3.10}/deepagents/middleware/__init__.py +0 -0
  30. {deepagents-0.3.8 → deepagents-0.3.10}/deepagents/middleware/_utils.py +0 -0
  31. {deepagents-0.3.8 → deepagents-0.3.10}/deepagents/middleware/memory.py +0 -0
  32. {deepagents-0.3.8 → deepagents-0.3.10}/deepagents/middleware/patch_tool_calls.py +0 -0
  33. {deepagents-0.3.8 → deepagents-0.3.10}/deepagents.egg-info/dependency_links.txt +0 -0
  34. {deepagents-0.3.8 → deepagents-0.3.10}/deepagents.egg-info/top_level.txt +0 -0
  35. {deepagents-0.3.8 → deepagents-0.3.10}/setup.cfg +0 -0
@@ -0,0 +1,76 @@
1
+ Metadata-Version: 2.4
2
+ Name: deepagents
3
+ Version: 0.3.10
4
+ Summary: General purpose 'deep agent' with sub-agent spawning, todo list capabilities, and mock file system. Built on LangGraph.
5
+ License: MIT
6
+ Project-URL: Homepage, https://docs.langchain.com/oss/python/deepagents/overview
7
+ Project-URL: Documentation, https://reference.langchain.com/python/deepagents/
8
+ Project-URL: Repository, https://github.com/langchain-ai/deepagents
9
+ Project-URL: Issues, https://github.com/langchain-ai/deepagents/issues
10
+ Project-URL: Twitter, https://x.com/LangChain
11
+ Project-URL: Slack, https://www.langchain.com/join-community
12
+ Project-URL: Reddit, https://www.reddit.com/r/LangChain/
13
+ Keywords: agents,ai,llm,langgraph,langchain,deep-agent,sub-agents,agentic
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
23
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
+ Requires-Python: <4.0,>=3.11
25
+ Description-Content-Type: text/markdown
26
+ Requires-Dist: langchain-core<2.0.0,>=1.2.7
27
+ Requires-Dist: langchain<2.0.0,>=1.2.7
28
+ Requires-Dist: langchain-anthropic<2.0.0,>=1.3.1
29
+ Requires-Dist: langchain-google-genai<5.0.0,>=4.2.0
30
+ Requires-Dist: wcmatch
31
+
32
+ # 🧠🤖 Deep Agents
33
+
34
+ [![PyPI - Version](https://img.shields.io/pypi/v/deepagents?label=%20)](https://pypi.org/project/deepagents/#history)
35
+ [![PyPI - License](https://img.shields.io/pypi/l/deepagents)](https://opensource.org/licenses/MIT)
36
+ [![PyPI - Downloads](https://img.shields.io/pepy/dt/deepagents)](https://pypistats.org/packages/deepagents)
37
+ [![Twitter](https://img.shields.io/twitter/url/https/twitter.com/langchain.svg?style=social&label=Follow%20%40LangChain)](https://x.com/langchain)
38
+
39
+ Looking for the JS/TS version? Check out [Deep Agents.js](https://github.com/langchain-ai/deepagentsjs).
40
+
41
+ To help you ship LangChain apps to production faster, check out [LangSmith](https://smith.langchain.com).
42
+ LangSmith is a unified developer platform for building, testing, and monitoring LLM applications.
43
+
44
+ ## Quick Install
45
+
46
+ ```bash
47
+ pip install deepagents
48
+ # or
49
+ uv add deepagents
50
+ ```
51
+
52
+ ## 🤔 What is this?
53
+
54
+ Using an LLM to call tools in a loop is the simplest form of an agent. This architecture, however, can yield agents that are "shallow" and fail to plan and act over longer, more complex tasks.
55
+
56
+ Applications like "Deep Research", "Manus", and "Claude Code" have gotten around this limitation by implementing a combination of four things: a **planning tool**, **sub agents**, access to a **file system**, and a **detailed prompt**.
57
+
58
+ `deepagents` is a Python package that implements these in a general purpose way so that you can easily create a Deep Agent for your application. For a full overview and quickstart of Deep Agents, the best resource is our [docs](https://docs.langchain.com/oss/python/deepagents/overview).
59
+
60
+ **Acknowledgements: This project was primarily inspired by Claude Code, and initially was largely an attempt to see what made Claude Code general purpose, and make it even more so.**
61
+
62
+ ## 📖 Resources
63
+
64
+ - **[Documentation](https://docs.langchain.com/oss/python/deepagents)** — Full documentation
65
+ - **[API Reference](https://reference.langchain.com/python/deepagents/)** — Full SDK reference documentation
66
+ - **[Chat LangChain](https://chat.langchain.com)** - Chat interactively with the docs
67
+
68
+ ## 📕 Releases & Versioning
69
+
70
+ See our [Releases](https://docs.langchain.com/oss/python/release-policy) and [Versioning](https://docs.langchain.com/oss/python/versioning) policies.
71
+
72
+ ## 💁 Contributing
73
+
74
+ As an open-source project in a rapidly developing field, we are extremely open to contributions, whether it be in the form of a new feature, improved infrastructure, or better documentation.
75
+
76
+ For detailed information on how to contribute, see the [Contributing Guide](https://docs.langchain.com/oss/python/contributing/overview).
@@ -0,0 +1,45 @@
1
+ # 🧠🤖 Deep Agents
2
+
3
+ [![PyPI - Version](https://img.shields.io/pypi/v/deepagents?label=%20)](https://pypi.org/project/deepagents/#history)
4
+ [![PyPI - License](https://img.shields.io/pypi/l/deepagents)](https://opensource.org/licenses/MIT)
5
+ [![PyPI - Downloads](https://img.shields.io/pepy/dt/deepagents)](https://pypistats.org/packages/deepagents)
6
+ [![Twitter](https://img.shields.io/twitter/url/https/twitter.com/langchain.svg?style=social&label=Follow%20%40LangChain)](https://x.com/langchain)
7
+
8
+ Looking for the JS/TS version? Check out [Deep Agents.js](https://github.com/langchain-ai/deepagentsjs).
9
+
10
+ To help you ship LangChain apps to production faster, check out [LangSmith](https://smith.langchain.com).
11
+ LangSmith is a unified developer platform for building, testing, and monitoring LLM applications.
12
+
13
+ ## Quick Install
14
+
15
+ ```bash
16
+ pip install deepagents
17
+ # or
18
+ uv add deepagents
19
+ ```
20
+
21
+ ## 🤔 What is this?
22
+
23
+ Using an LLM to call tools in a loop is the simplest form of an agent. This architecture, however, can yield agents that are "shallow" and fail to plan and act over longer, more complex tasks.
24
+
25
+ Applications like "Deep Research", "Manus", and "Claude Code" have gotten around this limitation by implementing a combination of four things: a **planning tool**, **sub agents**, access to a **file system**, and a **detailed prompt**.
26
+
27
+ `deepagents` is a Python package that implements these in a general purpose way so that you can easily create a Deep Agent for your application. For a full overview and quickstart of Deep Agents, the best resource is our [docs](https://docs.langchain.com/oss/python/deepagents/overview).
28
+
29
+ **Acknowledgements: This project was primarily inspired by Claude Code, and initially was largely an attempt to see what made Claude Code general purpose, and make it even more so.**
30
+
31
+ ## 📖 Resources
32
+
33
+ - **[Documentation](https://docs.langchain.com/oss/python/deepagents)** — Full documentation
34
+ - **[API Reference](https://reference.langchain.com/python/deepagents/)** — Full SDK reference documentation
35
+ - **[Chat LangChain](https://chat.langchain.com)** - Chat interactively with the docs
36
+
37
+ ## 📕 Releases & Versioning
38
+
39
+ See our [Releases](https://docs.langchain.com/oss/python/release-policy) and [Versioning](https://docs.langchain.com/oss/python/versioning) policies.
40
+
41
+ ## 💁 Contributing
42
+
43
+ As an open-source project in a rapidly developing field, we are extremely open to contributions, whether it be in the form of a new feature, improved infrastructure, or better documentation.
44
+
45
+ For detailed information on how to contribute, see the [Contributing Guide](https://docs.langchain.com/oss/python/contributing/overview).
@@ -1,5 +1,6 @@
1
- """DeepAgents package."""
1
+ """Deep Agents package."""
2
2
 
3
+ from deepagents._version import __version__
3
4
  from deepagents.graph import create_deep_agent
4
5
  from deepagents.middleware.filesystem import FilesystemMiddleware
5
6
  from deepagents.middleware.memory import MemoryMiddleware
@@ -11,5 +12,6 @@ __all__ = [
11
12
  "MemoryMiddleware",
12
13
  "SubAgent",
13
14
  "SubAgentMiddleware",
15
+ "__version__",
14
16
  "create_deep_agent",
15
17
  ]
@@ -0,0 +1,3 @@
1
+ """deepagents version information."""
2
+
3
+ __version__ = "0.3.10" # x-release-please-version
@@ -2,6 +2,7 @@
2
2
 
3
3
  from deepagents.backends.composite import CompositeBackend
4
4
  from deepagents.backends.filesystem import FilesystemBackend
5
+ from deepagents.backends.local_shell import LocalShellBackend
5
6
  from deepagents.backends.protocol import BackendProtocol
6
7
  from deepagents.backends.state import StateBackend
7
8
  from deepagents.backends.store import StoreBackend
@@ -10,6 +11,7 @@ __all__ = [
10
11
  "BackendProtocol",
11
12
  "CompositeBackend",
12
13
  "FilesystemBackend",
14
+ "LocalShellBackend",
13
15
  "StateBackend",
14
16
  "StoreBackend",
15
17
  ]
@@ -222,13 +222,13 @@ class CompositeBackend(BackendProtocol):
222
222
  path: str | None = None,
223
223
  glob: str | None = None,
224
224
  ) -> list[GrepMatch] | str:
225
- """Search files for regex pattern.
225
+ """Search files for literal text pattern.
226
226
 
227
227
  Routes to backends based on path: specific route searches one backend,
228
228
  "/" or None searches all backends, otherwise searches default backend.
229
229
 
230
230
  Args:
231
- pattern: Regex pattern to search for.
231
+ pattern: Literal text to search for (NOT regex).
232
232
  path: Directory to search. None searches all backends.
233
233
  glob: Glob pattern to filter files (e.g., "*.py", "**/*.txt").
234
234
  Filters by filename, not content.
@@ -388,25 +388,18 @@ class FilesystemBackend(BackendProtocol):
388
388
  path: str | None = None,
389
389
  glob: str | None = None,
390
390
  ) -> list[GrepMatch] | str:
391
- """Search for a regex pattern in files.
391
+ """Search for a literal text pattern in files.
392
392
 
393
- Uses ripgrep if available, falling back to Python regex search.
393
+ Uses ripgrep if available, falling back to Python search.
394
394
 
395
395
  Args:
396
- pattern: Regular expression pattern to search for.
396
+ pattern: Literal string to search for (NOT regex).
397
397
  path: Directory or file path to search in. Defaults to current directory.
398
398
  glob: Optional glob pattern to filter which files to search.
399
399
 
400
400
  Returns:
401
401
  List of GrepMatch dicts containing path, line number, and matched text.
402
- Returns an error string if the regex pattern is invalid.
403
402
  """
404
- # Validate regex
405
- try:
406
- re.compile(pattern)
407
- except re.error as e:
408
- return f"Invalid regex pattern: {e}"
409
-
410
403
  # Resolve base path
411
404
  try:
412
405
  base_full = self._resolve_path(path or ".")
@@ -416,10 +409,11 @@ class FilesystemBackend(BackendProtocol):
416
409
  if not base_full.exists():
417
410
  return []
418
411
 
419
- # Try ripgrep first
412
+ # Try ripgrep first (with -F flag for literal search)
420
413
  results = self._ripgrep_search(pattern, base_full, glob)
421
414
  if results is None:
422
- results = self._python_search(pattern, base_full, glob)
415
+ # Python fallback needs escaped pattern for literal search
416
+ results = self._python_search(re.escape(pattern), base_full, glob)
423
417
 
424
418
  matches: list[GrepMatch] = []
425
419
  for fpath, items in results.items():
@@ -428,10 +422,10 @@ class FilesystemBackend(BackendProtocol):
428
422
  return matches
429
423
 
430
424
  def _ripgrep_search(self, pattern: str, base_full: Path, include_glob: str | None) -> dict[str, list[tuple[int, str]]] | None:
431
- """Search using ripgrep with JSON output parsing.
425
+ """Search using ripgrep with fixed-string (literal) mode.
432
426
 
433
427
  Args:
434
- pattern: Regex pattern to search for.
428
+ pattern: Literal string to search for (unescaped).
435
429
  base_full: Resolved base path to search in.
436
430
  include_glob: Optional glob pattern to filter files.
437
431
 
@@ -439,7 +433,7 @@ class FilesystemBackend(BackendProtocol):
439
433
  Dict mapping file paths to list of `(line_number, line_text)` tuples.
440
434
  Returns `None` if ripgrep is unavailable or times out.
441
435
  """
442
- cmd = ["rg", "--json"]
436
+ cmd = ["rg", "--json", "-F"] # -F enables fixed-string (literal) mode
443
437
  if include_glob:
444
438
  cmd.extend(["--glob", include_glob])
445
439
  cmd.extend(["--", pattern, str(base_full)])
@@ -484,22 +478,20 @@ class FilesystemBackend(BackendProtocol):
484
478
  return results
485
479
 
486
480
  def _python_search(self, pattern: str, base_full: Path, include_glob: str | None) -> dict[str, list[tuple[int, str]]]:
487
- """Fallback search using Python regex when ripgrep is unavailable.
481
+ """Fallback search using Python when ripgrep is unavailable.
488
482
 
489
483
  Recursively searches files, respecting `max_file_size_bytes` limit.
490
484
 
491
485
  Args:
492
- pattern: Regex pattern to search for.
486
+ pattern: Escaped regex pattern (from re.escape) for literal search.
493
487
  base_full: Resolved base path to search in.
494
488
  include_glob: Optional glob pattern to filter files by name.
495
489
 
496
490
  Returns:
497
491
  Dict mapping file paths to list of `(line_number, line_text)` tuples.
498
492
  """
499
- try:
500
- regex = re.compile(pattern)
501
- except re.error:
502
- return {}
493
+ # Compile escaped pattern once for efficiency (used in loop)
494
+ regex = re.compile(pattern)
503
495
 
504
496
  results: dict[str, list[tuple[int, str]]] = {}
505
497
  root = base_full if base_full.is_dir() else base_full.parent
@@ -0,0 +1,305 @@
1
+ """`LocalShellBackend`: Filesystem backend with unrestricted local shell execution.
2
+
3
+ This backend extends FilesystemBackend to add shell command execution on the local
4
+ host system. It provides NO sandboxing or isolation - all operations run directly
5
+ on the host machine with full system access.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import os
11
+ import subprocess
12
+ import uuid
13
+ from typing import TYPE_CHECKING
14
+
15
+ from deepagents.backends.filesystem import FilesystemBackend
16
+ from deepagents.backends.protocol import ExecuteResponse, SandboxBackendProtocol
17
+
18
+ if TYPE_CHECKING:
19
+ from pathlib import Path
20
+
21
+
22
+ class LocalShellBackend(FilesystemBackend, SandboxBackendProtocol):
23
+ """Filesystem backend with unrestricted local shell command execution.
24
+
25
+ This backend extends `FilesystemBackend` to add shell command execution
26
+ capabilities. Commands are executed directly on the host system without any
27
+ sandboxing, process isolation, or security restrictions.
28
+
29
+ !!! warning "Security Warning"
30
+
31
+ This backend grants agents BOTH direct filesystem access AND unrestricted
32
+ shell execution on your local machine. Use with extreme caution and only in
33
+ appropriate environments.
34
+
35
+ **Appropriate use cases:**
36
+
37
+ - Local development CLIs (coding assistants, development tools)
38
+ - Personal development environments where you trust the agent's code
39
+ - CI/CD pipelines with proper secret management (see security considerations)
40
+
41
+ **Inappropriate use cases:**
42
+
43
+ - Production environments (e.g., web servers, APIs, multi-tenant systems)
44
+ - Processing untrusted user input or executing untrusted code
45
+
46
+ Use `StateBackend`, `StoreBackend`, or extend `BaseSandbox` for production.
47
+
48
+ **Security risks:**
49
+
50
+ - Agents can execute **arbitrary shell commands** with your user's permissions
51
+ - Agents can read **any accessible file**, including secrets (API keys,
52
+ credentials, `.env` files, SSH keys, etc.)
53
+ - Combined with network tools, secrets may be exfiltrated via SSRF attacks
54
+ - File modifications and command execution are **permanent and irreversible**
55
+ - Agents can install packages, modify system files, spawn processes, etc.
56
+ - **No process isolation** - commands run directly on your host system
57
+ - **No resource limits** - commands can consume unlimited CPU, memory, disk
58
+
59
+ **Recommended safeguards:**
60
+
61
+ Since shell access is unrestricted and can bypass filesystem restrictions:
62
+
63
+ 1. **Enable Human-in-the-Loop (HITL) middleware** to review and approve ALL
64
+ operations before execution. This is STRONGLY RECOMMENDED as your primary
65
+ safeguard when using this backend.
66
+ 2. Run in dedicated development environments only - never on shared or
67
+ production systems
68
+ 3. Never expose to untrusted users or allow execution of untrusted code
69
+ 4. For production environments requiring code execution, extend `BaseSandbox`
70
+ to create a properly isolated backend (Docker containers, VMs, or other
71
+ sandboxed execution environments)
72
+
73
+ !!! note
74
+
75
+ `virtual_mode=True` and path-based restrictions provide NO security
76
+ with shell access enabled, since commands can access any path on the system
77
+
78
+ Examples:
79
+ ```python
80
+ from deepagents.backends import LocalShellBackend
81
+
82
+ # Create backend with explicit environment
83
+ backend = LocalShellBackend(root_dir="/home/user/project", env={"PATH": "/usr/bin:/bin"})
84
+
85
+ # Execute shell commands (runs directly on host)
86
+ result = backend.execute("ls -la")
87
+ print(result.output)
88
+ print(result.exit_code)
89
+
90
+ # Use filesystem operations (inherited from FilesystemBackend)
91
+ content = backend.read("/README.md")
92
+ backend.write("/output.txt", "Hello world")
93
+
94
+ # Inherit all environment variables
95
+ backend = LocalShellBackend(root_dir="/home/user/project", inherit_env=True)
96
+ ```
97
+ """
98
+
99
+ def __init__(
100
+ self,
101
+ root_dir: str | Path | None = None,
102
+ *,
103
+ virtual_mode: bool = False,
104
+ timeout: float = 120.0,
105
+ max_output_bytes: int = 100_000,
106
+ env: dict[str, str] | None = None,
107
+ inherit_env: bool = False,
108
+ ) -> None:
109
+ """Initialize local shell backend with filesystem access.
110
+
111
+ Args:
112
+ root_dir: Working directory for both filesystem operations and shell commands.
113
+
114
+ - If not provided, defaults to the current working directory.
115
+ - Shell commands execute with this as their working directory.
116
+ - When `virtual_mode=False` (default): Paths are used as-is. Agents can
117
+ access any file using absolute paths or `..` sequences.
118
+ - When `virtual_mode=True`: Acts as a virtual root for filesystem operations.
119
+ Useful with `CompositeBackend` to support routing file operations across
120
+ different backend implementations. **Note:** This does NOT restrict shell
121
+ commands.
122
+
123
+ virtual_mode: Enable virtual path mode for filesystem operations.
124
+
125
+ When `True`, treats `root_dir` as a virtual root filesystem. All paths
126
+ are interpreted relative to `root_dir` (e.g., `/file.txt` maps to
127
+ `{root_dir}/file.txt`). Path traversal (`..`, `~`) is blocked.
128
+
129
+ **Primary use case:** Working with `CompositeBackend`, which routes
130
+ different path prefixes to different backends. Virtual mode allows the
131
+ CompositeBackend to strip route prefixes and pass normalized paths to
132
+ each backend, enabling file operations to work correctly across multiple
133
+ backend implementations.
134
+
135
+ **Important:** This only affects filesystem operations. Shell commands
136
+ executed via `execute()` are NOT restricted and can access any path.
137
+
138
+ timeout: Maximum time in seconds to wait for shell command execution.
139
+ Commands exceeding this timeout will be terminated. Defaults to 120 seconds.
140
+
141
+ max_output_bytes: Maximum number of bytes to capture from command output.
142
+ Output exceeding this limit will be truncated. Defaults to 100,000 bytes.
143
+
144
+ env: Environment variables for shell commands. If None, starts with an empty
145
+ environment (unless `inherit_env=True`).
146
+
147
+ inherit_env: Whether to inherit the parent process's environment variables.
148
+ When False (default), only variables in `env` dict are available.
149
+ When True, inherits all `os.environ` variables and applies `env` overrides.
150
+ """
151
+ # Initialize parent FilesystemBackend
152
+ super().__init__(
153
+ root_dir=root_dir,
154
+ virtual_mode=virtual_mode,
155
+ max_file_size_mb=10,
156
+ )
157
+
158
+ # Store execution parameters
159
+ self._timeout = timeout
160
+ self._max_output_bytes = max_output_bytes
161
+
162
+ # Build environment based on inherit_env setting
163
+ if inherit_env:
164
+ self._env = os.environ.copy()
165
+ if env is not None:
166
+ self._env.update(env)
167
+ else:
168
+ self._env = env if env is not None else {}
169
+
170
+ # Generate unique sandbox ID
171
+ self._sandbox_id = f"local-{uuid.uuid4().hex[:8]}"
172
+
173
+ @property
174
+ def id(self) -> str:
175
+ """Unique identifier for this backend instance.
176
+
177
+ Returns:
178
+ String identifier in format "local-{random_hex}".
179
+ """
180
+ return self._sandbox_id
181
+
182
+ def execute(
183
+ self,
184
+ command: str,
185
+ ) -> ExecuteResponse:
186
+ r"""Execute a shell command directly on the host system.
187
+
188
+ !!! danger "Unrestricted Execution"
189
+ Commands are executed directly on your host system using `subprocess.run()`
190
+ with `shell=True`. There is **no sandboxing, isolation, or security
191
+ restrictions**. The command runs with your user's full permissions and can:
192
+
193
+ - Access any file on the filesystem (regardless of `virtual_mode`)
194
+ - Execute any program or script
195
+ - Make network connections
196
+ - Modify system configuration
197
+ - Spawn additional processes
198
+ - Install packages or modify dependencies
199
+
200
+ **Always use Human-in-the-Loop (HITL) middleware when using this method.**
201
+
202
+ The command is executed using the system shell (`/bin/sh` or equivalent) with
203
+ the working directory set to the backend's `root_dir`. Stdout and stderr are
204
+ combined into a single output stream.
205
+
206
+ Args:
207
+ command: Shell command string to execute.
208
+ Examples: "python script.py", "ls -la", "grep pattern file.txt"
209
+
210
+ **Security:** This string is passed directly to the shell. Agents can
211
+ execute arbitrary commands including pipes, redirects, command
212
+ substitution, etc.
213
+
214
+ Returns:
215
+ ExecuteResponse containing:
216
+ - output: Combined stdout and stderr (stderr lines prefixed with [stderr])
217
+ - exit_code: Process exit code (0 for success, non-zero for failure)
218
+ - truncated: True if output was truncated due to size limits
219
+
220
+ Examples:
221
+ ```python
222
+ # Run a simple command
223
+ result = backend.execute("echo hello")
224
+ assert result.output == "hello\\n"
225
+ assert result.exit_code == 0
226
+
227
+ # Handle errors
228
+ result = backend.execute("cat nonexistent.txt")
229
+ assert result.exit_code != 0
230
+ assert "[stderr]" in result.output
231
+
232
+ # Check for truncation
233
+ result = backend.execute("cat huge_file.txt")
234
+ if result.truncated:
235
+ print("Output was truncated")
236
+
237
+ # Commands run in root_dir, but can access any path
238
+ result = backend.execute("cat /etc/passwd") # Can read system files!
239
+ ```
240
+ """
241
+ if not command or not isinstance(command, str):
242
+ return ExecuteResponse(
243
+ output="Error: Command must be a non-empty string.",
244
+ exit_code=1,
245
+ truncated=False,
246
+ )
247
+
248
+ try:
249
+ result = subprocess.run( # noqa: S602
250
+ command,
251
+ check=False,
252
+ shell=True, # Intentional: designed for LLM-controlled shell execution
253
+ capture_output=True,
254
+ text=True,
255
+ timeout=self._timeout,
256
+ env=self._env,
257
+ cwd=str(self.cwd), # Use the root_dir from FilesystemBackend
258
+ )
259
+
260
+ # Combine stdout and stderr
261
+ # Prefix each stderr line with [stderr] for clear attribution.
262
+ # Example: "hello\n[stderr] error: file not found" # noqa: ERA001
263
+ output_parts = []
264
+ if result.stdout:
265
+ output_parts.append(result.stdout)
266
+ if result.stderr:
267
+ stderr_lines = result.stderr.strip().split("\n")
268
+ output_parts.extend(f"[stderr] {line}" for line in stderr_lines)
269
+
270
+ output = "\n".join(output_parts) if output_parts else "<no output>"
271
+
272
+ # Check for truncation
273
+ truncated = False
274
+ if len(output) > self._max_output_bytes:
275
+ output = output[: self._max_output_bytes]
276
+ output += f"\n\n... Output truncated at {self._max_output_bytes} bytes."
277
+ truncated = True
278
+
279
+ # Add exit code info if non-zero
280
+ if result.returncode != 0:
281
+ output = f"{output.rstrip()}\n\nExit code: {result.returncode}"
282
+
283
+ return ExecuteResponse(
284
+ output=output,
285
+ exit_code=result.returncode,
286
+ truncated=truncated,
287
+ )
288
+
289
+ except subprocess.TimeoutExpired:
290
+ return ExecuteResponse(
291
+ output=f"Error: Command timed out after {self._timeout:.1f} seconds.",
292
+ exit_code=124, # Standard timeout exit code
293
+ truncated=False,
294
+ )
295
+ except Exception as e: # noqa: BLE001
296
+ # Broad exception catch is intentional: we want to catch all execution errors
297
+ # and return a consistent ExecuteResponse rather than propagating exceptions
298
+ return ExecuteResponse(
299
+ output=f"Error executing command: {e}",
300
+ exit_code=1,
301
+ truncated=False,
302
+ )
303
+
304
+
305
+ __all__ = ["LocalShellBackend"]