friday-framework-sandbox 0.1.0a0__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.
@@ -0,0 +1,23 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .env
4
+ .venv/
5
+ .idea/
6
+ .vscode/
7
+ dist/
8
+ build/
9
+ *.egg-info/
10
+ chroma_db/
11
+ .friday/
12
+ /vector/
13
+ /exports/
14
+ tests/integration/memory/vector/*/
15
+ tests/integration/memory/vector/backups/*/
16
+ tests/integration/memory/vector/reorganization_logs/*
17
+ *.gpickle
18
+ keys.txt
19
+ experiments/*/config/runtime.local.yaml
20
+ experiments/*/artifacts/*
21
+ !experiments/*/artifacts/.gitkeep
22
+ scripts/source-friday-chat-example-env.sh
23
+ /docs/reference/PyPI-Recovery-Codes-cichuck-2026-07-20T15_36_49.709334.txt
@@ -0,0 +1,14 @@
1
+ Metadata-Version: 2.4
2
+ Name: friday-framework-sandbox
3
+ Version: 0.1.0a0
4
+ Summary: Docker-based code execution sandbox for AI agents
5
+ Author: Friday Team
6
+ License-Expression: MIT
7
+ Keywords: agents,ai,code-execution,docker,sandbox
8
+ Requires-Python: >=3.10
9
+ Requires-Dist: docker>=7.0.0
10
+ Requires-Dist: pydantic>=2.0.0
11
+ Provides-Extra: dev
12
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
13
+ Requires-Dist: pytest-mock>=3.10.0; extra == 'dev'
14
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
@@ -0,0 +1,45 @@
1
+ # Friday Sandbox Python Image
2
+ # Provides a secure, minimal Python environment for code execution
3
+ #
4
+ # Ref: docs/specs/sandbox_specifications.md - Section 9: Docker Image
5
+
6
+ FROM python:3.11-slim-bookworm
7
+
8
+ # Labels
9
+ LABEL org.opencontainers.image.title="Friday Sandbox Python"
10
+ LABEL org.opencontainers.image.description="Secure Python execution environment for AI agents"
11
+ LABEL org.opencontainers.image.version="0.1.0"
12
+ LABEL org.opencontainers.image.vendor="Friday Team"
13
+
14
+ # Security: Create non-root user
15
+ RUN groupadd -r sandbox && useradd -r -g sandbox sandbox
16
+
17
+ # Install common Python packages for data processing and analysis
18
+ RUN pip install --no-cache-dir \
19
+ # Data processing
20
+ numpy>=1.24.0 \
21
+ pandas>=2.0.0 \
22
+ # JSON/YAML handling
23
+ pyyaml>=6.0 \
24
+ # HTTP requests
25
+ requests>=2.28.0 \
26
+ httpx>=0.24.0 \
27
+ # Pydantic for structured returns
28
+ pydantic>=2.0.0 \
29
+ # Date handling
30
+ python-dateutil>=2.8.0 \
31
+ # Math
32
+ sympy>=1.12 \
33
+ && rm -rf /root/.cache/pip
34
+
35
+ # Create workspace directory with correct permissions
36
+ RUN mkdir -p /workspace && chown sandbox:sandbox /workspace
37
+
38
+ # Set working directory
39
+ WORKDIR /workspace
40
+
41
+ # Switch to non-root user
42
+ USER sandbox
43
+
44
+ # Default command (will be overridden)
45
+ CMD ["python", "--version"]
@@ -0,0 +1,34 @@
1
+ #!/bin/bash
2
+ # Build the Friday Sandbox Python Docker image
3
+ #
4
+ # Usage:
5
+ # ./build.sh # Build with default tag
6
+ # ./build.sh v1.0.0 # Build with specific version tag
7
+ #
8
+ # Ref: docs/specs/sandbox_specifications.md - Section 9: Docker Image
9
+
10
+ set -e
11
+
12
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13
+ IMAGE_NAME="friday-sandbox-python"
14
+ VERSION="${1:-latest}"
15
+
16
+ echo "Building Friday Sandbox Python image..."
17
+ echo " Image: ${IMAGE_NAME}:${VERSION}"
18
+
19
+ docker build \
20
+ -t "${IMAGE_NAME}:${VERSION}" \
21
+ -f "${SCRIPT_DIR}/Dockerfile.python" \
22
+ "${SCRIPT_DIR}"
23
+
24
+ # Also tag as latest if version was specified
25
+ if [ "${VERSION}" != "latest" ]; then
26
+ docker tag "${IMAGE_NAME}:${VERSION}" "${IMAGE_NAME}:latest"
27
+ echo " Also tagged as: ${IMAGE_NAME}:latest"
28
+ fi
29
+
30
+ echo ""
31
+ echo "Build complete!"
32
+ echo ""
33
+ echo "Test the image with:"
34
+ echo " docker run --rm ${IMAGE_NAME}:${VERSION} python -c 'print(\"Hello from Friday Sandbox!\")'"
@@ -0,0 +1,39 @@
1
+ [project]
2
+ name = "friday-framework-sandbox"
3
+ version = "0.1.0a0"
4
+ description = "Docker-based code execution sandbox for AI agents"
5
+ requires-python = ">=3.10"
6
+ license = "MIT"
7
+ authors = [{ name = "Friday Team" }]
8
+ keywords = ["ai", "agents", "sandbox", "docker", "code-execution"]
9
+
10
+ dependencies = [
11
+ "docker>=7.0.0",
12
+ "pydantic>=2.0.0",
13
+ ]
14
+
15
+ [project.optional-dependencies]
16
+ dev = [
17
+ "pytest>=8.0.0",
18
+ "pytest-asyncio>=0.23.0",
19
+ "pytest-mock>=3.10.0",
20
+ ]
21
+
22
+ [build-system]
23
+ requires = ["hatchling"]
24
+ build-backend = "hatchling.build"
25
+
26
+ [tool.hatch.build.targets.wheel]
27
+ packages = ["src/friday_sandbox"]
28
+
29
+ [tool.pytest.ini_options]
30
+ testpaths = ["tests_sandbox"]
31
+ asyncio_mode = "auto"
32
+ asyncio_default_fixture_loop_scope = "function"
33
+
34
+ [tool.ruff]
35
+ line-length = 88
36
+ target-version = "py310"
37
+
38
+ [tool.ruff.lint]
39
+ select = ["E", "F", "I", "UP", "B", "SIM"]
@@ -0,0 +1,80 @@
1
+ # mypy: ignore-errors
2
+ """
3
+ Friday Sandbox - Docker-based code execution for AI agents.
4
+
5
+ This package provides secure, isolated code execution environments
6
+ for AI agents to run dynamically generated code safely.
7
+
8
+ Ref: docs/reference/specs/sandbox_specifications.md
9
+ """
10
+
11
+ from .config import DockerConfig, SandboxConfig, WorkspaceConfig
12
+ from .models import (
13
+ ExecutionResult,
14
+ SandboxError,
15
+ SandboxExecutionError,
16
+ SandboxTimeoutError,
17
+ Workspace,
18
+ WorkspaceExpiredError,
19
+ WorkspaceNotFoundError,
20
+ )
21
+ from .protocols import SandboxProtocol
22
+ from .workspace import WorkspaceManager
23
+
24
+
25
+ # Lazy imports for sandbox implementations to avoid import errors
26
+ # when Docker SDK is not installed
27
+ def get_docker_sandbox():
28
+ """Get DockerSandbox class (requires docker SDK)."""
29
+ from .docker import DockerSandbox
30
+
31
+ return DockerSandbox
32
+
33
+
34
+ def get_subprocess_sandbox():
35
+ """Get SubprocessSandbox class."""
36
+ from .subprocess import SubprocessSandbox
37
+
38
+ return SubprocessSandbox
39
+
40
+
41
+ def get_mock_sandbox():
42
+ """Get MockSandbox class."""
43
+ from .mock import MockSandbox
44
+
45
+ return MockSandbox
46
+
47
+
48
+ def get_mock_sandbox_builder():
49
+ """Get MockSandboxBuilder class."""
50
+ from .mock import MockSandboxBuilder
51
+
52
+ return MockSandboxBuilder
53
+
54
+
55
+ __all__ = [
56
+ # Protocols
57
+ "SandboxProtocol",
58
+ # Models
59
+ "ExecutionResult",
60
+ "Workspace",
61
+ # Errors
62
+ "SandboxError",
63
+ "SandboxTimeoutError",
64
+ "SandboxExecutionError",
65
+ "WorkspaceNotFoundError",
66
+ "WorkspaceExpiredError",
67
+ # Config
68
+ "SandboxConfig",
69
+ "WorkspaceConfig",
70
+ "DockerConfig",
71
+ # Manager
72
+ "WorkspaceManager",
73
+ # Sandbox implementations (lazy loaded)
74
+ "get_docker_sandbox",
75
+ "get_subprocess_sandbox",
76
+ "get_mock_sandbox",
77
+ "get_mock_sandbox_builder",
78
+ ]
79
+
80
+ __version__ = "0.1.0"
@@ -0,0 +1,252 @@
1
+ # mypy: ignore-errors
2
+ """
3
+ Configuration models for sandbox and workspace management.
4
+
5
+ Ref: docs/reference/specs/sandbox_specifications.md - Section 4: Configuration
6
+
7
+ Configuration follows a layered approach:
8
+ 1. Package defaults (defined here)
9
+ 2. Environment variables (FRIDAY_SANDBOX_*)
10
+ 3. Agent profile settings
11
+ 4. Per-execution overrides
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import os
17
+ from pathlib import Path
18
+ from typing import Any
19
+
20
+ from pydantic import BaseModel, Field, field_validator
21
+
22
+
23
+ class WorkspaceConfig(BaseModel):
24
+ """
25
+ Configuration for workspace lifecycle management.
26
+
27
+ Controls TTL, cleanup behavior, and resource limits for workspaces.
28
+ """
29
+
30
+ # TTL Configuration
31
+ ttl_seconds: int = Field(
32
+ default=3600,
33
+ ge=60,
34
+ le=86400,
35
+ description="Default workspace TTL in seconds (1 hour default)",
36
+ )
37
+ ttl_soft_limit_seconds: int = Field(
38
+ default=3600,
39
+ ge=60,
40
+ description="Soft limit before warning (1 hour)",
41
+ )
42
+ ttl_hard_limit_seconds: int = Field(
43
+ default=86400,
44
+ ge=3600,
45
+ description="Hard limit - force cleanup (24 hours)",
46
+ )
47
+
48
+ # Cleanup Configuration
49
+ cleanup_interval_seconds: int = Field(
50
+ default=300,
51
+ ge=60,
52
+ description="Interval between cleanup sweeps (5 minutes)",
53
+ )
54
+ preserve_on_error: bool = Field(
55
+ default=True,
56
+ description="Preserve workspace if execution fails (for debugging)",
57
+ )
58
+
59
+ # Resource Limits
60
+ max_workspace_size_mb: int = Field(
61
+ default=100,
62
+ ge=10,
63
+ le=1000,
64
+ description="Maximum workspace size in MB",
65
+ )
66
+ max_files_per_workspace: int = Field(
67
+ default=1000,
68
+ ge=10,
69
+ description="Maximum number of files in workspace",
70
+ )
71
+
72
+ @classmethod
73
+ def from_environment(cls) -> WorkspaceConfig:
74
+ """Create config from environment variables."""
75
+ return cls(
76
+ ttl_seconds=int(os.getenv("FRIDAY_SANDBOX_WORKSPACE_TTL", "3600")),
77
+ ttl_soft_limit_seconds=int(
78
+ os.getenv("FRIDAY_SANDBOX_WORKSPACE_TTL_SOFT", "3600")
79
+ ),
80
+ ttl_hard_limit_seconds=int(
81
+ os.getenv("FRIDAY_SANDBOX_WORKSPACE_TTL_HARD", "86400")
82
+ ),
83
+ cleanup_interval_seconds=int(
84
+ os.getenv("FRIDAY_SANDBOX_CLEANUP_INTERVAL", "300")
85
+ ),
86
+ preserve_on_error=os.getenv(
87
+ "FRIDAY_SANDBOX_PRESERVE_ON_ERROR", "true"
88
+ ).lower()
89
+ == "true",
90
+ max_workspace_size_mb=int(
91
+ os.getenv("FRIDAY_SANDBOX_MAX_WORKSPACE_SIZE_MB", "100")
92
+ ),
93
+ max_files_per_workspace=int(os.getenv("FRIDAY_SANDBOX_MAX_FILES", "1000")),
94
+ )
95
+
96
+
97
+ class DockerConfig(BaseModel):
98
+ """
99
+ Configuration for Docker sandbox execution.
100
+
101
+ Controls container resources, networking, and security settings.
102
+ """
103
+
104
+ # Image Configuration
105
+ image: str = Field(
106
+ default="friday-sandbox-python:latest",
107
+ description="Docker image to use for execution",
108
+ )
109
+ pull_policy: str = Field(
110
+ default="if-not-present",
111
+ description="Image pull policy: always, if-not-present, never",
112
+ )
113
+
114
+ # Resource Limits
115
+ memory_limit: str = Field(
116
+ default="512m",
117
+ description="Container memory limit",
118
+ )
119
+ cpu_limit: float = Field(
120
+ default=1.0,
121
+ ge=0.1,
122
+ le=4.0,
123
+ description="CPU core limit",
124
+ )
125
+
126
+ # Execution Limits
127
+ default_timeout_seconds: int = Field(
128
+ default=30,
129
+ ge=1,
130
+ le=300,
131
+ description="Default execution timeout",
132
+ )
133
+ max_timeout_seconds: int = Field(
134
+ default=300,
135
+ ge=30,
136
+ le=600,
137
+ description="Maximum allowed timeout",
138
+ )
139
+
140
+ # Security
141
+ network_enabled: bool = Field(
142
+ default=False,
143
+ description="Enable network access in container",
144
+ )
145
+ read_only_rootfs: bool = Field(
146
+ default=True,
147
+ description="Mount root filesystem as read-only",
148
+ )
149
+ user: str = Field(
150
+ default="sandbox:sandbox",
151
+ description="User:group to run as in container",
152
+ )
153
+
154
+ # Paths
155
+ workspace_base_path: Path = Field(
156
+ default=Path("/tmp/friday-workspaces"),
157
+ description="Base path for workspace directories",
158
+ )
159
+
160
+ @field_validator("pull_policy")
161
+ @classmethod
162
+ def validate_pull_policy(cls, v: str) -> str:
163
+ valid = {"always", "if-not-present", "never"}
164
+ if v not in valid:
165
+ raise ValueError(f"pull_policy must be one of: {valid}")
166
+ return v
167
+
168
+ @classmethod
169
+ def from_environment(cls) -> DockerConfig:
170
+ """Create config from environment variables."""
171
+ return cls(
172
+ image=os.getenv("FRIDAY_SANDBOX_IMAGE", "friday-sandbox-python:latest"),
173
+ pull_policy=os.getenv("FRIDAY_SANDBOX_PULL_POLICY", "if-not-present"),
174
+ memory_limit=os.getenv("FRIDAY_SANDBOX_MEMORY_LIMIT", "512m"),
175
+ cpu_limit=float(os.getenv("FRIDAY_SANDBOX_CPU_LIMIT", "1.0")),
176
+ default_timeout_seconds=int(
177
+ os.getenv("FRIDAY_SANDBOX_DEFAULT_TIMEOUT", "30")
178
+ ),
179
+ max_timeout_seconds=int(os.getenv("FRIDAY_SANDBOX_MAX_TIMEOUT", "300")),
180
+ network_enabled=os.getenv("FRIDAY_SANDBOX_NETWORK_ENABLED", "false").lower()
181
+ == "true",
182
+ read_only_rootfs=os.getenv(
183
+ "FRIDAY_SANDBOX_READ_ONLY_ROOTFS", "true"
184
+ ).lower()
185
+ == "true",
186
+ workspace_base_path=Path(
187
+ os.getenv("FRIDAY_SANDBOX_WORKSPACE_PATH", "/tmp/friday-workspaces")
188
+ ),
189
+ )
190
+
191
+
192
+ class SandboxConfig(BaseModel):
193
+ """
194
+ Top-level sandbox configuration combining all settings.
195
+ """
196
+
197
+ workspace: WorkspaceConfig = Field(default_factory=WorkspaceConfig)
198
+ docker: DockerConfig = Field(default_factory=DockerConfig)
199
+
200
+ # Sandbox Selection
201
+ sandbox_type: str = Field(
202
+ default="docker",
203
+ description="Sandbox type: docker, subprocess, mock",
204
+ )
205
+ fallback_to_subprocess: bool = Field(
206
+ default=True,
207
+ description="Fall back to subprocess if Docker unavailable",
208
+ )
209
+
210
+ @field_validator("sandbox_type")
211
+ @classmethod
212
+ def validate_sandbox_type(cls, v: str) -> str:
213
+ valid = {"docker", "subprocess", "mock"}
214
+ if v not in valid:
215
+ raise ValueError(f"sandbox_type must be one of: {valid}")
216
+ return v
217
+
218
+ @classmethod
219
+ def from_environment(cls) -> SandboxConfig:
220
+ """Create complete config from environment variables."""
221
+ return cls(
222
+ workspace=WorkspaceConfig.from_environment(),
223
+ docker=DockerConfig.from_environment(),
224
+ sandbox_type=os.getenv("FRIDAY_SANDBOX_TYPE", "docker"),
225
+ fallback_to_subprocess=os.getenv(
226
+ "FRIDAY_SANDBOX_FALLBACK_SUBPROCESS", "true"
227
+ ).lower()
228
+ == "true",
229
+ )
230
+
231
+ def merge_overrides(self, overrides: dict[str, Any]) -> SandboxConfig:
232
+ """
233
+ Create a new config with overrides applied.
234
+
235
+ Args:
236
+ overrides: Dictionary of override values.
237
+
238
+ Returns:
239
+ New SandboxConfig with overrides merged.
240
+ """
241
+ data = self.model_dump()
242
+ for key, value in overrides.items():
243
+ if "." in key:
244
+ # Handle nested keys like "workspace.ttl_seconds"
245
+ parts = key.split(".")
246
+ current = data
247
+ for part in parts[:-1]:
248
+ current = current.setdefault(part, {})
249
+ current[parts[-1]] = value
250
+ else:
251
+ data[key] = value
252
+ return SandboxConfig.model_validate(data)