Skip to main content
The Sail Python SDK (sail on PyPI) supports Python 3.9+. It shares one engine with the TypeScript and Rust SDKs, so behavior matches across languages.

Install

Installing the Python SDK also puts the sail CLI on your PATH. To install the CLI on its own, see Install the CLI. The Sail API warns when your SDK version is nearing the end of its support window. The SDK emits it as a SailDeprecationWarning through Python’s warnings module, once per process. A version past the end of its support window is rejected with an upgrade error before any operation runs. Upgrade with pip install -U sail.

Configure

Set SAIL_API_KEY in the environment; the SDK also reads the credential sail auth login stores under ~/.sail. See Configuration.

Quickstart

Sync and async

Every method that does I/O has an async twin under .aio (the interactive shell is sync-only), so the same code works from scripts and from asyncio. You choose sync or async once, at the call. A handle returned by an .aio call is already async (await proc.wait(), async for chunk in proc.stdout), with no further .aio:
See Sailbox → Sync and async for streaming and end-to-end examples.

Python-only features

Errors

Product and transport failures derive from sail.SailError, and the error classes that match a Python builtin also inherit it (sail.NotFoundError is a LookupError), so both except sail.SailError and idiomatic builtin handlers work. A few argument mistakes raise plain ValueError/TypeError. See Errors.

Reference

The docs below are auto-generated.

Sailbox

A sandbox instance on the Sail platform: the operable handle plus the monitoring snapshot from the call that produced it.sailbox_id is the stable identifier and the durable external handle. Equality and hashing follow it: two handles for the same Sailbox compare equal, regardless of when their snapshots were taken. The remaining fields are the read-only snapshot as of get/list (status, resource usage, image, timestamps); a handle born from create carries only what the create response returns. Fetch a fresh snapshot with Sailbox.get(sailbox_id). Every operation resolves the live endpoint on demand, waking a sleeping box only when the operation needs it.Attributes:

create

Create a new Sailbox.
Custom image definitions are built first; the call then returns once the new box is running or creation has failed. Building follows the image contract (see ImageDefinition.build): the image may boot hidden at build time and periodically while in active use, sharing those boots’ state with every Sailbox created from it via a start snapshot, so generate per-instance identity at runtime, not in boot-time jobs.timeout (seconds) bounds each create attempt, since creating a Sailbox can block for many minutes while it queues for capacity and boots the VM. The create is retried (reattaching to the same box), so it returns as soon as the box is ready and gives up after roughly three attempts. If the budget is exhausted it raises rather than hanging; the box may still come up server-side. To recover it, find its id with Sailbox.list(search=name), then bind it with from_id or terminate it. Pass 0 to leave each attempt unbounded.ingress_ports exposes guest ports for ingress. Each entry is either a bare int (shorthand for an HTTP port) or an IngressPort carrying an explicit protocol, e.g. ingress_ports=[80, 443, IngressPort(22, "tcp")]. Call listener / listeners on the returned Sailbox for the public address of each exposed port: an HTTP listener’s endpoint is an HttpEndpoint with a routable url and a TCP listener’s is a TcpEndpoint with a host/port any TCP client can dial (for example psql -h <host> -p <port>).Pass ssh=True to get an SSH-ready box in one call. It calls enable_ssh on the new box, which trusts your org’s SSH certificate authority, starts sshd, and exposes guest port 22 as tcp. A port-22 entry in ingress_ports is then not exposed at create; only its allowlist is kept, applied when enable_ssh exposes the port. To connect, wire up your machine with sail box ssh alias <id> (or sail box ssh enable <id>) and then ssh <name>.sail; a plain ssh to the raw port will not present your certificate.By default a box is org-wide: any credential in your org can exec, copy files, SSH, or run lifecycle operations on it. Pass private=True to restrict all of that to you. An org admin can override that with a recorded reason for exec, files, and pause/resume/terminate/upgrade. SSH, exposing or removing listeners, and fork/checkpoint/restore stay creator-only. Requires an API key minted by your user (not a service key).volumes mounts shared persistent NFS storage into the guest. Pass a mapping from absolute guest mount path to a sail.Volume returned by sail.Volume.find(), e.g. volumes={"/mnt/shared": volume}. Volumes are currently in Alpha. To pilot them, reach out in the Sail Slack: https://join.slack.com/t/sailresearchcrew/shared_invite/zt-41pdcym9j-UU0Ey~A~r6n2H0DQVQsQHQ.size selects the resource size: "s", "m" (the default), or "l". Each size sets the vCPU count plus default memory and disk. Ongoing billing is based on observed usage, so a bigger size does not reserve CPU, memory, or disk. Each size has a separate one-time creation charge. Choose "s" when you want the fastest cold starts, forks, and resumes. Its lower ceilings also cap what a runaway workload can consume, so you don’t accidentally use more than you need. memory_gib and disk_gib tune that size’s default memory and disk ceilings in whole GiB, within its range.image is the image to boot; omit it for the prebuilt Debian base (an instant create with no build). A custom image is built at create if it is not already cached; image_build_timeout (seconds) bounds that build.Sail may sleep a fully idle box; it wakes transparently on traffic or the next operation.await Sailbox.create.aio(...) is the async form, building the image and provisioning the VM without blocking the event loop.

list

List the Sailboxes for the current org that match the filters, fetching pages until every match (or limit of them) is collected. Use list_page to page through results manually instead.
Filters are server-side. app_id filters by the owning app: a sail.App value or an app id string (resolve an app name through App.find first if needed). order sorts the results: "newest_active" returns the most recently active first (the default the server applies), "newest_created" the newest-created first. limit caps the total returned, bounding the fetch for large orgs; None returns every match.

