Resoluto sandbox: store-mediated, Kata-isolated, cloud-agnostic execution substrate
Project description
resoluto-sandbox
Run untrusted code โ AI-generated, third-party, or adversarial โ with a dedicated Linux kernel per
run. resoluto-sandbox executes any program inside a Kata microVM (a hardware-virtualized guest
with its own kernel, the isolation model serverless platforms use to run untrusted multi-tenant code)
and moves data across the boundary through a durable store. A workload that gets root, forks a miner,
or reaches for your credentials is confined to a VM with no host filesystem, no host network, and
nothing you didn't hand it.
The security model is zero-trust: the workload is assumed hostile and granted nothing by default.
Your program stays plain โ it reads argv, writes stdout/files, exits, and never imports
resoluto.sandbox. A script that runs as uv run agent.py on your machine runs unchanged inside the
microVM.
๐ Documentation ยท ๐ค llms.txt for AI agents
Install
The Python package installs anywhere (Python 3.12+); its base is pydantic-only. Heavy deps are gated behind extras:
pip install resoluto-sandbox # base โ pydantic only, no cloud/k8s deps
pip install "resoluto-sandbox[s3]" # S3 / minio Conduit (aioboto3)
pip install "resoluto-sandbox[k8s]" # k8s backend (kubernetes-asyncio + aioboto3)
pip install "resoluto-sandbox[gcs]" # GCS Conduit (gcloud-aio-storage)
uv is recommended for running programs/examples.
Requirements (host)
Running a sandbox needs an isolation host. The local backend (Kata microVMs) is Linux + KVM only
โ the k8s backend's client runs anywhere (the microVMs run in your cluster).
| Backend | Runs the microVM on | Host needs |
|---|---|---|
local |
Linux with /dev/kvm โ bare metal, or a VM with nested virtualization |
Kata Containers, a nerdctl-full bundle (containerd + CNI), Docker (builds images), a local OCI registry, sudo |
k8s |
your Kubernetes cluster | a cluster with the Kata runtime class + an S3/minio store; resoluto-sandbox[k8s] on the client |
macOS / Windows: Kata microVMs need a Linux kernel + KVM, so the
localbackend does not run natively. Use a Linux VM with nested virt enabled, or point thek8sbackend at a remote cluster โ the client side runs on any OS.
local backend components (Linux)
scripts/local-backend-up.sh verifies these, then provisions the rest (dedicated containerd, CNI
bridge, local.env, and a green Kata-microVM canary). Egress is runtime-managed per run
(Sandbox.run(egress=[...])) โ the script provisions no firewall. It does not
install the kernel/Kata/nerdctl for you:
| Component | Why | How to get it |
|---|---|---|
KVM (/dev/kvm) |
Kata boots a real VM | your distro's virtualization packages (e.g. qemu-kvm/libvirt on Debian/Ubuntu, qemu on Fedora/Arch); on a VM, enable nested virtualization |
Kata Containers โ /opt/kata |
the VM runtime (kata-runtime, containerd-shim-kata-v2) |
static release tarball from kata-containers releases extracted to /opt/kata (verified with 3.31.0) โ not a distro package |
nerdctl-full โ /opt/resoluto-local |
containerd + nerdctl + CNI plugins, standalone from Docker/k3s | the nerdctl-full-* release tarball from nerdctl releases (verified with 2.3.3) |
| Docker | builds the base / provider / your own images (docker build, image build, image push) |
Docker Engine โ your distro's package or the official apt repo |
OCI registry on localhost:5000 |
bridges Docker's image store โ the sandbox's separate containerd (see Prebuilt provider images) | local-backend-up.sh starts one for you if Docker is present; or run it yourself: docker run -d --restart unless-stopped -p 5000:5000 --name registry registry:2 |
Architecture: amd64 and arm64 Linux both work โ match the Kata/nerdctl tarball to your arch (the images build for the host arch automatically). Then:
bash scripts/local-backend-up.sh # verify + provision โ green canary
set -a; source local.env; set +a # exports RESOLUTO_SANDBOX_IMAGE etc.
Quickstart
The sandbox runs your program in a Kata microVM โ there is no zero-isolation mode โ so it needs a
one-time host provisioning first: /dev/kvm + Kata + a dedicated containerd + the sandbox image. The
provisioning script does all of it and writes local.env (which is git-ignored and does not
ship in the repo). From a repo clone:
bash scripts/local-backend-up.sh # provisions the local backend + image, writes local.env, ends on a green canary
set -a; source local.env; set +a # exports RESOLUTO_SANDBOX_IMAGE
uv run python examples/run_hello_in_sandbox.py # the bare mechanics โ a plain program, isolated
Then, in your own code:
from resoluto.sandbox import Sandbox
result = Sandbox(backend="local").run(
["python", "-c", "print('hello from the sandbox')"]
)
print(result.output) # hello from the sandbox
print(result.ok) # True
The result captures the output, the exit code, and any files you asked to collect (output_paths).
stdin is not supported โ pass inputs via argv, env, or workspace files.
The local backend runs in a Kata microVM and needs a sandbox image present in its dedicated containerd (not your regular Docker daemon โ see Prebuilt provider images for the transfer step) โ pass
Sandbox(backend="local", image="โฆ")(defaultresoluto-sandbox-base:<installed wheel version>โdefault_local_image(), never a floating tag; build it fromDockerfile.baseorresoluto-sandbox image build). Run argv with the guest'spythonand paths relative toworkspace, not host absolute paths.
Run an arbitrary program, isolated โ the sandbox runs any untrusted program; an LLM agent is just
one example. run_agent_in_sandbox.py <provider> is symmetric across every provider image the sandbox
ships โ the name you pass selects the matching prebuilt image, payload, credential, and egress host
(nothing privileges one provider). Each payload is a plain program that never imports the library:
set -a; source local.env; set +a # provision the local Kata backend first; build the provider image too
export OPENAI_API_KEY=... # each provider brings its OWN credential; the sandbox just forwards it
uv run python examples/run_agent_in_sandbox.py openai "In five words, why isolate an agent?"
# provider : openai (resoluto-sandbox:openai-agents-0.17.7)
# INPUT : 'In five words, why isolate an agent?'
# OUTPUT : 'Untrusted code cannot escape containment.'
# ...or `claude` (CLAUDE_CODE_OAUTH_TOKEN) / `langchain` (ANTHROPIC_API_KEY) โ same driver, different image.
For the bare mechanics without an LLM, see examples/run_hello_in_sandbox.py. The end-to-end
verification harnesses that drive a minimal agent through BOTH backends (local + k8s) live in
tests/smoke/ (smoke_both_backends.py, smoke_llm.py).
The program contract
A sandbox program reads argv, writes to stdout / files, exits with a code, and never imports
resoluto.sandbox. A script that works as uv run agent.py works unchanged inside the sandbox; test
runners, LLM agents, and shell scripts all qualify. Dependencies are your program's concern โ put
uv run / pip install in your argv, or bake them into the image.
How it works
The host and the sandbox never hold a live connection. They rendezvous through a durable key/value
store (the Conduit): the sandbox is passive โ it writes append-only JSONL chunks and a final
result.json; the host launches it, tails the chunks, and reaps it. The same flow works whether the
sandbox is a microVM next to you or a pod in a cluster, and a network blip can't wedge a run.
Components
| Component | What it is | What it does |
|---|---|---|
| Your program | Any script/binary โ plain | Reads argv/env, writes stdout/files, exits. Never imports resoluto.sandbox. |
Sandbox |
Thin Python facade | Sandbox(backend=...).run(argv, ...) โ one call, identical for every backend. |
SubstrateBackend |
The one backend impl | Drives the 3-phase flow (stage โ run โ collect). Holds one SandboxRuntime + one Conduit. |
SandboxRuntime (ABC) |
The isolation/placement seam | Launches, checks status, destroys the isolated sandbox. Impls: KataNerdctlSandboxRuntime (local), K8sSandboxRuntime (k8s). |
Conduit (ABC) |
The durable exchange seam | put / get / list_prefix on a key/value store โ the ONLY channel between host and sandbox. Impls: LocalConduit (bind-mounted dir), S3Conduit (minio/S3), StdoutConduit, GcsConduit (unverified). |
runner_main |
The in-guest entrypoint | Runs inside the sandbox only โ your program never sees it. Verifies the egress canary, stages inputs from the Conduit, execs your argv, ships spans/heartbeat/result back to the Conduit. |
| sandbox image | The OCI image the sandbox boots | Must contain your program's runtime + the resoluto-sandbox wheel. Prebuilt overlays: resoluto-sandbox:claude-agent-sdk-<ver>, :langchain-<ver>, :openai-agents-<ver> (see Images below). |
Architecture โ local vs. k8s
Same isolation model on both backends โ a Kata microVM with its own guest kernel, egress denied by default until you open exactly what you need, and a fail-closed canary that verifies isolation before your program runs. Only the placement and the Conduit implementation differ:
LOCAL BACKEND (single host, no cluster)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Host process (your code) โ
โ Sandbox(backend="local") โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
nerdctl
(dedicated containerd)
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ISOLATED: Kata microVM โ
โ (own guest kernel) โ
โ runner_main -> your argv โ
โ egress: NONE by default (no network interface) โ
โ open per-run -> CNI bridge + SNI proxy โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
bind mount (its own run prefix)
โ
โผ
LocalConduit (host dir)
K8S BACKEND (Kubernetes cluster)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Host process (your code) โ
โ Sandbox(backend=SubstrateBackend( โ
โ K8sSandboxRuntime(...))) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
kube API
(pinned context)
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ISOLATED: Kata microVM pod โ
โ (own guest kernel) โ
โ runner_main -> your argv โ
โ egress: k8s NetworkPolicy, default-deny โ
โ (DNS + store only, until opened) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
HTTPS (S3 API)
โ
โผ
S3Conduit (minio / S3 bucket)
local needs /dev/kvm + nerdctl + a dedicated containerd, provisioned by
scripts/local-backend-up.sh (ends in a green Kata-microVM canary). k8s needs a cluster with Kata
Containers installed, an S3-compatible store, and a pinned kube context. Full setup โ
including the vendor-neutral k8s stack โ is in docs/backends.md.
Data flow โ the Conduit
The host and the sandbox never talk to each other directly โ every arrow below touches the Conduit, never the other side. That's what makes a network blip harmless: nothing is waiting on a live connection.
| Phase | Host does | Conduit holds | Sandbox does |
|---|---|---|---|
| 1. Stage | put_dir(workspace) โ writes the input archive |
the input archive, keyed to this run | (not launched yet) |
| 2. Run | launches the sandbox, then only tails the Conduit | spans + heartbeat + result.json + output archive, as runner_main writes them |
canary โ stages inputs from the Conduit โ execs your argv โ ships output to the Conduit |
| 3. Collect | list_prefix + get โ assembles RunResult, reaps the sandbox |
the final chunks, until reaped | destroyed |
Live output still streams to stream (default sys.stdout) as it happens during 2. Run โ the
table above is what's durable, not the only thing you see.
Security model
Zero-trust: the workload is assumed hostile and granted nothing by default. Every crossing of the boundary is explicit.
| Guarantee | How |
|---|---|
| Own kernel | Each run is a Kata microVM with its own Linux kernel โ hardware-virtualized, not a shared-kernel namespace/cgroup container. A guest-kernel exploit stops at the VM. |
| Unprivileged workload | The program runs as a non-root user with no Linux capabilities and no privilege escalation. The optional docker-in-docker mode is guest-scoped (privileged-without-host-devices) and still drops the workload to a non-root user. |
| No ambient host access | No host filesystem, no host devices, no host kernel memory, no host control-plane sockets (containerd/Docker), and its own PID namespace. Host credentials are never forwarded โ the guest gets only a scoped, short-lived store token. |
| Isolated per run | Each run is keyed to its own store prefix. On local the guest is bind-mounted only that prefix; on k8s it holds only a prefix-scoped credential. Runs cannot read each other's state. |
| Default-deny egress | A fresh workload has no network โ on local, no network interface at all; on k8s, a default-deny NetworkPolicy. You grant specific domains per run. Cloud metadata (IMDS 169.254.169.254) and RFC1918 private ranges are refused even on an allowlist match, so an opened domain can't pivot into your network. |
| Data in, contained out | Inputs and outputs cross as data, never as control. The host never executes what the workload produces; output collection is scoped to the paths you declare and rejects path-traversal and symlink escapes. |
| No silent downgrade | VM-grade or nothing. The Kata runtime-class guard is unconditional and egress is fail-closed; a weaker posture is only ever selected explicitly. A fail-closed canary proves isolation is in force before your program starts. |
Trust assumption. The boundary is a virtual machine, so the trusted computing base is the hypervisor and the host kernel โ the same assumption behind any virtualization or serverless platform. Keeping the isolation host (KVM, Kata, your kernel) patched is the residual risk, and the only one.
Egress โ deny by default
Untrusted code should not be able to phone home, and by default it can't.
localโ the default run gets no network interface. There is nothing to reach: no internet, no DNS, no LLM, no registries. The store is a host bind mount, not a network endpoint, so a run is fully functional with zero network. Opening an allowlist attaches a NIC on an isolated bridge, routed through a built-in SNI proxy that forwards only connections whose TLS SNI matches a domain you listed.k8sโ the pod reaches its S3 store over the network, so it always has an interface, governed by a default-deny NetworkPolicy: the store and DNS only, until you open specific domains.
Grant egress per run, by domain:
Sandbox(backend="local").run(argv) # no network (default)
Sandbox(backend="local").run(argv, egress=["api.anthropic.com"]) # only Anthropic, this run
Sandbox(backend="local").run(argv, egress=["registry.npmjs.org", "*.openai.com"])
The allowlist matches by domain (TLS SNI), not IP, so it survives the address rotation of CDN-backed
APIs; it does no CA/MITM. Private ranges and cloud metadata are refused even on a match. None/[]
returns to the no-network default. Open registry.npmjs.org and npm install reaches the registry and
nothing else; drop it and the same install fails.
run(egress=[...])is enforced by thelocalbackend. Onk8s, egress is a per-runtimeEgressConfigrendered to a default-denyNetworkPolicy;public_https=Trueis the explicit escape hatch that allows all outbound HTTPS for trusted code. Seedocs/networking.md.
Sandbox.run() reference
Sandbox(backend="local").run(
argv, # program + arguments
*,
workspace=None, # dir staged into the sandbox at /workspace; None = nothing staged at all
stdin=None, # unsupported โ raises NotImplementedError
env=None, # dict overlaid on the sandbox environment
env_file=None, # dotenv file merged UNDER env (env wins) โ convenience, not security (see below)
secrets=None, # dict[str, str | SecretKeyRef] โ see "Secrets" below
output_paths=None, # glob patterns collected back as artifacts
stream=None, # live output sink; None echoes to sys.stdout
egress=None, # domains allowed for THIS run (local); None/[] = no network at all
) -> RunResult
RunResult: exit_code, output, errors (always empty โ stdout/stderr are merged), artifacts,
result (parsed result.json if the program wrote one), ok (exit_code == 0).
Secrets
Three mechanisms, for three different jobs โ none is a drop-in replacement for another:
| Mechanism | Where the plaintext lands | Use for |
|---|---|---|
env_file="path/to/.env" |
Literal env entry (pod spec / nerdctl -e) โ same exposure as env= |
Local dev config/secrets; pure convenience, not a security boundary |
secrets={"VAR": SecretKeyRef("my-secret", "key")} |
Never touches resoluto-sandbox at all โ kubelet materializes it | k8s only. References a Secret object that already exists (created by kubectl, External Secrets Operator, or anything else) |
secrets={"VAR": "vault:secret/data/x#key"} |
Resolved inside the guest by a SecretProvider โ the host never sees the value |
Portable across local/k8s. Ships as an ABC only today โ see secrets.py; implement a concrete provider (Vault, AWS Secrets Manager, GCP Secret Manager, ...) and dispatch it in secrets_from_env() |
from resoluto.sandbox import Sandbox
from resoluto.sandbox.secrets import SecretKeyRef
# env_file โ host reads a dotenv file, merges it into the env (env= wins on conflict)
Sandbox(backend="local").run(argv, env_file=".env")
# SecretKeyRef โ k8s-native, zero fetch code, ignored on the local backend
Sandbox(backend="k8s", image=...).run(argv, secrets={"ANTHROPIC_API_KEY": SecretKeyRef("anthropic-key", "api_key")})
# SecretProvider ref โ guest resolves it itself; host only holds a scoped RESOLUTO_SECRETS_* credential
Sandbox(backend="local").run(argv, secrets={"ANTHROPIC_API_KEY": "vault:secret/data/anthropic#api_key"})
The host never mints credentials for the SecretProvider path โ same posture as the Conduit's
RESOLUTO_STORE_WRITE_TOKEN today: you mint an already-scoped, short-lived credential yourself
(a Vault token, an AWS STS AssumeRole triple, a GCP impersonation-minted OAuth2 token โ never a
static key file) and pass it via RESOLUTO_SECRETS_KIND/RESOLUTO_SECRETS_* env vars; resoluto-sandbox
only plumbs it to the guest and calls .get(ref) right before your program execs.
CLI
resoluto-sandbox run -- echo hi # local backend (default)
resoluto-sandbox run --backend k8s --image <img> -- python agent.py
resoluto-sandbox run --env-file .env -- python3 agent.py # dotenv merged into the sandbox env
resoluto-sandbox doctor # check backend readiness
-- separates sandbox options from the program argv. --env-file is CLI-only for now โ secrets=
(SecretKeyRef/SecretProvider) is a Python API construct (needs a typed value, not just a string)
and has no CLI flag yet.
Prebuilt provider images
Each overlay pins one SDK version and tags itself by it โ the tag says exactly what's inside.
image build builds with Docker and pushes to the registry the local backend pulls from (see the
box below), so after it runs the image is ready โ no manual transfer step. Bring the backend up first
(scripts/local-backend-up.sh) so the registry exists:
resoluto-sandbox image build --provider claude # -> pushed localhost:5000/resoluto-sandbox:claude-agent-sdk-0.2.110
resoluto-sandbox image build --provider openai # -> pushed localhost:5000/resoluto-sandbox:openai-agents-0.17.7 (also serves `openrouter`)
resoluto-sandbox image build --provider all # base once, then every overlay
| Provider | Bakes | Example agent | Auth |
|---|---|---|---|
claude |
@anthropic-ai/claude-code + claude-agent-sdk |
examples/payloads/claude_agent.py |
Claude Max/Pro subscription (claude setup-token) or ANTHROPIC_API_KEY โ see docs/auth.md |
langchain |
bare langchain + langgraph โ no LLM integration |
examples/payloads/langchain_agent.py |
Depends which integration you add โ see below |
openai |
openai-agents |
examples/payloads/openai_agent.py |
OPENAI_API_KEY โ pay-as-you-go API only, OPENAI_MODEL override |
openrouter |
(reuses the openai image) |
examples/payloads/openai_agent.py (via OPENAI_BASE_URL) |
OPENROUTER_API_KEY โ OpenAI-compatible endpoint https://openrouter.ai/api/v1, default model mistralai/mistral-small-3.2-24b-instruct, OPENROUTER_MODEL override |
The
langchainimage is bare on purpose. LangChain itself is provider-agnostic โ it has no built-in way to call an LLM. To actually use it, extend the image with the matching integration package for whichever provider you want:FROM resoluto-sandbox:langchain-1.3.11 RUN pip install --break-system-packages langchain-anthropic # or langchain-openai, etc.
examples/payloads/langchain_agent.pydemonstrates the Anthropic integration specifically (needslangchain-anthropic+ANTHROPIC_API_KEY, model overrideANTHROPIC_MODEL) โ it willImportErroragainst the plain prebuiltlangchainimage until you extend it this way.
Two image stores โ and how a built image reaches the sandbox.
docker buildlands the image in your regular Docker daemon. Thelocalbackend does not use Docker: it launches Kata microVMs vianerdctlagainst its OWN dedicated containerd (/run/resoluto-local/containerd/, set up byscripts/local-backend-up.sh) โ a separate image store that can't see whatdocker buildproduced. The bridge between the two is the on-box registry:
resoluto-sandbox image buildbuilds with Docker and pushes to the registry (localhost:5000by default; setRESOLUTO_SANDBOX_REGISTRYfor k8s or a shared registry).- The examples reference the registry-qualified tag (
localhost:5000/resoluto-sandbox:โฆ, viaimages.pullable()), and the backend pulls it on demand โlocalhostregistries are insecure/HTTP by default, sonerdctl runpulls with no extra flag.So after
image buildthere is nothing else to do โ the image is in the registry and the firstrunpulls it into the containerd (then it's cached). This mirrors how the base image already flows (local-backend-up.shpushes it to the same registry). It's the exact mechanismRESOLUTO_SANDBOX_IMAGEuses โ alocalhost:5000/โฆreference.No registry available? Set
RESOLUTO_SANDBOX_REGISTRY=""(build stays a bare tag) and transfer the image into the containerd directly instead:docker save resoluto-sandbox:openai-agents-0.17.7 \ | sudo "$RESOLUTO_LOCAL_NERDCTL" --address /run/resoluto-local/containerd/containerd.sock \ --namespace resoluto-local load
Bring your own image
The registry bridge is not provider-specific โ any image the backend can pull works, so your own
Dockerfile is a first-class citizen. Build it, publish it with image push, and pass the reference
as image= (Python) / --image (CLI):
cat > Dockerfile <<'DOCKER'
FROM localhost:5000/resoluto-sandbox:langchain-1.3.11 # or the base, or any image
RUN pip install --break-system-packages langchain-anthropic
DOCKER
docker build -t my-langchain-agent:1.0 .
resoluto-sandbox image push my-langchain-agent:1.0 # -> pushed localhost:5000/my-langchain-agent:1.0
resoluto-sandbox run --image localhost:5000/my-langchain-agent:1.0 -- python my_agent.py
Sandbox(backend="local", image="localhost:5000/my-langchain-agent:1.0").run([...])
image push tags the local image for the configured registry (RESOLUTO_SANDBOX_REGISTRY, default
localhost:5000) and pushes it; an already registry-qualified tag is pushed as-is. Equivalently, skip
it by building straight to the registry tag (docker build -t localhost:5000/my-agent:1.0 . && docker push โฆ). Either way the backend pulls it on first run. (FROM a provider tag needs that image in
your Docker store first โ resoluto-sandbox image build --provider langchain โ or FROM localhost:5000/โฆ to pull the base from the registry.)
Verified end to end against the real Kata sandbox (all three: canary passes, workspace stages, the
script runs and reaches its auth check). claude and openai run against the plain prebuilt image;
langchain needs the one-line extended image from above (built as my-langchain-anthropic:0.1.0 here):
from resoluto.sandbox import Sandbox
# workspace="examples/payloads" stages that DIRECTORY'S CONTENTS at /workspace โ argv paths are
# relative to that root, never prefixed with the directory again.
r = Sandbox(backend="local", image="my-langchain-anthropic:0.1.0").run(
["python3", "langchain_agent.py", "Say hello in five words"],
workspace="examples/payloads",
env={"ANTHROPIC_API_KEY": "..."},
)
print(r.output)
Swap the image= tag + example script + env var to switch providers โ everything else is identical:
Sandbox(backend="local", image="resoluto-sandbox:claude-agent-sdk-0.2.110").run(
["python3", "claude_agent.py", "Say hello in five words"], workspace="examples/payloads",
env={"CLAUDE_CODE_OAUTH_TOKEN": "..."}, # never set ANTHROPIC_API_KEY alongside this
)
Sandbox(backend="local", image="resoluto-sandbox:openai-agents-0.17.7").run(
["python3", "openai_agent.py", "Say hello in five words"], workspace="examples/payloads",
env={"OPENAI_API_KEY": "..."},
)
Or via the CLI (--workspace is REQUIRED to stage anything โ without it /workspace is empty and
your script won't be found; run from the repo root so . stages examples/ alongside it):
resoluto-sandbox run --workspace examples/payloads --image resoluto-sandbox:openai-agents-0.17.7 -- python3 openai_agent.py "hi"
On k8s, retag + push to your registry (docs/backends.md), then inject the same tag through
SubstrateBackend(image=...) โ see docs/concepts.md for the full wiring.
Status
| Feature | Status |
|---|---|
backend="local" โ Kata microVM via nerdctl + a dedicated containerd, runtime-managed per-run egress |
works today (run scripts/local-backend-up.sh) |
backend="k8s" โ Kata microVM pod + object-store Conduit + NetworkPolicy egress |
works today โ needs a Kata cluster + store + kube context |
Conduit + LocalConduit, StdoutConduit, S3Conduit (minio/S3) |
works today |
GcsConduit |
provided, unverified โ experimental |
| Language-neutral wire spec | published โ see spec/PROTOCOL.md |
Prebuilt image matrix + image build CLI |
works today โ resoluto-sandbox image build --provider claude|langchain|openai|all |
The Resoluto ecosystem
Three independent, plug-and-play packages under one resoluto.* namespace and one design language. Each stands alone โ use any without the others.
| Package | Role |
|---|---|
| resoluto-sandbox | Store-mediated, Kata-isolated execution substrate |
| resoluto-agent | Pluggable LLM agent-provider plugins |
| resoluto-engine | Durable orchestrator of sandboxed, gate-verified agentic work |
resoluto-engine builds on resoluto-agent (the provider contract) and optionally resoluto-sandbox (isolation); resoluto-sandbox and resoluto-agent depend on nothing else in the ecosystem.
Further reading
docs/concepts.mdโ the program contract, the run lifecycle, the Conduitdocs/backends.mdโ backend setup + the vendor-neutral k8s stack installdocs/networking.mdโ egress isolation (the canary + per-backend enforcement)docs/auth.mdโ credentials: passing each provider's secret to the guest (the subscription path is local-dev only)spec/PROTOCOL.mdโ the language-neutral host โ sandbox wire protocolexamples/โrun_agent_in_sandbox.py <claude|langchain|openai>runs any provider's agent isolated (symmetric across all three images);run_hello_in_sandbox.pyis the bare mechanics.payloads/holds the plain programs run inside (hello.py,claude_agent.py,langchain_agent.py,openai_agent.py, one per prebuilt provider image). Seeexamples/README.md.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file resoluto_sandbox-0.1.0.tar.gz.
File metadata
- Download URL: resoluto_sandbox-0.1.0.tar.gz
- Upload date:
- Size: 346.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b2845755fe2ffb2e08554f2496ae564d2b1310bd4f1bf213a36723820dbf33b3
|
|
| MD5 |
f16dcf915a14c4e7fc0775525a604d64
|
|
| BLAKE2b-256 |
8b5e559b2c415502cf96d65ebaa31863f7387d13eb020a8983c97164d97fdf3d
|
Provenance
The following attestation bundles were made for resoluto_sandbox-0.1.0.tar.gz:
Publisher:
workflow.yml on DeepBlueCoding/resoluto-sandbox
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
resoluto_sandbox-0.1.0.tar.gz -
Subject digest:
b2845755fe2ffb2e08554f2496ae564d2b1310bd4f1bf213a36723820dbf33b3 - Sigstore transparency entry: 2167193687
- Sigstore integration time:
-
Permalink:
DeepBlueCoding/resoluto-sandbox@8eec877795d9bf181bf40d266bbe1228bd20e7df -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/DeepBlueCoding
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
workflow.yml@8eec877795d9bf181bf40d266bbe1228bd20e7df -
Trigger Event:
push
-
Statement type:
File details
Details for the file resoluto_sandbox-0.1.0-py3-none-any.whl.
File metadata
- Download URL: resoluto_sandbox-0.1.0-py3-none-any.whl
- Upload date:
- Size: 79.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
eadf08ff7c47a3b2674cd62e79f1f01580259eb6babab4c04f9108347995dcfd
|
|
| MD5 |
1347de31bbdd48ce974441843789296b
|
|
| BLAKE2b-256 |
790b31eb5acb0c4b3cff0d6a7283549058dfcf051cbe668a6d481c79689f42af
|
Provenance
The following attestation bundles were made for resoluto_sandbox-0.1.0-py3-none-any.whl:
Publisher:
workflow.yml on DeepBlueCoding/resoluto-sandbox
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
resoluto_sandbox-0.1.0-py3-none-any.whl -
Subject digest:
eadf08ff7c47a3b2674cd62e79f1f01580259eb6babab4c04f9108347995dcfd - Sigstore transparency entry: 2167193712
- Sigstore integration time:
-
Permalink:
DeepBlueCoding/resoluto-sandbox@8eec877795d9bf181bf40d266bbe1228bd20e7df -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/DeepBlueCoding
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
workflow.yml@8eec877795d9bf181bf40d266bbe1228bd20e7df -
Trigger Event:
push
-
Statement type: