Skip to main content

maf-sandbox-wslc

PyPI Python License

Experimental. This package is early-stage (pre-1.0, Development Status :: 4 - Beta) — its API may change or be removed in a future release without notice. Importing it emits a one-time MafSandboxWslcExperimentalWarning; suppress it with warnings.filterwarnings("ignore", category=maf_sandbox_wslc.MafSandboxWslcExperimentalWarning) once you've read the notice.

This package is not affiliated with, endorsed by, or a product of Microsoft — it is a third-party reference implementation of microsoft/agent-framework#7568 for Microsoft Agent Framework.

app  ->  maf_sandbox  ->  maf_sandbox_wslc  ->  the container

The developer-machine sandbox backend: a container created by wslc, the container CLI that ships with WSL, in about half a second — no subscription, no daemon, no login, and no dependency but maf-sandbox itself. A workload written against the protocol runs here unchanged, which is what makes it a workload rather than an integration.

Quickstart

pip install maf-sandbox-wslc
from maf_sandbox import Isolation, SandboxRouter
from maf_sandbox_wslc import WslcSandboxBackend, WslcSandboxConfig

router = SandboxRouter([WslcSandboxBackend(WslcSandboxConfig())], min_isolation=Isolation.CONTAINER)

samples/02_wslc_bicep runs those two lines end to end: a one-turn agent that validates a Bicep file against the compiler and takes the container down afterwards. Its sibling samples/01_acas_bicep is the same program on a microVM-isolated Azure backend, and the diff between them is two imports and one constructor.

Requirements

Windows with WSL 2.9.3 or later. wslc is part of WSL; wsl --version reports the version and wsl --update moves it forward. There is nothing else to install. The command-line contract this backend depends on — argv passed to exec natively, cp from a tar on stdin, label filters on list, WSLC_E_* codes on stderr — was verified against wslc 2.9.4.0. Every call spawns wslc.exe, so the host's event loop has to be one that can start subprocesses — asyncio's default Proactor loop on Windows does, and a host that installs WindowsSelectorEventLoopPolicy has to undo that first, or every acquire fails with a message saying so.

What this backend declares

Isolation.CONTAINER. A container shares the host kernel and sits next to whatever the host process holds, below SandboxRouter's default min_isolation=Isolation.MICROVM floor — construct the router with min_isolation=Isolation.CONTAINER and it admits this backend; leave the floor at its default and construction raises SandboxBackendNotPermitted. That refusal is the feature: this is a backend for the machine you are already sitting at, and opting the floor down is the one thing that lets you use it — there is no flag left to forget. Use a microVM-isolated backend where a deployment's credentials are in the picture.

Egress.CLOSED by default, Egress.ALLOWLIST on request. With no proxy configured every container is created --network none: the CLI cannot allow one host and deny the rest, so a spec's allowlist is honoured by denying everything — confining more than a workload asked for, which the router permits with a warning precisely because the failure is loud, and a workload built for this reports the shortfall rather than passing an incomplete result off as a clean one.

Set egress_proxy_image and the declaration becomes ALLOWLIST: each sandbox gets its own internal network and a dual-homed filtering proxy, and the spec's allowlist is enforced by topology — the container has no route out except the proxy, which opens a CONNECT tunnel only to the hosts the spec names. TLS is not decrypted, and the sandbox never resolves an external name itself. The proxy is shipped as source, not as an image you must trust: build it from the packaged recipe, whose only pinned dependency is its Azure Linux base.

from pathlib import Path
from maf_sandbox_wslc import proxy_build_context, WslcSandboxConfig

print(f"wslc build -t maf-egress-proxy:local {proxy_build_context()}")  # run this once
config = WslcSandboxConfig(egress_proxy_image="maf-egress-proxy:local")

The backend

WslcSandboxBackend implements maf_sandbox.SandboxBackend:

acquire(key, spec) get-or-create, keyed (scope, thread, agent). A running container is reused, a stopped one started, a missing one created — so a fix-round loop does not pay a cold start per iteration
write_file(path, content, *, working_directory) a confined one-entry tar on stdin to cp - <container>:/, which creates the parent directories from the entry name
dispose(key) remove -f on the one container the key names
dispose_scope(scope, thread) delete every container for a conversation — by label, read back from wslc, not from process memory
isolation container — below the router's default microvm floor, so a host opts down explicitly with min_isolation=Isolation.CONTAINER
declarations.egress_modes {closed}, or {closed, allowlist} when egress_proxy_image is set — an internal network behind a filtering proxy, torn down with the sandbox
declarations.capabilities {EXEC, FILES_IN} — a command line and files written in; nothing more
declarations.os_families {posix} — a constant, because wslc runs Linux containers and has no other guest to hand out

