Skip to main content

DEFAULT_LIST_LIMIT

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

IngressPort

@dataclass(frozen=True)
class 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.

guest_port

The in-guest port to expose (1-65535).

protocol

"http" (the default) or "tcp".

allowlist

Source addresses allowed to reach the port; empty allows all.

ingress_auth_headers

def ingress_auth_headers() -> Dict[str, str]
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.

HttpEndpoint

@dataclass(frozen=True)
class HttpEndpoint
The routable HTTPS address of an "http" listener.

url

The routable HTTPS URL.

TcpEndpoint

@dataclass(frozen=True)
class TcpEndpoint
The host and port to connect to for a "tcp" listener.

host

Hostname to dial.

port

Port to dial.

Listener

@dataclass(frozen=True)
class 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.

guest_port

The in-guest port traffic is forwarded to.

protocol

Wire protocol exposed ("http" or "tcp").

route_status

Status of the listener’s ingress route.

endpoint

How to reach the listener; None until it is routable.

SailboxPage

@dataclass(frozen=True)
class SailboxPage
One page of Sailbox.list_page results plus the server’s pagination envelope.

items

The sailboxes on this page.

limit

The page size that was applied.

offset

The offset that was applied.

total

Total matching sailboxes across all pages.

has_more

Whether more results exist past this page.

UpgradeResult

@dataclass(frozen=True)
class UpgradeResult
The outcome of an in-guest agent upgrade.

applied

True when applied immediately (running box); False when deferred to the next wake.

status

Lifecycle status of the sailbox after the upgrade call.

SailboxCheckpoint

@dataclass(frozen=True)
class SailboxCheckpoint
A durable checkpoint handle that can be used to start new sailboxes.

checkpoint_id

The checkpoint id.

sailbox_id

The sailbox the checkpoint was taken from.

checkpoint_generation

Checkpoint generation captured by this checkpoint.

expires_at

RFC3339 UTC time at which the handle expires and is eligible for garbage collection, or None for a handle to an existing checkpoint that carries no fresh retention bound (a paused or sleeping source).

status

The source sailbox’s status after checkpointing.

Sailbox

@dataclass(frozen=True)
class 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. 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.

sailbox_id

The sailbox id: the stable, durable external handle.

name

The sailbox name.

status

Lifecycle status (for example "running").

app_id

Identifier of the owning app.

app_name

Name of the owning app.

image_id

Identifier of the image the sailbox was created from.

memory_mib

Configured memory, in MiB.

vcpu_count

Configured number of vCPUs.

state_disk_size_gib

Configured state-disk size, in GiB.

cpu_requested_vcpu

Requested CPU, in vCPUs.

cpu_used_vcpu

Current CPU usage, in vCPUs.

memory_requested_bytes

Requested memory, in bytes.

memory_used_bytes

Current memory usage, in bytes.

disk_requested_bytes

Requested disk, in bytes.

disk_used_bytes

Current disk usage, in bytes.

architecture

CPU architecture (for example "arm64").

guest_schema_version

In-guest agent version the sailbox last booted (or was created) with. None for handles born from create or for boxes created before agent versions were recorded.

error_message

Human-readable error detail when the box is in an error state.

checkpoint_generation

Monotonic checkpoint generation counter.

started_at

When the box last started, if it ever has.

last_checkpointed_at

When the most recent checkpoint was taken, if any.

created_at

When the sailbox was created.

updated_at

When the sailbox was last updated.

create

def create(cls,
           *,
           app: Union[App, str],
           image: Optional[ImageDefinition] = None,
           name: str,
           image_build_timeout: int = 1800,
           timeout: int = 600,
           size: Optional[SailboxSize] = None,
           memory_gib: Optional[int] = None,
           disk_gib: Optional[int] = None,
           autosleep_timeout_minutes: Optional[int] = None,
           ingress_ports: Optional[List[Union[int, IngressPort]]] = None,
           volumes: Optional[Mapping[str, Any]] = None,
           ssh: bool = False) -> Sailbox
