Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions python/PACKAGE_STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Status is grouped into these buckets:
| `agent-framework-github-copilot` | `python/packages/github_copilot` | `beta` |
| `agent-framework-hyperlight` | `python/packages/hyperlight` | `beta` |
| `agent-framework-lab` | `python/packages/lab` | `beta` |
| `agent-framework-local-codeact` | `python/packages/local_codeact` | `alpha` |
| `agent-framework-mem0` | `python/packages/mem0` | `beta` |
| `agent-framework-monty` | `python/packages/monty` | `alpha` |
| `agent-framework-ollama` | `python/packages/ollama` | `beta` |
Expand Down
70 changes: 70 additions & 0 deletions python/packages/local_codeact/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Local CodeAct Package (agent-framework-local-codeact)

Local subprocess-backed CodeAct integrations for the Microsoft Agent Framework.

> [!WARNING]
> This package runs LLM-generated Python in the local environment. It is **not**
> a Python security sandbox. Use it only inside an external sandbox such as a
> Foundry hosted-agent container, VM, or locked-down container runtime.

## Core Classes

- **`LocalCodeActProvider`** — `ContextProvider` that injects a run-scoped
`execute_code` tool plus dynamic CodeAct instructions.
- **`LocalExecuteCodeTool`** — `FunctionTool` that validates generated code
against AST allow-lists, then runs it in a local Python subprocess by default.
Same-interpreter execution is available only through
`execution_mode="unsafe_in_process"`.

## Public API

```python
from agent_framework_local_codeact import (
CodeValidationError,
ExecutionMode,
FileMount,
FileMountInput,
LocalCodeActProvider,
LocalExecuteCodeTool,
MountMode,
ProcessExecutionLimits,
)
```

## Architecture

- **`_types.py`** — public types for execution limits, execution mode, and file
mount metadata.
- **`_provider.py`** — provider wrapper around a managed execute-code tool.
- **`_execute_code_tool.py`** — tool management, approval propagation,
subprocess orchestration, state serialization, and output-file capture.
- **`_validator.py`** — AST-based allow-list code validation (blocks `eval`,
`exec`, dangerous imports/builtins, and risky os operations).
- **`_bridge.py`** — parent-side framed IPC and optional unsafe in-process
runner. Subprocess mode supports explicit `python_executable` and
`runner_script` configuration so non-Python hosts can launch the same runner
by file path.
- **`_runner.py`** — child-process entry point used by subprocess mode.
- **`_files.py`** — mount normalization and symlink-safe file capture helpers.
- **`_instructions.py`** — dynamic instructions and risk wording.

## Security posture

Do not describe this package as sandboxing Python code. AST validation, process
isolation, timeouts, output caps, environment allow-lists, and file capture
limits are defense-in-depth controls only. Host filesystem, network, credentials,
process table, and kernel resources must be isolated by the surrounding
environment.

## .NET portability notes

Keep the subprocess JSON-lines protocol stable where possible. A .NET port can
mirror the provider/tool/file/limit surface, but should omit
`unsafe_in_process` and require or strongly encourage an explicit
`python_executable`. If the .NET package bundles the Python runner instead of
requiring a Python wheel install, invoke it through the file-path
`runner_script` pattern rather than relying on `-m agent_framework_local_codeact._runner`.

The AST validator should be ported to .NET as well (likely using Roslyn for C#
code analysis if the .NET version supports generated C# execution, or a Python
AST parser if it still executes Python).
22 changes: 22 additions & 0 deletions python/packages/local_codeact/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
MIT License

Copyright (c) Microsoft Corporation.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE

188 changes: 188 additions & 0 deletions python/packages/local_codeact/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
# agent-framework-local-codeact

Local CodeAct integrations for Microsoft Agent Framework.

> [!WARNING]
> This package runs LLM-generated Python in the local environment. It is **not**
> a Python security sandbox and is not safe for untrusted prompts or code on a
> developer workstation or production host without an external sandbox.

`agent-framework-local-codeact` is intended for environments that already
provide process, filesystem, network, and credential isolation, especially
Foundry hosted agents. It provides the familiar CodeAct provider pattern used by
the Hyperlight and Monty packages while keeping the implementation local to the
agent container.

## Install

```bash
pip install agent-framework-local-codeact --pre
```

This is an alpha package and is not included in `agent-framework[all]`.

## Basic usage

```python
from agent_framework import Agent
from agent_framework_local_codeact import LocalCodeActProvider, ProcessExecutionLimits

agent = Agent(
client=...,
instructions="Use execute_code for Python control flow when it helps.",
context_providers=[
LocalCodeActProvider(
execution_limits=ProcessExecutionLimits(timeout_seconds=5),
# Optional: use a specific interpreter instead of the current one.
# python_executable="/usr/bin/python3",
)
],
)
```

For Foundry hosted agents, add the provider to the local agent before wrapping
it with `ResponsesHostServer`.

```python
from agent_framework_foundry_hosting import ResponsesHostServer

server = ResponsesHostServer(agent)
```

## What the package controls

- Validates generated code against AST allow-lists (allowed imports, builtins,
and operations) before execution.
- Runs generated code in a child Python process by default.
- Uses `sys.executable` by default, or an explicit `python_executable` when
configured.
- Does not inherit host environment variables unless explicitly provided.
- Does not invoke a shell.
- Applies code-size, timeout, stdout, stderr, and result-size limits.
- Allows only provider-owned host tools to be called from generated code.
- Propagates `always_require` approval from managed tools to `execute_code`.
- Captures new or modified files under configured writable mounts while
skipping symlinks.

These are defense-in-depth controls, not a containment boundary. The AST
validator blocks common dangerous operations (`eval`, `exec`, `import subprocess`,
etc.) but does not make Python execution safe on an unsandboxed host.

## What the package does not protect

- Malicious Python working within allowed imports and operations.
- Network access unless the surrounding environment blocks it.
- Prompt-injected exfiltration through allowed host tools.
- Resource exhaustion outside the configured limits.
- Log, stdout, stderr, or result poisoning.

Use Foundry hosted agents, containers, VMs, or equivalent infrastructure as the
actual security boundary.

## Host tools

Register host tools on the provider. Generated code calls them with `await`:

```python
async def add(a: int, b: int) -> int:
return a + b

provider = LocalCodeActProvider(tools=[add])
```

Inside `execute_code`:

```python
result = await add(a=2, b=3)
print(result)
```

`await call_tool("add", a=2, b=3)` is also available for tool names that are not
valid Python identifiers.

## Files

No project directory is exposed by default. If you configure `workspace_root` or
`file_mounts`, generated code receives the direct path to the configured host
directory inside the surrounding sandbox. Mount modes are used for instructions
and output capture; they are not an OS-level filesystem policy.

Only files under `read-write` mounts are captured after execution.

## Python interpreter and runner

Subprocess mode launches Python as:

```text
<python_executable> -I -m agent_framework_local_codeact._runner
```

`python_executable` defaults to the current Python interpreter. If you point it
at a different virtual environment or system Python, that environment must be
able to import `agent_framework_local_codeact._runner`.

For hosts that cannot rely on a Python package import, such as a future .NET
host bundling the runner itself, pass `runner_script` to execute the runner by
file path instead:

```python
LocalCodeActProvider(
python_executable="/usr/bin/python3",
runner_script="/app/local_codeact_runner.py",
)
```

The framed JSON-lines protocol between parent and runner is intended to be the
cross-language boundary for a .NET implementation. The .NET version should use
subprocess mode only; same-interpreter execution is Python-specific.

## Code validation

Generated code is validated against AST allow-lists before execution:

- **Allowed imports**: `asyncio`, `pathlib`, `json`, `math`, `datetime`, `time`,
`os` (limited to `os.environ`, `os.path`), and a few others.
- **Blocked imports**: `subprocess`, `sys`, `socket`, `urllib`, `requests`,
`threading`, `multiprocessing`, and others.
- **Blocked builtins**: `eval`, `exec`, `compile`, `__import__`, `globals`,
`locals`, `open`, and others.
- **Blocked os operations**: `os.system`, `os.exec*`, `os.popen`, `os.fork`,
file system modifications outside configured mounts, and others.

Validation errors are returned as `Content.from_error` with details about which
operations are not allowed. This is defense-in-depth only and does not make
Python execution safe on an unsandboxed host.

### Customizing allow-lists

Use custom allow/block lists to adapt validation to your use case:

```python
from agent_framework_local_codeact import LocalExecuteCodeTool

# Allow specific imports (replaces defaults)
tool = LocalExecuteCodeTool(
allowed_imports={"csv", "json", "pathlib"},
blocked_imports=set(), # Empty block-list
)

# Block specific imports (replaces defaults)
provider = LocalCodeActProvider(
blocked_imports={"json", "requests"},
)

# Block specific builtins (replaces defaults)
tool = LocalExecuteCodeTool(
blocked_builtins={"len", "sum"}, # Prevent common operations
)
```

Custom lists **replace** the defaults entirely (they do not augment them).

## Unsafe in-process mode

`execution_mode="unsafe_in_process"` runs generated code with `exec` in the
agent process. This mode is intended only for debugging package behavior because
timeouts cannot stop CPU-bound or blocking code in the same interpreter. It also
does not provide subprocess-only behavior such as an explicit environment map or
working-directory isolation.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Copyright (c) Microsoft. All rights reserved.

from __future__ import annotations

import importlib.metadata

from ._execute_code_tool import LocalExecuteCodeTool
from ._provider import LocalCodeActProvider
from ._types import ExecutionMode, FileMount, FileMountInput, MountMode, ProcessExecutionLimits
from ._validator import CodeValidationError

try:
__version__ = importlib.metadata.version(__name__)
except importlib.metadata.PackageNotFoundError:
__version__ = "0.0.0"

__all__ = [
"CodeValidationError",
"ExecutionMode",
"FileMount",
"FileMountInput",
"LocalCodeActProvider",
"LocalExecuteCodeTool",
"MountMode",
"ProcessExecutionLimits",
"__version__",
]
Loading
Loading