The filesystem path check on a write is answered inside the guest — the file name check is host-side text arithmetic and is not, and that is the residual to know about before choosing this backend. write_file refuses a path whose parents are links, which takes classifying every component from the filesystem root down. The cp tar header settles a directory and a missing path, and streams nothing for a regular file or a link — the two kinds that rule exists to catch — so those are settled by test run in the container being confined, through core's own maf_sandbox.paths.stat_by_asking_the_guest_as_root, which spells the probe and its ordering once so that no backend in this position invents a fourth version. The probe runs as --user 0, for the reason reclaim does: the file plane writes as root, so a probe as the image's user would be blind above a directory only root can search, and a cp still lands bytes there. Root is asked for reach and never for trust, and the helper checks that reach rather than assuming it, since a uid is not a capability set. A workload running as root can replace test in its own image and be believed, so the refusal is worth what the guest is. maf-sandbox-docker answers the same question out of its engine and this one has no equivalent until it can read an entry type without asking; #495 carries that decision.

declarations.os_families is {posix}, and it is stated rather than read. A workload names the guest shape its commands and scripts are written for in SandboxSpec.requires_os_family, and the router refuses a backend whose os_families does not hold it. wslc runs Linux containers in WSL 2's utility VM and has no other guest to hand out, so there is no engine to ask the way maf-sandbox-docker asks its daemon. The declaration is what this package's argv, its rm -rf reclaim and its posixpath path arithmetic already rest on. What it changes is one direction only: an undeclared os_families is the empty set, which refuses every spec that names a family, so a posix workload this backend could always have run was turned away at attach. A windows one is still refused here, as it should be — a backend that hands out Windows guests declares them and is matched instead.

Container names are derived from the key rather than remembered, so acquire and dispose agree on one without a registry to keep in sync. Labels are the durable record dispose_scope selects on, and their values are digested when they are long or carry a separator — the same mapping on both sides, because transforming one and not the other makes a purge quietly select nothing.

stop is never used to tear a sandbox down. A container whose init process ignores SIGTERM takes ten seconds to stop and under a quarter of a second to remove, and there is nothing in a sandbox worth waiting for. The one place it is used is the egress proxy, and only where a host registered an observer: its record has to be closed before it is read, or a request answered between the read and the removal reaches nobody. That pays the same ten-second worst case, on the proxy alone, on an acquire that is already collecting records.


Maintained by SOKOLAI BV.

Upgrading to 0.13

The four optional declarations moved into one BackendDeclarations. maf-sandbox 0.26 replaced capabilities, limits, egress_modes and os_families as backend attributes with one declarations object holding them as fields, and this backend follows it. A host that read them off the backend gets an AttributeError:

Was Is
backend.capabilities backend.declarations.capabilities
backend.egress_modes backend.declarations.egress_modes

limits is not in that table because this backend never declared one — the router read its silence as DEFAULT_SANDBOX_LIMITS, and there was no backend.limits to read. backend.declarations.limits now answers with that same constant, so the ceiling is unchanged and the value is newly reachable rather than renamed.

Nothing about what this backend declares changed — the values, and how they are derived from the config, are exactly as they were. maf-sandbox's own README carries the reasoning and what a backend author has to do.

Release files for maf-sandbox-wslc 0.17.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for maf-sandbox-wslc 0.17.0
File Size Uploaded
maf_sandbox_wslc-0.17.0.tar.gz 32.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for maf-sandbox-wslc 0.17.0
File Interpreter ABI Platform
maf_sandbox_wslc-0.17.0-py3-none-any.whl Python 3 none any Details

Total release size: 67.7 kB

Release files / maf_sandbox_wslc-0.17.0.tar.gz

Download URL maf_sandbox_wslc-0.17.0.tar.gz
Size 32.5 kB
Tags Source
SHA-256 checksum
How to use checksums
6d24a54465f0d0e6ad7c57152aee4fa890dd786e503d702f8872d5c1d4ce53b1
BLAKE2b-256 checksum
How to use checksums
a46a19c4ab2fdc7b9f4db479b955594893e21718e9f1dbcc3c9e8f8b7108e6a3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 7, 2026.

Transparency log

Release files / maf_sandbox_wslc-0.17.0-py3-none-any.whl

Download URL maf_sandbox_wslc-0.17.0-py3-none-any.whl
Size 35.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4f813e9e46fbd7c40f8d4e15831b847861b22e293594b5539660e4fe2f596482
BLAKE2b-256 checksum
How to use checksums
45e77b300bcd807fefe11c11a8954c0bf8c91a208a9f24246e57fb83c1f32310
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 7, 2026.

Transparency log

Release history Release notifications | RSS feed

0.25.1

2 release files

0.25.0

2 release files

0.24.0

2 release files

0.23.1

2 release files

0.23.0

2 release files

0.22.0

2 release files

0.21.1

2 release files

0.20.0

2 release files

0.19.1

2 release files

0.19.0

2 release files

This release

0.17.0 This release

2 release files

0.13.0

2 release files

0.12.0

2 release files

0.11.3

2 release files

0.11.2

2 release files

0.10.2

2 release files

0.10.1

2 release files

0.10.0

2 release files

0.9.0

2 release files

0.8.0

2 release files

0.7.0

2 release files

0.6.3

2 release files

0.6.2

2 release files

0.6.1

2 release files

0.6.0

2 release files

0.5.7

2 release files

0.5.6

2 release files

0.5.5

2 release files

0.5.4

2 release files

0.5.3

2 release files

0.5.2

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page