list_page

List one page of Sailboxes alongside the pagination envelope (limit/offset/total/has_more).
Takes the same filters as list, plus limit and offset to select the page. order sorts the results: "newest_active" returns the most recently active first (the default the server applies), "newest_created" the newest-created first.

get

Fetch a Sailbox by id: the operable handle plus a fresh snapshot.
Validates access first: wrong-org ids and unknown ids both surface as LookupError (the server returns 404 for both to avoid leaking ownership across orgs). Nothing wakes here; operations resume a paused or sleeping box on demand. Call get again for a fresh snapshot.

from_id

Bind a handle to an existing Sailbox id without a network call.
The returned handle carries no snapshot fields (its name and status are empty), just the operable surface. The id is not verified to exist: operations on an unknown or inaccessible id fail with NotFoundError. Use get to validate the id and fetch a fresh snapshot instead.

from_checkpoint

Create a new running Sailbox from a durable checkpoint handle.
This is a new box branched from the checkpoint’s state, not a resumption of the original. In-flight Sailbox.exec() sessions are reaped in the child: a command still running when the checkpoint was taken does not resume here. Its on-disk effects up to the checkpoint are preserved, but the command itself is not continued. Start fresh execs on the new box. name sets the new box’s display name; timeout (seconds) bounds the call, must be positive when given, and defaults to the server’s bound.

fork

Create a new running Sailbox from this one’s live state.
The child copies this box’s memory and writable disk as they are now, in one call, so it branches from the parent’s current state while the parent keeps running. The copy is transient: no durable checkpoint is taken. To branch from a saved point in time instead, take a durable checkpoint and start boxes from it with from_checkpoint, which works even after the parent is gone.Like from_checkpoint, the child is a new independent box: commands still running in the parent do not continue in the child (their on-disk effects up to the fork are preserved); start fresh execs on the child. name sets the child’s display name; timeout (seconds) bounds the call, must be positive when given, and defaults to the server’s bound.

terminate

Permanently terminate this Sailbox.
Idempotent: terminating a box that is already terminated succeeds, so cleanup paths can call it unconditionally.

pause

Checkpoint and pause this Sailbox until it is explicitly resumed.

sleep

Checkpoint and sleep this Sailbox until traffic or a wake restores it.
wake_at, when given, schedules a wall-clock wake before the sleep starts and returns the effective wake time: the sooner of this request and any wake already scheduled. If the Sailbox is sleeping when that moment arrives, Sail restores it. The wake can fire a little after the time you set, so treat it as approximate. A naive wake_at is interpreted as local time. Calling sleep on an already-sleeping Sailbox succeeds and just updates the scheduled wake.

checkpoint

Create a durable checkpoint handle for this Sailbox.
Running Sailboxes are snapshotted first. Paused and sleeping Sailboxes return a handle to their existing checkpoint.name sets a display name for the handle. ttl_seconds, when set, must be positive and overrides the server’s default retention window; use it to keep a checkpoint you intend to reuse as a template alive longer than the default. The returned handle’s expires_at reports when it will be garbage collected.

upgrade

Upgrade this Sailbox’s runtime to the latest version.
Upgrading picks up new Sailbox features, fixes, and performance improvements without recreating the box. A running Sailbox reboots in place on its current disk: all filesystem state is preserved, but processes restart as they would after a machine reboot (unflushed application state gets power-loss semantics). A paused or sleeping Sailbox is upgraded without waking. The upgrade is recorded and applies automatically at the next wake.Returns an UpgradeResult: applied is True when the upgrade happened immediately (the Sailbox was running) and False when it will apply at the next wake; already-up-to-date Sailboxes report True without rebooting.

resume

Resume this Sailbox through the public API.

listener

Fetch one listener by guest port without waking the box.

listeners

List this Sailbox’s listeners without waking it.

wait_for_listener

Block until the listener on guest_port is reachable end to end.
For an HTTP listener this probes the url, so a successful return means your guest HTTP server answered. For a TCP listener it opens a connection through the ingress edge and treats it as ready once the guest sends bytes (e.g. an SSH banner) or holds the connection open. This is a connectivity check, not an application-level health check. Raises TimeoutError if the listener does not become reachable within timeout seconds; float("inf") waits indefinitely.

expose

Expose an additional ingress port on this Sailbox at runtime.
protocol is "http" (a routable URL, the default) or "tcp" (a public host/port for raw TCP: ssh, Postgres, etc.). allowlist restricts which source IP CIDRs (e.g. ["203.0.113.0/24"]) or Sail app names may connect (app names on "http" listeners only; "tcp" allowlists must be CIDR prefixes). Re-exposing a port under the same protocol updates its allowlist to the value you pass. A raw-TCP port reclaims its previous address while your org still holds it idle; if another of your org’s Sailboxes took the address over, a new one is allocated, so read the endpoint from the returned Listener. Changing an exposed port’s protocol is rejected: unexpose an HTTP port and re-expose it, or use a different guest port for a raw-TCP one. Returns the Listener (its route_status is "unknown": the expose response does not report reachability).This works on a paused or sleeping Sailbox without waking it; a later resume serves the new listener. Probing reachability with wait_for_listener needs a running, connected box, so wait only once the box is running.

unexpose

