EU-hosted sandbox VMs for AI agents - official Python SDK
Project description
orkestr Python SDK
EU-hosted sandbox VMs for AI agents. Type-safe Python client for the
api.orkestr.eu/v1/sandboxes REST API.
Status: 0.1.0 - first stable release. The client surface may still have breaking changes within the 0.x line; 1.0.0 will signal API stability.
Install
pip install orkestr
Requires Python 3.10+.
Authenticate
Mint an API token in the orkestr console with the sandboxes:write scope.
export ORKESTR_API_KEY="ork_..."
The SDK picks the key up from ORKESTR_API_KEY or you pass it explicitly.
Quick start
One-shot execution inside a fresh sandbox. The with block auto-terminates
the sandbox on exit, preventing runaway costs if the caller crashes.
from orkestr import Sandbox
with Sandbox.create(template="python-3.12") as sbx:
sbx.files.write("/workspace/main.py", "print(sum(range(1_000_000)))")
result = sbx.exec("python /workspace/main.py")
print(result.stdout) # 499999500000
print(result.duration_ms) # ~120
API
Create a sandbox
sbx = Sandbox.create(
template="python-3.12", # one of the templates listed below
size="small", # "small" | "medium" | "large" (plan-capped)
network="off", # "off" | "restricted" | "open"
timeout_seconds=600, # auto-terminate after this many seconds
env={"OPENAI_API_KEY": "sk-..."},
metadata={"agent_run": "r_123"},
region="fsn1", # "fsn1" (DE) | "hel1" (FI) | None for auto
api_key=None, # falls back to ORKESTR_API_KEY
)
print(sbx.id) # "sbx_01HXYZ..."
print(sbx.status) # "running"
Templates
| Template | Description |
|---|---|
python-3.12 |
CPython 3.12 with pip and common libs |
python-3.12-bare |
CPython 3.12 only, faster start |
node-22 |
Node 22 with npm |
ubuntu-24.04 |
Minimal Ubuntu shell environment |
Sizes
size picks from a fixed menu. Allowed sizes are plan-capped.
| Size | vCPU | RAM | Plans |
|---|---|---|---|
small |
0.5 | 512 MB | free, pro, team |
medium |
2 | 4 GB | pro, team |
large |
4 | 8 GB | team |
Check your plan limits
Sandbox.limits() reports the sizes and caps available to your API
key's plan — pick a size up front instead of discovering the limit
from a PlanLimitError. Handy when the same code runs under keys on
different plans (e.g. a reseller provisioning per customer).
limits = Sandbox.limits()
limits.plan # "free"
limits.allowed_sizes # ["small"]
"medium" in limits.allowed_sizes # False
limits.max_concurrent # 1
limits.usage_gb_hours_used # 2.5 (memory consumed this month)
limits.usage_gb_hours_included # 10.0 (memory budget for the month)
limits.usage_cpu_hours_used # 0.42 (CPU consumed this month)
limits.usage_cpu_hours_included # 2.0 (CPU budget for the month)
limits.usage_resets_at # datetime - 1st of next month UTC
for s in limits.sizes: # full menu, each with .allowed
print(s.size, s.cpu, s.memory_mb, s.allowed)
When usage_gb_hours_used >= usage_gb_hours_included, Sandbox.create()
raises PlanLimitError with code="usage_exhausted" until the counter
resets. The metering rolls up mem_mb_seconds from the host's
per-second compute samples, so an idle sandbox still accrues toward the
budget until it is terminated.
Live metrics
sbx.metrics() returns this sandbox's live CPU and memory - the latest
reading, a rolling ~60s sample window for sparklines, and its lifetime
totals. Use it to watch a workload for saturation or memory pressure
without instrumenting the workload itself.
m = sbx.metrics()
m.cpu.usage_percent # 47.0 (% of allocated cores; pegged 1-core = 100)
m.cpu.usage_cores # 0.94 (cores in use of m.cpu.cores)
m.memory.usage_bytes # 1879048192 (working set, excludes reclaimable cache)
m.memory.usage_percent # 43.7 (% of m.memory.limit_bytes)
m.lifetime.cpu_seconds # 1284.31 (on-CPU seconds since the sandbox started)
for s in m.samples: # oldest first; s.t is a datetime
print(s.t, s.cpu_percent, s.mem_bytes)
It is telemetry, not a state change: a paused or terminated sandbox
returns a result with null live usage (check m.sandbox_status) and an
empty samples window - lifetime is still populated. Poll no faster
than m.sample_interval_seconds; pass since=<datetime> to fetch only
samples newer than your last poll.
Run a command
result = sbx.exec("python /workspace/main.py", timeout_seconds=60)
result.stdout # str
result.stderr # str
result.exit_code # int
result.duration_ms # int
Stream a long command
for chunk in sbx.exec_stream("python long_task.py"):
if chunk.stream == "stdout":
print(chunk.data, end="", flush=True)
else:
print(chunk.data, end="", flush=True, file=sys.stderr)
Files
sbx.files.write("/workspace/data.json", '{"x": 1}')
sbx.files.write_bytes("/workspace/blob.bin", b"\x00\x01\x02")
content = sbx.files.read("/workspace/out.txt") # returns str
raw = sbx.files.read_bytes("/workspace/blob.bin") # returns bytes
for entry in sbx.files.list("/workspace"):
print(entry.name, entry.is_dir, entry.size)
sbx.files.delete("/workspace/out.txt")
Pause + resume
Pausing snapshots the sandbox memory + disk and stops the compute meter.
Resume restores it on the same or a different host. pause() returns
the sandbox id; persist it across processes and pass to Sandbox.resume.
sbx = Sandbox.create(template="node-22", network="restricted")
sandbox_id = sbx.pause()
# ... minutes or hours later, from any worker:
sbx = Sandbox.resume(sandbox_id)
Snapshot retention is plan-capped (free: 1, pro: 3, team: 10). Calling
pause() over the cap raises SnapshotCapReached.
Terminate
Context-manager exit calls terminate(). Outside with:
sbx.terminate()
After terminate() the sandbox row stays in your account history but
the VM and any in-memory state are gone.
List your sandboxes
for sbx in Sandbox.list(status="running"):
print(sbx.id, sbx.template, sbx.created_at)
Errors
All SDK errors inherit from orkestr.OrkestrError.
| Exception | When |
|---|---|
AuthError |
Missing / invalid / expired API key, or scope mismatch |
RateLimitError |
Plan rate limit hit |
PlanLimitError |
Sandbox limit, concurrent limit, snapshot cap |
SandboxNotFound |
sandbox_id doesn't exist or isn't yours |
SandboxNotReady |
Operation called on a paused / terminated sandbox |
ExecTimeout |
exec() exceeded timeout_seconds |
NetworkPolicyError |
Command tried to reach a host the policy blocks |
OrkestrError |
Any other API-level error |
Async support
The MVP SDK is sync only. Async (AsyncSandbox) lands in v0.2.0 if a
design partner blocks on it; the wire format is identical.
Versioning
Follows Semantic Versioning. The SDK targets the
/v1/sandboxes API; bumps to v1 of the API are non-breaking for SDK
callers. v2 of the API will require an SDK major.
Links
- Sandbox waitlist
- Public API docs
- Questions and bug reports: code@orkestr.eu
Project details
Release history Release notifications | RSS feed
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 orkestr-0.1.4.tar.gz.
File metadata
- Download URL: orkestr-0.1.4.tar.gz
- Upload date:
- Size: 25.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a1d9b8406422331fceaf9989358ec6e2b9aedf8f92d4517f01438469d6ea48e2
|
|
| MD5 |
f2abc09c056722ae606578a40c9a2348
|
|
| BLAKE2b-256 |
e87b0dc7b919d64ebfe24dea393b5c7d96bf36746ae46946e2fc6a744dd4512d
|
File details
Details for the file orkestr-0.1.4-py3-none-any.whl.
File metadata
- Download URL: orkestr-0.1.4-py3-none-any.whl
- Upload date:
- Size: 19.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b4fdd82cc2ba9a5b7ca4f8054bcce4279155915652b09a522ef283e3b1bf41c6
|
|
| MD5 |
f3fae69e416637701783dca782f19c46
|
|
| BLAKE2b-256 |
9446d8a18b76dedaeb996ebf699b125c93c1499643394eab01f008c413db8145
|