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.1.tar.gz (112.3 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.1-py3-none-any.whl (23.2 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for quixand-0.1.1.tar.gz
Algorithm Hash digest
SHA256 4ad3f5ed2cd7556a749a081973bfcab4c788da23c82a65ca121a55815333b67e
MD5 f20af776aff431d3910db44ee74593dc
BLAKE2b-256 895ad6a2e1e363adb1458288750e74377a3a9339050c12b72f45d868b41062dc

See more details on using hashes here.

File details

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

File metadata

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

File hashes

Hashes for quixand-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 ae29a9ff254426562ab79c65a71ca498c60465580d4db3a918a133363dfa1623
MD5 d04e44d393f195f52e9563af36fba550
BLAKE2b-256 38367b97ed416fb46c9bf1f48bc526eebb58a6d4503d9082733075ec3119a792

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