Skip to content

Commit b67506c

Browse files
committed
Fix sandbox data access issue
- Add automatic symbolic links to project data directories - Inject helper functions for absolute path access - Enhance configuration display with data access information - Ensure backward compatibility with existing relative paths Fixes issue where sandbox mode made project data inaccessible, causing FileNotFoundError for paths like './data/biomni_data/...' Now provides multiple data access methods: 1. Relative paths via automatic symlinks 2. get_project_path() helper function 3. Environment variables (__original_cwd__, etc.) All output files remain isolated in sandbox while maintaining seamless access to project data for analysis.
1 parent 9116a62 commit b67506c

File tree

2 files changed

+70
-3
lines changed

2 files changed

+70
-3
lines changed

biomni/agent/a1.py

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,10 +124,13 @@ def __init__(
124124
sandbox_path = os.path.join("sandbox", session_id)
125125

126126
self.sandbox_path = os.path.abspath(sandbox_path)
127+
self.original_cwd = os.getcwd() # Store original working directory
127128
os.makedirs(self.sandbox_path, exist_ok=True)
129+
128130
print(f"📁 Sandbox mode enabled: {self.sandbox_path}")
129131
else:
130132
self.sandbox_path = None
133+
self.original_cwd = None
131134

132135
# Display configuration in a nice, readable format
133136
print("\n" + "=" * 50)
@@ -167,6 +170,10 @@ def __init__(
167170
print(f" Enabled: True")
168171
print(f" Sandbox Path: {self.sandbox_path}")
169172
print(f" Files will be created in: {self.sandbox_path}")
173+
print(f" Original Project Path: {self.original_cwd}")
174+
print(" Data Access:")
175+
print(" - Relative paths: ./data/... (via symlinks)")
176+
print(" - Helper function: get_project_path('data/...') for absolute paths")
170177

171178
print("=" * 50 + "\n")
172179

@@ -176,6 +183,10 @@ def __init__(
176183
os.makedirs(path)
177184
print(f"Created directory: {path}")
178185

186+
# Setup sandbox data access after path is assigned
187+
if self.sandbox_mode:
188+
self._setup_sandbox_data_access()
189+
179190
# --- Begin custom folder/file checks ---
180191
benchmark_dir = os.path.join(path, "biomni_data", "benchmark")
181192
data_lake_dir = os.path.join(path, "biomni_data", "data_lake")
@@ -1415,9 +1426,9 @@ def execute(state: AgentState) -> AgentState:
14151426
# Inject custom functions into the Python execution environment
14161427
self._inject_custom_functions_to_repl()
14171428

1418-
# Pass sandbox path if sandbox mode is enabled
1429+
# Pass sandbox path and original directory if sandbox mode is enabled
14191430
if self.sandbox_mode and self.sandbox_path:
1420-
result = run_with_timeout(run_python_repl, [code, self.sandbox_path], timeout=timeout)
1431+
result = run_with_timeout(run_python_repl, [code, self.sandbox_path, self.original_cwd], timeout=timeout)
14211432
else:
14221433
result = run_with_timeout(run_python_repl, [code], timeout=timeout)
14231434

@@ -1929,6 +1940,46 @@ def get_sandbox_path(self) -> str | None:
19291940
"""
19301941
return self.sandbox_path if self.sandbox_mode else None
19311942

1943+
def _setup_sandbox_data_access(self) -> None:
1944+
"""Setup data access for sandbox mode by creating symbolic links to important directories."""
1945+
if not self.sandbox_mode or not self.sandbox_path:
1946+
return
1947+
1948+
# List of important directories/files to link into sandbox
1949+
important_paths = [
1950+
("data", os.path.join(self.original_cwd, "data")),
1951+
("biomni_data", os.path.join(self.original_cwd, "data", "biomni_data")),
1952+
]
1953+
1954+
# Create symbolic links for data access
1955+
for link_name, target_path in important_paths:
1956+
if os.path.exists(target_path):
1957+
sandbox_link = os.path.join(self.sandbox_path, link_name)
1958+
1959+
# Remove existing link if it exists
1960+
if os.path.islink(sandbox_link):
1961+
os.unlink(sandbox_link)
1962+
elif os.path.exists(sandbox_link):
1963+
# Don't overwrite real directories/files
1964+
continue
1965+
1966+
try:
1967+
os.symlink(target_path, sandbox_link)
1968+
print(f"🔗 Linked {link_name} -> {target_path}")
1969+
except OSError as e:
1970+
print(f"⚠️ Could not create symlink {link_name}: {e}")
1971+
1972+
# Also try to link the main data directory directly if path is provided
1973+
main_data_path = os.path.join(self.original_cwd, self.path)
1974+
if os.path.exists(main_data_path) and main_data_path != os.path.join(self.original_cwd, "data"):
1975+
sandbox_data_link = os.path.join(self.sandbox_path, "main_data")
1976+
if not os.path.exists(sandbox_data_link):
1977+
try:
1978+
os.symlink(main_data_path, sandbox_data_link)
1979+
print(f"🔗 Linked main_data -> {main_data_path}")
1980+
except OSError as e:
1981+
print(f"⚠️ Could not create main_data symlink: {e}")
1982+
19321983
def save_conversation_history(self, filepath: str, include_images: bool = True, save_pdf: bool = True) -> None:
19331984
"""Save the complete conversation history as PDF only.
19341985

biomni/tool/support_tools.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,14 @@
1111
_captured_plots = []
1212

1313

14-
def run_python_repl(command: str, working_dir: str | None = None) -> str:
14+
def run_python_repl(command: str, working_dir: str | None = None, original_cwd: str | None = None) -> str:
1515
"""Executes the provided Python command in a persistent environment and returns the output.
1616
Variables defined in one execution will be available in subsequent executions.
1717
1818
Args:
1919
command: Python command to execute
2020
working_dir: Optional working directory to change to before execution
21+
original_cwd: Original working directory (for sandbox mode data access)
2122
"""
2223

2324
def execute_in_repl(command: str) -> str:
@@ -35,6 +36,21 @@ def execute_in_repl(command: str) -> str:
3536
old_cwd = os.getcwd()
3637
os.chdir(working_dir)
3738

39+
# Inject sandbox-aware helper variables for data access
40+
if working_dir is not None and original_cwd is not None:
41+
_persistent_namespace['__sandbox_mode__'] = True
42+
_persistent_namespace['__original_cwd__'] = original_cwd
43+
_persistent_namespace['__sandbox_path__'] = working_dir
44+
45+
# Helper function for accessing original project data
46+
def _get_project_path(relative_path):
47+
"""Helper function to get absolute path to project data from sandbox."""
48+
return os.path.join(original_cwd, relative_path)
49+
50+
_persistent_namespace['get_project_path'] = _get_project_path
51+
else:
52+
_persistent_namespace['__sandbox_mode__'] = False
53+
3854
# Apply matplotlib monkey patches before execution
3955
_apply_matplotlib_patches()
4056

0 commit comments

Comments
 (0)