maf-sandbox-codeact 0.1.0__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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 SOKOLAI BV
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,79 @@
1
+ Metadata-Version: 2.4
2
+ Name: maf-sandbox-codeact
3
+ Version: 0.1.0
4
+ Summary: CodeAct as a Microsoft Agent Framework tool — the model writes a short Python program and it runs inside a sandbox — written against maf-sandbox so it runs on any sandbox backend.
5
+ Keywords: codeact,code-execution,sandbox,agent-framework,microsoft-agent-framework
6
+ Author: SOKOLAI BV
7
+ Author-email: SOKOLAI BV <info@sokolai.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Typing :: Typed
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Classifier: Topic :: Software Development :: Interpreters
18
+ Requires-Dist: maf-sandbox>=0.5.0,<0.6
19
+ Requires-Dist: agent-framework-core>=1.13.0,<2
20
+ Requires-Python: >=3.12, <3.15
21
+ Project-URL: Homepage, https://www.sokol.ai
22
+ Project-URL: Source, https://github.com/sokolaidev/maf-extensions
23
+ Project-URL: Issues, https://github.com/sokolaidev/maf-extensions/issues
24
+ Description-Content-Type: text/markdown
25
+
26
+ # maf-sandbox-codeact
27
+
28
+ > **Experimental.** This package is early-stage (pre-1.0, `Development Status :: 4 - Beta`) — its API may change or be removed in a future release without notice. Importing it emits a one-time `MafSandboxCodeactExperimentalWarning`; suppress it with `warnings.filterwarnings("ignore", category=maf_sandbox_codeact.MafSandboxCodeactExperimentalWarning)` once you've read the notice.
29
+
30
+ This package is not affiliated with, endorsed by, or a product of Microsoft — it is a third-party reference implementation of [microsoft/agent-framework#7568](https://github.com/microsoft/agent-framework/issues/7568) for [Microsoft Agent Framework](https://aka.ms/AgentFramework).
31
+
32
+ CodeAct as a Microsoft Agent Framework tool: the agent gets one tool, `execute_code`; the model writes a short Python program; the program runs inside a sandbox and the tool returns what it printed. Computing an answer beats reasoning about what the computation would produce — and the code that does it runs somewhere the host is not.
33
+
34
+ ```
35
+ app -> maf_sandbox -> a backend (maf-sandbox-acas, maf-sandbox-wslc, ...) -> this workload
36
+ ```
37
+
38
+ This package is a sandbox **kind** in the sense of [`maf-sandbox`](https://github.com/sokolaidev/maf-extensions/tree/main/packages/maf-sandbox)'s protocol. It contains no Azure import, no backend import and no sandbox lifecycle code; it asks a `SandboxRouter` for a sandbox and gets back `write_file` and `exec`, so the same tool runs unchanged against ACA Sandboxes, a WSL container or an in-process fake. Tests enforce both boundaries.
39
+
40
+ ## Quickstart
41
+
42
+ ```bash
43
+ pip install maf-sandbox-codeact
44
+ ```
45
+
46
+ ```python
47
+ from maf_sandbox_codeact import make_codeact_tools
48
+
49
+ tools = make_codeact_tools(router, "data-analyst", context,
50
+ image="mcr.microsoft.com/devcontainers/python:3.13-bookworm")
51
+ ```
52
+
53
+ Pass `router=None` — or a router with no backend — and you get `[]` back: an unconfigured host attaches no tool rather than one that fails when called. A backend that cannot `exec`, or cannot take files in, is refused right there with `SandboxCapabilityNotSupported`, before the model is shown a capability it does not have.
54
+
55
+ `router` and `context` are the host's, and this snippet shows neither being built. [`samples/03_acas_codeact`](https://github.com/sokolaidev/maf-extensions/tree/main/samples/03_acas_codeact) and [`samples/04_wslc_codeact`](https://github.com/sokolaidev/maf-extensions/tree/main/samples/04_wslc_codeact) are the whole wiring as runnable programs — the same agent on a microVM-isolated Azure backend and on a container on your own machine.
56
+
57
+ ## What the model gets
58
+
59
+ One tool, `execute_code(code)`. The program is written to `/work/program.py` and run as the argv `["python3", "/work/program.py"]`, and the result is its stdout, its stderr when it wrote any, and its exit code when that was not zero. Nothing else comes back: there is no REPL echo, and no file is read out of the sandbox, so a program that computes without printing returns a sentence saying so.
60
+
61
+ ## Threat model
62
+
63
+ **The source is never a command line.** Model-written code reaches the interpreter as file *content* and the command is a fixed two-element argv — a sequence, not a shell string — so there is no command line for the source to be part of, nothing to quote, and nothing to escape. That is the security-relevant decision in this package and it is pinned by a test.
64
+
65
+ **Egress is closed.** `SandboxSpec.egress_allow` is empty, stated as a property of the workload rather than of configuration: the program computes, it does not fetch. A backend that cannot confine egress at all is refused at attach.
66
+
67
+ **Nothing is dispatchable from inside.** There is no host-tool registry in this version, and that emptiness is the security story rather than a missing feature: with no network and no host functions reachable, nothing external can enter the sandbox and nothing leaves it but what the program printed. Adding that surface is what changes the calculus, so it is deliberately not here yet — see below.
68
+
69
+ **The tool declares no `source_integrity`.** The library's default is `"trusted"`, which is right for a workload whose result is a compiler's own diagnostics and wrong for this one: what comes back is whatever a model-written `print(...)` chose to emit. Undeclared, MAF's information-flow tracker applies its untrusted default and the result taints the conversation — the fail-safe direction, and the honest one.
70
+
71
+ **Isolation is the host's call.** This kind does not raise `SandboxSpec.min_isolation`, so the router's floor governs — `MICROVM` unless the host opted down. A kind that ran code influenced by untrusted external content would pin the floor itself; this one has no such input.
72
+
73
+ ## What this version is not
74
+
75
+ `FILES_OUT` (reading artefacts back out of the sandbox), a workspace-files parameter, host-tool dispatch, and the `RUN_CODE` road served by an embedded-interpreter backend are all absent on purpose. The design that governs them — capabilities declared by backends and required by specs, and what `HOST_TOOLS` would have to carry before it ships — is [`docs/design/two-axis-sandbox-policy.md`](https://github.com/sokolaidev/maf-extensions/blob/main/docs/design/two-axis-sandbox-policy.md).
76
+
77
+ ---
78
+
79
+ Maintained by [SOKOLAI BV](https://www.sokol.ai).
@@ -0,0 +1,54 @@
1
+ # maf-sandbox-codeact
2
+
3
+ > **Experimental.** This package is early-stage (pre-1.0, `Development Status :: 4 - Beta`) — its API may change or be removed in a future release without notice. Importing it emits a one-time `MafSandboxCodeactExperimentalWarning`; suppress it with `warnings.filterwarnings("ignore", category=maf_sandbox_codeact.MafSandboxCodeactExperimentalWarning)` once you've read the notice.
4
+
5
+ This package is not affiliated with, endorsed by, or a product of Microsoft — it is a third-party reference implementation of [microsoft/agent-framework#7568](https://github.com/microsoft/agent-framework/issues/7568) for [Microsoft Agent Framework](https://aka.ms/AgentFramework).
6
+
7
+ CodeAct as a Microsoft Agent Framework tool: the agent gets one tool, `execute_code`; the model writes a short Python program; the program runs inside a sandbox and the tool returns what it printed. Computing an answer beats reasoning about what the computation would produce — and the code that does it runs somewhere the host is not.
8
+
9
+ ```
10
+ app -> maf_sandbox -> a backend (maf-sandbox-acas, maf-sandbox-wslc, ...) -> this workload
11
+ ```
12
+
13
+ This package is a sandbox **kind** in the sense of [`maf-sandbox`](https://github.com/sokolaidev/maf-extensions/tree/main/packages/maf-sandbox)'s protocol. It contains no Azure import, no backend import and no sandbox lifecycle code; it asks a `SandboxRouter` for a sandbox and gets back `write_file` and `exec`, so the same tool runs unchanged against ACA Sandboxes, a WSL container or an in-process fake. Tests enforce both boundaries.
14
+
15
+ ## Quickstart
16
+
17
+ ```bash
18
+ pip install maf-sandbox-codeact
19
+ ```
20
+
21
+ ```python
22
+ from maf_sandbox_codeact import make_codeact_tools
23
+
24
+ tools = make_codeact_tools(router, "data-analyst", context,
25
+ image="mcr.microsoft.com/devcontainers/python:3.13-bookworm")
26
+ ```
27
+
28
+ Pass `router=None` — or a router with no backend — and you get `[]` back: an unconfigured host attaches no tool rather than one that fails when called. A backend that cannot `exec`, or cannot take files in, is refused right there with `SandboxCapabilityNotSupported`, before the model is shown a capability it does not have.
29
+
30
+ `router` and `context` are the host's, and this snippet shows neither being built. [`samples/03_acas_codeact`](https://github.com/sokolaidev/maf-extensions/tree/main/samples/03_acas_codeact) and [`samples/04_wslc_codeact`](https://github.com/sokolaidev/maf-extensions/tree/main/samples/04_wslc_codeact) are the whole wiring as runnable programs — the same agent on a microVM-isolated Azure backend and on a container on your own machine.
31
+
32
+ ## What the model gets
33
+
34
+ One tool, `execute_code(code)`. The program is written to `/work/program.py` and run as the argv `["python3", "/work/program.py"]`, and the result is its stdout, its stderr when it wrote any, and its exit code when that was not zero. Nothing else comes back: there is no REPL echo, and no file is read out of the sandbox, so a program that computes without printing returns a sentence saying so.
35
+
36
+ ## Threat model
37
+
38
+ **The source is never a command line.** Model-written code reaches the interpreter as file *content* and the command is a fixed two-element argv — a sequence, not a shell string — so there is no command line for the source to be part of, nothing to quote, and nothing to escape. That is the security-relevant decision in this package and it is pinned by a test.
39
+
40
+ **Egress is closed.** `SandboxSpec.egress_allow` is empty, stated as a property of the workload rather than of configuration: the program computes, it does not fetch. A backend that cannot confine egress at all is refused at attach.
41
+
42
+ **Nothing is dispatchable from inside.** There is no host-tool registry in this version, and that emptiness is the security story rather than a missing feature: with no network and no host functions reachable, nothing external can enter the sandbox and nothing leaves it but what the program printed. Adding that surface is what changes the calculus, so it is deliberately not here yet — see below.
43
+
44
+ **The tool declares no `source_integrity`.** The library's default is `"trusted"`, which is right for a workload whose result is a compiler's own diagnostics and wrong for this one: what comes back is whatever a model-written `print(...)` chose to emit. Undeclared, MAF's information-flow tracker applies its untrusted default and the result taints the conversation — the fail-safe direction, and the honest one.
45
+
46
+ **Isolation is the host's call.** This kind does not raise `SandboxSpec.min_isolation`, so the router's floor governs — `MICROVM` unless the host opted down. A kind that ran code influenced by untrusted external content would pin the floor itself; this one has no such input.
47
+
48
+ ## What this version is not
49
+
50
+ `FILES_OUT` (reading artefacts back out of the sandbox), a workspace-files parameter, host-tool dispatch, and the `RUN_CODE` road served by an embedded-interpreter backend are all absent on purpose. The design that governs them — capabilities declared by backends and required by specs, and what `HOST_TOOLS` would have to carry before it ships — is [`docs/design/two-axis-sandbox-policy.md`](https://github.com/sokolaidev/maf-extensions/blob/main/docs/design/two-axis-sandbox-policy.md).
51
+
52
+ ---
53
+
54
+ Maintained by [SOKOLAI BV](https://www.sokol.ai).
@@ -0,0 +1,77 @@
1
+ [project]
2
+ name = "maf-sandbox-codeact"
3
+ version = "0.1.0"
4
+ description = "CodeAct as a Microsoft Agent Framework tool — the model writes a short Python program and it runs inside a sandbox — written against maf-sandbox so it runs on any sandbox backend."
5
+ readme = "README.md"
6
+ requires-python = ">=3.12,<3.15"
7
+ license = "MIT"
8
+ license-files = ["LICENSE"]
9
+ keywords = [
10
+ "codeact",
11
+ "code-execution",
12
+ "sandbox",
13
+ "agent-framework",
14
+ "microsoft-agent-framework",
15
+ ]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Intended Audience :: Developers",
19
+ "Typing :: Typed",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Programming Language :: Python :: 3.14",
24
+ "Topic :: Software Development :: Interpreters",
25
+ ]
26
+ dependencies = [
27
+ "maf-sandbox>=0.5.0,<0.6",
28
+ "agent-framework-core>=1.13.0,<2",
29
+ ]
30
+
31
+ [[project.authors]]
32
+ name = "SOKOLAI BV"
33
+ email = "info@sokolai.com"
34
+
35
+ [project.urls]
36
+ Homepage = "https://www.sokol.ai"
37
+ Source = "https://github.com/sokolaidev/maf-extensions"
38
+ Issues = "https://github.com/sokolaidev/maf-extensions/issues"
39
+
40
+ [tool.uv.sources.maf-sandbox]
41
+ workspace = true
42
+
43
+ [tool.uv.build-backend]
44
+ module-name = "maf_sandbox_codeact"
45
+ module-root = "src"
46
+
47
+ [tool.ruff]
48
+ line-length = 100
49
+ target-version = "py312"
50
+
51
+ [tool.ruff.lint]
52
+ extend-select = [
53
+ "I",
54
+ "UP",
55
+ "D100",
56
+ "D101",
57
+ "D103",
58
+ "D104",
59
+ ]
60
+ ignore = ["UP037"]
61
+
62
+ [tool.ruff.lint.per-file-ignores]
63
+ "tests/**" = [
64
+ "D101",
65
+ "D103",
66
+ ]
67
+
68
+ [tool.pyright]
69
+ include = ["src"]
70
+ typeCheckingMode = "strict"
71
+
72
+ [tool.pytest.ini_options]
73
+ testpaths = ["tests"]
74
+
75
+ [build-system]
76
+ requires = ["uv_build>=0.11.24,<0.12.0"]
77
+ build-backend = "uv_build"
@@ -0,0 +1,84 @@
1
+ [project]
2
+ name = "maf-sandbox-codeact"
3
+ version = "0.1.0"
4
+ description = "CodeAct as a Microsoft Agent Framework tool — the model writes a short Python program and it runs inside a sandbox — written against maf-sandbox so it runs on any sandbox backend."
5
+ readme = "README.md"
6
+ requires-python = ">=3.12,<3.15"
7
+ authors = [{ name = "SOKOLAI BV", email = "info@sokolai.com" }]
8
+ license = "MIT"
9
+ license-files = ["LICENSE"]
10
+ keywords = [
11
+ "codeact",
12
+ "code-execution",
13
+ "sandbox",
14
+ "agent-framework",
15
+ "microsoft-agent-framework",
16
+ ]
17
+ classifiers = [
18
+ "Development Status :: 4 - Beta",
19
+ "Intended Audience :: Developers",
20
+ "Typing :: Typed",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Programming Language :: Python :: 3.14",
25
+ "Topic :: Software Development :: Interpreters",
26
+ ]
27
+ dependencies = [
28
+ # The sandbox protocol this workload is written against — its ONLY infrastructure
29
+ # dependency. No Azure import, no backend import; a test enforces both.
30
+ # Upper-bounded too: maf-sandbox has not yet reached a stable API, so every release
31
+ # before 1.0.0 may include breaking changes (see its CHANGELOG). Moving either end of
32
+ # this range has an order to it — see RELEASING.md. The floor is where `Capability` and
33
+ # `SandboxSpec.requires` actually arrive — the 0.5.0 release the isolation-floor branch
34
+ # creates — not 0.4.0, which predates them; a floor below 0.5.0 would let a published
35
+ # wheel resolve a maf-sandbox base missing the imports this kind's spec needs. This
36
+ # package's own clean-environment wheel check cannot go green until maf-sandbox 0.5.0 is
37
+ # published; that ordering is deliberate (RELEASING.md release order), not a bug to work
38
+ # around here.
39
+ "maf-sandbox>=0.5.0,<0.6",
40
+ # The `@tool` decorator. MAF is a genuine dependency of the tool itself, not of the host
41
+ # application.
42
+ "agent-framework-core>=1.13.0,<2",
43
+ ]
44
+
45
+ [project.urls]
46
+ Homepage = "https://www.sokol.ai"
47
+ Source = "https://github.com/sokolaidev/maf-extensions"
48
+ Issues = "https://github.com/sokolaidev/maf-extensions/issues"
49
+
50
+ # agent-framework-core resolves from PyPI at the released range declared above — no git
51
+ # source in this repository, deliberately: the tests run against the same artifact every
52
+ # consumer of the published wheel gets.
53
+ [tool.uv.sources]
54
+ maf-sandbox = { workspace = true }
55
+
56
+ [tool.uv.build-backend]
57
+ module-name = "maf_sandbox_codeact"
58
+ module-root = "src"
59
+
60
+ [tool.ruff]
61
+ line-length = 100
62
+ target-version = "py312"
63
+
64
+ [tool.ruff.lint]
65
+ extend-select = ["I", "UP", "D100", "D101", "D103", "D104"]
66
+ ignore = ["UP037"]
67
+
68
+ [tool.ruff.lint.per-file-ignores]
69
+ # ruff resolves [tool.ruff] per file by the NEAREST ancestor pyproject.toml that has one, so
70
+ # this package carries its own copy rather than inheriting one from the workspace root.
71
+ "tests/**" = ["D101", "D103"]
72
+
73
+ # Self-contained type checking, scoped to this package — strict. `tests/` stays out (fakes,
74
+ # not signal for a strict checker).
75
+ [tool.pyright]
76
+ include = ["src"]
77
+ typeCheckingMode = "strict"
78
+
79
+ [tool.pytest.ini_options]
80
+ testpaths = ["tests"]
81
+
82
+ [build-system]
83
+ requires = ["uv_build>=0.11.24,<0.12.0"]
84
+ build-backend = "uv_build"
@@ -0,0 +1,50 @@
1
+ """The CodeAct sandbox: ``execute_code`` as a MAF tool.
2
+
3
+ A sibling of the Bicep kind rather than a variant of it: both are written against
4
+ :mod:`maf_sandbox`'s protocol alone, and neither knows which backend answers. Where that one
5
+ runs a fixed compiler over files an agent authored, this one runs a program the model just
6
+ wrote, and returns what it printed.
7
+
8
+ Nothing in this subpackage imports Azure, a backend, or a host application: it asks a
9
+ :class:`~maf_sandbox.SandboxRouter` for a sandbox and gets back ``write_file`` and ``exec``.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from ._tool import (
15
+ CODEACT_KIND,
16
+ EXECUTE_CODE_TOOL_NAME,
17
+ codeact_sandbox_spec,
18
+ make_codeact_tools,
19
+ )
20
+
21
+ __all__ = [
22
+ "CODEACT_KIND",
23
+ "EXECUTE_CODE_TOOL_NAME",
24
+ "MafSandboxCodeactExperimentalWarning",
25
+ "codeact_sandbox_spec",
26
+ "make_codeact_tools",
27
+ ]
28
+
29
+ # Experimental package (Beta): importing it emits a UserWarning rather than a FutureWarning,
30
+ # so a host running under `python -W error` can still import it.
31
+ import warnings as _warnings
32
+
33
+
34
+ class MafSandboxCodeactExperimentalWarning(UserWarning):
35
+ """Warning category for maf-sandbox-codeact's experimental-package notice."""
36
+
37
+
38
+ def _warn_experimental() -> None:
39
+ message = (
40
+ "maf_sandbox_codeact is experimental and may change or be removed in future versions "
41
+ "without notice."
42
+ )
43
+ try:
44
+ _warnings.warn(message, category=MafSandboxCodeactExperimentalWarning, stacklevel=2)
45
+ except MafSandboxCodeactExperimentalWarning:
46
+ # Deliberate: under `-W error` an informational notice must not fail the import.
47
+ pass
48
+
49
+
50
+ _warn_experimental()
@@ -0,0 +1,211 @@
1
+ """``execute_code``: the CodeAct sandbox workload.
2
+
3
+ The agent gets one tool, the model writes a short Python program, and the program runs inside
4
+ a sandbox — computing an answer instead of reasoning about what the computation would produce.
5
+
6
+ **This module contains no Azure import, no backend import and no sandbox lifecycle code.** It
7
+ talks to a :class:`~maf_sandbox.SandboxRouter` and gets back ``write_file`` and ``exec``, so
8
+ the same tool runs unchanged against ACA Sandboxes, a WSL container or an in-process fake.
9
+
10
+ This version is the ``EXEC`` road and it is stdout-only. Nothing is read back out of the
11
+ sandbox, no workspace file is shared into it, and no host function is dispatchable from
12
+ inside: with the spec's egress closed as well, nothing external can enter and nothing leaves
13
+ but what the program printed. That empty dispatch surface is what makes running model-written
14
+ code defensible here, so it is a property of this version rather than a gap in it.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import logging
20
+ from typing import TYPE_CHECKING, Any
21
+
22
+ from maf_sandbox import (
23
+ Capability,
24
+ ExecResult,
25
+ SandboxRouter,
26
+ SandboxSpec,
27
+ WorkspaceContext,
28
+ error_detail,
29
+ )
30
+ from maf_sandbox.maf import SandboxToolSession, sandbox_tool_declarations, sandboxed_tool
31
+
32
+ if TYPE_CHECKING:
33
+ from collections.abc import Awaitable, Callable
34
+
35
+ logger = logging.getLogger(__name__)
36
+
37
+ __all__ = [
38
+ "CODEACT_KIND",
39
+ "EXECUTE_CODE_TOOL_NAME",
40
+ "codeact_sandbox_spec",
41
+ "make_codeact_tools",
42
+ ]
43
+
44
+ EXECUTE_CODE_TOOL_NAME = "execute_code"
45
+
46
+ #: The sandbox kind this workload asks for.
47
+ CODEACT_KIND = "codeact"
48
+
49
+ #: Where the program is written and run — a dedicated root rather than the image's own tree.
50
+ _WORK_DIR = "/work"
51
+
52
+ #: One fixed name, rewritten on every call, since the sandbox is reused across calls.
53
+ _PROGRAM_FILENAME = "program.py"
54
+
55
+ _INTERPRETER = "python3"
56
+
57
+ #: Writing an expression and expecting a REPL to echo it is the commonest way a first CodeAct
58
+ #: call comes back empty, so the answer says what to do instead.
59
+ _NO_OUTPUT = (
60
+ "The program ran and printed nothing. Only what you print comes back — end the program "
61
+ "with print(...) of what you need to see."
62
+ )
63
+
64
+
65
+ def codeact_sandbox_spec(image: str | None = None, image_id: str | None = None) -> SandboxSpec:
66
+ """The sandbox a CodeAct program needs, in backend-neutral terms.
67
+
68
+ ``egress_allow=()`` and no ``min_isolation`` are both deliberate: the program computes
69
+ rather than fetches, and this kind runs only what the model wrote, so the host's floor
70
+ governs.
71
+ """
72
+ return SandboxSpec(
73
+ kind=CODEACT_KIND,
74
+ image=image,
75
+ image_id=image_id,
76
+ egress_allow=(),
77
+ work_dir=_WORK_DIR,
78
+ requires=frozenset({Capability.EXEC, Capability.FILES_IN}),
79
+ )
80
+
81
+
82
+ def make_codeact_tools(
83
+ router: SandboxRouter | None,
84
+ agent_dir: str,
85
+ context: WorkspaceContext,
86
+ *,
87
+ image: str | None = None,
88
+ image_id: str | None = None,
89
+ exec_timeout_seconds: int = 120,
90
+ ) -> list[Any]:
91
+ """Return the ``[execute_code]`` tool list, or ``[]`` when no sandbox is available.
92
+
93
+ Args:
94
+ router: The sandbox router, or ``None`` when sandboxing is not configured.
95
+ agent_dir: The agent's directory name. Baked into the sandbox key at factory time
96
+ rather than taken from the model at call time.
97
+ context: How to read the caller's scope and thread.
98
+ image: OCI reference of a sandbox image with a Python interpreter on its path.
99
+ image_id: A backend-native disk-image id, skipping resolution.
100
+ exec_timeout_seconds: Per-program bound. A sandbox that stops answering must not hold
101
+ the caller's turn open.
102
+ """
103
+ spec = codeact_sandbox_spec(image, image_id)
104
+ return sandboxed_tool(
105
+ lambda session: _execute_code_tool(session, exec_timeout_seconds),
106
+ router=router,
107
+ context=context,
108
+ agent_dir=agent_dir,
109
+ spec=spec,
110
+ name=EXECUTE_CODE_TOOL_NAME,
111
+ approval_mode="never_require",
112
+ # No `source_integrity`: undeclared defaults to untrusted, the fail-safe direction.
113
+ declarations=sandbox_tool_declarations(spec, source_integrity=None),
114
+ logger=logger,
115
+ )
116
+
117
+
118
+ def _execute_code_tool(
119
+ session: SandboxToolSession,
120
+ timeout: int,
121
+ ) -> "Callable[..., Awaitable[str]]":
122
+ """Build the ``execute_code`` body for one attached tool.
123
+
124
+ Defined at module level rather than nested inside :func:`make_codeact_tools`, and that is
125
+ not a style choice: the function below's **docstring is the tool's description** — MAF
126
+ passes ``__doc__`` through verbatim, indentation and all — so nesting this one level
127
+ deeper would re-indent every line of what the model reads at call time.
128
+ """
129
+
130
+ async def execute_code(code: str) -> str:
131
+ """Run a short Python program inside a sandbox and return what it printed.
132
+
133
+ Use this to compute rather than to reason: parse, transform, count, check, simulate —
134
+ anything where running the code beats predicting what it would do. The program runs
135
+ as ``python3 program.py`` in a sandbox with **no network access**, so it can compute
136
+ but cannot fetch.
137
+
138
+ **Only what you print comes back.** There is no REPL echo and the value of the last
139
+ expression is not returned, so end the program with ``print(...)`` of everything you
140
+ need to see. Nothing is read back out of the sandbox either — a file your program
141
+ writes may still be there on a later call in the same conversation, but the printed
142
+ output is the whole result.
143
+
144
+ Write a complete, self-contained program every time. Each call replaces the previous
145
+ one, and nothing carries over except what the program itself wrote to disk.
146
+
147
+ Args:
148
+ code: The Python source to run. The standard library, plus whatever the sandbox
149
+ image ships.
150
+
151
+ Returns:
152
+ The program's stdout, its stderr when it wrote any, and its exit code when that
153
+ was not zero. If the sandbox is unavailable the tool returns an error message
154
+ instead, so the run degrades rather than blocking.
155
+ """
156
+ # Scope and thread come from the host's request context, never from model input.
157
+ key = session.key()
158
+ if isinstance(key, str):
159
+ return key
160
+
161
+ sandbox = await session.acquire(key)
162
+ if isinstance(sandbox, str):
163
+ return sandbox
164
+
165
+ program_path = f"{session.spec.work_dir}/{_PROGRAM_FILENAME}"
166
+ try:
167
+ await sandbox.write_file(program_path, code)
168
+ except Exception as exc: # noqa: BLE001
169
+ logger.warning(
170
+ "execute_code: could not write the program into the sandbox: %s",
171
+ error_detail(exc),
172
+ )
173
+ return "Error: could not write the program into the sandbox"
174
+
175
+ try:
176
+ # An argv sequence, never a command line: the model's source never reaches a shell.
177
+ result = await sandbox.exec(
178
+ [_INTERPRETER, program_path],
179
+ working_directory=session.spec.work_dir,
180
+ timeout=timeout,
181
+ )
182
+ except TimeoutError:
183
+ logger.warning("execute_code: the program timed out after %ss", timeout)
184
+ return f"Error: the program timed out after {timeout}s"
185
+ except Exception as exc: # noqa: BLE001
186
+ # Provider/transport detail can carry account ids — must not reach the transcript.
187
+ logger.warning("execute_code: exec failed: %s", error_detail(exc))
188
+ return "Error: could not run the program in the sandbox"
189
+
190
+ logger.info("execute_code: ran exit_code=%d", result.exit_code)
191
+ return _format_result(result)
192
+
193
+ return execute_code
194
+
195
+
196
+ def _format_result(result: ExecResult) -> str:
197
+ """Render one run for a model that has to fix its own program.
198
+
199
+ Empty sections are omitted rather than shown blank, and the trailing newline ``print``
200
+ leaves is dropped, so a one-line program's answer is one line.
201
+ """
202
+ stdout = (result.stdout or "").rstrip("\n")
203
+ stderr = (result.stderr or "").rstrip("\n")
204
+ sections: list[str] = []
205
+ if stdout:
206
+ sections.append(f"stdout:\n{stdout}")
207
+ if stderr:
208
+ sections.append(f"stderr:\n{stderr}")
209
+ if result.exit_code:
210
+ sections.append(f"exit code: {result.exit_code}")
211
+ return "\n\n".join(sections) if sections else _NO_OUTPUT