Stop serving an exposed ingress port on this Sailbox.
A "tcp" port stops counting against your org’s raw-TCP quota once removed, but its public host/port stays owned by your org: another of your org’s Sailboxes may reuse the idle address, and it is never given to a different org. Re-expose-ing the same guest port reclaims the exact address while your org still holds it idle; after a reuse you get a new one. An "http" port carries no such reservation and is removed outright. Removing a port that is not exposed raises a LookupError.

ingress_auth_headers

Fetch the ingress-identity headers for this Sailbox via the API.
Attach the returned headers to HTTP requests so they authenticate as this Sailbox against another listener whose allowlist contains this Sailbox’s app name, useful for host-side orchestrators and tests that drive Sailboxes from outside. Requires an organization-scoped API key and a live (non-terminated) Sailbox.Inside a Sailbox guest, prefer the module-level sail.ingress_auth_headers, which reads the same values from the guest environment without an API call.

enable_ssh

Make this Sailbox reachable over SSH, returning its endpoint.
Installs your org’s SSH certificate authority as trusted, (re)starts sshd, and exposes guest port 22 as tcp ingress once the CA-only daemon verifiably owns it (a failed enable never leaves port 22 newly exposed). Works on any running Sailbox; create(ssh=True) is sugar for calling this right after create. Safe to re-run: the box’s host key is generated once and never rotated, so a caller’s known_hosts stays valid; re-run it to bring sshd back up if the (unsupervised) daemon stops. sshd survives sleep and checkpoint→resume.allowlist restricts which source CIDRs may connect to port 22, replacing any existing restriction. Left empty, a first enable opens the port to any source, and a re-enable leaves an existing restriction unchanged. Disabling SSH (sail box ssh disable) unexposes port 22 together with its restriction, so a later enable is a first enable.Anyone in the org connects with a short-lived certificate signed for their key, rather than installed keys (a private box is the exception, accepting only its creator’s certificates). The sail box ssh CLI fetches that certificate and writes the local SSH config; this method only prepares the box. By default it blocks until the port-22 listener is reachable and returns its TcpEndpoint; pass wait=False to skip the readiness probe and return None.

fs

Filesystem operations on this Sailbox’s guest: read and write files (buffered or streaming), and directory helpers.

run

Run a command to completion and return its buffered result.
A one-shot convenience over exec followed by wait(). A str runs via /bin/sh -lc; a sequence is exec’d directly. env adds environment variables for the command; cwd sets the working directory (string commands only, like exec). The result’s stdout/stderr are the buffered output (capped, drop-oldest); for unbounded output, stream it live via exec instead.check=True raises sail.CommandFailedError (carrying the completed result as result) when the command exits nonzero or times out.When timeout (seconds) elapses, the command is killed and run returns an ExecResult with timed_out=True, raising only when check=True.idempotency_key deduplicates retries: calling run again with the same key returns the original command’s result instead of launching it a second time.

exec

Run a shell command or decorated Python function in the Sailbox.
For shell commands, returns a ExecProcess immediately after the backend accepts the command: iterate proc.stdout / proc.stderr for live output and call proc.wait() for the result. open_stdin=True opens the command’s stdin for proc.stdin writes; by default stdin is /dev/null so stdin-reading commands see immediate EOF instead of blocking.pty=True runs the command under a pseudo-terminal: isatty() is true, control bytes written to proc.stdin become signals (Ctrl-C is b"\x03"), and proc.resize(cols, rows) adjusts the window. stdout and stderr merge onto proc.output (proc.stderr stays empty). pty implies open_stdin. For a full interactive shell that drives the local terminal, use shell instead.env adds environment variables for the command. Entries override the guest defaults (including LANG) and the image environment. A few reserved variables that identify the Sailbox (such as SAILBOX_ID) cannot be overridden. For pty execs the local terminal environment (COLORTERM, LANG, LC_*, TERM_PROGRAM) is forwarded automatically for keys not set here.background=True launches the command through a detached shell that returns immediately. Its output is discarded, so proc.stdout / proc.stderr stay empty and proc.wait() only confirms the launcher started it.await sb.exec.aio(...) is the async form: a shell command resolves to an AsyncExecProcess, a function to its return value.

shell

Open an interactive pty session on the Sailbox, driving the local terminal.
With no command, runs an interactive login shell. Pass command to run that under a pty instead (e.g. a REPL or vim). Either way the session is bridged to the local terminal: raw-mode keystrokes (including Ctrl-C, Ctrl-Z, and Ctrl-D) reach the remote process, its output renders locally, and terminal resizes propagate. Blocks until the remote process exits and returns its exit code. Requires an interactive local terminal (stdin and stdout must be TTYs) on a Unix machine.This is the equivalent of ssh-ing into the box, without a separate SSH server. shell overrides the login shell (default $SHELL or /bin/bash); it is ignored when command is given.While attached, several local conveniences are forwarded: the box’s browser opens and localhost servers reach your machine, files dragged onto the terminal upload into the box and paste as guest paths, and Ctrl+V forwards your clipboard. On devbox images the clipboard is two-way: pasted images and text land on the box’s clipboard, and text copied inside the box comes back to yours. Other images upload a pasted image as a file and paste its path instead. Pass no_forward=True to turn all of it off (for example for an untrusted or automated session), or no_forward_browser=True to keep everything forwarded except browser opens.

App

A Sail application.Attributes:

find

Find an app by name, optionally creating it if it doesn’t exist.

list

Return every app the current org owns, newest first.
Apps with no Sailboxes yet are included. The response is not paginated; the per-org app count is small.

ImageDefinition

apt_install

Add an apt-get install step for packages.

pip_install

Add a pip install step for packages.

run_commands

Add shell commands to the build, each as its own step.

add_local_file

Bake the contents of one local file into the image at remote_path.
The local file is hashed (sha256) and uploaded to Sail’s content-addressed asset store; only the hash, target path, and mode flow into the image spec. A one-byte change to the local file therefore changes the resulting image_id and forces a rebuild.remote_path must be an absolute POSIX path. If it ends with a slash, the basename of local_path is appended. mode is the POSIX permission bits (low 9 bits, max 0o777). When omitted (None) or 0 the default 0o644 applies; an explicit mode=0 is treated the same as omitting the argument.

add_local_dir

Bake a local directory into the image at remote_path.
Each regular file under local_path is hashed and uploaded; per-file modes come from the local stat(). Symlinks are skipped. ignore accepts a sequence of gitignore patterns or a Path to a file containing them (e.g. .dockerignore); pass a list to use patterns directly. remote_path must be an absolute POSIX path.

env

Bake environment variables into the image.

filesystem

Select the writable root filesystem for Sailboxes using this image.
"ext4" is the default and preserves the existing image identity. Select "btrfs" for native Btrfs subvolumes, snapshots, reflinks, quotas, and send/receive. The choice is immutable like every other image setting and is preserved by later builder calls.

build

Build the image now and return a handle pinned to its image_id.
Submits the spec to Sail and polls until the build is ready or fails, raising TimeoutError if the build does not finish within timeout seconds.Sail may boot the image outside of any Sailbox — once as the final stage of the build, and again periodically while the image is in active use — to capture and refresh a start snapshot so Sailboxes created from it skip the cold boot. Boot-time initialization therefore runs at times you don’t control, and anything it writes becomes part of the snapshot shared by every Sailbox created from this image — generate per-instance identity (machine IDs, nonces, cached credentials) at runtime, not during boot. Per-Sailbox environment, networking, and credentials are injected at create time either way. See the “Hidden boots and start snapshots” section of the Sailbox images guide.

ImageNamespace

Base images a Sailbox can build on.Access these through the module-level sail.Image singleton, for example sail.Image.debian_arm64 or sail.Image.devbox_arm64. See the Images guide for how to choose between the Debian and devbox bases and the CPU architectures.

builtin_debian

Prebuilt Debian base, matching the Sailbox host architecture.

debian_amd64

Debian base for x86-64, pinned to your local Python version.

debian_amd

Alias for debian_amd64.

builtin_debian_amd64

Debian base for x86-64, without Python-version pinning.

debian_arm64

Debian base for arm64, pinned to your local Python version.

debian_arm

Alias for debian_arm64.

builtin_debian_arm64

Debian base for arm64, without Python-version pinning.

devbox_amd64

Devbox base for x86-64: Debian plus a baked development toolchain.

devbox_arm64

Devbox base for arm64: Debian plus a baked development toolchain.

builtin_devbox_amd64

Alias for devbox_amd64.

builtin_devbox_arm64

Alias for devbox_arm64.

ExecProcess

A command running in a Sailbox.Returned by Sailbox.exec. stdout/stderr iterate live output as it arrives, streamed into bounded buffers and resuming if the stream breaks; wait() returns the authoritative final result even when the stream cannot be resumed. The command runs inside the Sailbox independent of this handle and the connection that launched it, so closing the handle or losing the network leaves it running and its result retrievable through wait().

exec_request_id

Stable server-assigned identifier of this exec.

stdout

Live stdout iterator yielding str chunks (incrementally decoded UTF-8; use stdout_bytes for the raw byte stream). Ends when the stream ends; a reader that falls more than the buffer cap behind skips the dropped head. Each access returns a fresh iterator that replays the retained output from the beginning before following live.

stderr

Live stderr iterator; same semantics as stdout. Empty for a pty exec, which merges stderr onto stdout.

output

Live merged terminal output for a pty exec (alias of stdout).

stdout_bytes

Live stdout iterator yielding raw bytes chunks, exactly as the command wrote them (escape sequences and binary payloads included).

stderr_bytes

Raw bytes twin of stderr.

output_bytes

Raw bytes twin of output (alias of stdout_bytes).

stdin

Stdin writer for an open_stdin=True exec; raises sail.InvalidArgumentError otherwise.

exit_code

Exit code if the live stream delivered it, else None.
None does not mean still-running: if the stream broke the exit code may not arrive here, so wait() is authoritative. An exec whose host was lost before it produced a real exit code raises SailboxHostLostError here, exactly as wait() does.

poll

Alias of exit_code; never blocks.

cancel

Signal the guest command: SIGINT by default, SIGKILL if force=True.
Idempotent on the server. Transient failures are retried briefly, covering the window right after the command starts when the guest cannot accept signals for it yet.

resize

Set the pty window (cols x rows) for a pty=True exec.
Advisory and best-effort: an unknown, finished, or not-yet-placed exec is a server no-op, and transient transport errors are swallowed since the next resize resends. A no-op for a non-pty exec.

resync

Ask a pty=True exec to repaint its current screen.
A command runs at full speed and never waits for a slow reader, so if you fall far behind the oldest output is dropped. Call this after that happens to receive the current screen instead of a broken, partial one. Advisory and best-effort; a no-op for a non-pty exec.

close

Release the live stream without touching the remote command.

wait

Wait for the exec to complete and return its buffered result.
If the live stream delivers a clean exit it resolves immediately; otherwise it fetches the authoritative result from the server. result.stdout/result.stderr are always the full capped tail, independent of how much was consumed via live iteration.Ctrl-C sends SIGINT and resumes waiting (the guest’s natural 128+SIGINT=130 exit code flows back); a second Ctrl-C escalates to SIGKILL and re-raises so a wedged guest can’t trap the caller.If stop is given and fires before the stream ends, wait() returns None and leaves the exec running, so a caller that no longer needs the result can return promptly. Without stop the result is non-None.If this exec opened a Voyages auto-span, wait() closes it so the span records this run’s outcome.

AsyncExecProcess

A command running in a Sailbox, with an async interface.Returned by await Sailbox.exec.aio(...). stdout and stderr are async iterators over live output chunks (async for chunk in proc.stdout; chunks are arbitrary slices of the stream, not lines), and await proc.wait() returns the buffered result. Like the sync handle, the command runs detached inside the Sailbox, so dropping this handle leaves it running and its result retrievable through wait().Cancelling the task awaiting wait() stops waiting but leaves the command running; call await proc.cancel() to signal the command itself.

exec_request_id

Stable server-assigned identifier of this exec.

stdout

Live stdout async iterator yielding str chunks (incrementally decoded UTF-8; use stdout_bytes for raw bytes). Each access returns a fresh iterator that replays the retained output from the start, then follows live.

stderr

Live stderr async iterator; same semantics as stdout. Empty for a pty exec, which merges stderr onto stdout.

output

Live merged terminal output for a pty exec (alias of stdout).

stdout_bytes

Live stdout async iterator yielding raw bytes chunks, exactly as the command wrote them.

stderr_bytes

Raw bytes twin of stderr.

output_bytes

Raw bytes twin of output (alias of stdout_bytes).

stdin

Stdin writer for an open_stdin=True exec; raises sail.InvalidArgumentError otherwise.

exit_code

Exit code if the live stream delivered it, else None (see the sync handle’s note).

poll

Alias of exit_code; never blocks.

cancel

Signal the guest command: SIGINT by default, SIGKILL if force=True.

resize

Set the pty window for a pty=True exec; a no-op otherwise.

resync

Ask a pty=True exec to repaint its current screen; a no-op otherwise. See the sync ExecProcess.resync.

close

Release the live stream without touching the remote command.

wait

Wait for the exec to complete and return its buffered result.
result.stdout/result.stderr are the full capped tail, independent of how much was consumed via live iteration. A repeat wait() returns the cached result. If this exec opened a Voyages auto-span, wait() closes it with the run’s outcome.

StdinWriter

File-like write side of an exec’s stdin, reached via proc.stdin.Writes block (with backoff) while the guest buffer is full, like a real pipe; a completed or stdin-closed exec surfaces as BrokenPipeError.

close

Send EOF; the guest closes the pipe once the backlog drains.

AsyncStdinWriter

Async write side of an exec’s stdin, reached via proc.stdin.await stdin.write(data) applies backpressure (it resolves once the guest accepts the bytes); await stdin.close() sends EOF.

close

Send EOF; the guest closes the pipe once the backlog drains.

function

Decorate a Python function so it can run through Sailbox.exec.

SailFunction

A Python function that can be executed inside a Sailbox.

SailboxFs

Filesystem operations on a Sailbox’s guest, reached via Sailbox.fs.File I/O streams bytes to/from the guest; the directory helpers create, remove, and test paths. Paths are remote POSIX paths in the guest, accepted as str or PurePosixPath.

read

Read a regular file from the Sailbox as bytes.
Loads the entire file into memory. For files larger than a few hundred MiB (model checkpoints, datasets) prefer read_stream, which yields chunks without buffering.

read_stream

Stream a regular file’s contents from the Sailbox as chunks.
The result is iterable both ways, so the same call serves sync and async code::for chunk in sb.fs.read_stream(path): … async for chunk in sb.fs.read_stream(path): …Chunks arrive at a fixed transfer size (currently 1 MiB). Iterate to completion so the underlying stream is released.

write_stream

Open a streaming write to a regular file in the Sailbox.
Returns a FileWriter: push chunks with write and confirm with finish (only finish commits the write). Best used as a context manager, which finishes on a clean exit and aborts on an exception::with sb.fs.write_stream(“/logs/run.log”) as writer: for chunk in produce_chunks(): writer.write(chunk)The async form returns an AsyncFileWriter whose write / finish / abort are awaited::async with await sb.fs.write_stream.aio(“/logs/run.log”) as writer: await writer.write(chunk)

write

Write data to a regular file in the Sailbox.
Missing parent directories are created by default.

mkdir

Create a directory and any missing parents (like mkdir -p); a no-op if it already exists.

remove

Remove a file or directory tree (like rm -rf); a no-op if it is already absent.

exists

Whether path exists in the guest. Follows symlinks (like test -e), so a dangling symlink reports False even though ls lists it.

ls

List a directory’s immediate entries as DirEntry records (no recursion). Runs GNU find in the guest, which the default Debian image ships. A missing path raises, as does a path that is not a directory and a listing too large for the exec output cap. An entry whose name is not valid UTF-8 fails the listing, since the path API cannot address it.

FileWriter

A streaming write to a guest file.Push chunks with write and confirm the write with finish; only finish commits it. An unfinished writer aborts on __exit__ (or explicit abort), so a stream that ends without finish is never committed as a completed write; the guest file state after an abort is unspecified. Usable as a context manager: a clean exit finishes, an exception aborts and propagates. AsyncFileWriter, returned by write_stream.aio, is the async form.

write

Write bytes (or UTF-8 text); writes are chunked at the transport size.

finish

Confirm the write, creating an empty file when nothing was written.

abort

Cancel the write so it is never committed. Idempotent; a no-op after finish.

