.NET: feat(dotnet): Add LocalCodeAct package for local Python execution - #6105
Conversation
There was a problem hiding this comment.
Automated Code Review
Reviewers: 4 | Confidence: 91%
✓ Correctness
This PR has several critical correctness bugs: (1) ProcessBridge deserializes JSON to Dictionary<string, object?> which yields JsonElement values, then uses
as Dictionary<string, object?>andConvert.ToInt32()casts that always fail, silently discarding subprocess results and crashing tool calls; (2) LocalCodeActProvider overides a non-existentProvideContextAsyncmethod instead of the actual base class methodProvideAIContextAsync(InvokingContext, CancellationToken)with incompatible signature and return type; (3) The runner script path is incorrectly passed as the validator script path to CodeValidator; (4) validator.py has no__main__entry point, so AST validation always passes without actually validating.
✓ Security Reliability
The PR introduces a local Python code execution package with critical security and reliability issues: (1) the
runnerScriptparameter is incorrectly passed toCodeValidatoras thevalidatorScript, which means if a custom runner script is configured alongside validation lists, the validation step will execute the code instead of validating it; (2) theCodeValidatorsubprocess has no timeout, creating a potential hang/DoS vector; (3) the_call_toolin runner.py usesid(kwargs)as call_id which can produce 64-bit integers that would overflow when the .NET side usesConvert.ToInt32.
✓ Test Coverage
The test suite covers only construction, metadata, and default values for 3 of the 5 production classes. There are no tests for the core behavior: LocalCodeActProvider.ProvideContextAsync (tool injection and disposal guard), LocalExecuteCodeFunction.InvokeCoreAsync (argument validation, post-dispose exception), CodeValidator.ValidateAsync, or ProcessBridge.RunAsync. The PR rationale acknowledges 'Full execution tests require Python installed and will be added in follow-up,' but several behavioral tests are achievable without Python (e.g., ProvideContextAsync output, InvokeCoreAsync missing 'code' argument, ObjectDisposedException after dispose, CodeValidator construction). Given this is marked as having 'remaining work' for additional tests, these are suggestions rather than blocking issues.
✗ Design Approach
The overall package shape matches the intended CodeAct surface, but I found three design-level blockers in the execution path: the default runner path cannot launch the bundled runner, successful subprocess results are silently discarded when parsed back into .NET, and the default constructor bypasses AST validation even though the package README documents validation as the default behavior.
Flagged Issues
- ProcessBridge.cs:46 — the default path invokes
python -m agent_framework_local_codeact._runner, but the runner is only shipped as an embedded .NET resource with no extraction logic (unlike CodeValidator which extracts its embedded script). Default construction always fails with ModuleNotFoundError. - ProcessBridge.cs:128-151 —
JsonSerializer.Deserialize<Dictionary<string, object?>>materializes nested JSON as boxedJsonElementvalues. Theas Dictionary<string, object?>cast always returns null, silently discarding all subprocess stdout/stderr/output on every successful execution. - LocalExecuteCodeFunction.cs:71-80 —
CodeValidatoris only instantiated when custom allow/block lists are supplied, contradicting README.md:91 which documents validation as the default behavior. The default constructor path runs unvalidated Python.
Automated review by eavanvalkenburg's agents
There was a problem hiding this comment.
Pull request overview
Adds a new .NET package (Microsoft.Agents.AI.LocalCodeAct) intended to run LLM-generated Python in a local subprocess with JSON-lines IPC, plus unit tests and a sample showing basic wiring.
Changes:
- Introduces a new LocalCodeAct implementation (provider +
execute_codefunction) with embedded Python runner/validator resources. - Adds initial unit test project covering basic construction/metadata/defaults.
- Adds a new sample project and README documentation for usage and configuration.
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 18 comments.
Show a summary per file
| File | Description |
|---|---|
| dotnet/src/Microsoft.Agents.AI.LocalCodeAct/Microsoft.Agents.AI.LocalCodeAct.csproj | New packable project; embeds Resources/*.py and exposes internals to tests. |
| dotnet/src/Microsoft.Agents.AI.LocalCodeAct/ProcessBridge.cs | Parent-side subprocess IPC bridge + tool dispatch (currently has critical JSON/type and runner-resolution issues). |
| dotnet/src/Microsoft.Agents.AI.LocalCodeAct/CodeValidator.cs | .NET wrapper intended to run embedded Python AST validator (currently mismatched with validator script behavior). |
| dotnet/src/Microsoft.Agents.AI.LocalCodeAct/LocalExecuteCodeFunction.cs | execute_code tool implementation (currently mismatched with repo’s AIFunction API and validation wiring). |
| dotnet/src/Microsoft.Agents.AI.LocalCodeAct/LocalCodeActProvider.cs | AIContextProvider integration (currently overrides a non-existent method in this repo). |
| dotnet/src/Microsoft.Agents.AI.LocalCodeAct/ProcessExecutionLimits.cs | Resource limit record (timeout/stdout/stderr/file size limits). |
| dotnet/src/Microsoft.Agents.AI.LocalCodeAct/FileMount.cs | File mount type definitions (host path + logical mount path + mode). |
| dotnet/src/Microsoft.Agents.AI.LocalCodeAct/ExecutionMode.cs | ExecutionMode enum (Subprocess-only). |
| dotnet/src/Microsoft.Agents.AI.LocalCodeAct/Resources/runner.py | Embedded Python child runner implementing JSON-lines protocol + tool calls. |
| dotnet/src/Microsoft.Agents.AI.LocalCodeAct/Resources/validator.py | Embedded AST validator (currently missing CLI/stdio protocol entrypoint). |
| dotnet/src/Microsoft.Agents.AI.LocalCodeAct/README.md | Package documentation and examples (currently diverges from actual API/behavior). |
| dotnet/tests/Microsoft.Agents.AI.LocalCodeAct.UnitTests/Microsoft.Agents.AI.LocalCodeAct.UnitTests.csproj | New unit test project referencing the new package. |
| dotnet/tests/Microsoft.Agents.AI.LocalCodeAct.UnitTests/LocalExecuteCodeFunctionTests.cs | Basic tests for construction/metadata/disposal (currently missing using System;). |
| dotnet/tests/Microsoft.Agents.AI.LocalCodeAct.UnitTests/ProcessExecutionLimitsTests.cs | Tests for default/custom limit values. |
| dotnet/tests/Microsoft.Agents.AI.LocalCodeAct.UnitTests/FileMountTests.cs | Tests for FileMount defaults/customization. |
| dotnet/samples/LocalCodeAct/LocalCodeAct.csproj | New sample project referencing LocalCodeAct + core agent project. |
| dotnet/samples/LocalCodeAct/Program.cs | Sample demonstrating provider/function instantiation (currently missing using System / System.Threading.Tasks). |
| dotnet/samples/LocalCodeAct/README.md | Sample README with safety guidance and run instructions. |
File Mount Support and Integration Tests AddedCommit: fad8409 New Features
Implementation ParityAll features from Python agent-framework-local-codeact are now implemented in .NET:
Note: Tests are marked to skip when Python is not available, allowing the package to build and basic tests to pass even without Python installed. |
Complete rewrite that follows the Hyperlight package conventions (see Microsoft.Agents.AI.Hyperlight) and addresses all 24 review comments on PR microsoft#6105: Architectural fixes: * LocalCodeActProvider now uses options-class constructor pattern matching HyperlightCodeActProvider. * Override of ProvideAIContextAsync uses the correct (InvokingContext, CancellationToken) signature returning ValueTask<AIContext>. * ExecuteCodeFunction follows the AIFunction Name/Description/JsonSchema property pattern with InvokeCoreAsync override. * Provider exposes AddTools/GetTools/RemoveTools/ClearTools and AddFileMounts/GetFileMounts/RemoveFileMounts/ClearFileMounts CRUD methods, with snapshot-at-invocation semantics under a lock. Runtime/security fixes: * Subprocess IPC uses JsonObject/JsonNode end-to-end (no Dictionary<string, object?> casts that broke under JsonElement deserialization). * Validator runs in its own subprocess with a dedicated timeout (ProcessExecutionLimits.ValidationTimeoutSeconds), never reuses the runner script. * Validation enabled by default; can be opt-ed out via ValidationEnabled = false. * validator.py has a __main__ entrypoint that reads JSON from stdin and exits with structured errors. * validator.py is now compatible with Python 3.9+ (Match nodes added conditionally). * call_id parsed as long to match Python id(kwargs) range. Other: * README rewritten with valid C# syntax (options-class, FileMount constructor) and accurate descriptions of validator and file capture behavior. * Added integration tests that exercise the real subprocess and validator (skipped gracefully when python3 is not on PATH). * All 18 tests pass (15 unit + 3 integration) across net8/net9/net10. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
99ff847 to
fca0bbc
Compare
Complete rewrite that follows the Hyperlight package conventions (see Microsoft.Agents.AI.Hyperlight) and addresses all 24 review comments on PR microsoft#6105: Architectural fixes: * LocalCodeActProvider now uses options-class constructor pattern matching HyperlightCodeActProvider. * Override of ProvideAIContextAsync uses the correct (InvokingContext, CancellationToken) signature returning ValueTask<AIContext>. * ExecuteCodeFunction follows the AIFunction Name/Description/JsonSchema property pattern with InvokeCoreAsync override. * Provider exposes AddTools/GetTools/RemoveTools/ClearTools and AddFileMounts/GetFileMounts/RemoveFileMounts/ClearFileMounts CRUD methods, with snapshot-at-invocation semantics under a lock. Runtime/security fixes: * Subprocess IPC uses JsonObject/JsonNode end-to-end (no Dictionary<string, object?> casts that broke under JsonElement deserialization). * Validator runs in its own subprocess with a dedicated timeout (ProcessExecutionLimits.ValidationTimeoutSeconds), never reuses the runner script. * Validation enabled by default; can be opt-ed out via ValidationEnabled = false. * validator.py has a __main__ entrypoint that reads JSON from stdin and exits with structured errors. * validator.py is now compatible with Python 3.9+ (Match nodes added conditionally). * call_id parsed as long to match Python id(kwargs) range. Other: * README rewritten with valid C# syntax (options-class, FileMount constructor) and accurate descriptions of validator and file capture behavior. * Added integration tests that exercise the real subprocess and validator (skipped gracefully when python3 is not on PATH). * All 18 tests pass (15 unit + 3 integration) across net8/net9/net10. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Complete rewrite that follows the Hyperlight package conventions (see Microsoft.Agents.AI.Hyperlight) and addresses all 24 review comments on PR microsoft#6105: Architectural fixes: * LocalCodeActProvider now uses options-class constructor pattern matching HyperlightCodeActProvider. * Override of ProvideAIContextAsync uses the correct (InvokingContext, CancellationToken) signature returning ValueTask<AIContext>. * ExecuteCodeFunction follows the AIFunction Name/Description/JsonSchema property pattern with InvokeCoreAsync override. * Provider exposes AddTools/GetTools/RemoveTools/ClearTools and AddFileMounts/GetFileMounts/RemoveFileMounts/ClearFileMounts CRUD methods, with snapshot-at-invocation semantics under a lock. Runtime/security fixes: * Subprocess IPC uses JsonObject/JsonNode end-to-end (no Dictionary<string, object?> casts that broke under JsonElement deserialization). * Validator runs in its own subprocess with a dedicated timeout (ProcessExecutionLimits.ValidationTimeoutSeconds), never reuses the runner script. * Validation enabled by default; can be opt-ed out via ValidationEnabled = false. * validator.py has a __main__ entrypoint that reads JSON from stdin and exits with structured errors. * validator.py is now compatible with Python 3.9+ (Match nodes added conditionally). * call_id parsed as long to match Python id(kwargs) range. Other: * README rewritten with valid C# syntax (options-class, FileMount constructor) and accurate descriptions of validator and file capture behavior. * Added integration tests that exercise the real subprocess and validator (skipped gracefully when python3 is not on PATH). * All 18 tests pass (15 unit + 3 integration) across net8/net9/net10. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
fca0bbc to
cb11573
Compare
The dotnet/samples/LocalCodeAct/ scaffolding sample referenced APIs that don't exist in the current package (`ExecutionMode`, FileMount object-initializer syntax, the old LocalExecuteCodeFunction constructor signature, function.Metadata.*), produced a long list of check-format violations (CHARSET, IMPORTS, IDE0073 header, IDE0005 unused using, IDE1006 Async suffix, RCS1037 trailing whitespace), and did not match any of the documented sample layouts. The hosted-agent example at dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-LocalCodeAct is the supported entry-point sample for this package. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add UTF-8 BOM to source files (CHARSET) - Remove unused using directives (IDE0005) - Simplify type names (IDE0001/IDE0002/IDE0090) - Rename static field JsonOptions -> s_jsonOptions (IDE1006) - Rename static field SyncRoot -> s_syncRoot (IDE1006) - Add missing this. qualifications in ProcessBridge (IDE0009) - Remove unused _options field from LocalCodeActProvider (IDE0052) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move the required Python executable path to LocalCodeAct constructors, invert the validation flag default, and apply small project/file mount cleanup suggestions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
41b53de to
9945d56
Compare
Surface unauthorized mount traversal errors and use concurrent provider registries for LocalCodeAct tool and file mount CRUD operations. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use AIFunctionFactory-created inner functions for LocalCodeAct execute_code wrappers and remove redundant script cache and JsonNode cloning logic. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Handle JsonElement result values produced by AIFunctionFactory delegation in LocalCodeAct execute_code integration tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Motivation and Context
Microsoft Foundry hosted agents are already container-isolated, but agent code today has no way to let an LLM author and run Python in-process inside that sandbox. This PR adds a CodeAct provider for .NET that complements the existing
HyperlightandMontyproviders with an in-process Python subprocess option, intended for hosts that have already established their own isolation boundary (Foundry hosted agents, Container Apps, dedicated VMs).This is the .NET counterpart of the Python
agent-framework-local-codeactpackage (PR #6091); both packages share the same Python validator/runner scripts so behavior stays aligned across languages.Description
Adds a new
Microsoft.Agents.AI.LocalCodeActpackage plus a unit-test project and two samples.Package (
dotnet/src/Microsoft.Agents.AI.LocalCodeAct):LocalCodeActProvider—AIContextProviderthat injects anexecute_codeAIFunctionand per-run system instructions describing host tools and mounts.LocalCodeActProviderOptions— approval mode, host tools, file mounts, execution limits, environment, Python executable/runner overrides, and allow/block-list overrides for imports/builtins.LocalExecuteCodeFunction(viaInternal/ExecuteCodeFunction) — theexecute_codetool surface invoked by the model.Internal/ProcessBridge— JSON-lines IPC between host and Python subprocess (request framing, tool callbacks, response parsing, timeouts).Internal/CodeExecutor— orchestrates validation → subprocess execution → captured-file collection.Internal/CodeValidator— invokes the embedded Python validator via stdin JSON and converts errors toCodeValidationException.Internal/FileMountHelper— normalizes mount paths, snapshots writable mounts before execution, and captures new/modified files after.Internal/InstructionBuilder— renders system instructions that document the tool surface, mounts, and policy.FileMount,ProcessExecutionLimits,LocalCodeActProviderOptions,CodeValidationException— public types.Resources/runner.py,Resources/validator.py— embedded Python scripts kept in sync with the Python package; the validator script is identical apart from a_main()stdin entrypoint used by the .NET host.Tests (
dotnet/tests/Microsoft.Agents.AI.LocalCodeAct.UnitTests):18 tests covering option validation, instruction-builder output, file-mount normalization, capture limits, provider wiring, and end-to-end Python subprocess integration (
LocalExecuteCodeFunctionIntegrationTests).Samples:
dotnet/samples/GettingStarted/AgentProviders/LocalCodeAct— minimal getting-started console sample.dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-LocalCodeAct— Foundry hosted-agent sample withComputeandFetchDatatools (mirrors the Pythonfoundry_hosted_agentsample and the existingHosted-LocalToolslayout). IncludesProgram.cs,HostedLocalCodeAct.csproj,agent.yaml,agent.manifest.yaml,Dockerfile+Dockerfile.contributor(install Python 3 and setLOCAL_CODEACT_PYTHON=python3),.env.example, and a README with the standard run/curl flow.Validation policy:
os.*attribute allow-list of{environ, path}only; every otheros.<attr>is rejected. Override via the validator'sallowed_os_attrsJSON field.read-writemounts; the capture helpers skip symlinks and hardlinks and verify that every entry's resolved path stays under the mount root, so a virtual mount path cannot be redirected (via symlink, hardlink, junction, or..) to data outside its host directory.Contribution Checklist
Related: companion Python PR #6091.