Skip to main content

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 Adapter protocol 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-http
  • QS_TIMEOUT_DEFAULT: default timeout seconds (default 300)
  • QS_IMAGE: default container image (default python: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 containers
  • ChutesAdapter (skeleton) — wire to your infra
  • RemoteHTTPAdapter (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); none supported
  • 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 docker group (Linux) or run Docker Desktop
  • Hanging exec or timeouts: increase timeout in run() or QS_TIMEOUT_DEFAULT
  • Unicode output: CommandResult.text decodes UTF-8 with replacement; use stdout bytes for raw data

Examples

See examples/:

  • minimal.py
  • async_minimal.py
  • template_build.py
  • streaming.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

quixand-0.1.3.tar.gz (130.6 kB view details)

Uploaded Source

Built Distribution

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

quixand-0.1.3-py3-none-any.whl (23.7 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: quixand-0.1.3.tar.gz
  • Upload date:
  • Size: 130.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.10.15

File hashes

Hashes for quixand-0.1.3.tar.gz
Algorithm Hash digest
SHA256 69613c753ee675ec4c9092819abdfb785724a9f00e383a2bbb992c4bb5571446
MD5 7b2d3ccad126e89122070e0bcf1de951
BLAKE2b-256 458c8941c918149edf92d4245e58a04f857d1a17325075520d5a7f3e22e0f251

See more details on using hashes here.

File details

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

File metadata

  • Download URL: quixand-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 23.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.10.15

File hashes

Hashes for quixand-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 18e05284fea08ec7336e75653ef0429344756d78cdcad091490fab37de8a0478
MD5 b9ac829d58489aadd8a18ba2ffc3ecf0
BLAKE2b-256 dac70bc934924662790cf39aa5a56daff1978ffeb464380d9e538ef0e2265aeb

See more details on using hashes here.

Supported by

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