AsyncFileWriter

A streaming write to a guest file, with an async interface.Returned by write_stream.aio; same commit semantics as FileWriter with await-able write, finish, and abort. Usable as an async context manager: a clean exit finishes, an exception aborts and propagates.

write

Write bytes (or UTF-8 text); writes are chunked at the transport size.

finish

Confirm the write, creating an empty file when nothing was written.

abort

Cancel the write so it is never committed. Idempotent; a no-op after finish.

FileStream

An iterable stream of file chunks that opens on first use.Opening resolves the box’s live endpoint (waking it on demand). Deferring that to first iteration keeps read_stream cheap to call, and the async path runs it off the event loop so other tasks keep running. Iterate to completion, or call close (or use it as a context manager) to release the stream early.

close

Stop the stream and release its resources; safe to call twice.

aclose

Async twin of close, run off the event loop.

Volume

A managed NFS volume that can be mounted into one or more Sailboxes.Volumes are currently in Alpha. To pilot them, reach out in the Sail Slack: https://join.slack.com/t/sailresearchcrew/shared_invite/zt-41pdcym9j-UU0Ey~A~r6n2H0DQVQsQHQ.Attributes:

find

Get an org-scoped NFS volume by name, optionally creating it.

list

List active NFS volumes for the current organization, newest first.

delete

Delete this NFS volume, returning the deleted handle.
With allow_missing=True an already-deleted volume returns None instead of raising. To delete by name without a handle, use delete_by_name.

delete_by_name

Delete the NFS volume with the given name, returning the deleted handle.
With allow_missing=True a name that does not resolve to a volume returns None instead of raising.

from_mount

Load the volume handle for a path mounted into a Sailbox.

ingress_auth_headers

Headers that authenticate this Sailbox as an ingress allowlist source.
Use these when making HTTP requests from one Sailbox to another listener whose allowlist contains the caller’s app name. The helper is only available inside a Sailbox.

Voyages

A Voyage records what an agent run did. The module-level calls below act on the Voyage attached to the current process, so most code never holds a Voyage object itself.

create

Create a new Voyage and make it the current Voyage.
The current Voyage is tracked per execution context with a process-wide fallback: concurrent tasks or threads that each create their own Voyage keep their own attribution, and a context that never created one (a raw worker thread, code after asyncio.run returns) resolves the process’s most recently created Voyage.Always creates, even when SAIL_VOYAGE_ID is set in the environment; a child process joining its parent’s Voyage uses attach instead. Without a Sail API key (SAIL_API_KEY or a sail auth login credential) this degrades to a NoopVoyage so instrumented code keeps running with telemetry disabled. Arguments are validated before that gate: a malformed call raises even when telemetry is off.

attach

Attach to an existing Voyage and make it the current Voyage (context-scoped, like create).
voyage_id defaults to SAIL_VOYAGE_ID, the handoff a parent process sets so its children join the parent’s Voyage. Raises ValueError when neither is provided and telemetry is enabled. Without a Sail API key (SAIL_API_KEY or a sail auth login credential) this degrades to a NoopVoyage so instrumented code keeps running with telemetry disabled. This includes a keyless child whose parent exported no handoff env. An explicitly malformed argument always raises; an absent ambient config in a telemetry-off environment is the no-op state.

run

Run one Voyage around a block. This is the recommended entry point.
with sail.voyage.run("code-review") as voyage: creates the Voyage on enter (same arguments and semantics as create: always creates, never reads SAIL_VOYAGE_ID), emits voyage.completed on a clean exit, and on an exception emits voyage.failed with the exception’s type and message, then re-raises. Use async with sail.voyage.run(...) for the async form, which drives the same lifecycle without blocking the event loop. Terminal delivery is a bounded best-effort flush; call voyage.flush() inside the block for strict delivery confirmation. Without a Sail API key the block runs with telemetry disabled, exactly like create. Controllers that create and complete the Voyage in different places keep using create / attach directly.

disable

Disable Voyage telemetry (context-scoped, like create).
Like create, this also resets the process-wide fallback, so contexts that never started their own Voyage (a raw worker thread, code after asyncio.run) resolve the disabled state too.Installs and returns a NoopVoyage as the current Voyage. This is the public form of the telemetry-off state create()/attach() enter when no Sail API key is available. For controllers that catch a startup telemetry failure and choose to continue unobserved: except VoyageError: voyage = sail.voyage.disable().

child_env

Env vars a child process needs to attach() to the current Voyage.
Returns {} when there is no current Voyage or telemetry is disabled, so the handoff pattern is safe to use without an API key. See Voyage.child_env.

voyage_id

The current Voyage’s id, or None when no Voyage is current.

headers

Headers attributing a Sail API request to the current Voyage and to the span/agent context active at call time. Compute per request, never once at client construction.

wrap_openai

Attribute an OpenAI-style client’s Sail calls to the live Voyage context.
Wraps the client’s request methods in place (responses.create, responses.retrieve, and chat.completions.create, whichever exist) so every call computes the attribution headers AT CALL TIME (voyage id plus the span/agent active at that moment) and injects them via extra_headers. Snapshotting stale headers at client construction (default_headers=sail.voyage.headers()) becomes impossible: there is nothing to snapshot. Like the sail.inference wrappers, un-spanned create calls get a synthesized auto-span so the model call lands scoped; retrieve polls carry headers but never synthesize.voyage= pins attribution to one Voyage handle; the default follows the current Voyage per call. Async clients (AsyncOpenAI) are supported: coroutine-function methods get an async wrapper whose auto-span covers the awaited request, not just coroutine creation. Wrapping mutates the client in place (every holder of the object sees attribution), is idempotent, returns the client, and raises TypeError for an object exposing none of the known request methods.

