Skip to main content

asta-sandbox

Shared code-execution sandbox abstractions for Asta projects. Provides a uniform async interface over four backends — in-process IPython, Docker, and two Modal modes (stateful kernel, stateless ephemeral) — so application code doesn't depend on a specific execution backend.

Installation

# Base package (in-process backend only, requires ipython)
pip install asta-sandbox

# Modal stateful backend (persistent sandbox + Jupyter Kernel Gateway)
pip install "asta-sandbox[modal-kernel]"

# Modal stateless backend (fresh sandbox per call)
pip install "asta-sandbox[modal-ephemeral]"

# Docker backend
pip install "asta-sandbox[docker]"

# Everything
pip install "asta-sandbox[all]"

For local development against an unpublished branch, swap to a path source (and keep the git source commented out for CI):

[tool.uv.sources]
# Local dev: uncomment below and comment out the git source
# asta-sandbox = { path = "../dv-core-asta-integration/packages/sandbox", editable = true }
asta-sandbox = { git = "https://github.com/allenai/dv-core-asta-integration", subdirectory = "packages/sandbox", rev = "main" }

Then add "asta-sandbox" to the dependencies list of any workspace member that needs it, and run uv sync --all-packages to install.

Quick start — Modal stateful sandbox

ModalKernelExecutor runs a persistent Jupyter Kernel Gateway inside a Modal sandbox. Variable and import state accumulates across run_code() calls, mirroring interactive notebook behaviour.

from asta_sandbox.backends.modal_kernel import ModalKernelExecutor

executor = ModalKernelExecutor(app_name="my-analysis")

result = await executor.run_code("""
%pip install pandas
import pandas as pd
""")
assert not result.error

result = await executor.run_code("""
df = pd.DataFrame({'x': [1, 2, 3], 'y': [4, 5, 6]})
print(df.describe())
""")
print(result.stdout)
print("success:", result.success)

With cloud-mounted buckets

Pass CloudShare objects to make S3 or GCS bucket prefixes available as local paths inside the sandbox, without downloading to the host machine. Multiple buckets (including a mix of S3 and GCS) can be mounted simultaneously by providing one CloudShare per bucket with distinct dest values.

import modal
from asta_sandbox import CloudShare
from asta_sandbox.backends.modal_kernel import ModalKernelExecutor

# modal_secret carries the bucket credentials (AWS or GCS keys) — separate
# from Modal's own authentication, which is configured via `modal token new`.
share = CloudShare(
    bucket="my-data-bucket",
    key_prefix="datasets/project/",
    dest="/data/project/",
    read_only=True,
    modal_secret=modal.Secret.from_name("aws-credentials"),
)

executor = ModalKernelExecutor(app_name="my-analysis")
await executor.add_shares(share)
async with executor:
    # The bucket prefix is now visible at /data/project/ inside the sandbox
    result = await executor.run_code("import os; print(os.listdir('/data/project/'))")
    print(result.stdout)

For GCS, add bucket_endpoint_url="https://storage.googleapis.com" to the CloudShare.

Installing packages at runtime

Use a %pip install magic inside run_code(). Because the stateful backends use a persistent kernel, the installed package stays available for subsequent calls in the same session:

async with ModalKernelExecutor(app_name="my-analysis") as executor:
    await executor.run_code("%pip install scanpy anndata")
    result = await executor.run_code("import scanpy; print(scanpy.__version__)")

Custom image

Use build_modal_kernel_image to bake packages into the image instead of installing them at runtime (faster cold starts, reproducible environment):

from asta_sandbox.images import build_modal_kernel_image
from asta_sandbox.backends.modal_kernel import ModalKernelExecutor

image = build_modal_kernel_image(extra_packages=["scanpy", "anndata", "matplotlib"])

async with ModalKernelExecutor(app_name="my-analysis", image=image) as executor:
    result = await executor.run_code("import scanpy; print(scanpy.__version__)")

Quick start — Modal stateless (ephemeral) sandbox

ModalEphemeralExecutor creates a fresh Modal sandbox for every run_code() call. No state survives between calls. The executor instance itself is long-lived; construct it once and call it many times.

from asta_sandbox.backends.modal_ephemeral import ModalEphemeralExecutor

async with ModalEphemeralExecutor(app_name="my-runner") as executor:
    r1 = await executor.run_code("x = 42; print(x)")
    r2 = await executor.run_code("print(x)")   # NameError — x not in this sandbox

    print(r1.stdout)        # "42"
    print(r2.success)       # False
    print(r2.error.etype)   # "NameError"

For a known fixed set of packages, bake them into the image (faster, reproducible):

from asta_sandbox.images import build_modal_ephemeral_image

image = build_modal_ephemeral_image(extra_packages=["httpx"])
executor = ModalEphemeralExecutor(app_name="my-runner", image=image)

