Metadata-Version: 2.4
Name: orkestr
Version: 0.1.0a1
Summary: EU-hosted sandbox VMs for AI agents - official Python SDK
Author-email: Orkestr <hello@orkestr.eu>
License-Expression: MIT
Project-URL: Homepage, https://orkestr.eu/sandboxes
Project-URL: Documentation, https://orkestr.eu/docs
Keywords: orkestr,sandbox,ai,agents,code-execution,eu
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx>=0.27
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: respx>=0.21; extra == "dev"
Requires-Dist: ruff>=0.6; extra == "dev"
Dynamic: license-file

# orkestr Python SDK

EU-hosted sandbox VMs for AI agents. Type-safe Python client for the
`api.orkestr.eu/v1/sandboxes` REST API.

> **Status: private beta pre-release (`0.1.0a1`).** The client surface is
> stable and tested, but the `api.orkestr.eu/v1/sandboxes` backend is not
> live yet - every endpoint returns `501 Not Implemented` until the public
> beta. This release exists so you can pin the package and review the API.
> See `docs/sandbox-mvp-plan.md` in the orkestr repo for the timeline.

## Install

```bash
pip install --pre orkestr
```

Requires Python 3.10+. The `--pre` flag is needed while the SDK is a
pre-release; it is dropped once `0.1.0` ships with the public beta.

## Authenticate

Mint an API token in the orkestr console with the `sandboxes:write` scope.

```bash
export ORKESTR_API_KEY="sp_live_..."
```

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.

```python
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

```python
sbx = Sandbox.create(
    template="python-3.12",          # one of the templates listed below
    cpu=1.0,                         # vCPU (default 1, plan-capped)
    memory_mb=2048,                  # RAM (default 2048, 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         |

### Run a command

```python
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

```python
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

```python
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`.

```python
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`:

```python
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

```python
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](https://semver.org). 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](https://orkestr.eu/sandboxes)
- [Public API docs](https://orkestr.eu/docs)
- Questions and bug reports during the private beta: <hello@orkestr.eu>
