diff --git a/cli.py b/cli.py index a09d5016..fb24f846 100755 --- a/cli.py +++ b/cli.py @@ -285,6 +285,7 @@ def load_cli_config() -> Dict[str, Any]: "container_memory": "TERMINAL_CONTAINER_MEMORY", "container_disk": "TERMINAL_CONTAINER_DISK", "container_persistent": "TERMINAL_CONTAINER_PERSISTENT", + "docker_volumes": "TERMINAL_DOCKER_VOLUMES", # Sudo support (works with all backends) "sudo_password": "SUDO_PASSWORD", } @@ -297,7 +298,12 @@ def load_cli_config() -> Dict[str, Any]: for config_key, env_var in env_mappings.items(): if config_key in terminal_config: if _file_has_terminal_config or env_var not in os.environ: - os.environ[env_var] = str(terminal_config[config_key]) + val = terminal_config[config_key] + if isinstance(val, list): + import json + os.environ[env_var] = json.dumps(val) + else: + os.environ[env_var] = str(val) # Apply browser config to environment variables browser_config = defaults.get("browser", {}) diff --git a/tools/environments/docker.py b/tools/environments/docker.py index c839f9b8..1254f011 100644 --- a/tools/environments/docker.py +++ b/tools/environments/docker.py @@ -51,6 +51,7 @@ class DockerEnvironment(BaseEnvironment): disk: int = 0, persistent_filesystem: bool = False, task_id: str = "default", + volumes: list = None, network: bool = True, ): super().__init__(cwd=cwd, timeout=timeout) @@ -58,6 +59,11 @@ class DockerEnvironment(BaseEnvironment): self._persistent = persistent_filesystem self._task_id = task_id self._container_id: Optional[str] = None + logger.info(f"DockerEnvironment volumes: {volumes}") + # Ensure volumes is a list (config.yaml could be malformed) + if volumes is not None and not isinstance(volumes, list): + logger.warning(f"docker_volumes config is not a list: {volumes!r}") + volumes = [] from minisweagent.environments.docker import DockerEnvironment as _Docker @@ -99,10 +105,26 @@ class DockerEnvironment(BaseEnvironment): # All containers get full security hardening (read-only root + writable # mounts for the workspace). Persistence uses Docker volumes, not # filesystem layer commits, so --read-only is always safe. - all_run_args = list(_SECURITY_ARGS) + writable_args + resource_args + # User-configured volume mounts (from config.yaml docker_volumes) + volume_args = [] + for vol in (volumes or []): + if not isinstance(vol, str): + logger.warning(f"Docker volume entry is not a string: {vol!r}") + continue + vol = vol.strip() + if not vol: + continue + if ":" in vol: + volume_args.extend(["-v", vol]) + else: + logger.warning(f"Docker volume '{vol}' missing colon, skipping") + + logger.info(f"Docker volume_args: {volume_args}") + all_run_args = list(_SECURITY_ARGS) + writable_args + resource_args + volume_args + logger.info(f"Docker run_args: {all_run_args}") self._inner = _Docker( - image=effective_image, cwd=cwd, timeout=timeout, + image=image, cwd=cwd, timeout=timeout, run_args=all_run_args, ) self._container_id = self._inner.container_id diff --git a/tools/file_tools.py b/tools/file_tools.py index 91d69c41..6182630b 100644 --- a/tools/file_tools.py +++ b/tools/file_tools.py @@ -81,11 +81,20 @@ def _get_file_ops(task_id: str = "default") -> ShellFileOperations: cwd = overrides.get("cwd") or config["cwd"] logger.info("Creating new %s environment for task %s...", env_type, task_id[:8]) + container_config = None + if env_type in ("docker", "singularity", "modal"): + container_config = { + "container_cpu": config.get("container_cpu", 1), + "container_memory": config.get("container_memory", 5120), + "container_disk": config.get("container_disk", 51200), + "container_persistent": config.get("container_persistent", True), + } terminal_env = _create_environment( env_type=env_type, image=image, cwd=cwd, timeout=config["timeout"], + container_config=container_config, ) with _env_lock: diff --git a/tools/terminal_tool.py b/tools/terminal_tool.py index 8af8c9d2..886624ce 100644 --- a/tools/terminal_tool.py +++ b/tools/terminal_tool.py @@ -445,6 +445,7 @@ def _get_env_config() -> Dict[str, Any]: "container_memory": int(os.getenv("TERMINAL_CONTAINER_MEMORY", "5120")), # MB (default 5GB) "container_disk": int(os.getenv("TERMINAL_CONTAINER_DISK", "51200")), # MB (default 50GB) "container_persistent": os.getenv("TERMINAL_CONTAINER_PERSISTENT", "true").lower() in ("true", "1", "yes"), + "docker_volumes": json.loads(os.getenv("TERMINAL_DOCKER_VOLUMES", "[]")), } @@ -471,6 +472,7 @@ def _create_environment(env_type: str, image: str, cwd: str, timeout: int, memory = cc.get("container_memory", 5120) disk = cc.get("container_disk", 51200) persistent = cc.get("container_persistent", True) + volumes = cc.get("docker_volumes", []) if env_type == "local": return _LocalEnvironment(cwd=cwd, timeout=timeout) @@ -480,6 +482,7 @@ def _create_environment(env_type: str, image: str, cwd: str, timeout: int, image=image, cwd=cwd, timeout=timeout, cpu=cpu, memory=memory, disk=disk, persistent_filesystem=persistent, task_id=task_id, + volumes=volumes, ) elif env_type == "singularity": @@ -848,6 +851,7 @@ def terminal_tool( "container_memory": config.get("container_memory", 5120), "container_disk": config.get("container_disk", 51200), "container_persistent": config.get("container_persistent", True), + "docker_volumes": config.get("docker_volumes", []), } new_env = _create_environment(