Create a new sailbox. Custom image definitions are built first. Then this sends a synchronous POST /v1/sailboxes that returns once the VM is running or creation has failed. 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, and because the request was aborted before returning an id you find or terminate it by name. Pass 0 to leave each attempt unbounded. size picks the vCPU count and the default memory/disk ceilings; memory_gib and disk_gib tune those ceilings in whole GiB within the size’s range. 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. 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}. size selects the resource size: "s" or "m" (the default). Each size sets the vCPU count plus default memory and disk. Billing is based on observed usage, so a bigger size costs no more on its own. 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. autosleep_timeout_minutes optionally lets Sail sleep the box after that many minutes of continuous idleness. When set, Sail will sleep the box after this many minutes with no active execs, no recent inbound network ingress, and CPU usage less than 0.05. Leave it as None to disable autosleep. The value must be between 1 and 1440 minutes. await Sailbox.create.aio(...) is the async form, building the image and provisioning the VM without blocking the event loop.

list

def list(cls,
         *,
         app_id: Optional[str] = None,
         status: Optional[SailboxStatus] = None,
         search: Optional[str] = None,
         max_guest_schema_version: Optional[int] = None,
         limit: int = DEFAULT_LIST_LIMIT,
         offset: int = 0) -> List[Sailbox]
List sailboxes for the current org via public HTTP. Filters are server-side. app_id filters by the owning app id (resolve an app name through App.find first if needed). max_guest_schema_version keeps only sailboxes whose in-guest agent version is at or below the given version (sailboxes created before agent versions were recorded always match), useful for finding sailboxes that need upgrade(). Returns just the sailboxes. Use list_page for the same call with the pagination envelope (limit/offset/total/has_more).

list_page

def list_page(cls,
              *,
              app_id: Optional[str] = None,
              status: Optional[SailboxStatus] = None,
              search: Optional[str] = None,
              max_guest_schema_version: Optional[int] = None,
              limit: int = DEFAULT_LIST_LIMIT,
              offset: int = 0) -> SailboxPage
List sailboxes and return them alongside the pagination envelope.

get

def get(cls, sailbox_id: str) -> Sailbox
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_checkpoint

def from_checkpoint(cls,
                    checkpoint_id: str,
                    *,
                    name: Optional[str] = None,
                    timeout: Optional[int] = None) -> Sailbox
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.

terminate

def terminate() -> None
Permanently terminate this sailbox.

pause

def pause() -> None
Checkpoint and pause this sailbox until it is explicitly resumed.

sleep

def sleep() -> None
Checkpoint and sleep this sailbox until traffic or explicit resume wakes it.

checkpoint

def checkpoint(*,
               name: Optional[str] = None,
               ttl_seconds: Optional[int] = None) -> SailboxCheckpoint
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

def upgrade() -> UpgradeResult
Upgrade this sailbox’s in-guest agent 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

def resume() -> Sailbox
Resume this sailbox through the public API.

listener

def listener(guest_port: int) -> Listener
Fetch one listener by guest port without waking the box.

listeners

def listeners() -> list[Listener]
List this sailbox’s listeners without waking it.

wait_for_listener

def wait_for_listener(guest_port: int,
                      *,
                      timeout: float = 60.0,
                      poll_interval: float = 1.0) -> 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

def expose(guest_port: int,
           protocol: IngressProtocol = "http",
           allowlist: Optional[List[str]] = None) -> Listener
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 (otherwise keeping the same address: a raw-TCP port reclaims its reserved one). 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 (its address stays reserved). Returns the Listener (its route_status is "unspecified": 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

def unexpose(guest_port: int) -> None
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 reserved to this sailbox. Re-expose-ing the same guest port reclaims the exact address, and no other sailbox ever takes it (a sailbox that ran on an address may have an ssh host key pinned to it, so the address is never reused). An "http" port carries no such reservation and is removed outright. Removing a port that is not exposed raises a LookupError.

ingress_auth_headers

def ingress_auth_headers() -> Dict[str, str]
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

def enable_ssh(*,
               allowlist: Optional[List[str]] = None,
               wait: bool = True,
               timeout: float = 60.0) -> Optional[TcpEndpoint]
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. 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.

read

def read(path: str) -> bytes
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

def read_stream(path: str) -> Any
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.read_stream(path): … async for chunk in sb.read_stream(path): … Chunks are sized by the guest file service (currently 1 MiB per response). Iterate to completion so the underlying stream is released.

write_stream

def write_stream(path: str,
                 *,
                 create_parents: bool = True,
                 mode: Optional[int] = None) -> FileWriter
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.write_stream(“/logs/run.log”) as writer: for chunk in produce_chunks(): writer.write(chunk)

write

def write(path: str,
          data: Union[str, bytes, bytearray, memoryview, IOBase],
          *,
          create_parents: bool = True,
          mode: Optional[int] = None) -> None
Write data to a regular file in the sailbox. Missing parent directories are created by default.

exec

def exec(
        command: Union[str, Sequence[str], SailFunction],
        *function_args: Any,
        timeout: Optional[int] = None,
        background: bool = False,
        cwd: Optional[str] = None,
        idempotency_key: Optional[str] = None,
        open_stdin: bool = False,
        pty: bool = False,
        term: Optional[str] = None,
        cols: int = 0,
        rows: int = 0,
        retry_timeout: float = EXEC_TRANSIENT_RETRY_TIMEOUT_SECONDS,
        args: Optional[Union[List[Any], Tuple[Any, ...]]] = None,
        kwargs: Optional[Mapping[str, Any]] = None) -> Union[ExecProcess, Any]
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. 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. retry_timeout budgets the transient-RPC retries while the backend admits the command, covering the delay of waking or migrating a sailbox. await sb.exec.aio(...) is the async form: a shell command resolves to an AsyncExecProcess, a function to its return value.

shell

def shell(command: Optional[str] = None,
          *,
          shell: Optional[str] = None,
          term: Optional[str] = None,
          cwd: Optional[str] = None,
          timeout: Optional[int] = None) -> int
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). This is the streaming-exec equivalent of ssh-ing into the box, without a separate SSH server: it rides the same channel as exec(pty=True). shell overrides the login shell (default $SHELL or /bin/bash); it is ignored when command is given.

FileWriter

class 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), canceling the RPC so the server does not treat the stream end as a completed write; the guest file state after an abort is unspecified. Usable as a context manager (sync and async): a clean exit finishes, an exception aborts and propagates.

write

def write(data: Union[str, bytes, bytearray, memoryview]) -> None
Write bytes (or UTF-8 text); writes are chunked at the transport size.

write_aio

async def write_aio(data: Union[str, bytes, bytearray, memoryview]) -> None
Async variant of write.

finish

def finish() -> None
Confirm the write, creating an empty file when nothing was written.

finish_aio

async def finish_aio() -> None
Async variant of finish.

abort

def abort() -> None
Cancel the write so the server does not commit it. Idempotent; a no-op after finish.

FileStream

class 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

def close() -> None
Stop the stream and release its resources; safe to call twice.

App

@dataclass(frozen=True)
class App
A Sail application.

id

Stable server-assigned app identifier.

name

Human-readable app name unique within the owning org.

created_at

App creation time.

find

def find(cls, *, name: str, mint_if_missing: bool = False) -> App
Find an app by name, optionally creating it if it doesn’t exist.

list

def list(cls) -> list[App]
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

@dataclass(frozen=True)
class ImageDefinition

apt_install

def apt_install(*packages: str) -> ImageDefinition
Add an apt-get install step for packages.

pip_install

def pip_install(*packages: str) -> ImageDefinition
Add a pip install step for packages.

run_commands

def run_commands(*cmd: str) -> ImageDefinition
Add shell commands to the build, each as its own step.

add_local_file

def add_local_file(local_path: Union[str, Path],
                   remote_path: str,
                   *,
                   mode: Optional[int] = None) -> ImageDefinition
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

def add_local_dir(
    local_path: Union[str, Path],
    remote_path: str,
    *,
    ignore: Optional[Union[Sequence[str], Path,
                           str]] = None) -> ImageDefinition
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

def env(env: Dict[str, str]) -> ImageDefinition
Bake environment variables into the image.

build

def build(*, timeout: int = 1800) -> ImageDefinition
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.

ImageNamespace

class 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

@property
def builtin_debian() -> ImageDefinition
Prebuilt Debian base, matching the Sailbox host architecture.

debian_amd64

@property
def debian_amd64() -> ImageDefinition
Debian base for x86-64, pinned to your local Python version.

debian_amd

@property
def debian_amd() -> ImageDefinition
Alias for debian_amd64.

builtin_debian_amd64

@property
def builtin_debian_amd64() -> ImageDefinition
Debian base for x86-64, without Python-version pinning.

debian_arm64

@property
def debian_arm64() -> ImageDefinition
Debian base for arm64, pinned to your local Python version.

debian_arm

@property
def debian_arm() -> ImageDefinition
Alias for debian_arm64.

builtin_debian_arm64

@property
def builtin_debian_arm64() -> ImageDefinition
Debian base for arm64, without Python-version pinning.

devbox_amd64

@property
def devbox_amd64() -> ImageDefinition
Devbox base for x86-64: Debian plus a baked development toolchain.

devbox_arm64

@property
def devbox_arm64() -> ImageDefinition
Devbox base for arm64: Debian plus a baked development toolchain.

builtin_devbox_amd64

@property
def builtin_devbox_amd64() -> ImageDefinition
Alias for devbox_amd64.

builtin_devbox_arm64

@property
def builtin_devbox_arm64() -> ImageDefinition
Alias for devbox_arm64.

Volume

@dataclass(frozen=True)
class Volume
A managed NFS volume that can be mounted into one or more Sailboxes.

volume_id

The volume id.

name

The volume name.

backend

Storage backend serving the volume.

status

Lifecycle status.

mount_path

Mount path inside the sailbox, if reported.

created_at

Creation time, if reported.

updated_at

Last-update time, if reported.

find

def find(name: str, *, mint_if_missing: bool = False) -> "Volume"
Get an org-scoped NFS volume by name, optionally creating it.

list

def list(*, max_objects: Optional[int] = None) -> List["Volume"]
List active NFS volumes for the current organization, newest first.

delete

def delete(*, allow_missing: bool = False) -> Optional["Volume"]
Delete this NFS volume. This method also supports sail.Volume.delete("name") as a convenience for deleting a named volume without first keeping the returned handle.

from_mount

@staticmethod
def from_mount(path: str | os.PathLike[str]) -> "Volume"
Load the volume handle for a path mounted into a Sailbox.

ExecResult

@dataclass(frozen=True)
class ExecResult

stdout

Buffered stdout: a capped, drop-oldest tail of the output.

stderr

Buffered stderr: a capped, drop-oldest tail of the output.

exit_code

The command’s exit code.

timed_out

Whether the command was killed for exceeding its timeout.

stdout_truncated

The server output ring overflowed and dropped the oldest bytes of the buffered copy returned here.

stderr_truncated

Like stdout_truncated, for stderr.

stdout_complete

The live stream delivered the output through to the command’s exit, so a consumer that streamed it already holds the complete output even when the buffered copy here is a truncated tail.

stderr_complete

Like stdout_complete, for stderr.

ExecProcess

class 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

@property
def exec_request_id() -> str
Stable server-assigned identifier of this exec.

stdout

@property
def stdout() -> Iterator[str]
Live stdout iterator. 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

@property
def stderr() -> Iterator[str]
Live stderr iterator; same semantics as stdout. Empty for a pty exec, which merges stderr onto stdout.

output

@property
def output() -> Iterator[str]
Live merged terminal output for a pty exec (alias of stdout).

stdin

@property
def stdin() -> _StdinWriter
Stdin writer for an open_stdin=True exec; raises ValueError otherwise.

exit_code

@property
def exit_code() -> Optional[int]
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 worker was lost before it produced a real exit code raises SailboxWorkerLostError here, exactly as wait() does.

poll

def poll() -> Optional[int]
Alias of exit_code; never blocks.

cancel

def cancel(*, force: bool = False, retry_timeout: float = 0.0) -> None
Signal the guest command: SIGINT by default, SIGKILL if force=True. Idempotent on the server. By default does not retry transient errors; retry_timeout > 0 keeps retrying within that budget, covering the brief window right after the command starts when the guest cannot accept signals for it yet.

resize

def resize(cols: int, rows: int) -> None
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.

close

def close() -> None
Release the live stream without touching the remote command.

wait

def wait(*, stop: Optional[threading.Event] = None) -> Optional[ExecResult]
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

class 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 (async for line in proc.stdout), 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

@property
def exec_request_id() -> str
Stable server-assigned identifier of this exec.

stdout

@property
def stdout() -> Any
Live stdout async iterator. Each access returns a fresh iterator that replays the retained output from the start, then follows live.

stderr

@property
def stderr() -> Any
Live stderr async iterator; same semantics as stdout. Empty for a pty exec, which merges stderr onto stdout.

output

@property
def output() -> Any
Live merged terminal output for a pty exec (alias of stdout).

stdin

@property
def stdin() -> _AsyncStdinWriter
Stdin writer for an open_stdin=True exec; raises ValueError otherwise.

exit_code

@property
def exit_code() -> Optional[int]
Exit code if the live stream delivered it, else None (see the sync handle’s note).

poll

def poll() -> Optional[int]
Alias of exit_code; never blocks.

cancel

async def cancel(*, force: bool = False, retry_timeout: float = 0.0) -> None
Signal the guest command: SIGINT by default, SIGKILL if force=True.

resize

async def resize(cols: int, rows: int) -> None
Set the pty window for a pty=True exec; a no-op otherwise.

close

def close() -> None
Release the live stream without touching the remote command.

wait

async def wait() -> ExecResult
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.

SailFunction

@dataclass
class SailFunction
A Python function that can be executed inside a Sailbox.

function

def function(func: Optional[F] = None)
Decorate a Python function so it can run through Sailbox.exec.

SailError

class SailError(Exception)
Base class for Sail SDK errors.

SailboxError

class SailboxError(SailError)
Base class for sailbox-specific SDK errors.

SailboxCreationError

class SailboxCreationError(SailboxError)
Raised when sailbox creation fails.

ImageBuildError

class ImageBuildError(SailboxError)
Raised when a custom image build fails.

SailboxExecutionError

class SailboxExecutionError(SailboxError)
Base class for sailbox exec-related SDK errors.

SailboxTerminatedError

class SailboxTerminatedError(SailboxExecutionError)
Raised when the sailbox no longer exists.

SailboxExecRequestNotFoundError

class SailboxExecRequestNotFoundError(SailboxExecutionError)
Raised when a wait references an unknown exec request.

SailboxWorkerLostError

class SailboxWorkerLostError(SailboxExecutionError)
Raised when the machine hosting your sailbox failed before the command finished. 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

class SailboxFunctionError(SailboxExecutionError)
Raised when a Python function fails while running in a sailbox.

SailboxFunctionSerializationError

class SailboxFunctionSerializationError(SailboxExecutionError)
Raised when a Python function payload or result cannot be serialized.

Config

class 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

@classmethod
def from_env(cls) -> Config
Load SDK config, raising ValueError when no API key is configured.

from_env_optional_api_key

@classmethod
def from_env_optional_api_key(cls) -> Config
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

@dataclass(frozen=True)
class 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.

max_attempts

Attempts including the initial request.

base_delay

First backoff delay, in seconds.

max_delay

Backoff cap, in seconds; a server Retry-After is capped here too.

multiplier

Backoff growth factor between attempts.