Skip to content

Commit b03cb32

Browse files
Python: Add Hyperlight CodeAct package and docs (microsoft#5185)
* initial work on code_mode * updated samples * updates to codeact * udpated codeact * Draft CodeAct ADR and sample updates Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * initial implementation and adr and feature * Python: Limit Hyperlight wasm backend to Python <3.14 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Python: Fix CI for Hyperlight CodeAct PR Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Python: Run Hyperlight integration when available Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Python: Address Hyperlight review feedback Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Python: Simplify Hyperlight file mount inputs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Python: Accept Path host paths in Hyperlight mounts Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Python: Fix Hyperlight mount typing for CI Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * temp run integration test * Python: Strengthen Hyperlight real sandbox tests Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * added additional tests * Python: Simplify Hyperlight CodeAct API Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * set tests as non-integration * Retry Hyperlight allowed-domain registration Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Gate Hyperlight integration tests by runtime support Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix Hyperlight skip test on Python 3.14 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Delay Hyperlight runtime probe until test execution Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Relax Hyperlight Windows integration stdout assertion Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Scan Hyperlight output directory for artifacts Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Retry Hyperlight output artifact collection Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Harden Hyperlight integration output assertions Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Retry Hyperlight read-back check in integration test Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Simplify Hyperlight integration write assertion Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Avoid pathlib in Hyperlight integration sandbox Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Use socket network check in Hyperlight sandbox Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Replace blocked Azure AI Search blog link Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Clarify Hyperlight guest stdlib limits Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Use _socket in Hyperlight integration sandbox Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Handle Hyperlight mounted file paths Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Broaden Hyperlight sandbox path fallbacks Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Search Hyperlight guest mounts recursively Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Split Hyperlight mount coverage Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Split Hyperlight live network tests Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix Hyperlight file-write test on Windows Enable the sandbox filesystem by providing a workspace_root so /output is mounted. Remove os.path.exists assertion (unsupported in WASM guest) and fix Content data assertion to use .uri. Skip the network integration test on Windows where the WASM sandbox lacks the encodings.idna codec. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address PR review: ADR intro, manual wiring sample, doc clarifications - Add CodeAct introduction section to ADR for unfamiliar readers - Clarify 'less runtime efficient' con with specific overhead description - Add note in Python impl doc clarifying ADR vs impl doc split - Explain why before_run hooks must be per-run (CRUD, concurrency, approval) - Rename code_interpreter variable to codeact in E2E sample - Add manual static wiring sample (codeact_manual_wiring.py) - Add 'when to use which pattern' guidance to samples README Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address PR microsoft#5185 review comments and add .NET CodeAct design doc - Fix async callback: _make_sandbox_callback returns sync wrapper with thread + asyncio.run() bridge (was broken with real Wasm FFI) - Fix stale output: clear output_dir before each sandbox.run() call - Fix blocking event loop: _run_code now async with asyncio.to_thread() - Revert _agents.py options['tools'] injection (unnecessary; provider uses context.extend_tools()) - Revert SessionContext.options docstring back to read-only - Add real-sandbox test fixtures (shared/restored/fresh) - Add 8 new real-sandbox tests for callback round-trip, stale output, event loop non-blocking, basic execution, stdout/stderr, errors, snapshot/restore, and tool registration - Add comprehensive .NET HyperlightCodeActProvider design document Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Update hyperlight README with code snippets and remove Public API section Replace bare export list with Quick Start code examples covering the context provider, standalone tool, manual static wiring, and file mounts / network access patterns. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent dbf935b commit b03cb32

25 files changed

Lines changed: 4176 additions & 9 deletions

File tree

.github/workflows/python-integration-tests.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ jobs:
131131
--timeout=120 --session-timeout=900 --timeout_method thread
132132
--retries 2 --retry-delay 5
133133
134-
# Misc integration tests (Anthropic, Ollama, MCP)
134+
# Misc integration tests (Anthropic, Hyperlight, Ollama, MCP)
135135
python-tests-misc-integration:
136136
name: Python Integration Tests - Misc
137137
runs-on: ubuntu-latest
@@ -162,10 +162,11 @@ jobs:
162162
fallback_url: ${{ env.LOCAL_MCP_URL }}
163163
- name: Prefer local MCP URL when available
164164
run: echo "LOCAL_MCP_URL=${{ steps.local-mcp.outputs.effective_url }}" >> "$GITHUB_ENV"
165-
- name: Test with pytest (Anthropic, Ollama, MCP integration)
165+
- name: Test with pytest (Anthropic, Hyperlight, Ollama, MCP integration)
166166
run: >
167167
uv run pytest --import-mode=importlib
168168
packages/anthropic/tests
169+
packages/hyperlight/tests
169170
packages/ollama/tests
170171
packages/core/tests/core/test_mcp.py
171172
-m integration

.github/workflows/python-merge-tests.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ jobs:
6565
- 'python/samples/**/providers/azure/**'
6666
misc:
6767
- 'python/packages/anthropic/**'
68+
- 'python/packages/hyperlight/**'
6869
- 'python/packages/ollama/**'
6970
- 'python/packages/core/agent_framework/_mcp.py'
7071
- 'python/packages/core/tests/core/test_mcp.py'
@@ -278,10 +279,11 @@ jobs:
278279
fallback_url: ${{ env.LOCAL_MCP_URL }}
279280
- name: Prefer local MCP URL when available
280281
run: echo "LOCAL_MCP_URL=${{ steps.local-mcp.outputs.effective_url }}" >> "$GITHUB_ENV"
281-
- name: Test with pytest (Anthropic, Ollama, MCP integration)
282+
- name: Test with pytest (Anthropic, Hyperlight, Ollama, MCP integration)
282283
run: >
283284
uv run pytest --import-mode=importlib
284285
packages/anthropic/tests
286+
packages/hyperlight/tests
285287
packages/ollama/tests
286288
packages/core/tests/core/test_mcp.py
287289
-m integration

docs/decisions/0024-codeact-integration.md

Lines changed: 233 additions & 0 deletions
Large diffs are not rendered by default.

docs/features/code_act/dotnet-implementation.md

Lines changed: 625 additions & 0 deletions
Large diffs are not rendered by default.

docs/features/code_act/python-implementation.md

Lines changed: 385 additions & 0 deletions
Large diffs are not rendered by default.

python/.cspell.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"azuredocs",
3131
"azurefunctions",
3232
"boto",
33+
"codeact",
3334
"contentvector",
3435
"contoso",
3536
"datamodel",
@@ -45,6 +46,7 @@
4546
"hnsw",
4647
"httpx",
4748
"huggingface",
49+
"hyperlight",
4850
"Instrumentor",
4951
"logit",
5052
"logprobs",

python/PACKAGE_STATUS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Status is grouped into these buckets:
3333
| `agent-framework-foundry-local` | `python/packages/foundry_local` | `beta` |
3434
| `agent-framework-gemini` | `python/packages/gemini` | `alpha` |
3535
| `agent-framework-github-copilot` | `python/packages/github_copilot` | `beta` |
36+
| `agent-framework-hyperlight` | `python/packages/hyperlight` | `alpha` |
3637
| `agent-framework-lab` | `python/packages/lab` | `beta` |
3738
| `agent-framework-mem0` | `python/packages/mem0` | `beta` |
3839
| `agent-framework-ollama` | `python/packages/ollama` | `beta` |

python/packages/core/agent_framework/_tools.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
DEFAULT_MAX_ITERATIONS: Final[int] = 40
9090
DEFAULT_MAX_CONSECUTIVE_ERRORS_PER_REQUEST: Final[int] = 3
9191
SHELL_TOOL_KIND_VALUE: Final[str] = "shell"
92+
ApprovalMode: TypeAlias = Literal["always_require", "never_require"]
9293
ChatClientT = TypeVar("ChatClientT", bound="SupportsChatGetResponse[Any]")
9394
ResponseModelBoundT = TypeVar("ResponseModelBoundT", bound=BaseModel)
9495

@@ -270,7 +271,7 @@ def __init__(
270271
*,
271272
name: str,
272273
description: str = "",
273-
approval_mode: Literal["always_require", "never_require"] | None = None,
274+
approval_mode: ApprovalMode | None = None,
274275
kind: str | None = None,
275276
max_invocations: int | None = None,
276277
max_invocation_exceptions: int | None = None,
@@ -1033,7 +1034,7 @@ def tool(
10331034
name: str | None = None,
10341035
description: str | None = None,
10351036
schema: type[BaseModel] | Mapping[str, Any] | None = None,
1036-
approval_mode: Literal["always_require", "never_require"] | None = None,
1037+
approval_mode: ApprovalMode | None = None,
10371038
kind: str | None = None,
10381039
max_invocations: int | None = None,
10391040
max_invocation_exceptions: int | None = None,
@@ -1049,7 +1050,7 @@ def tool(
10491050
name: str | None = None,
10501051
description: str | None = None,
10511052
schema: type[BaseModel] | Mapping[str, Any] | None = None,
1052-
approval_mode: Literal["always_require", "never_require"] | None = None,
1053+
approval_mode: ApprovalMode | None = None,
10531054
kind: str | None = None,
10541055
max_invocations: int | None = None,
10551056
max_invocation_exceptions: int | None = None,
@@ -1064,7 +1065,7 @@ def tool(
10641065
name: str | None = None,
10651066
description: str | None = None,
10661067
schema: type[BaseModel] | Mapping[str, Any] | None = None,
1067-
approval_mode: Literal["always_require", "never_require"] | None = None,
1068+
approval_mode: ApprovalMode | None = None,
10681069
kind: str | None = None,
10691070
max_invocations: int | None = None,
10701071
max_invocation_exceptions: int | None = None,

python/packages/hyperlight/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) Microsoft Corporation.
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
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# agent-framework-hyperlight
2+
3+
Alpha Hyperlight-backed CodeAct integrations for Microsoft Agent Framework.
4+
5+
## Installation
6+
7+
```bash
8+
pip install agent-framework-hyperlight --pre
9+
```
10+
11+
This package depends on `hyperlight-sandbox`, the packaged Python guest, and the
12+
Wasm backend package on supported platforms. If the backend is not published for
13+
your current platform yet, `execute_code` will fail at runtime when it tries to
14+
create the sandbox.
15+
16+
## Quick start
17+
18+
### Context provider (recommended)
19+
20+
Use `HyperlightCodeActProvider` to automatically inject the `execute_code` tool
21+
and CodeAct instructions into every agent run. Tools registered on the provider
22+
are available inside the sandbox via `call_tool(...)` but are **not** exposed as
23+
direct agent tools.
24+
25+
```python
26+
from agent_framework import Agent, tool
27+
from agent_framework_hyperlight import HyperlightCodeActProvider
28+
29+
@tool
30+
def compute(operation: str, a: float, b: float) -> float:
31+
"""Perform a math operation."""
32+
ops = {"add": a + b, "subtract": a - b, "multiply": a * b, "divide": a / b}
33+
return ops[operation]
34+
35+
codeact = HyperlightCodeActProvider(
36+
tools=[compute],
37+
approval_mode="never_require",
38+
)
39+
40+
agent = Agent(
41+
client=client,
42+
name="CodeActAgent",
43+
instructions="You are a helpful assistant.",
44+
context_providers=[codeact],
45+
)
46+
47+
result = await agent.run("Multiply 6 by 7 using execute_code.")
48+
```
49+
50+
### Standalone tool
51+
52+
Use `HyperlightExecuteCodeTool` directly when you want full control over how the
53+
tool is added to the agent. This is useful when mixing sandbox tools with
54+
direct-only tools on the same agent.
55+
56+
```python
57+
from agent_framework import Agent, tool
58+
from agent_framework_hyperlight import HyperlightExecuteCodeTool
59+
60+
@tool
61+
def send_email(to: str, subject: str, body: str) -> str:
62+
"""Send an email (direct-only, not available inside the sandbox)."""
63+
return f"Email sent to {to}"
64+
65+
execute_code = HyperlightExecuteCodeTool(
66+
tools=[compute],
67+
approval_mode="never_require",
68+
)
69+
70+
agent = Agent(
71+
client=client,
72+
name="MixedToolsAgent",
73+
instructions="You are a helpful assistant.",
74+
tools=[send_email, execute_code],
75+
)
76+
```
77+
78+
### Manual static wiring
79+
80+
For fixed configurations where provider lifecycle overhead is unnecessary, build
81+
the CodeAct instructions once and pass them to the agent at construction time:
82+
83+
```python
84+
execute_code = HyperlightExecuteCodeTool(
85+
tools=[compute],
86+
approval_mode="never_require",
87+
)
88+
89+
codeact_instructions = execute_code.build_instructions(tools_visible_to_model=False)
90+
91+
agent = Agent(
92+
client=client,
93+
name="StaticWiringAgent",
94+
instructions=f"You are a helpful assistant.\n\n{codeact_instructions}",
95+
tools=[execute_code],
96+
)
97+
```
98+
99+
### File mounts and network access
100+
101+
Mount host directories into the sandbox and allow outbound HTTP to specific
102+
domains:
103+
104+
```python
105+
from agent_framework_hyperlight import HyperlightCodeActProvider, FileMount
106+
107+
codeact = HyperlightCodeActProvider(
108+
tools=[compute],
109+
file_mounts=[
110+
"/host/data", # shorthand — same path in sandbox
111+
("/host/models", "/sandbox/models"), # explicit host → sandbox mapping
112+
FileMount("/host/config", "/sandbox/config"), # named tuple
113+
],
114+
allowed_domains=[
115+
"api.github.com", # all methods
116+
("internal.api.example.com", "GET"), # GET only
117+
],
118+
)
119+
```
120+
121+
## Notes
122+
123+
- This package is intentionally separate from `agent-framework-core` so CodeAct
124+
usage and installation remain optional.
125+
- Alpha-package samples live under `packages/hyperlight/samples/`.
126+
- `file_mounts` accepts a single string shorthand, an explicit `(host_path,
127+
mount_path)` pair, or a `FileMount` named tuple. The host-side path in the
128+
explicit forms may be a `str` or `Path`. Use the explicit two-value form when
129+
the host path differs from the sandbox path.
130+
- `allowed_domains` accepts a single string target such as `"github.com"` to
131+
allow all backend-supported methods, an explicit `(target, method_or_methods)`
132+
tuple such as `("github.com", "GET")`, or an `AllowedDomain` named tuple.

0 commit comments

Comments
 (0)