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.5.tar.gz (144.7 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.5-py3-none-any.whl (38.5 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: quixand-0.1.5.tar.gz
  • Upload date:
  • Size: 144.7 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.5.tar.gz
Algorithm Hash digest
SHA256 c1be96f0e53248ee00f87d104686fa494f52344c4d9501a44ebf58fa0c13aaf0
MD5 d00792ceb770db5921a59736f877131b
BLAKE2b-256 870026b196de258c1f93496391746d6263c448a8d0223cbe3cf3c0296c865776

See more details on using hashes here.

File details

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

File metadata

  • Download URL: quixand-0.1.5-py3-none-any.whl
  • Upload date:
  • Size: 38.5 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.5-py3-none-any.whl
Algorithm Hash digest
SHA256 b0a325b86b4773361fff7ba3353a1b8f5e26f19f6205a3bd451f9de192e45933
MD5 c5e9dabf108283aa0fa0ba35de120047
BLAKE2b-256 5d1b06200803e7dcfe13b49ff6d3c91849c0b0134fb9d6761e8e96835f5bbbbf

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