Python SDK for the SQL-FS API — persistent bash sandboxes for AI agents
Project description
sqlfs (Python SDK)
Official Python client for the SQL-FS API — persistent bash sandboxes for AI agents.
Handles JWT minting, JSON serialization, retries, and streaming so callers don't rebuild exec_sync boilerplate every session (see issue #29).
Install
pip install sql-fs-sdk
Local (from this repo):
pip install -e clients/python
Quick start
from sqlfs import Client
with Client(base_url="https://api.example.com", auth_secret="<AUTH_SECRET>", sub="agent-001") as fs:
sb = fs.sandboxes.create(name="demo", python=True)
# Bash execution
result = sb.exec("echo hello && ls /home/user")
print(result.stdout) # "hello\n..."
print(result.error) # alias for stderr
print(result.exit_code) # 0
print(result.ok) # True
print(result.duration_ms)
# Python execution — CPython on WASM, stdlib only, isolated per call.
# Each python3 call cold-boots a fresh interpreter (~1.4 s); state is not
# shared across calls, so persist data via the filesystem.
sb.exec("python3 -c 'print(1 + 1)'")
# For multi-step Python work, write a script and run it once (avoids paying
# the cold-boot per step):
sb.fs.write("/home/user/script.py", "for i in range(5):\n print(i)\n")
result = sb.exec("python3 /home/user/script.py")
# File operations
sb.fs.write("/home/user/main.py", "print('hi')\n")
text = sb.fs.read_text("/home/user/main.py")
entries = sb.fs.tree(prefix="/home/user", depth=2)
sb.delete()
If you already hold a JWT (e.g. minted via pnpm token:create), pass token= instead of auth_secret=:
fs = Client(base_url="...", token="eyJhbGciOi...")
Per-file size limit
Client(max_file_size=...) (default 64 MiB) caps individual files on every
write path — ingest_files, fs.write, fs.write_files — and is checked
client-side before anything is base64-encoded or sent. An oversized file
raises ValidationError(code="EFILE_TOO_LARGE") (with status=None) naming each
offending path and size; nothing is transmitted. The limit is threaded to every
Sandbox the client creates or attaches.
fs = Client(base_url="...", auth_secret="...", sub="agent", max_file_size=128 * 1024 * 1024) # raise to 128 MiB
fs = Client(base_url="...", auth_secret="...", sub="agent", max_file_size=0) # disable the check
The server also caps the whole request body (
MAX_REQUEST_BODY_BYTES, default 256 MB); after ~33% base64 inflation that's ~190 MB of raw bytes per call across all files. The 64 MiB default keeps a single file well inside that.
API surface
Client
| Method | HTTP | Notes |
|---|---|---|
client.sandboxes.list() |
GET /v1/sandboxes |
→ list[SandboxRecord] |
client.sandboxes.create(name=, env=, files=, python=, javascript=) |
POST /v1/sandboxes |
→ Sandbox |
client.sandboxes.get(id) |
GET /v1/sandboxes/{id} |
→ SandboxInfo |
client.sandboxes.attach(id) |
(no network) | → Sandbox for an existing id |
client.sandboxes.delete(id) |
DELETE /v1/sandboxes/{id} |
Sandbox
Files (sb.fs.*)
| Method | HTTP |
|---|---|
sb.fs.read(path) -> ReadResult |
GET /files/{path} |
sb.fs.read_text(path) -> str |
GET /files/{path} |
sb.fs.write(path, content) |
PUT /files/{path} |
sb.fs.write_files({path: content, ...}) |
POST /writeFiles |
sb.fs.delete(path, recursive=False) |
DELETE /files/{path} |
sb.fs.mkdir(path, recursive=False) |
POST /mkdir |
sb.fs.tree(prefix=, depth=) -> list[TreeEntry] |
GET /tree |
Exec
| Method | HTTP |
|---|---|
sb.exec(script, cwd=, env=, timeout_ms=, debug=) -> ExecResult |
POST /exec-sync |
sb.exec_batch([{id, script}, ...], timeout_ms=, read_only=) -> list[BatchExecResult] |
POST /exec-sync-batch |
for ev in sb.exec_stream(script, ...) |
POST /exec (SSE) |
Ingest / Export
| Method | HTTP |
|---|---|
sb.ingest_archive(file_obj, base_path=) |
POST /ingest (multipart) |
sb.ingest_files({path: bytes, ...}, base_path=) |
POST /ingest-files (auto base64) |
sb.export(base_path=) -> bytes |
GET /export |
for chunk in sb.export_stream(base_path=) |
GET /export (streaming) |
sb.delete() |
DELETE /sandboxes/{id} |
Errors
All exceptions derive from SQLFSError. HTTP status codes map to:
| Status | Exception |
|---|---|
| 400 | ValidationError |
| 401 / 403 | AuthError |
| 404 | NotFoundError |
| 408 | ExecTimeoutError (carries .duration_ms) |
| 409 | ConflictError |
| 429 | RateLimitError (carries .retry_after) |
| 5xx | ServerError (after retries exhausted) |
| network | TransportError |
Each error exposes .code (server error code, e.g. ENOENT), .status, and .details.
ValidationError is also raised client-side with code="EFILE_TOO_LARGE" and
status=None when a file exceeds Client(max_file_size=...) — before any HTTP
request is made. .details lists each offending path (size > limit).
Performance patterns
exec_batch is for collapsing many round-trips, not for parallelising
CPU-bound work. The lock model determines what runs in parallel:
| Goal | Recommended call | Notes |
|---|---|---|
| Many cheap independent reads (find/grep/cat/stat) | sb.exec_batch([...], read_only=True) |
Parallel under shared read-lock, ordered results. |
| Atomic multi-step write | sb.exec_batch([...]) (default) |
Sequential inside one write-lock. Scripts share shell state. |
| Multi-pattern grep over the same file set | sb.exec("grep -E 'pat1|pat2|pat3' ...") |
One filesystem traversal beats N. |
| One-shot read or write | sb.exec("...") |
Holds the lock for the whole script — bundle logic into one script. |
Benchmark snapshot (951-file repo, 8 grep patterns):
| Approach | Wall-clock |
|---|---|
exec_batch of 8 scripts (default, sequential) |
~1100ms |
exec_batch of 8 scripts, read_only=True (parallel) |
faster, varies with vCPU count |
Single grep -E 'pat1|pat2|...' (alternation) |
~420ms |
The sandbox container is typically single-core; bash &/wait
parallelism beyond ~2 jobs is usually slower than sequential on CPU-bound
work.
Streaming exec
for event in sb.exec_stream("for i in 1 2 3; do echo $i; sleep 1; done"):
if event.type == "stdout":
print(event.data, end="")
elif event.type == "exit":
print(f"\nexit={event.exit_code} in {event.duration_ms}ms")
Retries
The SDK retries up to 3 times on 429 and 5xx responses, honouring Retry-After when present and falling back to exponential jitter otherwise. 4xx errors (other than 429) are surfaced immediately. Streaming endpoints are not retried — at-most-once semantics.
Status
Alpha. The SDK lives in this repo so that server-side contract changes can be made together with the SDK in a single PR. It may be split out to a standalone repo once the surface stabilizes.
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 sql_fs_sdk-0.3.1.tar.gz.
File metadata
- Download URL: sql_fs_sdk-0.3.1.tar.gz
- Upload date:
- Size: 17.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f58dca4ab115f0a597b1101a835bc855bf860333f918d6fc54898669163fbc4e
|
|
| MD5 |
4def86ae339db530fd04642d258b5d40
|
|
| BLAKE2b-256 |
72ba8cf325642885f08d8419ab9983833fb029ec3e33b0b52a32c49e20a06369
|
Provenance
The following attestation bundles were made for sql_fs_sdk-0.3.1.tar.gz:
Publisher:
python-sdk-release.yml on Hazzng/sql-fs
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sql_fs_sdk-0.3.1.tar.gz -
Subject digest:
f58dca4ab115f0a597b1101a835bc855bf860333f918d6fc54898669163fbc4e - Sigstore transparency entry: 1754056758
- Sigstore integration time:
-
Permalink:
Hazzng/sql-fs@d02fd753264b825c70cc4b174b70119084b8e949 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Hazzng
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-sdk-release.yml@d02fd753264b825c70cc4b174b70119084b8e949 -
Trigger Event:
push
-
Statement type:
File details
Details for the file sql_fs_sdk-0.3.1-py3-none-any.whl.
File metadata
- Download URL: sql_fs_sdk-0.3.1-py3-none-any.whl
- Upload date:
- Size: 21.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3a4922ac11e466376e57318662975586035da926802015c34dc2ac20a04a5cb7
|
|
| MD5 |
8a32bf8581f66edca87fdf3c58b9128f
|
|
| BLAKE2b-256 |
53aa710cbc04f16972fe43100a5ee5485b665ba94cfa32652935c725f37f7f0d
|
Provenance
The following attestation bundles were made for sql_fs_sdk-0.3.1-py3-none-any.whl:
Publisher:
python-sdk-release.yml on Hazzng/sql-fs
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sql_fs_sdk-0.3.1-py3-none-any.whl -
Subject digest:
3a4922ac11e466376e57318662975586035da926802015c34dc2ac20a04a5cb7 - Sigstore transparency entry: 1754056849
- Sigstore integration time:
-
Permalink:
Hazzng/sql-fs@d02fd753264b825c70cc4b174b70119084b8e949 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Hazzng
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-sdk-release.yml@d02fd753264b825c70cc4b174b70119084b8e949 -
Trigger Event:
push
-
Statement type: