.NET: Add Microsoft.Agents.AI.Hyperlight package for CodeAct integration (.NET)#5329
.NET: Add Microsoft.Agents.AI.Hyperlight package for CodeAct integration (.NET)#5329eavanvalkenburg wants to merge 1 commit intomicrosoft:mainfrom
Conversation
Introduces a new Microsoft.Agents.AI.Hyperlight package that enables CodeAct-style sandboxed code execution via Hyperlight (hyperlight-sandbox .NET SDK, PR microsoft#46) for .NET agents, following the docs/features/code_act/dotnet-implementation.md design and the Python agent_framework_hyperlight reference. Highlights: - HyperlightCodeActProvider (AIContextProvider): injects an execute_code tool and CodeAct guidance per invocation; single-instance-per-agent via a fixed StateKeys value; supports multiple provider-owned tools (exposed inside the sandbox via call_tool), file mounts, and an outbound domain allow-list; snapshot/restore per run. - HyperlightExecuteCodeFunction: standalone AIFunction for manual/static wiring when the sandbox configuration is fixed. - Approval model via CodeActApprovalMode (AlwaysRequire / NeverRequire) with propagation from ApprovalRequiredAIFunction-wrapped tools. - Unit tests (instruction builder, tool bridge, approval computation, provider CRUD, ProvideAIContextAsync snapshot isolation and approval wrapping). - Env-gated integration test (HYPERLIGHT_PYTHON_GUEST_PATH). - Three samples under samples/02-agents/AgentWithCodeAct (interpreter, tool-enabled, manual wiring). Build is not yet runnable: requires .NET SDK 10.0.200 and the not-yet-published HyperlightSandbox.Api 0.1.0-preview NuGet package. Package is marked IsPackable=false until the dependency is available. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <PackageReference Include="Microsoft.Extensions.AI.Abstractions" /> |
There was a problem hiding this comment.
This is already included as a transient dependency from Microsoft.Agents.AI.Abstractions, so we should be able to remove this it.
| <PackageReference Include="Microsoft.Extensions.AI.Abstractions" /> |
| /// Optional list of HTTP methods to allow (for example <c>["GET", "POST"]</c>). | ||
| /// When <see langword="null"/>, all methods supported by the backend are allowed. | ||
| /// </param> | ||
| public sealed record AllowedDomain(string Target, IReadOnlyList<string>? Methods = null); |
There was a problem hiding this comment.
We generally avoid record types because of their cost, unless we are using their functionality. They come with a lot of functionality by default, which is usually not used.
Since this is essentially just config, a class is probably fine.
| /// <summary> | ||
| /// Gets or sets the initial outbound network allow-list entries. | ||
| /// </summary> | ||
| public IEnumerable<AllowedDomain>? AllowedDomains { get; set; } |
There was a problem hiding this comment.
Would this also work as a Dictionary<string, IReadOnlyList<string>>?
It'll avoid someone providing the same domain twice, but at the same it might be tricker to understand, i.e. you have to read the comments to know what IReadOnlyList<string> should contain.
| /// Gets or sets an optional workspace root directory on the host. | ||
| /// When set, it is exposed as the sandbox's <c>/input</c> directory. | ||
| /// </summary> | ||
| public string? WorkspaceRoot { get; set; } |
There was a problem hiding this comment.
Is this term something that Hyperlight uses? If it's a folder on the host that maps to an input folder in the sandbox, WorkspaceRoot isn't the first name that springs to mind 😄
| /// Required for the <see cref="SandboxBackend.Wasm"/> backend; not needed for | ||
| /// <see cref="SandboxBackend.JavaScript"/>. | ||
| /// </summary> | ||
| public string? ModulePath { get; set; } |
There was a problem hiding this comment.
Looks like this is optional only for javascript, but required for wasm.
Since we default to wasm though, it means that providing no options will always fail.
We should consider switching to javascript as the default.
Another option is to add some factory methods on the provider like:
HyperlightCodeActProviderOptions.CreateForWasm(string modulePath, HyperlightCodeActProviderOptions? options = null);
HyperlightCodeActProviderOptions.CreateForJavaScript(HyperlightCodeActProviderOptions? options = null);
And then we don't have backend or module path on the options.
| foreach (var name in names) | ||
| { | ||
| if (name is null) | ||
| { | ||
| continue; | ||
| } | ||
|
|
||
| _ = this._tools.Remove(name); | ||
| } |
There was a problem hiding this comment.
This can be shortened a little bit into the following, here and in other cases here.
| foreach (var name in names) | |
| { | |
| if (name is null) | |
| { | |
| continue; | |
| } | |
| _ = this._tools.Remove(name); | |
| } | |
| foreach (var name in names.Where(x => x is not null)) | |
| { | |
| _ = this._tools.Remove(name); | |
| } |
| { | ||
| lock (this._gate) | ||
| { | ||
| return this._tools.Values.ToArray(); |
There was a problem hiding this comment.
Since we are returning a List, we might as well convert to a list instead of an array, which will then be converted to a list again.
| return this._tools.Values.ToArray(); | |
| return this._tools.Values.ToList(); |
| public override IReadOnlyList<string> StateKeys => s_stateKeys; | ||
|
|
||
| // ------------------------------------------------------------------- | ||
| // Tool registry |
There was a problem hiding this comment.
Do we need the public modification methods for tools, mounts, etc?
In other places, we generally only add this type of thing if there is a good user case.
And in that case, we probably could expose the properties publicly to allow the user to directly modify. The only checks we do is for null values, and that we can do at consumption time as well.
| { | ||
| this.ThrowIfDisposed(); | ||
| snapshot = new SandboxExecutor.RunSnapshot( | ||
| this._tools.Values.ToArray(), |
There was a problem hiding this comment.
Are these explicitly requiring array as input? Just double checking that we aren't unnecessarily converting to array.
| internal static bool ComputeApprovalRequired(CodeActApprovalMode mode, IReadOnlyList<AIFunction> tools) | ||
| { | ||
| if (mode == CodeActApprovalMode.AlwaysRequire) | ||
| { | ||
| return true; | ||
| } | ||
|
|
||
| foreach (var tool in tools) | ||
| { | ||
| if (tool is ApprovalRequiredAIFunction) | ||
| { | ||
| return true; | ||
| } | ||
| } | ||
|
|
||
| return false; | ||
| } |
There was a problem hiding this comment.
I generally don't comment on style, but since you are new to this, suggesting some shortcuts:
| internal static bool ComputeApprovalRequired(CodeActApprovalMode mode, IReadOnlyList<AIFunction> tools) | |
| { | |
| if (mode == CodeActApprovalMode.AlwaysRequire) | |
| { | |
| return true; | |
| } | |
| foreach (var tool in tools) | |
| { | |
| if (tool is ApprovalRequiredAIFunction) | |
| { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| internal static bool ComputeApprovalRequired(CodeActApprovalMode mode, IReadOnlyList<AIFunction> tools) | |
| => mode == CodeActApprovalMode.AlwaysRequire ? | |
| true : | |
| tools.Any(x => x is ApprovalRequiredAIFunction); |
| private static string SerializeResult(object? result) | ||
| { | ||
| switch (result) | ||
| { | ||
| case null: | ||
| return "null"; | ||
| case string s: | ||
| return JsonSerializer.Serialize(s); | ||
| case JsonElement element: | ||
| return element.GetRawText(); | ||
| case JsonNode node: | ||
| return node.ToJsonString(); | ||
| default: | ||
| return JsonSerializer.Serialize(result); | ||
| } | ||
| } |
There was a problem hiding this comment.
| private static string SerializeResult(object? result) | |
| { | |
| switch (result) | |
| { | |
| case null: | |
| return "null"; | |
| case string s: | |
| return JsonSerializer.Serialize(s); | |
| case JsonElement element: | |
| return element.GetRawText(); | |
| case JsonNode node: | |
| return node.ToJsonString(); | |
| default: | |
| return JsonSerializer.Serialize(result); | |
| } | |
| } | |
| private static string SerializeResult(object? result) | |
| => result switch | |
| { | |
| null => "null", | |
| string s => JsonSerializer.Serialize(s), | |
| JsonElement element => element.GetRawText(), | |
| JsonNode node => node.ToJsonString(), | |
| _ => JsonSerializer.Serialize(result) | |
| }; |
| { | ||
| tool = wrapper.InnerFunction; | ||
| } | ||
|
|
There was a problem hiding this comment.
I don't think this unwrapping is required. ApprovalRequiredAIFunction is invocable just like a regular AIFunction. It doesn't stop execution, and is essentially just metadata. It's up to whatever uses it to ask for approval if it wants to respect the metadata.
| return new Dictionary<string, object?>(StringComparer.Ordinal); | ||
| } | ||
|
|
||
| var node = JsonNode.Parse(argsJson); |
There was a problem hiding this comment.
Also consider deserializating into a dictionary directly, e.g.
JsonSerializer.Deserialize<Dictionary<string, object?>>(argsJson);| return new Dictionary<string, object?>(StringComparer.Ordinal); | ||
| } | ||
|
|
||
| var node = JsonNode.Parse(argsJson); |
There was a problem hiding this comment.
Do we need to support NativeAOT with this feature?
| case null: | ||
| return "null"; | ||
| case string s: | ||
| return JsonSerializer.Serialize(s); |
There was a problem hiding this comment.
Do we need to serialize a string? I suppose it'll just put it in quotes, but not sure if that is required.
| case JsonElement element: | ||
| return element.GetRawText(); | ||
| case JsonNode node: | ||
| return node.ToJsonString(); | ||
| default: | ||
| return JsonSerializer.Serialize(result); |
There was a problem hiding this comment.
I think JsonSerializer.Serialize(result); should work for JsonElement and JsonNode as well, so you may not required the switch at all.
There was a problem hiding this comment.
Pull request overview
Adds a new .NET package (Microsoft.Agents.AI.Hyperlight) that integrates Hyperlight-backed CodeAct into the Agent Framework, including an AIContextProvider that injects an execute_code tool plus companion tests and samples.
Changes:
- Introduces
Microsoft.Agents.AI.Hyperlight(provider, options, execute_code function wrapper, internal sandbox executor/tool bridge, and supporting models). - Adds unit + integration tests validating instruction/description building, tool bridging, approval behavior, and an env-gated end-to-end sandbox run.
- Adds three new CodeAct samples and wires them into the .NET solution + samples index.
Reviewed changes
Copilot reviewed 33 out of 33 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| dotnet/tests/Microsoft.Agents.AI.Hyperlight.UnitTests/ToolBridgeTests.cs | Unit coverage for ToolBridge argument passing, error wrapping, and approval unwrap behavior. |
| dotnet/tests/Microsoft.Agents.AI.Hyperlight.UnitTests/ProvideAIContextTests.cs | Validates provider context injection, approval wrapping behavior, and snapshot isolation in tool description. |
| dotnet/tests/Microsoft.Agents.AI.Hyperlight.UnitTests/Microsoft.Agents.AI.Hyperlight.UnitTests.csproj | Adds new unit test project referencing Hyperlight + core AI projects. |
| dotnet/tests/Microsoft.Agents.AI.Hyperlight.UnitTests/InstructionBuilderTests.cs | Tests CodeAct instruction/description text generation for tools, mounts, and network config. |
| dotnet/tests/Microsoft.Agents.AI.Hyperlight.UnitTests/HyperlightCodeActProviderTests.cs | CRUD + lifecycle tests for provider-owned registries and fixed StateKeys behavior. |
| dotnet/tests/Microsoft.Agents.AI.Hyperlight.UnitTests/ApprovalComputationTests.cs | Tests approval-required computation matrix. |
| dotnet/tests/Microsoft.Agents.AI.Hyperlight.IntegrationTests/Microsoft.Agents.AI.Hyperlight.IntegrationTests.csproj | Adds new integration test project referencing Hyperlight. |
| dotnet/tests/Microsoft.Agents.AI.Hyperlight.IntegrationTests/CodeActEndToEndTests.cs | Env-gated end-to-end execute_code run against a real sandbox guest module. |
| dotnet/src/Microsoft.Agents.AI.Hyperlight/README.md | Package-level documentation and requirements/status notes. |
| dotnet/src/Microsoft.Agents.AI.Hyperlight/Microsoft.Agents.AI.Hyperlight.csproj | New multi-targeted Hyperlight integration project and dependencies. |
| dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/ToolBridge.cs | Bridges host AIFunctions to sandbox tool callbacks (JSON in/out) and unwraps approval wrappers. |
| dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/SandboxExecutor.cs | Central executor managing sandbox lifecycle and snapshot/restore per invocation. |
| dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/InstructionBuilder.cs | Generates model-facing guidance + execute_code description sections. |
| dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/ExecuteCodeFunction.cs | Implements the run-scoped execute_code AIFunction bound to a snapshot + executor. |
| dotnet/src/Microsoft.Agents.AI.Hyperlight/HyperlightExecuteCodeFunction.cs | Public manual-wiring wrapper for registering execute_code directly. |
| dotnet/src/Microsoft.Agents.AI.Hyperlight/HyperlightCodeActProviderOptions.cs | Public options surface for backend/module path/tools/mounts/allow-list/approval mode. |
| dotnet/src/Microsoft.Agents.AI.Hyperlight/HyperlightCodeActProvider.cs | AIContextProvider that injects execute_code and maintains provider-owned registries with snapshotting. |
| dotnet/src/Microsoft.Agents.AI.Hyperlight/FileMount.cs | Public model for describing host-to-sandbox mounts. |
| dotnet/src/Microsoft.Agents.AI.Hyperlight/CodeActApprovalMode.cs | Public enum controlling approval semantics for execute_code. |
| dotnet/src/Microsoft.Agents.AI.Hyperlight/AllowedDomain.cs | Public model for outbound network allow-list entries. |
| dotnet/samples/02-agents/README.md | Adds the new “AgentWithCodeAct (Hyperlight)” entry to the samples index. |
| dotnet/samples/02-agents/AgentWithCodeAct/README.md | New sample group README describing the three CodeAct sample steps. |
| dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step03_ManualWiring/README.md | Docs for manual wiring sample configuration and run instructions. |
| dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step03_ManualWiring/Program.cs | Manual wiring example using HyperlightExecuteCodeFunction. |
| dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step03_ManualWiring/AgentWithCodeAct_Step03_ManualWiring.csproj | Manual wiring sample project wiring + dependencies. |
| dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step02_ToolEnabled/README.md | Docs for provider-owned tool orchestration + approval-required tool example. |
| dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step02_ToolEnabled/Program.cs | Tool-enabled sample using HyperlightCodeActProvider with provider-owned tools. |
| dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step02_ToolEnabled/AgentWithCodeAct_Step02_ToolEnabled.csproj | Tool-enabled sample project wiring + dependencies. |
| dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step01_Interpreter/README.md | Docs for interpreter-only CodeAct sample. |
| dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step01_Interpreter/Program.cs | Interpreter mode sample using HyperlightCodeActProvider. |
| dotnet/samples/02-agents/AgentWithCodeAct/AgentWithCodeAct_Step01_Interpreter/AgentWithCodeAct_Step01_Interpreter.csproj | Interpreter sample project wiring + dependencies. |
| dotnet/agent-framework-dotnet.slnx | Adds the new Hyperlight project, tests, and sample projects into the solution structure. |
| dotnet/Directory.Packages.props | Introduces central package version entry for HyperlightSandbox.Api. |
| using System.Collections.Generic; | ||
| using System.ComponentModel; |
There was a problem hiding this comment.
System.Collections.Generic and System.ComponentModel appear unused in this file. The repo elevates IDE0005 (unnecessary usings) to warning, and warnings are treated as errors, so this will break the build. Remove the unused using directives (or use the types if they’re intended).
| using System.Collections.Generic; | |
| using System.ComponentModel; |
| // Copyright (c) Microsoft. All rights reserved. | ||
|
|
||
| using System; | ||
| using System.Collections.Generic; |
There was a problem hiding this comment.
System.Collections.Generic looks unused here. Since IDE0005 is configured as a warning and warnings are treated as errors, this will fail the build. Remove the unused using directive (or use the type if intended).
| using System.Collections.Generic; |
| { | ||
| if (this._sandbox is not null) | ||
| { | ||
| return; |
There was a problem hiding this comment.
ExecuteAsync accepts a per-run snapshot, but once _sandbox is initialized EnsureInitialized(snapshot) becomes a no-op. That means later registry/allow-list CRUD changes (captured in subsequent snapshots) won’t be applied to the actual sandbox configuration—only the tool description changes—leading to confusing/incorrect runtime behavior. Consider either rebuilding the sandbox when the snapshot’s relevant configuration changes, or re-applying snapshot state (tool registration / allow-domains / IO config) each run before Run().
| return; | |
| this._sandbox.Dispose(); | |
| this._sandbox = null; | |
| this._warmSnapshot = null; |
| "You can execute Python code in a secure sandbox by calling the `execute_code` tool. " | ||
| + "Use it for calculations, data analysis, and anything that benefits from running code. " | ||
| + "State does not persist between calls; pass any required values in the code you execute."; | ||
| } | ||
|
|
||
| return | ||
| "You can execute Python code in a secure sandbox by calling the `execute_code` tool. " |
There was a problem hiding this comment.
BuildContextInstructions hard-codes “Python code”, but the provider supports multiple backends (SandboxBackend.Wasm vs SandboxBackend.JavaScript). If a non-Python backend is configured, the guidance will be wrong for the model. Consider making the instructions backend-aware (e.g., “execute code” generically, or pass the backend/language into BuildContextInstructions).
| "You can execute Python code in a secure sandbox by calling the `execute_code` tool. " | |
| + "Use it for calculations, data analysis, and anything that benefits from running code. " | |
| + "State does not persist between calls; pass any required values in the code you execute."; | |
| } | |
| return | |
| "You can execute Python code in a secure sandbox by calling the `execute_code` tool. " | |
| "You can execute code in a secure sandbox by calling the `execute_code` tool. " | |
| + "Use it for calculations, data analysis, and anything that benefits from running code. " | |
| + "State does not persist between calls; pass any required values in the code you execute."; | |
| } | |
| return | |
| "You can execute code in a secure sandbox by calling the `execute_code` tool. " |
| if (!string.IsNullOrEmpty(workspaceRoot)) | ||
| { | ||
| sb.AppendLine( | ||
| string.Format( | ||
| CultureInfo.InvariantCulture, | ||
| "- Workspace directory mounted read-only at `/input` (host: `{0}`).", | ||
| workspaceRoot)); | ||
| } | ||
|
|
||
| foreach (var mount in fileMounts) | ||
| { | ||
| sb.AppendLine( | ||
| string.Format( | ||
| CultureInfo.InvariantCulture, | ||
| "- `{0}` (host: `{1}`)", | ||
| mount.MountPath, | ||
| mount.HostPath)); | ||
| } |
There was a problem hiding this comment.
The execute_code description includes host filesystem paths (e.g., workspaceRoot and mount.HostPath). Since tool descriptions are model-visible, this can leak host machine details (directory names, usernames, repo paths). Consider omitting host paths from the model-facing description (or redacting to just the sandbox mount path), and keep host paths only in logs / developer-facing diagnostics.
| <Project Sdk="Microsoft.NET.Sdk"> | ||
|
|
||
| <PropertyGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))"> | ||
| <JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <ProjectReference Include="..\..\src\Microsoft.Agents.AI.Hyperlight\Microsoft.Agents.AI.Hyperlight.csproj" /> | ||
| <ProjectReference Include="..\..\src\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" /> | ||
| </ItemGroup> |
There was a problem hiding this comment.
This test project inherits TargetFrameworks=net10.0;net472 from dotnet/tests/Directory.Build.props, but it project-references Microsoft.Agents.AI.Hyperlight, which only targets net8.0+. This will fail to build for the net472 target. Override TargetFrameworks here (e.g., to $(TargetFrameworksCore) or net10.0) so the test project doesn't target net472.
| <Project Sdk="Microsoft.NET.Sdk"> | ||
|
|
||
| <ItemGroup> | ||
| <ProjectReference Include="..\..\src\Microsoft.Agents.AI.Hyperlight\Microsoft.Agents.AI.Hyperlight.csproj" /> | ||
| </ItemGroup> | ||
|
|
There was a problem hiding this comment.
This test project inherits TargetFrameworks=net10.0;net472 from dotnet/tests/Directory.Build.props, but it references Microsoft.Agents.AI.Hyperlight (net8.0+ only). The net472 target will fail to build. Override TargetFrameworks in this csproj (e.g., $(TargetFrameworksCore) or net10.0) to avoid targeting net472.
| |--------------------------------|-------------------------------------------------------------------------------------------| | ||
| | `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. | |
There was a problem hiding this comment.
Details on how to get the guest module will go to capture or point to a website with the details, as most of the end users are new to Hyperligh
|
|
||
| Demonstrates adding provider-owned tools to `HyperlightCodeActProvider`. Those | ||
| tools are **only** available to code running inside the sandbox via | ||
| `call_tool("<name>", ...)` — they are never exposed to the model as direct |
There was a problem hiding this comment.
Is Tools the recommended approach to pass files to the sandbox? Use case: Let the user upload an Excel file, and the agent uses the code to get details from the Excel file, similar to how Copilot works (Copilot studio). A sample to showcase this use case will be good
Adds a new
Microsoft.Agents.AI.Hyperlightpackage that brings first-class CodeAct support to the .NET Agent Framework, backed by the Hyperlight sandbox via the new .NET SDK from hyperlight-dev/hyperlight-sandbox PR 46.Follows
docs/features/code_act/dotnet-implementation.mdand the Pythonagent_framework_hyperlightreference.What's included
HyperlightCodeActProvider(AIContextProvider):execute_codetool + CodeAct guidance into every invocation.StateKeysvalue (ChatClientAgentrejects duplicate state keys).call_tool(...).HyperlightExecuteCodeFunction: standaloneAIFunctionfor manual/static wiring.Sandbox.RegisterToolAsync).CodeActApprovalMode(AlwaysRequire/NeverRequirewith propagation fromApprovalRequiredAIFunction-wrapped tools).options = nulland fall back to defaults (mirroring the Python reference).Tests & samples
ProvideAIContextAsync.HYPERLIGHT_PYTHON_GUEST_PATH, runsprint("hi")end-to-end.dotnet/samples/02-agents/AgentWithCodeAct/: Interpreter, ToolEnabled (with anApprovalRequiredAIFunction-wrapped tool), ManualWiring.Draft — not yet buildable
global.json).HyperlightSandbox.Api 0.1.0-preview, which is not yet published (hyperlight-dev/hyperlight-sandboxPR 46). The src project isIsPackable=falseuntil the dependency is available.Open items to resolve before un-drafting
net8.0/net9.0/net10.0against the real SDK.Sandbox.RegisterToolAsyncreturn-type handling inToolBridge.RegisterOne.FileMountentries are surfaced only in theexecute_codedescription).Tracks: CodeAct .NET implementation (see
docs/features/code_act/dotnet-implementation.md).