Skip to content

Commit 9116a62

Browse files
committed
Add data sandbox mode for isolated file operations
- Add sandbox_mode and sandbox_path parameters to A1 constructor - Enable automatic session folder creation when sandbox_mode=True - Modify run_python_repl to support working directory changes - Add get_sandbox_path() method for sandbox path retrieval - Include comprehensive documentation and examples - Update .gitignore to exclude sandbox directories This feature allows users to isolate file operations in dedicated sandbox directories, preventing clutter in the main workspace and enabling easy cleanup of generated files.
1 parent eb22cef commit 9116a62

File tree

4 files changed

+166
-2
lines changed

4 files changed

+166
-2
lines changed

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,3 +227,9 @@ data/ddinter_raw/
227227
# Sphinx build
228228
/docs/build/
229229
/docs/source/api/
230+
*.csv
231+
*.txt
232+
*.json
233+
local_outputs/
234+
logs/
235+
sandbox/

biomni/agent/a1.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ def __init__(
6464
api_key: str | None = None,
6565
commercial_mode: bool | None = None,
6666
expected_data_lake_files: list | None = None,
67+
sandbox_mode: bool = False,
68+
sandbox_path: str | None = None,
6769
):
6870
"""Initialize the biomni agent.
6971
@@ -76,6 +78,8 @@ def __init__(
7678
base_url: Base URL for custom model serving (e.g., "http://localhost:8000/v1")
7779
api_key: API key for the custom LLM
7880
commercial_mode: If True, excludes datasets that require commercial licenses or are non-commercial only
81+
sandbox_mode: If True, enables data sandbox mode for file operations
82+
sandbox_path: Custom path for sandbox directory. If None and sandbox_mode=True, creates auto-generated session folder
7983
8084
"""
8185
# Use default_config values for unspecified parameters
@@ -111,6 +115,20 @@ def __init__(
111115
self.library_content_dict = library_content_dict
112116
self.commercial_mode = commercial_mode
113117

118+
# Setup sandbox mode
119+
self.sandbox_mode = sandbox_mode
120+
if sandbox_mode:
121+
if sandbox_path is None:
122+
# Auto-generate session folder name
123+
session_id = datetime.now().strftime("session_%Y%m%d_%H%M%S")
124+
sandbox_path = os.path.join("sandbox", session_id)
125+
126+
self.sandbox_path = os.path.abspath(sandbox_path)
127+
os.makedirs(self.sandbox_path, exist_ok=True)
128+
print(f"📁 Sandbox mode enabled: {self.sandbox_path}")
129+
else:
130+
self.sandbox_path = None
131+
114132
# Display configuration in a nice, readable format
115133
print("\n" + "=" * 50)
116134
print("🔧 BIOMNI CONFIGURATION")
@@ -143,6 +161,13 @@ def __init__(
143161
if api_key is not None and api_key != "EMPTY":
144162
print(f" API Key: {'*' * 8 + api_key[-4:] if len(api_key) > 8 else '***'}")
145163

164+
# Show sandbox configuration
165+
if self.sandbox_mode:
166+
print("\n📁 SANDBOX MODE:")
167+
print(f" Enabled: True")
168+
print(f" Sandbox Path: {self.sandbox_path}")
169+
print(f" Files will be created in: {self.sandbox_path}")
170+
146171
print("=" * 50 + "\n")
147172

148173
self.path = path
@@ -1389,7 +1414,12 @@ def execute(state: AgentState) -> AgentState:
13891414

13901415
# Inject custom functions into the Python execution environment
13911416
self._inject_custom_functions_to_repl()
1392-
result = run_with_timeout(run_python_repl, [code], timeout=timeout)
1417+
1418+
# Pass sandbox path if sandbox mode is enabled
1419+
if self.sandbox_mode and self.sandbox_path:
1420+
result = run_with_timeout(run_python_repl, [code, self.sandbox_path], timeout=timeout)
1421+
else:
1422+
result = run_with_timeout(run_python_repl, [code], timeout=timeout)
13931423

13941424
# Plots are now captured directly in the execution entry above
13951425

@@ -1891,6 +1921,14 @@ def create_mcp_server(self, tool_modules=None):
18911921
print(f"Created MCP server with {registered_tools} tools")
18921922
return mcp
18931923

1924+
def get_sandbox_path(self) -> str | None:
1925+
"""Get the current sandbox path if sandbox mode is enabled.
1926+
1927+
Returns:
1928+
str: The absolute path to the sandbox directory if sandbox mode is enabled, None otherwise
1929+
"""
1930+
return self.sandbox_path if self.sandbox_mode else None
1931+
18941932
def save_conversation_history(self, filepath: str, include_images: bool = True, save_pdf: bool = True) -> None:
18951933
"""Save the complete conversation history as PDF only.
18961934

biomni/tool/support_tools.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import base64
22
import io
3+
import os
34
import sys
45
from io import StringIO
56

@@ -10,20 +11,30 @@
1011
_captured_plots = []
1112

1213

13-
def run_python_repl(command: str) -> str:
14+
def run_python_repl(command: str, working_dir: str | None = None) -> str:
1415
"""Executes the provided Python command in a persistent environment and returns the output.
1516
Variables defined in one execution will be available in subsequent executions.
17+
18+
Args:
19+
command: Python command to execute
20+
working_dir: Optional working directory to change to before execution
1621
"""
1722

1823
def execute_in_repl(command: str) -> str:
1924
"""Helper function to execute the command in the persistent environment."""
2025
old_stdout = sys.stdout
26+
old_cwd = None
2127
sys.stdout = mystdout = StringIO()
2228

2329
# Use the persistent namespace
2430
global _persistent_namespace
2531

2632
try:
33+
# Change working directory if specified
34+
if working_dir is not None:
35+
old_cwd = os.getcwd()
36+
os.chdir(working_dir)
37+
2738
# Apply matplotlib monkey patches before execution
2839
_apply_matplotlib_patches()
2940

@@ -37,6 +48,9 @@ def execute_in_repl(command: str) -> str:
3748
except Exception as e:
3849
output = f"Error: {str(e)}"
3950
finally:
51+
# Restore original working directory
52+
if old_cwd is not None:
53+
os.chdir(old_cwd)
4054
sys.stdout = old_stdout
4155
return output
4256

docs/SANDBOX_EXAMPLE.md

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# Biomni Sandbox Mode Example
2+
3+
This example demonstrates how to use the new sandbox mode feature in Biomni.
4+
5+
## Basic Usage
6+
7+
```python
8+
from biomni.agent import A1
9+
10+
# Enable sandbox mode with auto-generated session folder
11+
agent = A1(
12+
path='./data',
13+
sandbox_mode=True, # Enable sandbox mode
14+
commercial_mode=True
15+
)
16+
17+
# The agent will automatically create a sandbox directory like:
18+
# sandbox/session_20251006_143022/
19+
20+
# All file operations in Python code will happen in the sandbox
21+
result = agent.go("""
22+
Create a simple analysis and save the results to a CSV file.
23+
24+
```python
25+
import pandas as pd
26+
import matplotlib.pyplot as plt
27+
28+
# Create sample data
29+
data = {
30+
'name': ['Alice', 'Bob', 'Charlie', 'Diana'],
31+
'age': [25, 30, 35, 28],
32+
'score': [95, 87, 92, 88]
33+
}
34+
df = pd.DataFrame(data)
35+
36+
# Save to CSV
37+
df.to_csv('analysis_results.csv', index=False)
38+
print("Data saved to analysis_results.csv")
39+
40+
# Create a simple plot
41+
plt.figure(figsize=(8, 6))
42+
plt.scatter(df['age'], df['score'])
43+
plt.xlabel('Age')
44+
plt.ylabel('Score')
45+
plt.title('Age vs Score Analysis')
46+
plt.savefig('analysis_plot.png')
47+
print("Plot saved to analysis_plot.png")
48+
49+
# List files in current directory
50+
import os
51+
print(f"Files created: {os.listdir('.')}")
52+
```
53+
""")
54+
55+
# Check where files were created
56+
sandbox_path = agent.get_sandbox_path()
57+
print(f"All files were created in: {sandbox_path}")
58+
```
59+
60+
## Custom Sandbox Path
61+
62+
```python
63+
# Use a custom sandbox directory
64+
agent = A1(
65+
path='./data',
66+
sandbox_mode=True,
67+
sandbox_path='/tmp/my_analysis_workspace', # Custom path
68+
commercial_mode=True
69+
)
70+
71+
# All file operations will happen in /tmp/my_analysis_workspace/
72+
result = agent.go("Create some analysis files...")
73+
```
74+
75+
## Regular Mode (No Sandbox)
76+
77+
```python
78+
# Disable sandbox mode (default behavior)
79+
agent = A1(
80+
path='./data',
81+
sandbox_mode=False, # Or omit this parameter (default is False)
82+
commercial_mode=True
83+
)
84+
85+
# Files will be created in the current working directory (existing behavior)
86+
result = agent.go("Create some files...")
87+
```
88+
89+
## Benefits of Sandbox Mode
90+
91+
1. **Clean Workspace**: Each session gets its own isolated directory
92+
2. **No Clutter**: Generated files don't mix with your project files
93+
3. **Easy Cleanup**: Simply delete the sandbox folder when done
94+
4. **Reproducible**: Each run starts with a clean environment
95+
5. **Safe Exploration**: Experimental code won't affect your main workspace
96+
97+
## API Reference
98+
99+
### New Parameters
100+
101+
- `sandbox_mode: bool = False` - Enable/disable sandbox mode
102+
- `sandbox_path: str | None = None` - Custom sandbox directory (optional)
103+
104+
### New Methods
105+
106+
- `agent.get_sandbox_path() -> str | None` - Get current sandbox path

0 commit comments

Comments
 (0)