Metadata-Version: 2.4
Name: shardflux
Version: 0.1.0
Summary: Python client for Shardflux: cloud computers for AI agents. Persistent workspaces you open by key, run commands in, suspend, resume and fork.
Project-URL: Homepage, https://shardflux.dev
Author: Shardflux
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: agents,ai-agents,code-execution,microvm,sandbox,sdk,shardflux,workspace
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
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: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: httpx<1,>=0.27
Description-Content-Type: text/markdown

# shardflux

Python client for [Shardflux](https://shardflux.dev): cloud computers for AI agents.

Open a persistent workspace by key, run commands in it, read and write its files, suspend it when
idle, resume it later with its disk and memory intact, and fork it.

> **Early access.** Shardflux is in early access. The API is versioned (`/v1`), but this client is
> below 1.0: a minor release may contain breaking changes.

- Python 3.10 or later; one dependency (`httpx`).
- Typed (`py.typed`).
- Retries, idempotency keys, operation polling and tool-token refresh are handled for you.

## Install

```sh
pip install shardflux
```

## Quick start

Create a project API key in the Shardflux console (`sfk_<key id>_<secret>`). API keys are server
credentials; keep them out of client-side code.

```python
from shardflux import Shardflux

sf = Shardflux()  # reads SHARDFLUX_API_KEY; or Shardflux(api_key="...")

# Creates the workspace on first use; afterwards reconnects (or resumes) the same one.
ws = sf.open(key="customer-42/main", template="python-node-browser")

result = ws.exec("python3 -c 'print(40 + 2)'")
print(result.exit_code, result.stdout)  # 0 42

ws.files.write("/home/user/notes.txt", "hello from Python\n")
print(ws.files.read_text("/home/user/notes.txt"))

ws.suspend()
```

`open()` waits until the workspace is running. Opening the same key again never resets it: files,
installed packages and running processes are still there.

## Configuration

| Argument | Environment variable | Default |
| --- | --- | --- |
| `api_key` | `SHARDFLUX_API_KEY` | required |
| `base_url` | `SHARDFLUX_API_URL` | `https://api.shardflux.dev` |
| `timeout` | | `30.0` seconds per request |
| `max_retries` | | `2` (safe or idempotent requests only) |
| `http_client` | | a new `httpx.Client` (pass your own for proxies or custom transports) |

`Shardflux` is a context manager (`with Shardflux() as sf: ...`); `close()` closes the HTTP client
it created.

## Commands

```python
r = ws.exec("pip install requests && python3 app.py", cwd="/home/user/project", env={"DEBUG": "1"}, timeout=600)
r = ws.exec(["python3", "-V"])  # a list runs as argv, without a shell

r.exit_code, r.stdout, r.stderr, r.timed_out, r.ok
```

A string runs through `bash -lc`; a list runs as argv. `timeout` (seconds) is enforced inside the
workspace. Pass `on_output=lambda stream, chunk: ...` to receive output as it arrives. If the
connection drops, `exec` resumes the output from byte offsets; it never starts the command twice.
Ctrl-C cancels the command in the workspace.

## Files

```python
ws.files.write("/home/user/data.bin", b"\x00\x01\x02", create_parents=True)
data = ws.files.read("/home/user/data.bin")  # bytes, the whole file
text = ws.files.read_text("/home/user/notes.txt")
ws.files.list("/home/user")  # {"entries": [...], "truncated": False}
ws.files.stat("/home/user/notes.txt")
ws.files.remove("/home/user/data.bin")
```

Writes are atomic and durable: they are acknowledged after the file and its directory are fsynced.

## Lifecycle

```python
ws.suspend(wait=True)  # memory and processes are checkpointed
ws.resume(wait=True)  # or open() the key again

copy = ws.fork("customer-42/experiment")  # waits until the fork is running
copy.delete()  # tool access ends at once; keys are never reused

for w in sf.workspaces.list_all(key_prefix="customer-42/"):
    print(w.key, w.state)
page = sf.workspaces.list(limit=50)  # page.data, page.next_cursor
```

`suspend`, `resume`, `snapshot` and `delete` return the `Operation`; with `wait=True` they wait
for it to finish. Waiting polls with backoff (250 ms doubling to 5 s, ±20 % jitter). If `timeout`
(default 300 s) passes first, `OperationTimeoutError` is raised and the operation keeps running
server side: `sf.workspaces.wait_for_operation(err.operation_id)` waits again.

## Errors

All errors derive from `ShardfluxError`.

- `ShardfluxApiError`: the API or the workspace refused the request. It mirrors the error
  envelope: `code`, `message`, `request_id`, `retryable`, plus `status`, `details`,
  `operation_id` and `retry_after`.
- `OperationFailedError`: an awaited operation ended `failed` or `canceled` (`error_code`,
  `operation`).
- `OperationTimeoutError`: waiting gave up; the operation continues (`operation_id`).
- `ShardfluxProtocolError`: a response was not the documented shape.

```python
from shardflux import ShardfluxApiError

try:
    sf.open(key="customer-42/main", template="python-node-browser")
except ShardfluxApiError as err:
    print(err.code, err.message, err.request_id, err.retryable)
```

Treat unknown error codes as generic errors: show `message`, and use `retryable`.

## Anything else

`sf.me()` returns the API key's organization and project. `sf.request(method, path, ...)` calls
any `/v1` route with the client's authentication, retries and error handling. The TypeScript SDK
(`npm install @shardflux/sdk`) covers the full API, including agent tool definitions for model
providers.

## License

Apache-2.0
