Modal Sandbox Backend
autodiscovery_modal provides helpers for running isolated code in Modal
ephemeral sandboxes. Each call creates a fresh sandbox, executes the code, and
terminates the sandbox. No state persists between calls.
The main types re-exported from asta_sandbox:
ModalEphemeralExecutor: Async executor that creates a fresh Modal sandbox perrun_code()call.CloudShare: Mounts a cloud storage bucket prefix into the sandbox filesystem.build_sandbox_image/build_modal_ephemeral_image: Build Modal images with IPython and optional extra packages.
Usage Examples
Basic execution
import asyncio
from autodiscovery_modal import ModalEphemeralExecutor, build_sandbox_image
image = build_sandbox_image(extra_packages=["numpy", "pandas", "matplotlib", "matplotlib-inline"])
executor = ModalEphemeralExecutor(app_name="my-app", image=image)
async def main():
await executor.start()
result = await executor.run_code("print('hello')\n1 + 1")
print(result.stdout)
print(result.success)
await executor.shutdown()
asyncio.run(main())
Execution with a GCS bucket mount
Mount a store prefix at its store-shaped path: key users/<uid>/... at
/mnt/data/users/<uid>/..., the same path the job containers see it at. AutoDiscovery's
modal backend mounts the run's users/<uid>/jobs/<jid>/data/ prefix this way, sets
DATASET_ROOT to it, and chdirs into it before each cell so generated code can open
datasets by bare filename.
import asyncio
import modal
from autodiscovery_modal import ModalEphemeralExecutor, CloudShare, build_sandbox_image
image = build_sandbox_image(extra_packages=["pandas"])
cloud_share = CloudShare(
dest="/mnt/data/users/u1/jobs/j1/data",
bucket="my-bucket",
key_prefix="users/u1/jobs/j1/data/",
read_only=True,
bucket_endpoint_url="https://storage.googleapis.com",
modal_secret=modal.Secret.from_name("gcs-my-bucket"),
)
executor = ModalEphemeralExecutor(
app_name="my-app",
image=image,
environment={"DATASET_ROOT": "/mnt/data/users/u1/jobs/j1/data"},
)
async def main():
await executor.start()
await executor.add_shares(cloud_share)
result = await executor.run_code(
"import os, pathlib; print(list(pathlib.Path(os.environ['DATASET_ROOT']).iterdir()))"
)
print(result.stdout)
await executor.shutdown()
asyncio.run(main())
Capturing matplotlib figures
Figures produced by plt.show() are returned as rich_outputs on the result:
import asyncio
from autodiscovery_modal import ModalEphemeralExecutor, build_sandbox_image
image = build_sandbox_image(extra_packages=["matplotlib", "matplotlib-inline"])
executor = ModalEphemeralExecutor(app_name="my-app", image=image)
async def main():
await executor.start()
result = await executor.run_code(
"import matplotlib.pyplot as plt\nplt.plot([1,2,3])\nplt.show()"
)
for ro in result.rich_outputs:
if "image/png" in ro.data:
print("Got PNG figure")
await executor.shutdown()
asyncio.run(main())
Notes
- Each
run_code()call creates a new sandbox. No variables, imports, or data persist between calls. CloudSharemounts are scoped per executor instance. Calladd_shares()beforerun_code().- Use
build_sandbox_image(extra_packages=[...])to bake in Python packages. Modal caches image layers by content hash, so unchanged images are free to reuse. - For the full
asta_sandboxAPI (includingSandboxBase,ExecutionResult,RichOutput), see the asta-sandbox package.