Local-first, adapter-pluggable sandboxes for AI code execution (E2B-like API)
Project description
quixand — Local & Pluggable Sandboxes for AI Code Execution
Quixand is a local-first sandbox and code interpreter library. It mirrors the developer-facing API of E2B’s Sandbox and AsyncSandbox while running locally by default via Docker/Podman, with a clean adapter interface to plug in other backends (your infra, remote HTTP, etc.).
Why quixand?
- Familiar: mirrors E2B’s concepts and method names where practical
- Local-first: default execution in local containers (Docker/Podman)
- Pluggable: unified
Adapterprotocol for custom backends - Async & streaming ready; simple, secure, fast
- CLI parity for everyday workflows
Requirements
- Python 3.10+
- Docker or Podman installed and in PATH
Install
Editable install with Docker extras:
python3 -m venv .venv
. .venv/bin/activate
pip install -U pip
pip install -e '.[docker]'
Configuration
Quixand works out of the box, but you can configure via environment variables or code.
Environment variables:
QS_ADAPTER:local-docker(default) |chutes|remote-httpQS_TIMEOUT_DEFAULT: default timeout seconds (default 300)QS_IMAGE: default container image (defaultpython:3.11-slim)QS_RUNTIME:docker|podman(auto-detects if unset)QS_ROOT: host directory for state/template cache (default~/.quixand)QS_METADATA: JSON string to tag sandboxes
Programmatic config:
import quixand as qs
cfg = qs.Config(timeout=600, image="python:3.11-slim")
Python API
Top-level imports:
from quixand import Sandbox, AsyncSandbox, connect, Templates
Sync Sandbox:
import quixand as qs
sbx = qs.Sandbox(template="python:3.11-slim", timeout=600, metadata={"user":"alice"})
sbx.files.write("hello.txt", "hi!")
print([f.path for f in sbx.files.ls(".")])
res = sbx.run(["python","-c","print(2+2)"])
print(res.text) # "4\n"
execn = sbx.run_code("x=1\nx+=1\nprint(x)")
print(execn.text.strip()) # "2"
sbx.shutdown()
Async Sandbox:
import asyncio, quixand as qs
async def main():
sbx = await qs.AsyncSandbox.create(template="python:3.11-slim")
await sbx.files.put("data.csv", "/workspace/data.csv")
res = await sbx.run(["python","-c","print(42)"])
print(res.text)
await sbx.shutdown()
asyncio.run(main())
Connect to running sandbox:
sbx1 = qs.Sandbox(template="python:3.11-slim")
sid = sbx1.id
sbx2 = qs.connect(sid)
print(sbx2.status())
Filesystem API:
sbx.files.write("/workspace/notes.txt", "hello") # text
sbx.files.write("/workspace/blob.bin", b"\x00\x01", mode="binary")
print(sbx.files.read("/workspace/notes.txt")) # returns str
print(sbx.files.read("/workspace/blob.bin", mode="binary")) # returns bytes
sbx.files.mkdir("/workspace/data", parents=True)
sbx.files.put("./local.txt", "/workspace/local.txt")
sbx.files.get("/workspace/local.txt", "./local_copy.txt")
sbx.files.rm("/workspace/local.txt")
paths = sbx.files.glob("/workspace/*.txt")
Process execution:
res = sbx.run("echo $USER && uname -a")
print(res.exit_code, res.text)
Python code convenience:
sbx.install_pkg("pandas==2.2.2")
execn = sbx.run_code("import pandas as pd; print(pd.__version__)")
print(execn.ok, execn.text)
Networking and ports (adapter-dependent):
binding = sbx.expose(port=8000, host_port=18000, proto="tcp")
print(binding)
Timeouts and lifecycle:
st = sbx.status() # created_at, last_active_at, timeout_at
sbx.refresh_timeout(900) # extend timeout
sbx.shutdown() # stop container and clean state
CLI
The qs CLI mirrors E2B’s verbs.
qs --help
# Sandbox lifecycle
qs sandbox create --template python:3.11-slim --timeout 900 --env FOO=bar --env BAZ=qux
qs sandbox ls
qs sandbox connect <id>
qs sandbox exec <id> -- echo hello
qs sandbox run-code <id> --code "x=1; print(x+1)"
qs sandbox refresh-timeout <id> --seconds 600
qs sandbox kill <id>
# Files
qs files ls <id> /workspace
qs files put <id> ./local.txt /workspace/local.txt
qs files get <id> /workspace/local.txt ./local_copy.txt
qs files mkdir <id> /workspace/data --parents
qs files rm <id> /workspace/local.txt --recursive
# Templates
qs templates build ./path --name py311-tools
qs templates ls
qs templates rm py311-tools
Templates
Quixand supports building images from e2b.Dockerfile or Dockerfile and caching references locally under ~/.quixand/templates.
qs templates build ./examples --name py311-tools
qs templates ls
In code:
from quixand import Templates, Sandbox
img = Templates.build("./examples", name="py311-tools")
sbx = Sandbox(template=img)
Adapters
Adapters implement a common protocol in quixand.adapters.base.Adapter. Included:
LocalDockerAdapter(default) — local Docker/Podman containersChutesAdapter(skeleton) — wire to your infraRemoteHTTPAdapter(skeleton) — call a remote service
Select adapter via QS_ADAPTER or passing an instance to Sandbox(adapter=...).
State and Timeout Enforcement
Quixand keeps a local registry at ~/.quixand/state.json. A lightweight watchdog process enforces idle timeouts and cleans up containers and state when deadlines are exceeded. Default timeout is 300s; refresh with refresh_timeout() or CLI.
Security Defaults
- Non-root user inside container (image-dependent)
- No privileged flags
- Bridge network by default (configurable);
nonesupported - CPU/memory/pids limits supported by the adapter
- Ephemeral writable layer
Troubleshooting
- Docker/Podman not found: ensure one is installed and in PATH
- Permission errors: add your user to the
dockergroup (Linux) or run Docker Desktop - Hanging exec or timeouts: increase
timeoutinrun()orQS_TIMEOUT_DEFAULT - Unicode output:
CommandResult.textdecodes UTF-8 with replacement; usestdoutbytes for raw data
Examples
See examples/:
minimal.pyasync_minimal.pytemplate_build.pystreaming.py(PTY placeholder; streaming to be implemented)
Roadmap
- Full PTY streaming
- Richer RemoteHTTP/Chutes adapters
- Test suite and CI
License
MIT
Project details
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 quixand-0.1.4.tar.gz.
File metadata
- Download URL: quixand-0.1.4.tar.gz
- Upload date:
- Size: 131.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a180488834e004bb9cf905efe9642f5b1b7d5c67f3ed5341fa99751be4d3080a
|
|
| MD5 |
d45a4427083d6626e8962fe4687604fb
|
|
| BLAKE2b-256 |
71d87bd564c613a492bb0edaa18e18521235fd4e344fff30fef029430ebeafd1
|
File details
Details for the file quixand-0.1.4-py3-none-any.whl.
File metadata
- Download URL: quixand-0.1.4-py3-none-any.whl
- Upload date:
- Size: 24.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b1b84a791790fdc40669d6f35cf2cd7221801270575a3a20d589acdd2cda89f6
|
|
| MD5 |
956f611d76691ecf6442571596e54e26
|
|
| BLAKE2b-256 |
fb266ac42d72106f970606bf7d1f6edf734891a138ab4014971f7b10bcb4e9b5
|