Python SDK
The Python SDK gives scripts, notebooks and agent frameworks the Wisps API. Client is synchronous; AsyncClient is the same API for asyncio. It needs Python 3.10 or later and depends on httpx and websockets. See the API reference for request and response fields../api/wisps.md).
Have ready
Section titled “Have ready”- Python 3.10 or later.
- An API URL and a team token (authentication).
-
Install the SDK artifact supplied by your provider. The SDK is not published on PyPI. Replace the path with the wheel you received.
Terminal window pip install /path/to/wisps-0.1.0-py3-none-any.whl -
Create a sandbox, use it and delete it. Without a name or mode you get an ephemeral wisp with a generated name.
import os, wispswith wisps.Client(os.environ["WISPS_URL"], os.environ["WISPS_TOKEN"]) as c:w = c.create(labels={"agent_run": "r-123"})w.write_file("app/main.py", "print(sum(range(10)))\n", mkdirs=True)res = w.run(["python3", "main.py"], dir="app")print(res.exit_code, res.stdout)w.delete() -
Or with asyncio, the same calls awaited:
async with wisps.AsyncClient(url, token) as c:w = await c.create()res = await w.run(script="make test")
The script prints 0 45.
Everyday calls
Section titled “Everyday calls”| Task | Call |
|---|---|
| Create an ephemeral sandbox | c.create(); options: labels, idle_timeout_seconds, max_lifetime_seconds, delete_after_stopped_seconds, region, near, size, no_start |
| Create a workspace that persists (person token) | c.create("dev", mode=wisps.MODE_ON_DEMAND) |
| Run to completion | w.run(["pytest"]) or w.run(script="cd app && make"); env, dir (relative to home), stdin, timeout_seconds, binary=True for bytes |
| Output as it arrives | w.run_stream(script=..., on_output=lambda stream, data: ...) |
| Interactive or long command | s = w.exec(["sh"], tty=True), then s.send(), s.close_stdin(), iterate s for Output, Control and Exit events, or s.wait() |
| Background work | w.exec(cmd, detachable=True), later w.sessions(), w.attach(id), w.hold_session(id), w.kill_session(id) |
| Files | w.write_file, w.read_file, w.list_dir (home by default), w.mkdir, w.remove |
| Find wisps | c.list(labels={"agent_run": "r-123"}) |
| Change mode, clocks or labels | w.update(...); w.keep() switches an ephemeral wisp to on-demand |
| Checkpoints | w.checkpoint("note", key=...), w.list_checkpoints(), w.restore(id), w.operation(key); resend with the same key after a disconnect |
w.info holds what the API last said about the wisp (status, mode, labels, lifetime_ends_at, delete_at). run is not cut off by the client’s request timeout; the command’s own timeout_seconds bounds it.
Errors
Section titled “Errors”Every error is a wisps.WispsError. API refusals are wisps.APIError subclasses with status, message and, for 410, reason:
| Class | When |
|---|---|
NotFound |
No such wisp, or another team’s |
Gone |
Deleted automatically; create a new one |
Conflict |
The name is taken, or an ephemeral wisp’s lifetime is over (keep() to use it again) |
Forbidden |
A quota, or an API token asking for on-demand or always |
BadRequest |
Invalid options, or a program that does not exist |
Unavailable |
No capacity, or the node is unreachable |
A command that fails is not an error: check RunResult.exit_code, timed_out and stdout_truncated.