Cloud bucket mounts (S3/GCS) are also supported for ephemeral sandboxes via add_shares(). See the Cloud mounts subsection under the stateful sandbox for the CloudShare API; it works identically here.

For on-demand installs (e.g. a coding agent that discovers mid-task it needs a package), embed a %pip install magic at the top of the same code string that uses the package. Because each run_code() call runs in a fresh container, the install and the import must be in the same call:

async with ModalEphemeralExecutor(app_name="my-runner") as executor:
    result = await executor.run_code("""\
%pip install httpx
import httpx
print(httpx.__version__)
""")
    print(result.stdout)

Quick start — in-process (local / testing)

from asta_sandbox import InProcessExecutor

async with InProcessExecutor() as executor:
    await executor.run_code("x = 1 + 1")
    result = await executor.run_code("print(x)")
    print(result.stdout)   # "2"

Quick start — Docker (local stateful sandbox)

DockerExecutor runs a Jupyter Kernel Gateway inside a Docker container. Stateful (like ModalKernelExecutor): variable state persists across calls. Useful for local development and CI without a Modal account.

from asta_sandbox.backends.docker import DockerExecutor

async with DockerExecutor() as executor:
    await executor.run_code("x = 42")
    result = await executor.run_code("print(x)")
    print(result.stdout)   # "42"

The default image is built from dockerfiles/kernel_gateway/Dockerfile. If the image is not present locally, DockerExecutor will build it automatically on first use (requires docker running).

Mounting and uploading files

Use add_shares() to make local paths available inside the container:

from pathlib import Path
from asta_sandbox import CopyShare
from asta_sandbox.backends.docker import DockerExecutor

async with DockerExecutor() as executor:
    await executor.add_shares(CopyShare(source=Path("results.csv"), dest="/workspace/results.csv"))
    result = await executor.run_code("import os; print(os.listdir('/workspace'))")

MountShare (bind-mount) must be added before the first run_code() call, since Docker can't bind-mount into a running container:

from asta_sandbox import MountShare

async with DockerExecutor() as executor:
    await executor.add_shares(MountShare(source=Path("my_data"), dest="/workspace/data"))
    result = await executor.run_code("import os; print(os.listdir('/workspace/data'))")

ModalKernelExecutor supports the same MountShare / CopyShare / CloudShare API. InProcessExecutor does not support file shares. See docs/components.md for the full per-type × backend matrix.


Per-call timeout

Pass timeout_seconds to limit how long a single run_code() call may run:

result = await executor.run_code(
    "import time; time.sleep(10)",
    timeout_seconds=5,
)
print(result.success)      # False
print(result.error.etype)  # "TimeoutError"

InProcessExecutor enforces a soft limit (asyncio cancels the wait; the underlying thread may still run). Remote backends (Docker, Modal) kill the sandbox/process, giving a hard deadline.


For a full description of all types, backend capabilities, and component relationships, see docs/components.md.


Publishing to PyPI

Publishing is manual. You need a PyPI API token (Account settings → API tokens).

cd packages/sandbox

# 1. Bump version in pyproject.toml (PEP 440: 0.1.0, 0.1.0.dev1, 0.2.0a1, ...)
# 2. Build and publish:

rm -rf dist/
uv build
uv publish --token pypi-YOUR_TOKEN_HERE

Dev/pre-release versions (0.1.0.dev1, 0.1.0a1, etc.) are not installed by pip install asta-sandbox by default — users must pin the exact version or pass --pre.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

asta_sandbox-0.1.3.tar.gz (24.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

asta_sandbox-0.1.3-py3-none-any.whl (33.5 kB view details)

Uploaded Python 3

File details

Details for the file asta_sandbox-0.1.3.tar.gz.

File metadata

  • Download URL: asta_sandbox-0.1.3.tar.gz
  • Upload date:
  • Size: 24.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.7.19

File hashes

Hashes for asta_sandbox-0.1.3.tar.gz
Algorithm Hash digest
SHA256 7dbf0c8f62d41053dd7db3cad4e4fef921d43800cea9227554e691bf27d77753
MD5 88593ddebdc2043ecc9755a836d5ca7e
BLAKE2b-256 068fc0fb2b579a8c8c596e91bcf4cedc77cc33b29c34a3c0314325e66dfde621

See more details on using hashes here.

File details

Details for the file asta_sandbox-0.1.3-py3-none-any.whl.

File metadata

File hashes

Hashes for asta_sandbox-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 4b0596d70517230fb6bd0ee3c8373edc442ded0fb555f3a7468126eb9d0f0783
MD5 a8be377285c4a86a067fb007ab7105f7
BLAKE2b-256 0c4a81cf79c318ac762d931bbb2785dd943fedd8a4e674588b8134683bf0c306

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.3 This release

2 files

0.1.2

2 files

0.1.1

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page