diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props
index 56de97dbcb..5ed970004f 100644
--- a/dotnet/Directory.Packages.props
+++ b/dotnet/Directory.Packages.props
@@ -109,6 +109,8 @@
+
+
diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx
index 4d7b2a2fc8..897d438c06 100644
--- a/dotnet/agent-framework-dotnet.slnx
+++ b/dotnet/agent-framework-dotnet.slnx
@@ -175,6 +175,12 @@
+
+
+
+
+
+
@@ -560,6 +566,7 @@
+
@@ -581,6 +588,7 @@
+
@@ -606,6 +614,7 @@
+
diff --git a/dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step01_Interpreter/AgentWithCodeAct_Step01_Interpreter.csproj b/dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step01_Interpreter/AgentWithCodeAct_Step01_Interpreter.csproj
new file mode 100644
index 0000000000..4e37243cce
--- /dev/null
+++ b/dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step01_Interpreter/AgentWithCodeAct_Step01_Interpreter.csproj
@@ -0,0 +1,22 @@
+
+
+
+ Exe
+ net10.0
+
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step01_Interpreter/Program.cs b/dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step01_Interpreter/Program.cs
new file mode 100644
index 0000000000..ed3b1315cf
--- /dev/null
+++ b/dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step01_Interpreter/Program.cs
@@ -0,0 +1,30 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+// This sample shows how to use HyperlightCodeActProvider as a sandboxed Python
+// code interpreter: the model can write and execute arbitrary Python code to
+// answer quantitative questions without calling any additional tools.
+
+using Azure.AI.OpenAI;
+using Azure.Identity;
+using Microsoft.Agents.AI;
+using Microsoft.Agents.AI.Hyperlight;
+using OpenAI.Chat;
+
+var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
+var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
+var guestPath = Environment.GetEnvironmentVariable("HYPERLIGHT_PYTHON_GUEST_PATH") ?? throw new InvalidOperationException("HYPERLIGHT_PYTHON_GUEST_PATH is not set.");
+
+using var codeAct = new HyperlightCodeActProvider(HyperlightCodeActProviderOptions.CreateForWasm(guestPath));
+
+AIAgent agent = new AzureOpenAIClient(
+ new Uri(endpoint),
+ new DefaultAzureCredential())
+ .GetChatClient(deploymentName)
+ .AsAIAgent(new ChatClientAgentOptions()
+ {
+ ChatOptions = new() { Instructions = "You are a helpful assistant. When the user asks something quantitative, write Python and call `execute_code` instead of guessing." },
+ AIContextProviders = [codeAct],
+ });
+
+Console.WriteLine(await agent.RunAsync("What is the 20th Fibonacci number?"));
+Console.WriteLine(await agent.RunAsync("Compute the mean and standard deviation of [1, 4, 9, 16, 25, 36]."));
diff --git a/dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step01_Interpreter/README.md b/dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step01_Interpreter/README.md
new file mode 100644
index 0000000000..ed67c388e6
--- /dev/null
+++ b/dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step01_Interpreter/README.md
@@ -0,0 +1,35 @@
+# AgentWithCodeAct_Step01_Interpreter
+
+A minimal CodeAct sample. The agent uses `HyperlightCodeActProvider` as a
+sandboxed Python interpreter: when the user asks something quantitative, the
+model writes Python and invokes the `execute_code` tool rather than answering
+from memory.
+
+## Configuration
+
+| Variable | Description |
+|--------------------------------|-------------------------------------------------------------------------------------------|
+| `AZURE_OPENAI_ENDPOINT` | Azure OpenAI endpoint. Required. |
+| `AZURE_OPENAI_DEPLOYMENT_NAME` | Azure OpenAI deployment. Defaults to `gpt-5.4-mini`. |
+| `HYPERLIGHT_PYTHON_GUEST_PATH` | Absolute path to the Hyperlight Python guest module (`.wasm` or `.aot` file). Required. |
+
+Authentication uses `DefaultAzureCredential`.
+
+## Getting the guest module
+
+The Python guest module is built from the
+[hyperlight-dev/hyperlight-sandbox](https://github.com/hyperlight-dev/hyperlight-sandbox)
+repository — see its README for the exact `cargo`/`just` invocations and
+the location of the resulting `.wasm` / `.aot` file. Set
+`HYPERLIGHT_PYTHON_GUEST_PATH` to the absolute path of that artifact
+before running the sample.
+
+Hyperlight requires a hardware virtualization back end on the host:
+KVM on Linux or WHP (Windows Hypervisor Platform) on Windows.
+
+## Run
+
+```shell
+cd AgentWithCodeAct_Step01_Interpreter
+dotnet run
+```
diff --git a/dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step02_ToolEnabled/AgentWithCodeAct_Step02_ToolEnabled.csproj b/dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step02_ToolEnabled/AgentWithCodeAct_Step02_ToolEnabled.csproj
new file mode 100644
index 0000000000..4e37243cce
--- /dev/null
+++ b/dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step02_ToolEnabled/AgentWithCodeAct_Step02_ToolEnabled.csproj
@@ -0,0 +1,22 @@
+
+
+
+ Exe
+ net10.0
+
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step02_ToolEnabled/Program.cs b/dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step02_ToolEnabled/Program.cs
new file mode 100644
index 0000000000..3ae1faccf2
--- /dev/null
+++ b/dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step02_ToolEnabled/Program.cs
@@ -0,0 +1,52 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+// This sample shows how to use HyperlightCodeActProvider with provider-owned
+// tools (exposed inside the sandbox via `call_tool(...)`). The model can
+// orchestrate those tools in a single Python block, reducing round-trips. A
+// sensitive tool (`send_email`) is additionally wrapped in
+// ApprovalRequiredAIFunction so any code that reaches it requires user approval
+// for the entire execute_code invocation.
+
+using Azure.AI.OpenAI;
+using Azure.Identity;
+using Microsoft.Agents.AI;
+using Microsoft.Agents.AI.Hyperlight;
+using Microsoft.Extensions.AI;
+using OpenAI.Chat;
+
+var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
+var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
+var guestPath = Environment.GetEnvironmentVariable("HYPERLIGHT_PYTHON_GUEST_PATH") ?? throw new InvalidOperationException("HYPERLIGHT_PYTHON_GUEST_PATH is not set.");
+
+AIFunction fetchDocs = AIFunctionFactory.Create(
+ (string topic) => $"Docs for {topic}: (...)",
+ name: "fetch_docs",
+ description: "Fetch documentation for a given topic.");
+
+AIFunction queryData = AIFunctionFactory.Create(
+ (string query) => $"Rows for `{query}`: []",
+ name: "query_data",
+ description: "Run a read-only SQL-like query against the sample store.");
+
+AIFunction sendEmail = new ApprovalRequiredAIFunction(
+ AIFunctionFactory.Create(
+ (string to, string subject) => $"Sent '{subject}' to {to}.",
+ name: "send_email",
+ description: "Send an email on behalf of the user."));
+
+var options = HyperlightCodeActProviderOptions.CreateForWasm(guestPath);
+options.Tools = [fetchDocs, queryData, sendEmail];
+
+using var codeAct = new HyperlightCodeActProvider(options);
+
+AIAgent agent = new AzureOpenAIClient(
+ new Uri(endpoint),
+ new DefaultAzureCredential())
+ .GetChatClient(deploymentName)
+ .AsAIAgent(new ChatClientAgentOptions()
+ {
+ ChatOptions = new() { Instructions = "You are a helpful assistant. Prefer orchestrating your work in a single `execute_code` block using `call_tool(...)` over issuing many direct tool calls." },
+ AIContextProviders = [codeAct],
+ });
+
+Console.WriteLine(await agent.RunAsync("Look up docs on 'retries' and query the 'orders' table, then summarize."));
diff --git a/dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step02_ToolEnabled/README.md b/dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step02_ToolEnabled/README.md
new file mode 100644
index 0000000000..e60e1caddb
--- /dev/null
+++ b/dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step02_ToolEnabled/README.md
@@ -0,0 +1,34 @@
+# AgentWithCodeAct_Step02_ToolEnabled
+
+Demonstrates adding provider-owned tools to `HyperlightCodeActProvider`. Those
+tools are **only** available to code running inside the sandbox via
+`call_tool("", ...)` — they are never exposed to the model as direct
+tools. This lets the model orchestrate multiple tool calls in a single Python
+block.
+
+One tool (`send_email`) is wrapped in `ApprovalRequiredAIFunction`, which causes
+the entire `execute_code` invocation to require user approval when that tool
+is configured.
+
+## Configuration
+
+| Variable | Description |
+|--------------------------------|-------------------------------------------------------------------------------------------|
+| `AZURE_OPENAI_ENDPOINT` | Azure OpenAI endpoint. Required. |
+| `AZURE_OPENAI_DEPLOYMENT_NAME` | Azure OpenAI deployment. Defaults to `gpt-5.4-mini`. |
+| `HYPERLIGHT_PYTHON_GUEST_PATH` | Absolute path to the Hyperlight Python guest module (`.wasm` or `.aot` file). Required. |
+
+## Run
+
+```shell
+cd AgentWithCodeAct_Step02_ToolEnabled
+dotnet run
+```
+
+## Planned follow-up
+
+A more realistic "upload a file (e.g. an Excel workbook), have the agent
+analyze it with code" sample is planned as a separate step that will use
+`HostInputDirectory` together with a guest tool capable of reading the
+uploaded file. It will be added in a follow-up PR once the corresponding
+guest module support is in place.
diff --git a/dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step03_ManualWiring/AgentWithCodeAct_Step03_ManualWiring.csproj b/dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step03_ManualWiring/AgentWithCodeAct_Step03_ManualWiring.csproj
new file mode 100644
index 0000000000..4e37243cce
--- /dev/null
+++ b/dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step03_ManualWiring/AgentWithCodeAct_Step03_ManualWiring.csproj
@@ -0,0 +1,22 @@
+
+
+
+ Exe
+ net10.0
+
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step03_ManualWiring/Program.cs b/dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step03_ManualWiring/Program.cs
new file mode 100644
index 0000000000..fae83b14fd
--- /dev/null
+++ b/dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step03_ManualWiring/Program.cs
@@ -0,0 +1,40 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+// This sample shows how to wire up CodeAct manually using
+// HyperlightExecuteCodeFunction rather than the AIContextProvider. Use this
+// when you want a fixed tool surface for the agent's lifetime and don't need
+// the per-run snapshot/registry semantics of HyperlightCodeActProvider.
+
+using Azure.AI.OpenAI;
+using Azure.Identity;
+using Microsoft.Agents.AI;
+using Microsoft.Agents.AI.Hyperlight;
+using Microsoft.Extensions.AI;
+using OpenAI.Chat;
+
+var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
+var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
+var guestPath = Environment.GetEnvironmentVariable("HYPERLIGHT_PYTHON_GUEST_PATH") ?? throw new InvalidOperationException("HYPERLIGHT_PYTHON_GUEST_PATH is not set.");
+
+AIFunction calculate = AIFunctionFactory.Create(
+ (double a, double b) => a * b,
+ name: "multiply",
+ description: "Multiply two numbers.");
+
+var options = HyperlightCodeActProviderOptions.CreateForWasm(guestPath);
+options.Tools = [calculate];
+
+using var executeCode = new HyperlightExecuteCodeFunction(options);
+
+var instructions =
+ "You are a helpful assistant. When math is involved, solve it by writing Python "
+ + "and calling `execute_code` instead of computing values yourself.\n\n"
+ + executeCode.BuildInstructions(toolsVisibleToModel: false);
+
+AIAgent agent = new AzureOpenAIClient(
+ new Uri(endpoint),
+ new DefaultAzureCredential())
+ .GetChatClient(deploymentName)
+ .AsAIAgent(instructions: instructions, tools: [executeCode]);
+
+Console.WriteLine(await agent.RunAsync("What is 12.3 * 4.5? Use the multiply tool from within `execute_code`."));
diff --git a/dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step03_ManualWiring/README.md b/dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step03_ManualWiring/README.md
new file mode 100644
index 0000000000..1c6db54930
--- /dev/null
+++ b/dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step03_ManualWiring/README.md
@@ -0,0 +1,21 @@
+# AgentWithCodeAct_Step03_ManualWiring
+
+Shows how to wire CodeAct manually using `HyperlightExecuteCodeFunction` as a
+direct agent tool instead of via an `AIContextProvider`. This is useful when
+the sandbox's tool surface and capabilities are fixed for the agent's
+lifetime, avoiding per-run snapshot/restore of the provider registry.
+
+## Configuration
+
+| Variable | Description |
+|--------------------------------|-------------------------------------------------------------------------------------------|
+| `AZURE_OPENAI_ENDPOINT` | Azure OpenAI endpoint. Required. |
+| `AZURE_OPENAI_DEPLOYMENT_NAME` | Azure OpenAI deployment. Defaults to `gpt-5.4-mini`. |
+| `HYPERLIGHT_PYTHON_GUEST_PATH` | Absolute path to the Hyperlight Python guest module (`.wasm` or `.aot` file). Required. |
+
+## Run
+
+```shell
+cd AgentWithCodeAct_Step03_ManualWiring
+dotnet run
+```
diff --git a/dotnet/samples/02-agents/AgentWithCodeAct/README.md b/dotnet/samples/02-agents/AgentWithCodeAct/README.md
new file mode 100644
index 0000000000..7506d0ff5a
--- /dev/null
+++ b/dotnet/samples/02-agents/AgentWithCodeAct/README.md
@@ -0,0 +1,16 @@
+# Agent Framework CodeAct (Hyperlight) Samples
+
+These samples show how to enable an agent to write and execute code in a
+Hyperlight-backed sandbox via the CodeAct pattern. Guest code can be pure
+Python (interpreter mode) or orchestrate host-provided tools through
+`call_tool(...)` — all inside a secure sandbox with opt-in filesystem and
+network access.
+
+|Sample|Description|
+|---|---|
+|[Code interpreter](./AgentWithCodeAct_Step01_Interpreter/)|Uses `HyperlightCodeActProvider` as a sandboxed Python interpreter with no host tools.|
+|[Tool-enabled CodeAct](./AgentWithCodeAct_Step02_ToolEnabled/)|Registers provider-owned tools that guest code can orchestrate via `call_tool(...)`, with an approval-required tool for sensitive actions.|
+|[Manual wiring](./AgentWithCodeAct_Step03_ManualWiring/)|Uses `HyperlightExecuteCodeFunction` directly as an agent tool when the sandbox configuration is fixed.|
+
+All samples require a Hyperlight Python guest module. Set
+`HYPERLIGHT_PYTHON_GUEST_PATH` to its absolute path before running.
diff --git a/dotnet/samples/02-agents/README.md b/dotnet/samples/02-agents/README.md
index f14387c604..4e072f4b0f 100644
--- a/dotnet/samples/02-agents/README.md
+++ b/dotnet/samples/02-agents/README.md
@@ -11,6 +11,7 @@ The getting started samples demonstrate the fundamental concepts and functionali
| [Agent Providers](./AgentProviders/README.md) | Getting started with creating agents using various providers |
| [Agents With Retrieval Augmented Generation (RAG)](./AgentWithRAG/README.md) | Adding Retrieval Augmented Generation (RAG) capabilities to your agents |
| [Agents With Memory](./AgentWithMemory/README.md) | Adding memory capabilities to your agents |
+| [Agents With CodeAct (Hyperlight)](./AgentWithCodeAct/README.md) | Enabling sandboxed code execution (CodeAct) for your agents via Hyperlight |
| [Agent Open Telemetry](./AgentOpenTelemetry/README.md) | Getting started with OpenTelemetry for agents |
| [Agent With OpenAI exchange types](./AgentWithOpenAI/README.md) | Using OpenAI exchange types with agents |
| [Agent With Anthropic](./AgentWithAnthropic/README.md) | Getting started with agents using Anthropic Claude |
diff --git a/dotnet/src/Microsoft.Agents.AI.Hyperlight/AllowedDomain.cs b/dotnet/src/Microsoft.Agents.AI.Hyperlight/AllowedDomain.cs
new file mode 100644
index 0000000000..8b8b711b12
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Hyperlight/AllowedDomain.cs
@@ -0,0 +1,32 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+
+namespace Microsoft.Agents.AI.Hyperlight;
+
+///
+/// Represents a single entry in the outbound network allow-list applied to the
+/// Hyperlight sandbox.
+///
+public sealed class AllowedDomain
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// URL or domain to allow, for example "https://api.github.com".
+ ///
+ /// Optional list of HTTP methods to allow (for example ["GET", "POST"]).
+ /// When , all methods supported by the backend are allowed.
+ ///
+ public AllowedDomain(string target, IReadOnlyList? methods = null)
+ {
+ this.Target = target;
+ this.Methods = methods;
+ }
+
+ /// Gets the URL or domain to allow.
+ public string Target { get; }
+
+ /// Gets the optional list of HTTP methods to allow.
+ public IReadOnlyList? Methods { get; }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Hyperlight/CodeActApprovalMode.cs b/dotnet/src/Microsoft.Agents.AI.Hyperlight/CodeActApprovalMode.cs
new file mode 100644
index 0000000000..05e5f22f11
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Hyperlight/CodeActApprovalMode.cs
@@ -0,0 +1,25 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Microsoft.Extensions.AI;
+
+namespace Microsoft.Agents.AI.Hyperlight;
+
+///
+/// Controls the approval behavior for the execute_code tool exposed by
+/// and .
+///
+public enum CodeActApprovalMode
+{
+ ///
+ /// execute_code always requires user approval before invocation.
+ ///
+ AlwaysRequire,
+
+ ///
+ /// Approval is derived from the provider-owned CodeAct tool registry.
+ /// If any configured tool is an
+ /// ,
+ /// execute_code also requires approval. Otherwise it does not.
+ ///
+ NeverRequire,
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Hyperlight/FileMount.cs b/dotnet/src/Microsoft.Agents.AI.Hyperlight/FileMount.cs
new file mode 100644
index 0000000000..13ace1f939
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Hyperlight/FileMount.cs
@@ -0,0 +1,29 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+namespace Microsoft.Agents.AI.Hyperlight;
+
+///
+/// Represents a host-to-sandbox file mount configuration used by
+/// .
+///
+public sealed class FileMount
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Absolute or relative path on the host filesystem to mount into the sandbox.
+ ///
+ /// Path inside the sandbox the host path is exposed at (for example "/input/data.csv").
+ ///
+ public FileMount(string hostPath, string mountPath)
+ {
+ this.HostPath = hostPath;
+ this.MountPath = mountPath;
+ }
+
+ /// Gets the path on the host filesystem that is mounted into the sandbox.
+ public string HostPath { get; }
+
+ /// Gets the path inside the sandbox at which the host path is exposed.
+ public string MountPath { get; }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Hyperlight/HyperlightCodeActProvider.cs b/dotnet/src/Microsoft.Agents.AI.Hyperlight/HyperlightCodeActProvider.cs
new file mode 100644
index 0000000000..3065a0a893
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Hyperlight/HyperlightCodeActProvider.cs
@@ -0,0 +1,324 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Agents.AI.Hyperlight.Internal;
+using Microsoft.Extensions.AI;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Agents.AI.Hyperlight;
+
+///
+/// An that enables CodeAct execution through a
+/// Hyperlight-backed sandbox.
+///
+///
+///
+/// The provider injects an execute_code tool into the model-facing tool
+/// surface and contributes a short CodeAct guidance block through
+/// . Guest code executed via
+/// execute_code runs in an isolated Hyperlight sandbox with
+/// snapshot/restore for clean state per invocation.
+///
+///
+/// If no CodeAct-managed tools are configured the provider behaves as a code
+/// interpreter. If one or more tools are configured they are exposed to guest
+/// code via call_tool(...) but not to the model directly.
+///
+///
+/// Only a single may be attached to a
+/// given agent. returns a fixed value so
+/// ChatClientAgent's state-key uniqueness validation rejects duplicate
+/// registrations.
+///
+///
+/// Security considerations: guest code runs with only the
+/// capabilities explicitly configured on this provider (file mounts, allowed
+/// outbound domains). Callers should configure the smallest capability set
+/// sufficient for the task and consider using
+/// when guest code can reach
+/// sensitive resources.
+///
+///
+public sealed class HyperlightCodeActProvider : AIContextProvider, IDisposable
+{
+ ///
+ /// Fixed state key used to enforce a single provider-per-agent.
+ ///
+ internal const string FixedStateKey = "HyperlightCodeActProvider";
+
+ private static readonly IReadOnlyList s_stateKeys = [FixedStateKey];
+
+ private readonly object _gate = new();
+ private readonly HyperlightCodeActProviderOptions _options;
+ private readonly SandboxExecutor _executor;
+
+ private readonly Dictionary _tools = new(StringComparer.Ordinal);
+ private readonly Dictionary _fileMounts = new(StringComparer.Ordinal);
+ private readonly Dictionary _allowedDomains = new(StringComparer.Ordinal);
+ private bool _disposed;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// Optional configuration options for the provider. When the provider
+ /// uses the defaults of (the
+ /// backend with no tools, mounts, or allow-list entries).
+ /// Use to target a Wasm
+ /// guest module instead.
+ ///
+ public HyperlightCodeActProvider(HyperlightCodeActProviderOptions? options = null)
+ {
+ this._options = options ?? new HyperlightCodeActProviderOptions();
+ this._executor = new SandboxExecutor(this._options);
+
+ if (this._options.Tools is not null)
+ {
+ foreach (var tool in this._options.Tools.Where(t => t is not null))
+ {
+ this._tools[tool.Name] = tool;
+ }
+ }
+
+ if (this._options.FileMounts is not null)
+ {
+ foreach (var mount in this._options.FileMounts.Where(m => m is not null))
+ {
+ this._fileMounts[mount.MountPath] = mount;
+ }
+ }
+
+ if (this._options.AllowedDomains is not null)
+ {
+ foreach (var domain in this._options.AllowedDomains.Where(d => d is not null))
+ {
+ this._allowedDomains[domain.Target] = domain;
+ }
+ }
+ }
+
+ ///
+ public override IReadOnlyList StateKeys => s_stateKeys;
+
+ // -------------------------------------------------------------------
+ // Tool registry
+ // -------------------------------------------------------------------
+
+ /// Adds tools to the provider-owned CodeAct tool registry. Tools with a duplicate name replace the existing registration.
+ /// The tools to add.
+ public void AddTools(params AIFunction[] tools)
+ {
+ _ = Throw.IfNull(tools);
+ lock (this._gate)
+ {
+ this.ThrowIfDisposed();
+ foreach (var tool in tools.Where(t => t is not null))
+ {
+ this._tools[tool.Name] = tool;
+ }
+ }
+ }
+
+ /// Returns the current CodeAct-managed tools.
+ public IReadOnlyList GetTools()
+ {
+ lock (this._gate)
+ {
+ return this._tools.Values.ToList();
+ }
+ }
+
+ /// Removes tools by name from the CodeAct tool registry.
+ /// The names of the tools to remove.
+ public void RemoveTools(params string[] names)
+ {
+ _ = Throw.IfNull(names);
+ lock (this._gate)
+ {
+ foreach (var name in names.Where(n => n is not null))
+ {
+ _ = this._tools.Remove(name);
+ }
+ }
+ }
+
+ /// Removes all CodeAct-managed tools.
+ public void ClearTools()
+ {
+ lock (this._gate)
+ {
+ this._tools.Clear();
+ }
+ }
+
+ // -------------------------------------------------------------------
+ // File mounts
+ // -------------------------------------------------------------------
+
+ /// Adds file mount configurations. Mounts with a duplicate mount path replace the existing entry.
+ /// The mount configurations to add.
+ public void AddFileMounts(params FileMount[] mounts)
+ {
+ _ = Throw.IfNull(mounts);
+ lock (this._gate)
+ {
+ foreach (var mount in mounts.Where(m => m is not null))
+ {
+ this._fileMounts[mount.MountPath] = mount;
+ }
+ }
+ }
+
+ /// Returns the current file mount configurations.
+ public IReadOnlyList GetFileMounts()
+ {
+ lock (this._gate)
+ {
+ return this._fileMounts.Values.ToList();
+ }
+ }
+
+ /// Removes file mounts by sandbox mount path.
+ /// The mount paths to remove.
+ public void RemoveFileMounts(params string[] mountPaths)
+ {
+ _ = Throw.IfNull(mountPaths);
+ lock (this._gate)
+ {
+ foreach (var path in mountPaths.Where(p => p is not null))
+ {
+ _ = this._fileMounts.Remove(path);
+ }
+ }
+ }
+
+ /// Removes all file mount configurations.
+ public void ClearFileMounts()
+ {
+ lock (this._gate)
+ {
+ this._fileMounts.Clear();
+ }
+ }
+
+ // -------------------------------------------------------------------
+ // Network allow-list
+ // -------------------------------------------------------------------
+
+ /// Adds outbound network allow-list entries. Entries with a duplicate target replace the existing entry.
+ /// The allow-list entries to add.
+ public void AddAllowedDomains(params AllowedDomain[] domains)
+ {
+ _ = Throw.IfNull(domains);
+ lock (this._gate)
+ {
+ foreach (var domain in domains.Where(d => d is not null))
+ {
+ this._allowedDomains[domain.Target] = domain;
+ }
+ }
+ }
+
+ /// Returns the current outbound allow-list entries.
+ public IReadOnlyList GetAllowedDomains()
+ {
+ lock (this._gate)
+ {
+ return this._allowedDomains.Values.ToList();
+ }
+ }
+
+ /// Removes allow-list entries by target.
+ /// The targets to remove.
+ public void RemoveAllowedDomains(params string[] targets)
+ {
+ _ = Throw.IfNull(targets);
+ lock (this._gate)
+ {
+ foreach (var target in targets.Where(t => t is not null))
+ {
+ _ = this._allowedDomains.Remove(target);
+ }
+ }
+ }
+
+ /// Removes all outbound allow-list entries.
+ public void ClearAllowedDomains()
+ {
+ lock (this._gate)
+ {
+ this._allowedDomains.Clear();
+ }
+ }
+
+ // -------------------------------------------------------------------
+ // AIContextProvider implementation
+ // -------------------------------------------------------------------
+
+ ///
+ protected override ValueTask ProvideAIContextAsync(InvokingContext context, CancellationToken cancellationToken = default)
+ {
+ _ = Throw.IfNull(context);
+
+ SandboxExecutor.RunSnapshot snapshot;
+ lock (this._gate)
+ {
+ this.ThrowIfDisposed();
+ snapshot = new SandboxExecutor.RunSnapshot(
+ this._tools.Values.ToList(),
+ this._fileMounts.Values.ToList(),
+ this._allowedDomains.Values.ToList(),
+ this._options.HostInputDirectory);
+ }
+
+ var approvalRequired = ComputeApprovalRequired(this._options.ApprovalMode, snapshot.Tools);
+
+ var description = InstructionBuilder.BuildExecuteCodeDescription(
+ snapshot.Tools,
+ snapshot.FileMounts,
+ snapshot.AllowedDomains,
+ hasHostInputDirectory: !string.IsNullOrEmpty(snapshot.HostInputDirectory));
+
+ AIFunction executeCode = new ExecuteCodeFunction(this._executor, snapshot, description);
+ if (approvalRequired)
+ {
+ executeCode = new ApprovalRequiredAIFunction(executeCode);
+ }
+
+ var instructions = InstructionBuilder.BuildContextInstructions(toolsVisibleToModel: false);
+
+ var result = new AIContext
+ {
+ Instructions = instructions,
+ Tools = [executeCode],
+ };
+
+ return new ValueTask(result);
+ }
+
+ internal static bool ComputeApprovalRequired(CodeActApprovalMode mode, IReadOnlyList tools) =>
+ mode == CodeActApprovalMode.AlwaysRequire
+ || tools.Any(t => t.GetService() is not null);
+
+ private void ThrowIfDisposed() => ObjectDisposedException.ThrowIf(this._disposed, this);
+
+ /// Releases the underlying sandbox and associated native resources.
+ public void Dispose()
+ {
+ lock (this._gate)
+ {
+ if (this._disposed)
+ {
+ return;
+ }
+
+ this._disposed = true;
+ }
+
+ this._executor.Dispose();
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Hyperlight/HyperlightCodeActProviderOptions.cs b/dotnet/src/Microsoft.Agents.AI.Hyperlight/HyperlightCodeActProviderOptions.cs
new file mode 100644
index 0000000000..93e5a09c39
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Hyperlight/HyperlightCodeActProviderOptions.cs
@@ -0,0 +1,99 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using HyperlightSandbox.Api;
+using Microsoft.Extensions.AI;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Agents.AI.Hyperlight;
+
+///
+/// Configuration options for and
+/// .
+///
+///
+/// Use the and
+/// factory methods to construct an instance with the desired sandbox backend.
+/// The parameterless constructor is equivalent to .
+///
+public sealed class HyperlightCodeActProviderOptions
+{
+ ///
+ /// Initializes a new instance configured for the JavaScript backend.
+ /// Equivalent to .
+ ///
+ public HyperlightCodeActProviderOptions()
+ : this(SandboxBackend.JavaScript, modulePath: null)
+ {
+ }
+
+ private HyperlightCodeActProviderOptions(SandboxBackend backend, string? modulePath)
+ {
+ this.Backend = backend;
+ this.ModulePath = modulePath;
+ }
+
+ ///
+ /// Creates options targeting the backend.
+ ///
+ /// Path to the guest module (.wasm or .aot file).
+ public static HyperlightCodeActProviderOptions CreateForWasm(string modulePath)
+ => new(SandboxBackend.Wasm, Throw.IfNullOrWhitespace(modulePath));
+
+ ///
+ /// Creates options targeting the backend.
+ ///
+ public static HyperlightCodeActProviderOptions CreateForJavaScript()
+ => new(SandboxBackend.JavaScript, modulePath: null);
+
+ ///
+ /// Gets the Hyperlight sandbox backend this options instance is configured for.
+ ///
+ public SandboxBackend Backend { get; }
+
+ ///
+ /// Gets the path to the guest module. Set when the options were created via
+ /// ; otherwise.
+ ///
+ public string? ModulePath { get; }
+
+ ///
+ /// Gets or sets the guest heap size. Accepts human-readable strings such as
+ /// "50Mi" or "2Gi". When the backend default is used.
+ ///
+ public string? HeapSize { get; set; }
+
+ ///
+ /// Gets or sets the guest stack size. Accepts human-readable strings such as
+ /// "35Mi". When the backend default is used.
+ ///
+ public string? StackSize { get; set; }
+
+ ///
+ /// Gets or sets the initial set of provider-owned CodeAct tools made available
+ /// inside the sandbox via call_tool(...).
+ ///
+ public IEnumerable? Tools { get; set; }
+
+ ///
+ /// Gets or sets the default approval mode for execute_code.
+ /// Defaults to .
+ ///
+ public CodeActApprovalMode ApprovalMode { get; set; } = CodeActApprovalMode.NeverRequire;
+
+ ///
+ /// Gets or sets an optional host directory exposed to the sandbox as its
+ /// /input directory.
+ ///
+ public string? HostInputDirectory { get; set; }
+
+ ///
+ /// Gets or sets the initial set of file mount configurations.
+ ///
+ public IEnumerable? FileMounts { get; set; }
+
+ ///
+ /// Gets or sets the initial outbound network allow-list entries.
+ ///
+ public IEnumerable? AllowedDomains { get; set; }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Hyperlight/HyperlightExecuteCodeFunction.cs b/dotnet/src/Microsoft.Agents.AI.Hyperlight/HyperlightExecuteCodeFunction.cs
new file mode 100644
index 0000000000..65c457bf78
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Hyperlight/HyperlightExecuteCodeFunction.cs
@@ -0,0 +1,162 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Linq;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Agents.AI.Hyperlight.Internal;
+using Microsoft.Extensions.AI;
+
+namespace Microsoft.Agents.AI.Hyperlight;
+
+///
+/// Standalone execute_code backed by a
+/// Hyperlight sandbox. Use this for manual/static wiring when an
+/// lifecycle is not needed — for example
+/// when the tool registry and capability configuration are fixed for the
+/// lifetime of the agent.
+///
+///
+/// Unlike , this type does not hook
+/// into the pipeline. It captures a single
+/// snapshot of the provided
+/// at construction time and reuses it for the lifetime of the instance.
+/// The instance can be passed directly anywhere an
+/// is accepted; when the configuration requires approval (per
+/// or because a
+/// configured tool is itself an ),
+/// the instance surfaces an via
+/// , which is how the rest of
+/// the framework discovers approval requirements.
+///
+public sealed class HyperlightExecuteCodeFunction : AIFunction, IDisposable
+{
+ private const string ExecuteCodeName = "execute_code";
+
+ private static readonly JsonElement s_schema = JsonDocument.Parse(
+ """
+ {
+ "type": "object",
+ "properties": {
+ "code": {
+ "type": "string",
+ "description": "Code to execute using the provider's configured backend/runtime behavior."
+ }
+ },
+ "required": ["code"]
+ }
+ """).RootElement;
+
+ private readonly SandboxExecutor _executor;
+ private readonly SandboxExecutor.RunSnapshot _snapshot;
+ private readonly string _description;
+ private readonly bool _approvalRequired;
+ private ApprovalRequiredAIFunction? _approvalProxy;
+ private bool _disposed;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// Optional configuration options. When the defaults of
+ /// are used.
+ ///
+ public HyperlightExecuteCodeFunction(HyperlightCodeActProviderOptions? options = null)
+ {
+ var effective = options ?? new HyperlightCodeActProviderOptions();
+ this._executor = new SandboxExecutor(effective);
+
+ var tools = (effective.Tools?.Where(t => t is not null) ?? []).ToList();
+ var fileMounts = (effective.FileMounts?.Where(m => m is not null) ?? []).ToList();
+ var allowedDomains = (effective.AllowedDomains?.Where(d => d is not null) ?? []).ToList();
+
+ this._snapshot = new SandboxExecutor.RunSnapshot(tools, fileMounts, allowedDomains, effective.HostInputDirectory);
+
+ this._description = InstructionBuilder.BuildExecuteCodeDescription(
+ this._snapshot.Tools,
+ this._snapshot.FileMounts,
+ this._snapshot.AllowedDomains,
+ hasHostInputDirectory: !string.IsNullOrEmpty(this._snapshot.HostInputDirectory));
+
+ this._approvalRequired = HyperlightCodeActProvider.ComputeApprovalRequired(effective.ApprovalMode, this._snapshot.Tools);
+ }
+
+ ///
+ public override string Name => ExecuteCodeName;
+
+ ///
+ public override string Description => this._description;
+
+ ///
+ public override JsonElement JsonSchema => s_schema;
+
+ ///
+ /// Builds a CodeAct instruction string describing the available tools and capabilities.
+ ///
+ ///
+ /// When , the instructions assume tools are only accessible
+ /// through CodeAct (via call_tool). When , the instructions
+ /// are abbreviated for cases where the same tools are already visible to the model as
+ /// direct agent tools.
+ ///
+ public string BuildInstructions(bool toolsVisibleToModel = false)
+ {
+ this.ThrowIfDisposed();
+ return InstructionBuilder.BuildContextInstructions(toolsVisibleToModel);
+ }
+
+ ///
+ public override object? GetService(Type serviceType, object? serviceKey = null)
+ {
+ if (serviceKey is null
+ && this._approvalRequired
+ && serviceType == typeof(ApprovalRequiredAIFunction))
+ {
+ return this._approvalProxy ??= new ApprovalRequiredAIFunction(this);
+ }
+
+ return base.GetService(serviceType, serviceKey);
+ }
+
+ ///
+ protected override async ValueTask