This release is a pre-release and may not be stable for production use.
sai-sdk (Python)
Python SDK for the Sai API. Hand desktop work to Sai on a computer (a Sai cloud computer, or your own PC with the Sai app) and get the answer back, or drive a cloud computer yourself.
Status: alpha (0.1). The surface may still change before 1.0.
| Layer | What it is | Status |
|---|---|---|
sai.sessions |
A conversation with Sai on one computer | available |
sai.control_sessions |
Drive a cloud computer directly with Simulang | research preview |
sai.runs |
Typed tasks: inputs, output schema, evidence | not yet |
Install
pip install sai-sdk # import name: sai
export SAI_API_KEY=sapi_... # create one at https://platform.simular.ai
Python 3.9+. One dependency, httpx.
Give Sai a task
from sai import Sai
sai = Sai()
pc = sai.machines.list()[0]
session = sai.sessions.create(machine=pc) # a fresh conversation
result = session.run("Open Notepad, type hello, save it to the desktop as hello.txt")
print(result.status) # idle | needs_approval | error
print(result.text) # Sai's answer
print(result.usage) # tokens and cost for this task
machine can be left out when the account has one computer. Further session.run(...) calls continue
the same conversation; sai.sessions.create() starts a new one with no memory of the old.
Watch it work
turn = session.send("Find last month's invoices in Downloads and total them")
for ev in turn.events():
if ev.kind == "step":
print(" ", ev.text)
elif ev.kind == "approval":
print("Sai asks:", ev.approval.title, ev.approval.command)
turn.respond(ev.approval, "yes") # or "no", or "task" for the rest of the task
result = turn.wait()
ev.kind is one of step, message, approval, tool_error, error, finished.
turn.events(raw=True) adds the wire frames (reasoning, tool calls) as other.
Approvals
Sai asks before risky actions. run() / wait() return with status == "needs_approval" unless you pass
a handler:
def decide(approval):
if approval.is_link_only:
print("Approve in a browser:", approval.url) # a human answers; keep waiting
return None
if approval.type == "choice":
return ("yes", [[q["options"][0]["value"]] for q in approval.questions])
return "yes" if approval.command and "del " not in approval.command else "no"
result = session.run("Clean up the temp folder", on_approval=decide)
Files, models, stopping
report = sai.files.upload("report.pdf")
session.run("Summarise the attached report", attachments=[report])
[m.id for m in sai.models.list() if m.allowed]
session.run("...", model="anthropic/claude-opus-5-5") # sticks to the session
turn = session.send("...")
turn.abort()
Drive a computer yourself
Control sessions run Simulang
blocks on a cloud computer. No agent, no model cost. While one is open, Sai's own tasks on that
computer wait, so always close it (a with block does).
with sai.control_sessions.open(machine=pc) as cs:
print(cs.shell("hostname")["stdout"])
r = cs.exec("""
var page = await machine.browser.newtab('https://news.ycombinator.com')
var snap = await page.snapshot()
snap.snapshot.grep('points')
""")
print(r.result)
open("hn.png", "wb").write(r.screenshot_png) # a page or app snapshot carries a screenshot
cs.screenshot() grabs the whole screen. It needs the computer's screen-capture service; where that is
off it raises BlockError, and a page or app snapshot is the way to get an image.
exec() returns an ExecResult; a block that threw is still a result, so check r.error
(exception, timeout, interrupted). Use var, not let/const, for values the next block needs.
Errors
Every non-2xx response raises a SaiError subclass with status, code, message and retry_after:
| Exception | When |
|---|---|
AuthenticationError |
401: the key is missing, unknown or revoked |
PermissionDeniedError |
403: plan or ownership (model_not_allowed_for_plan, ...) |
BadRequestError |
400: e.g. several computers and no machine= |
ConflictError |
409: machine_busy, approval no longer pending, ... |
LinkOnlyApprovalError |
the approval must be answered in a browser: e.approval_url |
GoneError |
410: the control session lost its computer; open a new one |
RateLimitError |
429: a rate limit, or the free plan's daily budget |
ServiceUnavailableError |
503: the computer did not come online; honor retry_after |
A task that ran out of credit ends with result.status == "error" and result.error_code set to
insufficient_credits or free_computer_time_exhausted: stop retrying for today.
Configuration
| Argument | Env | Default |
|---|---|---|
api_key |
SAI_API_KEY |
required |
base_url |
SAI_API_URL |
https://api.simular.ai |
timeout |
60 s per request |
Development
cd python
uv sync
uv run pytest
Metadata
Release files for sai-sdk 0.1.0a1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| sai_sdk-0.1.0a1.tar.gz | 29.0 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| sai_sdk-0.1.0a1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 47.4 kB
Release files / sai_sdk-0.1.0a1.tar.gz
| Download URL | sai_sdk-0.1.0a1.tar.gz |
|---|---|
| Size | 29.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
94a086a77bbb845efae2edb54b08a8bde96b459b92784f07079ee2278beeea72
|
|
BLAKE2b-256 checksum How to use checksums |
8070c061c7d22d2daa93abc8125fd9f604e798a9d6105a2ca99f2550a9490eeb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.
Transparency logRelease files / sai_sdk-0.1.0a1-py3-none-any.whl
| Download URL | sai_sdk-0.1.0a1-py3-none-any.whl |
|---|---|
| Size | 18.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4303db8b0287c2268034f279561071f943e8c5bc35b94067a88b24c5d422f347
|
|
BLAKE2b-256 checksum How to use checksums |
5b7d66183ccf65fdd7d2cc406f1b7d3060494664f1765fdf1970ffc1c980afc5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.
Transparency logSecurity for sai-sdk 0.1.0a1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for sai_sdk-0.1.0a1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.