Skip to content

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).

  1. 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
  2. Create a sandbox, use it and delete it. Without a name or mode you get an ephemeral wisp with a generated name.

    import os, wisps
    with 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()
  3. 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.

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.

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.