event

Record one timestamped event on the current Voyage.
Everything after payload is keyword-only so a stale positional caller fails loudly rather than being silently reinterpreted.Agent attribution comes from the enclosing agent() context (or the SAIL_AGENT_* env defaults); there is no per-event override.

span

Open a named span on the current Voyage; context manager or decorator.
The current Voyage is resolved when the context is entered (or the decorated function is called), not when span() is evaluated. A module-level @sail.span(...) declared before create() attributes correctly. span_name may be omitted only in the decorator form, where it defaults to the function’s __qualname__.

agent

Declare the named agent on the current Voyage; context manager or decorator.
The current Voyage is resolved at enter/call time, not at construction, so a module-level @sail.agent(...) declared before create() attributes correctly. Arguments are validated eagerly: a bad name or slug raises at the declaration site regardless of voyage state.

complete

Mark the current Voyage completed. A no-op when no Voyage is active.

fail

Mark the current Voyage failed. A no-op when no Voyage is active.

cancel

Mark the current Voyage canceled. A no-op when no Voyage is active.

flush

Flush the current Voyage’s buffered events. A no-op when none is active.

Voyage

A Sail Voyage attached to the current process.

headers

Headers attributing a Sail API request to this Voyage and to the span/agent context active at call time.
Compute per request, never once at client construction, so each call carries the span and agent actually active when it is made. Stale Voyage context headers in existing are replaced. Agent ids are slug-derived and therefore header-safe by construction; a non-header-safe caller-supplied span id is omitted rather than sent.

child_env

Env vars a child process needs to attach() to this Voyage.
Merge into the child’s environment: subprocess.run(cmd, env={**os.environ, **voyage.child_env()}). With agent=True (default) the active agent() context rides along as the child’s SAIL_AGENT_* defaults, so the child’s events attribute to the same agent without re-declaring it. NoopVoyage.child_env() returns {}. The handoff is safe to call without an API key.

event

Record one timestamped event.
Everything after payload is keyword-only so a stale positional caller fails loudly rather than having an argument silently reinterpreted as span_id.Agent attribution carries no per-event override: it comes from the enclosing agent() context, or from the SAIL_AGENT_* env defaults when no context is active. A one-shot attributed event is with voyage.agent(...): voyage.event(...).

span

Open a named span of work; nests under the active span automatically.
Usable as a context manager or as a decorator. span_name may be omitted only in the decorator form (@voyage.span()), where it defaults to the decorated function’s __qualname__; the with form requires a name.A span carries no agent identity of its own. Events emitted inside it (including the span’s own lifecycle events) are attributed to the enclosing agent() context.

agent

Declare the named agent as the active participant.
name is the display identity shown in the dashboard; the stable attribution key (agent_id) is derived from it: lowercased, ASCII-folded, hyphenated. Pass slug= to pin the attribution key across display renames or multi-process attach; role= is an optional freeform taxonomy used for dashboard filtering.

cancel

Mark this Voyage cancelled without emitting a customer event.
Cancel stops recording for the Voyage; it does not terminate external agent code. NoopVoyage.cancel() and unattached Voyage instances are no-ops when no API key is configured.Unlike complete()/fail() (which never raise: there is a buffered terminal flush behind them), cancel() is a single synchronous control-plane request and raises VoyageHTTPError on a failed response. Wrap it if you call it from a finally/cleanup path where an exception would mask the original error.

NoopVoyage

No-op Voyage used when no Sail API key is available.Attribute-compatible with Voyage so fail-open code that reads voyage fields does not crash when telemetry is disabled.

SailTokenCompleter

Tinker TokenCompleter backed by Sail’s raw-token Responses path.Extends TokenCompleter.

Config

SDK configuration resolved from the environment and ~/.sail.Sail resolves the API key and service endpoints, with environment variables taking precedence over the stored ~/.sail credentials. Set SAIL_API_KEY (or run sail auth login) to authenticate. SAIL_API_URL, SAILBOX_API_URL, SAIL_IMAGEBUILDER_URL, and SAILBOX_INGRESS_URL override individual endpoints, for custom or self-hosted stacks. Configuration is resolved once per process; in a long-lived process, call sail.reset_transports() after changing these variables. ingress_base and ingress_scheme describe how a listener’s public URL is built from the Sailbox id and port when the server does not return one: "path" addresses <base>/_sailbox/{id}/{port}, "subdomain" addresses <sailbox>-<port>.<base host>.

from_env

Load SDK config, raising ValueError when no API key is configured.

from_env_optional_api_key

Load SDK config without requiring an API key.
Like from_env() but does not raise when no key is configured, so endpoints still resolve for paths that do not need to authenticate (such as building a listener’s public URL).

RetryPolicy

Knobs governing the auto-retry path.max_attempts counts the initial request, so max_attempts=3 means at most two retries. base_delay and max_delay are seconds; the schedule is exponential with full jitter, capped at max_delay, and a server-sent Retry-After wins (also capped at max_delay). Retried mutations carry an Idempotency-Key header, so a retry cannot double-apply.Attributes:

Types

Plain data types accepted by and returned from the calls above.

IngressPort

A guest port to expose for ingress.protocol selects how the port is published:
  • "http" (the default) exposes the port as an HTTP service with a stable HTTPS URL.
  • "tcp" exposes the port as a byte-transparent raw-TCP service reachable at a stable host and port by any TCP client (for example a database client such as psql -h <host> -p <port>).
