darwin-agentic-cloud 0.1.0__py3-none-any.whl

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,32 @@
1
+ """Deterministic hashing for DAC attestations.
2
+
3
+ Canonical JSON (sorted keys, no whitespace, UTF-8) + SHA-256 so the same
4
+ logical content always produces the same hash across implementations.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import hashlib
10
+ import json
11
+ from typing import Any
12
+
13
+
14
+ def canonical_json(obj: Any) -> bytes:
15
+ """Serialize obj to canonical JSON bytes (sorted keys, no whitespace, UTF-8)."""
16
+ return json.dumps(
17
+ obj,
18
+ ensure_ascii=False,
19
+ sort_keys=True,
20
+ separators=(",", ":"),
21
+ allow_nan=False,
22
+ ).encode("utf-8")
23
+
24
+
25
+ def sha256_hex(data: bytes) -> str:
26
+ """Return the hex-encoded SHA-256 of data."""
27
+ return hashlib.sha256(data).hexdigest()
28
+
29
+
30
+ def content_hash(obj: Any) -> str:
31
+ """Hex SHA-256 of obj serialized as canonical JSON."""
32
+ return sha256_hex(canonical_json(obj))
@@ -0,0 +1,303 @@
1
+ """DAC MCP server.
2
+
3
+ Exposes DAC as an MCP server over stdio so Claude Desktop, Cursor,
4
+ and other MCP clients can call DAC as a tool.
5
+
6
+ Tools:
7
+ dac_run_python — execute Python code, return signed attestation
8
+ dac_run_node — execute Node.js code, return signed attestation
9
+ dac_verify_attestation — verify a signed attestation
10
+ dac_identity — show this DAC instance's identity
11
+ dac_history_recent — list recent attestations
12
+ dac_history_stats — aggregate stats across stored attestations
13
+ dac_history_get — fetch a specific attestation by ID
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import asyncio
19
+ import json
20
+ from typing import Any
21
+
22
+ from mcp.server import Server
23
+ from mcp.server.stdio import stdio_server
24
+ from mcp.types import TextContent, Tool
25
+
26
+ from darwin import agenticcloud as dac
27
+ from darwin.agenticcloud.attestation import verify_attestation
28
+ from darwin.agenticcloud.runtime import Runtime
29
+ from darwin.agenticcloud.sandbox import SUBSTRATE_ID
30
+
31
+ # TODO(v0.2): per-role signing keys (cli vs http vs mcp) via HKDF.
32
+ _runtime = Runtime()
33
+ _server = Server("darwin-agenticcloud")
34
+
35
+
36
+ @_server.list_tools()
37
+ async def list_tools() -> list[Tool]:
38
+ return [
39
+ Tool(
40
+ name="dac_run_python",
41
+ description=(
42
+ "Execute Python code in an isolated Docker sandbox and return a "
43
+ "cryptographically signed attestation of the execution. The attestation "
44
+ "binds the source code, the output, the substrate identity, the cost, "
45
+ "and the signer's identity. Any tampering with any field breaks "
46
+ "verification. Workloads whose maximum possible cost exceeds "
47
+ "cost_cap_usd are rejected before the sandbox is launched; the "
48
+ "rejection itself is signed."
49
+ ),
50
+ inputSchema={
51
+ "type": "object",
52
+ "properties": {
53
+ "code": {"type": "string", "description": "Python source code."},
54
+ "timeout_sec": {"type": "integer", "minimum": 1, "maximum": 600, "default": 30},
55
+ "memory_mb": {
56
+ "type": "integer",
57
+ "minimum": 64,
58
+ "maximum": 8192,
59
+ "default": 512,
60
+ },
61
+ "cost_cap_usd": {
62
+ "type": "number",
63
+ "minimum": 0.0001,
64
+ "default": 0.01,
65
+ "description": "Maximum cost the workload may incur. Pre-flight enforced.",
66
+ },
67
+ },
68
+ "required": ["code"],
69
+ },
70
+ ),
71
+ Tool(
72
+ name="dac_run_node",
73
+ description=(
74
+ "Execute Node.js code in an isolated Docker sandbox and return a "
75
+ "cryptographically signed attestation."
76
+ ),
77
+ inputSchema={
78
+ "type": "object",
79
+ "properties": {
80
+ "code": {"type": "string", "description": "JS/Node source code."},
81
+ "timeout_sec": {"type": "integer", "minimum": 1, "maximum": 600, "default": 30},
82
+ "memory_mb": {
83
+ "type": "integer",
84
+ "minimum": 64,
85
+ "maximum": 8192,
86
+ "default": 512,
87
+ },
88
+ "cost_cap_usd": {"type": "number", "minimum": 0.0001, "default": 0.01},
89
+ },
90
+ "required": ["code"],
91
+ },
92
+ ),
93
+ Tool(
94
+ name="dac_verify_attestation",
95
+ description=(
96
+ "Verify a signed DAC attestation. Returns whether the signature is "
97
+ "valid for the attestation payload under the embedded public key."
98
+ ),
99
+ inputSchema={
100
+ "type": "object",
101
+ "properties": {
102
+ "attestation": {"type": "object", "description": "Attestation payload."},
103
+ "signature_b64": {"type": "string", "description": "Base64 Ed25519 signature."},
104
+ "public_key_b64": {
105
+ "type": "string",
106
+ "description": "Base64 Ed25519 public key.",
107
+ },
108
+ },
109
+ "required": ["attestation", "signature_b64", "public_key_b64"],
110
+ },
111
+ ),
112
+ Tool(
113
+ name="dac_identity",
114
+ description="Return this DAC instance's identity.",
115
+ inputSchema={"type": "object", "properties": {}},
116
+ ),
117
+ Tool(
118
+ name="dac_history_recent",
119
+ description=(
120
+ "List the most recent attestations stored by this DAC instance. "
121
+ "Useful for an agent to audit its own past execution. Returns a "
122
+ "summary per attestation (id, workload_id, status, cost, timing, "
123
+ "substrate)."
124
+ ),
125
+ inputSchema={
126
+ "type": "object",
127
+ "properties": {
128
+ "limit": {"type": "integer", "minimum": 1, "maximum": 200, "default": 20},
129
+ "status": {
130
+ "type": "string",
131
+ "description": "Filter by status (ok, error, timeout, oom, cost_exceeded).",
132
+ },
133
+ },
134
+ },
135
+ ),
136
+ Tool(
137
+ name="dac_history_stats",
138
+ description=(
139
+ "Return aggregate stats across all attestations stored by this DAC "
140
+ "instance: total count, total cost USD, count by status."
141
+ ),
142
+ inputSchema={"type": "object", "properties": {}},
143
+ ),
144
+ Tool(
145
+ name="dac_history_get",
146
+ description=(
147
+ "Fetch the full signed attestation for a given attestation ID. "
148
+ "Accepts the full UUID. Returns the complete signed attestation "
149
+ "(attestation, signature_b64, public_key_b64) which can be passed "
150
+ "to dac_verify_attestation."
151
+ ),
152
+ inputSchema={
153
+ "type": "object",
154
+ "properties": {
155
+ "attestation_id": {"type": "string", "description": "Full attestation UUID."},
156
+ },
157
+ "required": ["attestation_id"],
158
+ },
159
+ ),
160
+ ]
161
+
162
+
163
+ @_server.call_tool()
164
+ async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]:
165
+ if name == "dac_run_python":
166
+ return await _handle_run(arguments, language="python")
167
+ if name == "dac_run_node":
168
+ return await _handle_run(arguments, language="node")
169
+ if name == "dac_verify_attestation":
170
+ return _handle_verify(arguments)
171
+ if name == "dac_identity":
172
+ return _handle_identity()
173
+ if name == "dac_history_recent":
174
+ return _handle_history_recent(arguments)
175
+ if name == "dac_history_stats":
176
+ return _handle_history_stats()
177
+ if name == "dac_history_get":
178
+ return _handle_history_get(arguments)
179
+ return [TextContent(type="text", text=f"Unknown tool: {name}")]
180
+
181
+
182
+ async def _handle_run(arguments: dict[str, Any], language: str) -> list[TextContent]:
183
+ from darwin.agenticcloud.types import WorkloadSpec
184
+
185
+ spec = WorkloadSpec(
186
+ code=arguments["code"],
187
+ language=language,
188
+ timeout_sec=int(arguments.get("timeout_sec", 30)),
189
+ memory_mb=int(arguments.get("memory_mb", 512)),
190
+ cost_cap_usd=float(arguments.get("cost_cap_usd", 0.01)),
191
+ )
192
+ signed = await asyncio.to_thread(_runtime.run, spec)
193
+ payload = {
194
+ "attestation": signed.attestation,
195
+ "signature_b64": signed.signature_b64,
196
+ "public_key_b64": signed.public_key_b64,
197
+ }
198
+ return [TextContent(type="text", text=json.dumps(payload, indent=2))]
199
+
200
+
201
+ def _handle_verify(arguments: dict[str, Any]) -> list[TextContent]:
202
+ try:
203
+ ok = verify_attestation(
204
+ {
205
+ "attestation": arguments["attestation"],
206
+ "signature_b64": arguments["signature_b64"],
207
+ "public_key_b64": arguments["public_key_b64"],
208
+ }
209
+ )
210
+ result = {
211
+ "verified": ok,
212
+ "attestation_id": arguments["attestation"].get("attestation_id"),
213
+ "signer_key_id": arguments["attestation"].get("signer_key_id"),
214
+ }
215
+ return [TextContent(type="text", text=json.dumps(result, indent=2))]
216
+ except Exception as e:
217
+ return [
218
+ TextContent(
219
+ type="text", text=json.dumps({"verified": False, "error": str(e)}, indent=2)
220
+ )
221
+ ]
222
+
223
+
224
+ def _handle_identity() -> list[TextContent]:
225
+ signer = _runtime.signer
226
+ payload = {
227
+ "key_id": signer.key_id(),
228
+ "public_key_b64": signer.public_key_b64(),
229
+ "substrate_id": SUBSTRATE_ID,
230
+ "schema": dac.ATTESTATION_SCHEMA,
231
+ "version": dac.__version__,
232
+ }
233
+ return [TextContent(type="text", text=json.dumps(payload, indent=2))]
234
+
235
+
236
+ def _handle_history_recent(arguments: dict[str, Any]) -> list[TextContent]:
237
+ limit = int(arguments.get("limit", 20))
238
+ status = arguments.get("status")
239
+ store = _runtime.store
240
+ rows = store.list_by_status(status, limit=limit) if status else store.list_recent(limit=limit)
241
+ summaries = [
242
+ {
243
+ "attestation_id": r.attestation_id,
244
+ "workload_id": r.workload_id,
245
+ "signer_key_id": r.signer_key_id,
246
+ "substrate_id": r.substrate_id,
247
+ "status": r.status,
248
+ "issued_at": r.issued_at,
249
+ "cost_usd": r.cost_usd,
250
+ "wall_time_sec": r.wall_time_sec,
251
+ "schema_version": r.schema_version,
252
+ }
253
+ for r in rows
254
+ ]
255
+ return [
256
+ TextContent(
257
+ type="text",
258
+ text=json.dumps({"count": len(summaries), "attestations": summaries}, indent=2),
259
+ )
260
+ ]
261
+
262
+
263
+ def _handle_history_stats() -> list[TextContent]:
264
+ store = _runtime.store
265
+ payload = {
266
+ "total_count": store.count(),
267
+ "total_cost_usd": store.total_cost_usd(),
268
+ "by_status": {
269
+ "ok": len(store.list_by_status("ok", limit=10**9)),
270
+ "error": len(store.list_by_status("error", limit=10**9)),
271
+ "timeout": len(store.list_by_status("timeout", limit=10**9)),
272
+ "oom": len(store.list_by_status("oom", limit=10**9)),
273
+ "cost_exceeded": len(store.list_by_status("cost_exceeded", limit=10**9)),
274
+ },
275
+ }
276
+ return [TextContent(type="text", text=json.dumps(payload, indent=2))]
277
+
278
+
279
+ def _handle_history_get(arguments: dict[str, Any]) -> list[TextContent]:
280
+ store = _runtime.store
281
+ attestation_id = arguments["attestation_id"]
282
+ fetched = store.get(attestation_id)
283
+ if fetched is None:
284
+ return [
285
+ TextContent(
286
+ type="text",
287
+ text=json.dumps({"error": "not_found", "attestation_id": attestation_id}, indent=2),
288
+ )
289
+ ]
290
+ return [TextContent(type="text", text=json.dumps(fetched.signed_attestation, indent=2))]
291
+
292
+
293
+ async def main() -> None:
294
+ async with stdio_server() as (read_stream, write_stream):
295
+ await _server.run(read_stream, write_stream, _server.create_initialization_options())
296
+
297
+
298
+ def run() -> None:
299
+ asyncio.run(main())
300
+
301
+
302
+ if __name__ == "__main__":
303
+ run()
@@ -0,0 +1,109 @@
1
+ """DAC runtime orchestrator.
2
+
3
+ The single entry point that turns a WorkloadSpec into a SignedAttestation:
4
+
5
+ spec → budget check → sandbox.execute → ExecutionResult → signed attestation → store
6
+
7
+ Every signed attestation (including budget rejections) is persisted to
8
+ the AttestationStore for audit and history.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import time
14
+ import uuid
15
+
16
+ from darwin.agenticcloud.attestation import build_signed_attestation
17
+ from darwin.agenticcloud.cost import BudgetExceeded, check_budget, cost_for_seconds
18
+ from darwin.agenticcloud.hashing import sha256_hex
19
+ from darwin.agenticcloud.sandbox import SUBSTRATE_ID, DockerSandbox, SandboxResult
20
+ from darwin.agenticcloud.signing import Signer
21
+ from darwin.agenticcloud.storage import AttestationStore
22
+ from darwin.agenticcloud.types import ExecutionResult, SignedAttestation, WorkloadSpec
23
+
24
+
25
+ class Runtime:
26
+ """The DAC execution runtime."""
27
+
28
+ def __init__(
29
+ self,
30
+ sandbox: DockerSandbox | None = None,
31
+ signer: Signer | None = None,
32
+ store: AttestationStore | None = None,
33
+ ) -> None:
34
+ self._sandbox = sandbox or DockerSandbox()
35
+ self._signer = signer or Signer()
36
+ self._store = store if store is not None else AttestationStore()
37
+
38
+ @property
39
+ def signer(self) -> Signer:
40
+ return self._signer
41
+
42
+ @property
43
+ def store(self) -> AttestationStore:
44
+ return self._store
45
+
46
+ def run(self, spec: WorkloadSpec) -> SignedAttestation:
47
+ """Execute a workload spec and return a signed attestation.
48
+
49
+ Every signed attestation (success, error, timeout, cost_exceeded)
50
+ is persisted before returning.
51
+ """
52
+ workload_id = f"wl-{uuid.uuid4().hex[:12]}"
53
+
54
+ # Pre-flight budget check (no sandbox launch on rejection)
55
+ try:
56
+ check_budget(spec, SUBSTRATE_ID)
57
+ except BudgetExceeded as e:
58
+ signed = self._build_rejection_attestation(spec, workload_id, str(e))
59
+ self._store.save(signed)
60
+ return signed
61
+
62
+ # Execute in the sandbox
63
+ sandbox_result: SandboxResult = self._sandbox.execute(
64
+ code=spec.code,
65
+ language=spec.language,
66
+ timeout_sec=spec.timeout_sec,
67
+ memory_mb=spec.memory_mb,
68
+ )
69
+
70
+ cost_usd = cost_for_seconds(sandbox_result.wall_time_sec, sandbox_result.substrate_id)
71
+
72
+ execution_result = ExecutionResult(
73
+ workload_id=workload_id,
74
+ status=sandbox_result.status,
75
+ stdout=sandbox_result.stdout,
76
+ stderr=sandbox_result.stderr,
77
+ exit_code=sandbox_result.exit_code,
78
+ started_at=sandbox_result.started_at,
79
+ ended_at=sandbox_result.ended_at,
80
+ wall_time_sec=sandbox_result.wall_time_sec,
81
+ cost_usd=cost_usd,
82
+ substrate_id=sandbox_result.substrate_id,
83
+ output_hash=sandbox_result.output_hash,
84
+ error=sandbox_result.error,
85
+ )
86
+
87
+ signed = build_signed_attestation(spec, execution_result, self._signer)
88
+ self._store.save(signed)
89
+ return signed
90
+
91
+ def _build_rejection_attestation(
92
+ self, spec: WorkloadSpec, workload_id: str, error: str
93
+ ) -> SignedAttestation:
94
+ now = time.time()
95
+ execution_result = ExecutionResult(
96
+ workload_id=workload_id,
97
+ status="cost_exceeded",
98
+ stdout="",
99
+ stderr="",
100
+ exit_code=None,
101
+ started_at=now,
102
+ ended_at=now,
103
+ wall_time_sec=0.0,
104
+ cost_usd=0.0,
105
+ substrate_id=SUBSTRATE_ID,
106
+ output_hash=sha256_hex(b""),
107
+ error=error,
108
+ )
109
+ return build_signed_attestation(spec, execution_result, self._signer)
@@ -0,0 +1,235 @@
1
+ """Docker-based sandbox for executing workloads.
2
+
3
+ Runs code in an isolated container with:
4
+ - Memory and CPU limits
5
+ - Wall-time timeout
6
+ - No network access (default)
7
+ - Non-root user
8
+ - Read-only root filesystem with writable /tmp
9
+ - Dropped capabilities
10
+ - No /proc, no /sys mounts beyond defaults
11
+
12
+ This is the v0 sandbox. Production will swap for Firecracker microVMs
13
+ or Kata Containers with the same interface.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import contextlib
19
+ import io
20
+ import tarfile
21
+ import time
22
+ import uuid
23
+ from dataclasses import dataclass
24
+
25
+ import docker
26
+ from docker.errors import APIError, ContainerError, ImageNotFound
27
+ from docker.models.containers import Container
28
+
29
+ from darwin.agenticcloud.hashing import sha256_hex
30
+
31
+ # Base images we trust. Pin by digest in production.
32
+ IMAGE_PYTHON = "python:3.11-slim"
33
+ IMAGE_NODE = "node:20-slim"
34
+
35
+ LANGUAGE_IMAGES = {
36
+ "python": IMAGE_PYTHON,
37
+ "node": IMAGE_NODE,
38
+ }
39
+
40
+ LANGUAGE_COMMANDS = {
41
+ "python": ["python", "/workload/main.py"],
42
+ "node": ["node", "/workload/main.js"],
43
+ }
44
+
45
+ LANGUAGE_FILENAMES = {
46
+ "python": "main.py",
47
+ "node": "main.js",
48
+ }
49
+
50
+ SUBSTRATE_ID = "local-docker-v0"
51
+
52
+
53
+ @dataclass
54
+ class SandboxResult:
55
+ """Raw execution data from the sandbox."""
56
+
57
+ status: str # "ok" | "error" | "timeout" | "oom"
58
+ stdout: str
59
+ stderr: str
60
+ exit_code: int | None
61
+ started_at: float
62
+ ended_at: float
63
+ wall_time_sec: float
64
+ substrate_id: str
65
+ output_hash: str
66
+ error: str | None = None
67
+
68
+
69
+ class DockerSandbox:
70
+ """Execute code in a Docker container with strict isolation."""
71
+
72
+ def __init__(self, client: docker.DockerClient | None = None) -> None:
73
+ # Lazy: don't connect until execute() is first called. Lets the server
74
+ # start in environments without a local Docker daemon (e.g. hosted
75
+ # read-only demos). Calling execute() without a reachable daemon
76
+ # still raises a clear error, just later instead of at import time.
77
+ self._client_arg = client
78
+ self._client: docker.DockerClient | None = None
79
+
80
+ def _ensure_client(self) -> docker.DockerClient:
81
+ if self._client is None:
82
+ self._client = self._client_arg or docker.from_env()
83
+ self._verify_daemon()
84
+ return self._client
85
+
86
+ def _verify_daemon(self) -> None:
87
+ """Fail fast if Docker isn't reachable."""
88
+ try:
89
+ self._ensure_client().ping()
90
+ except Exception as e:
91
+ raise RuntimeError("Docker daemon is not reachable. Is Docker Desktop running?") from e
92
+
93
+ def execute(
94
+ self,
95
+ code: str,
96
+ language: str = "python",
97
+ timeout_sec: int = 30,
98
+ memory_mb: int = 512,
99
+ cpu_quota: float = 1.0,
100
+ ) -> SandboxResult:
101
+ """Run code in a sandboxed container and return the result."""
102
+ if language not in LANGUAGE_IMAGES:
103
+ raise ValueError(
104
+ f"Unsupported language: {language!r}. Supported: {sorted(LANGUAGE_IMAGES)}"
105
+ )
106
+
107
+ image = LANGUAGE_IMAGES[language]
108
+ command = LANGUAGE_COMMANDS[language]
109
+ filename = LANGUAGE_FILENAMES[language]
110
+
111
+ self._ensure_image(image)
112
+
113
+ container_name = f"dac-{uuid.uuid4().hex[:12]}"
114
+ started_at = time.time()
115
+
116
+ container: Container | None = None
117
+ try:
118
+ container = self._ensure_client().containers.create(
119
+ image=image,
120
+ command=command,
121
+ name=container_name,
122
+ # Resource limits
123
+ mem_limit=f"{memory_mb}m",
124
+ memswap_limit=f"{memory_mb}m", # no swap
125
+ nano_cpus=int(cpu_quota * 1_000_000_000),
126
+ pids_limit=128,
127
+ # Isolation
128
+ network_disabled=True,
129
+ read_only=False,
130
+ tmpfs={"/tmp": "size=64m,mode=1777"},
131
+ cap_drop=["ALL"],
132
+ security_opt=["no-new-privileges"],
133
+ user="65534:65534", # nobody:nogroup
134
+ working_dir="/workload",
135
+ # Cleanup
136
+ auto_remove=False,
137
+ detach=True,
138
+ )
139
+
140
+ self._copy_code_into_container(container, filename, code)
141
+ container.start()
142
+
143
+ try:
144
+ exit_status = container.wait(timeout=timeout_sec)
145
+ exit_code = exit_status.get("StatusCode")
146
+ timed_out = False
147
+ except Exception:
148
+ # docker-py raises requests.exceptions.ReadTimeout on wait timeout
149
+ container.kill()
150
+ exit_code = None
151
+ timed_out = True
152
+
153
+ ended_at = time.time()
154
+
155
+ stdout = self._safe_logs(container, stdout=True, stderr=False)
156
+ stderr = self._safe_logs(container, stdout=False, stderr=True)
157
+
158
+ if timed_out:
159
+ status = "timeout"
160
+ elif self._was_oom_killed(container):
161
+ status = "oom"
162
+ elif exit_code == 0:
163
+ status = "ok"
164
+ else:
165
+ status = "error"
166
+
167
+ return SandboxResult(
168
+ status=status,
169
+ stdout=stdout,
170
+ stderr=stderr,
171
+ exit_code=exit_code,
172
+ started_at=started_at,
173
+ ended_at=ended_at,
174
+ wall_time_sec=ended_at - started_at,
175
+ substrate_id=SUBSTRATE_ID,
176
+ output_hash=sha256_hex(stdout.encode("utf-8")),
177
+ )
178
+
179
+ except (ContainerError, APIError) as e:
180
+ ended_at = time.time()
181
+ return SandboxResult(
182
+ status="error",
183
+ stdout="",
184
+ stderr="",
185
+ exit_code=None,
186
+ started_at=started_at,
187
+ ended_at=ended_at,
188
+ wall_time_sec=ended_at - started_at,
189
+ substrate_id=SUBSTRATE_ID,
190
+ output_hash=sha256_hex(b""),
191
+ error=f"Sandbox error: {type(e).__name__}: {e}",
192
+ )
193
+
194
+ finally:
195
+ if container is not None:
196
+ with contextlib.suppress(Exception):
197
+ container.remove(force=True)
198
+
199
+ def _ensure_image(self, image: str) -> None:
200
+ """Pull the image if it's not present locally."""
201
+ try:
202
+ self._ensure_client().images.get(image)
203
+ except ImageNotFound:
204
+ self._ensure_client().images.pull(image)
205
+
206
+ @staticmethod
207
+ def _copy_code_into_container(container: Container, filename: str, code: str) -> None:
208
+ """Stream code into the container as a tar archive at /workload/<filename>."""
209
+ tar_stream = io.BytesIO()
210
+ with tarfile.open(fileobj=tar_stream, mode="w") as tar:
211
+ data = code.encode("utf-8")
212
+ info = tarfile.TarInfo(name=filename)
213
+ info.size = len(data)
214
+ info.mode = 0o644
215
+ tar.addfile(info, io.BytesIO(data))
216
+ tar_stream.seek(0)
217
+ container.put_archive("/workload", tar_stream.read())
218
+
219
+ @staticmethod
220
+ def _safe_logs(container: Container, *, stdout: bool, stderr: bool) -> str:
221
+ try:
222
+ raw = container.logs(stdout=stdout, stderr=stderr)
223
+ if isinstance(raw, bytes):
224
+ return raw.decode("utf-8", errors="replace")
225
+ return str(raw)
226
+ except Exception:
227
+ return ""
228
+
229
+ @staticmethod
230
+ def _was_oom_killed(container: Container) -> bool:
231
+ try:
232
+ container.reload()
233
+ return bool(container.attrs.get("State", {}).get("OOMKilled", False))
234
+ except Exception:
235
+ return False