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.
- maf_sandbox_codeact-0.1.0/LICENSE +21 -0
- maf_sandbox_codeact-0.1.0/PKG-INFO +79 -0
- maf_sandbox_codeact-0.1.0/README.md +54 -0
- maf_sandbox_codeact-0.1.0/pyproject.toml +77 -0
- maf_sandbox_codeact-0.1.0/pyproject.toml.orig +84 -0
- maf_sandbox_codeact-0.1.0/src/maf_sandbox_codeact/__init__.py +50 -0
- maf_sandbox_codeact-0.1.0/src/maf_sandbox_codeact/_tool.py +211 -0
- maf_sandbox_codeact-0.1.0/src/maf_sandbox_codeact/py.typed +0 -0
|
@@ -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
|
|
File without changes
|