Sailbox.create(ingress_ports=...) also accepts a bare int as shorthand for IngressPort(port) (i.e. an HTTP port).allowlist restricts which sources may connect to this port. Entries that parse as CIDR prefixes (e.g. ["203.0.113.0/24"]) match source IPs; other entries are treated as Sail app names whose Sailboxes may connect. App-name entries are supported on "http" listeners only. Raw-TCP connections carry no source app identity, so "tcp" allowlists must be CIDR prefixes. Each port carries its own allowlist. An empty/omitted list means any source may connect.Exposing a well-known unauthenticated service port (e.g. a database) as raw TCP without an explicit allowlist is rejected. Use source restrictions, or pass ["0.0.0.0/0", "::/0"] to explicitly allow every source.Attributes:

HttpEndpoint

The routable HTTPS address of an "http" listener.Attributes:

TcpEndpoint

The host and port to connect to for a "tcp" listener.Attributes:

Listener

An exposed guest port.guest_port is the guest port you exposed. endpoint is how you reach it: an HttpEndpoint for "http" listeners or a TcpEndpoint for "tcp" listeners. It is None until the listener is routable.Attributes:

SailboxPage

One page of Sailbox.list_page results plus the server’s pagination envelope.Attributes:

SailboxCheckpoint

A durable checkpoint handle that can be used to start new Sailboxes.Attributes:

UpgradeResult

The outcome of a Sailbox runtime upgrade.Attributes:

SailboxDeprecation

Actionable notice that a Sailbox’s runtime should be upgraded.Attributes:

DirEntry

One entry in a directory listing from SailboxFs.ls.Attributes:

ExecResult

The completed result of a Sailbox command: its buffered output, exit code, whether it was killed on timeout, and flags describing how complete the buffered output is.Attributes:

GuestPath

A path inside the guest: a str or a PurePosixPath. Guest paths are remote POSIX paths, independent of the local platform.

DEFAULT_LIST_LIMIT

Default page size for Sailbox.list and Sailbox.list_page, sourced from the shared core.

Errors

Exceptions raised by this SDK surface. Every one of them extends SailError, so except sail.SailError catches them all. SailDeprecationWarning is the one entry below that is not an error: it is a warning the SDK emits through Python’s warnings module.

SailError

Base class for Sail SDK errors.Every operation failure the SDK raises derives from this class, and the classes that match a Python builtin also inherit it (for example NotFoundError is a LookupError), so except sail.SailError and builtin-based handlers both work. A few argument-type mistakes raise the plain builtin TypeError.Attributes:

NotFoundError

Raised when a resource (Sailbox, app, volume, or checkpoint) is not found.Extends SailError, LookupError.

PermissionDeniedError

Raised for a missing or invalid API key, or insufficient scope.Extends SailError, PermissionError.

InvalidArgumentError

Raised when an argument or the SDK configuration is rejected as invalid.Extends SailError, ValueError.

InternalError

Raised for an unexpected internal SDK failure.Extends SailError, RuntimeError.

FileNotFoundError

Raised when a guest file operation references a path that does not exist.Extends SailError, builtins.FileNotFoundError.

BrokenPipeError

Raised when a stdin write hits a command that already finished.Extends SailError, builtins.BrokenPipeError.

TimeoutError

Raised when a transport attempt exceeds its deadline.Extends SailError, builtins.TimeoutError.

TransportError

Raised when the transport cannot establish or maintain a connection.Extends SailError, ConnectionError.

ApiError

Raised for any other non-2xx API response.Extends SailError, RuntimeError.RuntimeError inheritance keeps generic retry-on-RuntimeError loops working; prefer branching on retryable.

SailDeprecationWarning

Warning that a Sail client or Sailbox runtime should be upgraded.Extends UserWarning.

SailboxError

Base class for Sailbox-specific SDK errors.Extends SailError.

SailboxCreationError

Raised when Sailbox creation fails.Extends SailboxError.

ImageBuildError

Raised when a custom image build fails.Extends SailboxError.

SailboxExecutionError

Base class for Sailbox exec-related SDK errors.Extends SailboxError.

SailboxTerminatedError

Raised when the Sailbox no longer exists.Extends SailboxExecutionError.

SailboxExecRequestNotFoundError

Raised when a wait references an unknown exec request.Extends SailboxExecutionError.

SailboxHostLostError

Raised when the machine hosting your Sailbox failed before the command finished.Extends SailboxExecutionError.The command may have run only partially, and its output is gone. The run cannot be resumed. Calling Sailbox.exec again starts it over from the beginning, so any side effects the partial run applied will happen again. The Sailbox itself recovers automatically, so you do not need to resume it.

SailboxFunctionError

Raised when a Python function fails while running in a Sailbox.Extends SailboxExecutionError.

SailboxFunctionSerializationError

Raised when a Python function payload or result cannot be serialized.Extends SailboxExecutionError.

CommandFailedError

Raised by run(check=True) when the command exits nonzero or times out.Extends SailboxExecutionError.Carries the completed sail.ExecResult as result.

VoyageError

Base class for Voyage SDK errors.Extends SailError.

VoyageHTTPError

Raised when the Voyage API returns an HTTP error.Extends VoyageError.

VoyageNotFoundError

Raised when a Voyage cannot be found for the current API key.Extends VoyageHTTPError.

InferenceError

Base class for Sail inference wrapper errors.Extends SailError.

InferenceHTTPError

Raised when a Sail inference endpoint returns an HTTP error.Extends InferenceError.