Python SDK for the VirtualFS API — persistent bash sandboxes for AI agents
Project description
virtualfs (Python SDK)
Official Python client for the VirtualFS 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 virtualfs-sdk
Local (from this repo):
pip install -e clients/python
Quick start
from virtualfs 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 — use py-exec, not python3.
# py-exec keeps the interpreter warm: ~1.4 s first call, < 5 ms after.
# python3 cold-boots every call (~1.4 s each).
sb.exec("py-exec -c 'print(1 + 1)'") # first call warms interpreter
sb.exec("py-exec -c 'print(\"still warm\")'") # < 5 ms
# For multi-step Python work, write a script and run it once:
sb.fs.write("/home/user/script.py", "for i in range(5):\n print(i)\n")
result = sb.exec("py-exec /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...")
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 VirtualFSError. 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.
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
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 virtualfs_sdk-0.2.3.tar.gz.
File metadata
- Download URL: virtualfs_sdk-0.2.3.tar.gz
- Upload date:
- Size: 14.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
af91ce442471eb392e45e94af008e0b4f2947668cd4128a4b3578ad5e1131098
|
|
| MD5 |
ba3ace8c36a038b5ad5183133ac8c9aa
|
|
| BLAKE2b-256 |
e6730feb2b69e9815a53924b351c9e77fa85d41ea1cb930895721d0dc47afa80
|
Provenance
The following attestation bundles were made for virtualfs_sdk-0.2.3.tar.gz:
Publisher:
python-sdk-release.yml on Hazzng/virtualFS
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
virtualfs_sdk-0.2.3.tar.gz -
Subject digest:
af91ce442471eb392e45e94af008e0b4f2947668cd4128a4b3578ad5e1131098 - Sigstore transparency entry: 1558067782
- Sigstore integration time:
-
Permalink:
Hazzng/virtualFS@c95e0b91822ef089dd4e2f63107288c187202959 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Hazzng
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-sdk-release.yml@c95e0b91822ef089dd4e2f63107288c187202959 -
Trigger Event:
push
-
Statement type:
File details
Details for the file virtualfs_sdk-0.2.3-py3-none-any.whl.
File metadata
- Download URL: virtualfs_sdk-0.2.3-py3-none-any.whl
- Upload date:
- Size: 18.2 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 |
fcf67edff03db653d190b5e6deb544153159b175848682fcbdf452fc75de76b3
|
|
| MD5 |
056a3cbfb74ddc104efecb197a1fa995
|
|
| BLAKE2b-256 |
dee49f7e52d7eb55e2648ab067b982ac090da11c2d42ce74f0c03c96dd63e4ce
|
Provenance
The following attestation bundles were made for virtualfs_sdk-0.2.3-py3-none-any.whl:
Publisher:
python-sdk-release.yml on Hazzng/virtualFS
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
virtualfs_sdk-0.2.3-py3-none-any.whl -
Subject digest:
fcf67edff03db653d190b5e6deb544153159b175848682fcbdf452fc75de76b3 - Sigstore transparency entry: 1558067903
- Sigstore integration time:
-
Permalink:
Hazzng/virtualFS@c95e0b91822ef089dd4e2f63107288c187202959 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Hazzng
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-sdk-release.yml@c95e0b91822ef089dd4e2f63107288c187202959 -
Trigger Event:
push
-
Statement type: