Skip to content

Session

@final
class Session

One running MicroVM’s control API, with the proxy auth handled for you.

Session declares no __new__ or __init__, so it is not constructed directly: an instance comes back from another call in this package.

agent_token: str

The guest bearer credential. Store only in a private encrypted control record. It is never included in repr or ordinary status output.

egress_posture: str

The launch’s egress posture: "open", "unsealed", "best-effort", or "sealed".

The value the CLI envelope’s egressPosture reports for the same launch options, and what egress_posture_for answers before the launch. A session that does not hold its launch options (Session.direct, Session.attach, an adopted sandbox) reports "unsealed". Advertise network isolation only for "sealed".

endpoint: str

The endpoint this session addresses.

A String rather than a &str because a sandbox-held session reads it under the lock, and a reference would outlive the guard.

launch_metrics: dict | None

Launch observations, or None for a session not created by a launch.

port: int

The port the proxy token is scoped to.

proxy_mint_count: int | None

How many proxy tokens this session has minted, or None for a direct session.

Exposed because it is the only observable that distinguishes a client which re-minted after a resume from one that kept a stale token (STATE-8), and a harness asserting on that behaviour needs to be able to read it. The token itself is not exposed and cannot be: the core’s ProxyToken has no Display, no as_str, and no Deref, so “treat authToken as a string” is as inexpressible here as it is there (TRAP-7).

@staticmethod
def attach(
region: Region,
microvm_id: str,
endpoint: str,
agent_token: str,
*,
port: SupportsIndex | None = None,
request_timeout: SupportsFloat | SupportsIndex | None = None,
) -> Session

Reattach using a private control record, without bootstrapping again. AWS credentials mint fresh proxy tokens. This session owns its transport and shares no sandbox lock; supervisors should attach separately for keepalives, with a short request_timeout. Never publish or log agent_token.

@staticmethod
def attach_async(
region: Region,
microvm_id: str,
endpoint: str,
agent_token: str,
*,
port: SupportsIndex | None = None,
request_timeout: SupportsFloat | SupportsIndex | None = None,
) -> Session

The awaitable twin of Session.attach.

def connect_headers(port: SupportsIndex) -> dict[str, str]

Both proxy headers for port, so a caller can open its own connection.

A dict[str, str], for pairing with endpoint when something outside this client opens a socket to a port on this VM. Minted through the session’s existing token cache, so this does not burn a second control-plane call and does not open a second refresh schedule (TRAP-9).

Empty for a direct session, which is the true answer rather than a missing one: a daemon reached directly takes no proxy headers.

The auth value is a bearer credential and this returns it. Nothing here logs or stores it, and no method takes a header map — so a caller cannot feed a forged or expired one back in.

def connect_headers_async(port: SupportsIndex) -> dict[str, str]

The awaitable twin of connect_headers.

def connect_subprotocols(port: SupportsIndex) -> list[str] | None

The three WebSocket subprotocols for port, in the order a handshake offers them.

A MicroVM endpoint takes the auth and the target port as Sec-WebSocket-Protocol values rather than as headers, because the browser WebSocket constructor cannot set a header — and the platform strips all three before forwarding, so a server inside the VM never negotiates them.

None for a direct session, and deliberately not an empty list: the subprotocol form exists only for a request through the endpoint proxy, so a list carrying the base value with no token would open a handshake refused for a reason naming neither the token nor the port.

The middle string carries the credential. Same rule as connect_headers.

def connect_subprotocols_async(port: SupportsIndex) -> list[str] | None

The awaitable twin of connect_subprotocols.

@staticmethod
def direct(endpoint: str, agent_token: str) -> Session

A session against a daemon reached directly, with no proxy headers.

The shape for a local binary, a test server, or a VM reached over a tunnel. There is deliberately no constructor that takes a proxy token: minting one is the control plane’s job and it happens inside every request (TRAP-9), so a caller handing a token in would be handing in one that expires.

def discovery() -> Discovery | None

Image configuration, or None if this daemon predates discovery.

def discovery_async() -> Discovery | None

The awaitable twin of discovery.

def download_dir(
remote: str,
local_dir: str | PathLike[str],
globs: SupportsGetItem[int, str],
) -> list[DownloadedFile]

Brings the files of a directory in the VM back under local_dir: the regular files globs select, and nothing else.

The daemon packs remote, and the archive describes the VM’s filesystem, where untrusted work runs, so core writes only regular-file members that match a glob, never under .git whatever the globs say, and never outside local_dir. A symlink, a special file or a ../ member is skipped, not refused. ["**"] brings every regular file back. Returns what was written. A local directory that can’t be written to raises InvalidArgError.

def download_dir_async(
remote: str,
local_dir: str | PathLike[str],
globs: SupportsGetItem[int, str],
) -> list[DownloadedFile]

The awaitable twin of download_dir. The local writes run on the shared runtime, not on the event loop.

def download_file(
path: str,
*,
start_line: SupportsIndex | None = None,
end_line: SupportsIndex | None = None,
) -> bytes

Reads one file, or lines start_line through end_line of it.

The range is 1-based and inclusive, and the daemon slices the file, so reading lines 40 to 60 of a large log reads those lines alone. Either bound may be None (line 1, through EOF), and an end_line past the last line reads through EOF. Line 0 and an end before the start raise InvalidArgError before any request.

def download_file_async(
path: str,
*,
start_line: SupportsIndex | None = None,
end_line: SupportsIndex | None = None,
) -> bytes

The awaitable twin of download_file.

def download_tar(remote: str) -> bytes

The raw tar bytes of a remote tree.

def download_tar_async(remote: str) -> bytes

The awaitable twin of download_tar.

def exec(exec_id: str) -> ExecHandle

A handle for an exec started earlier, possibly by another process.

The reattach path. Nothing is checked against the daemon here — the handle is an id plus a transport, and a poll is what discovers whether the exec exists.

def file_exists(path: str) -> bool

Whether a path exists, distinguishing absence from every other refusal.

def file_exists_async(path: str) -> bool

The awaitable twin of file_exists.

def health() -> Health

Unauthenticated liveness.

def health_async() -> Health

The awaitable twin of health.

def keep_awake(
interval: SupportsFloat | SupportsIndex | None = None,
*,
while_busy: bool = False,
max_duration: SupportsFloat | SupportsIndex | None = None,
idle_window: SupportsFloat | SupportsIndex | None = None,
tolerated_errors: SupportsIndex | None = None,
) -> KeepAwake

Keeps the VM awake by polling health from this process until stopped.

The platform counts only inbound requests as activity, so an exec working with no client traffic is suspended once maxIdleDurationSeconds passes. This polls /v1/health every interval seconds (default: a third of the idle window, at most 20) on a background task and returns at once. while_busy ends it once no exec is running; max_duration ends it after that many seconds. idle_window is the VM’s maxIdleDurationSeconds: a sandbox-held session knows it, an attached one assumes the platform minimum of 60, and interval may be at most half of it. tolerated_errors is how many retryable poll failures in a row it retries, a second apart, before it ends with the error; omitted, it’s the core’s DEFAULT_TOLERATED_ERRORS.

On a sandbox-held session a suspend or terminate through the sandbox ends the keepalive before its next poll. Stop it before suspending through anything else, or the next poll auto-resumes the VM. Dropping the returned handle stops it.

def kill(exec_id: str) -> bool

Signals an exec’s whole process group. Returns whether anything was signalled.

def kill_async(exec_id: str) -> bool

The awaitable twin of kill.

def port_forward(
guest_port: SupportsIndex,
*,
bind: str | None = None,
max_connections: SupportsIndex | None = None,
) -> PortForward

Serves a local port as an HTTP and WebSocket forward to guest_port in the VM, on a background task, and returns its handle at once: microvm port-forward’s loop.

Connections are served at once rather than one after another, so a slow request doesn’t hold the next. A request the endpoint proxy refuses is listed in the report with its status while the forward keeps serving. bind and max_connections are tunnel’s. Dropping the handle stops the forward.

def port_forward_async(
guest_port: SupportsIndex,
*,
bind: str | None = None,
max_connections: SupportsIndex | None = None,
) -> PortForward

The awaitable twin of port_forward, for tunnel_async’s reason.

def procs() -> list[ProcGroup]

Process accounting: every registered exec with its group’s live pids.

The daemon reads /proc itself, so this needs no ps in the guest. A ProcGroup with child_exited and a non-empty pids is a command that finished while something it backgrounded did not; pass its exec_id to kill.

def procs_async() -> list[ProcGroup]

The awaitable twin of procs.

def run(
command: SupportsGetItem[int, str] | str,
*,
shell: bool | str = ...,
cwd: str | None = None,
env: dict[str, str] | None = None,
user: SupportsIndex | str | None = None,
group: SupportsIndex | str | None = None,
timeout_sec: SupportsFloat | SupportsIndex | None = None,
stdin: bool = False,
exec_id: str | None = None,
reap_group_on_exit: bool = False,
poweroff_on_exit: bool = False,
inherit_image_env: bool = False,
) -> ExecHandle

Starts a command and returns its handle. Does not wait.

command is a list, or a string that becomes a one-element argv — never whitespace-split. shell=True wants a single script string for /bin/sh -c; shell="bash" runs it under a shell the daemon resolves in the guest, and a shell the guest does not have is refused (unknown_shell) before anything starts.

user and group are a numeric id or a name the daemon resolves against the guest’s /etc/passwd and /etc/group; an unknown name is refused (unknown_user, unknown_group) before anything starts. A user with a passwd row gets HOME, USER and LOGNAME from it, beneath the launch environment and env.

inherit_image_env starts the child’s environment from the image’s ENV (minus AGENTD_*, never the token), beneath everything else; off by default, which keeps the child’s environment exactly the launch environment plus env.

reap_group_on_exit asks the daemon to signal the whole process group once the command’s own child exits, so nothing it backgrounded outlives it; off by default, which keeps the backgrounded-grandchild-output guarantee for callers who rely on it. poweroff_on_exit asks a supporting daemon to power off the whole guest after the original process group has no live members. Other work ends too, and the start acknowledgment or result can be lost. It does not keep an idle VM awake.

def run_async(
command: SupportsGetItem[int, str] | str,
*,
shell: bool | str = ...,
cwd: str | None = None,
env: dict[str, str] | None = None,
user: SupportsIndex | str | None = None,
group: SupportsIndex | str | None = None,
timeout_sec: SupportsFloat | SupportsIndex | None = None,
stdin: bool = False,
exec_id: str | None = None,
reap_group_on_exit: bool = False,
poweroff_on_exit: bool = False,
inherit_image_env: bool = False,
) -> ExecHandle

The awaitable twin of run, with its keywords. Cancelling it before the daemon answers may leave the exec started, under the exec_id the call was given or minted; Session.exec(exec_id) reaches it, which is why a caller that cancels passes one.

def run_sync(
command: SupportsGetItem[int, str] | str,
*,
timeout: SupportsFloat | SupportsIndex = ...,
shell: bool | str = ...,
cwd: str | None = None,
env: dict[str, str] | None = None,
user: SupportsIndex | str | None = None,
group: SupportsIndex | str | None = None,
timeout_sec: SupportsFloat | SupportsIndex | None = None,
stdin: bool = False,
exec_id: str | None = None,
reap_group_on_exit: bool = False,
poweroff_on_exit: bool = False,
inherit_image_env: bool = False,
) -> ExecResult

Start, wait, ack. The one-shot shape, for when output is all you want.

def run_sync_async(
command: SupportsGetItem[int, str] | str,
*,
timeout: SupportsFloat | SupportsIndex = ...,
shell: bool | str = ...,
cwd: str | None = None,
env: dict[str, str] | None = None,
user: SupportsIndex | str | None = None,
group: SupportsIndex | str | None = None,
timeout_sec: SupportsFloat | SupportsIndex | None = None,
stdin: bool = False,
exec_id: str | None = None,
reap_group_on_exit: bool = False,
poweroff_on_exit: bool = False,
inherit_image_env: bool = False,
) -> ExecResult

The awaitable twin of run_sync, with its keywords. Cancelling it stops the wait, as a timeout would: the exec keeps running and its output stays until acked.

def run_to_completion(
command: SupportsGetItem[int, str] | str,
*,
on_output: Any | None = None,
shell: bool | str = ...,
cwd: str | None = None,
env: dict[str, str] | None = None,
user: SupportsIndex | str | None = None,
group: SupportsIndex | str | None = None,
timeout_sec: SupportsFloat | SupportsIndex | None = None,
exec_id: str | None = None,
reap_group_on_exit: bool = False,
poweroff_on_exit: bool = False,
inherit_image_env: bool = False,
client_grace_sec: SupportsFloat | SupportsIndex = ...,
) -> ExecResult

Start, stream, and collect one command: exactly one ExecResult back (BIND-6..10).

With on_output, each OutputChunk is handed to it as it arrives. The result then comes from the ack that follows the terminal exit event, or, when the stream ends without one, from a wait and ack. When timeout_sec + client_grace_sec passes first (or, with no timeout_sec, the VM’s maximum lifetime), the process group is killed and the exec waited for and acked within client_grace_sec once more; if that fails too the result is synthesized with posix_exit_code 124. posix_exit_code and notes say what ended the command.

An exception from on_output stops delivery; the exec is still waited for and acked so nothing is left behind, and then the exception is re-raised. shell, user, group, reap_group_on_exit, poweroff_on_exit and inherit_image_env mean what they mean on run(): shell="bash" with a script string runs it under bash, which dash-based images need for pipefail.

def run_to_completion_async(
command: SupportsGetItem[int, str] | str,
*,
on_output: Any | None = None,
shell: bool | str = ...,
cwd: str | None = None,
env: dict[str, str] | None = None,
user: SupportsIndex | str | None = None,
group: SupportsIndex | str | None = None,
timeout_sec: SupportsFloat | SupportsIndex | None = None,
exec_id: str | None = None,
reap_group_on_exit: bool = False,
poweroff_on_exit: bool = False,
inherit_image_env: bool = False,
client_grace_sec: SupportsFloat | SupportsIndex = ...,
) -> ExecResult

The awaitable twin of run_to_completion, with its keywords.

on_output is a plain function, called on the event loop’s thread between the coroutine’s steps, one chunk at a time: the drive on the shared runtime waits for each call to return before it reads on, so a slow callback slows the stream rather than buffering it. An exception from it stops delivery and is re-raised once the exec is waited for and acked, as in run_to_completion. Cancelling the call stops the drive and leaves the exec running in the VM.

def spawn(
command: SupportsGetItem[int, str] | str,
*,
shell: bool | str = ...,
cwd: str | None = None,
env: dict[str, str] | None = None,
user: SupportsIndex | str | None = None,
group: SupportsIndex | str | None = None,
timeout_sec: SupportsFloat | SupportsIndex | None = None,
stdin: bool = False,
exec_id: str | None = None,
reap_group_on_exit: bool = False,
poweroff_on_exit: bool = False,
inherit_image_env: bool = False,
offset: SupportsIndex = 0,
reconnect: bool = True,
max_reconnects: SupportsIndex | None = None,
idle_timeout: SupportsFloat | SupportsIndex | None = None,
gap_policy: str | None = None,
) -> ExecProcess

Starts a command and returns it as two byte iterators, a wait(), and a kill().

The process shape, as against run’s handle: the same start request and keyword arguments, so spawn and run with one exec_id address one server-side child. proc.stdout and proc.stderr iterate bytes, split out of the one stream the daemon sends, which reconnects at the byte cursor after a cut, so a suspend and resume doesn’t end them early. Read both sides, from two threads when a command writes much to both: each holds one unread chunk, like a pipe.

gap_policy is what an evicted byte range does. "error", the default when it’s None, raises PlatformError (wire kind OutputGap) from both iterators naming the range, since the wire can’t say which side lost the bytes; "event" records it on proc.gaps and keeps both going. offset, reconnect, max_reconnects and idle_timeout are ExecHandle.stream()’s.

def spawn_async(
command: SupportsGetItem[int, str] | str,
*,
shell: bool | str = ...,
cwd: str | None = None,
env: dict[str, str] | None = None,
user: SupportsIndex | str | None = None,
group: SupportsIndex | str | None = None,
timeout_sec: SupportsFloat | SupportsIndex | None = None,
stdin: bool = False,
exec_id: str | None = None,
reap_group_on_exit: bool = False,
poweroff_on_exit: bool = False,
inherit_image_env: bool = False,
offset: SupportsIndex = 0,
reconnect: bool = True,
max_reconnects: SupportsIndex | None = None,
idle_timeout: SupportsFloat | SupportsIndex | None = None,
gap_policy: str | None = None,
) -> ExecProcess

The awaitable twin of spawn, with its keywords. The process it answers iterates with async for as well as for; cancelling the call is run_async’s.

def sync_dir(
local_dir: str | PathLike[str],
*,
full: bool = False,
delete_timeout: SupportsFloat | SupportsIndex | None = None,
) -> SyncReport

Syncs local_dir into the VM’s /workspace once, uploading only what changed.

Core’s one pass, microvm sync’s: the local tree is hashed and diffed against the manifest the last sync left in the VM, the changed members travel as one archive, the paths gone locally are removed in the VM with one rm whose deadline is delete_timeout (core’s default when None), and the manifest is rewritten. full=True ignores the manifest and uploads everything. .git, target, node_modules and .venv never travel. A tree over the daemon’s budgets raises InvalidArgError before anything is sent.

def sync_dir_async(
local_dir: str | PathLike[str],
*,
full: bool = False,
delete_timeout: SupportsFloat | SupportsIndex | None = None,
) -> SyncReport

The awaitable twin of sync_dir. The hashing and the local reads run on the shared runtime, not on the event loop.

def tunnel(
guest_port: SupportsIndex,
*,
bind: str | None = None,
verify_identity: TunnelIdentity | None = None,
max_connections: SupportsIndex | None = None,
) -> Tunnel

Serves a local TCP port as a tunnel to guest_port in the VM, on a background task, and returns its handle at once: microvm tunnel’s loop.

Each local connection gets a WebSocket of its own through the endpoint proxy, and a connection the daemon refuses is listed in the report while the tunnel keeps serving. bind is a host:port to listen on, loopback on a port the OS picks by default, and the handle’s local_address says which. max_connections stops accepting after that many. With verify_identity (a TunnelIdentity, such as Sandbox.tunnel_identity), each connection first proves the far end is the daemon of the VM that identity was launched with, and a connection that can’t is refused.

The tunnel carries this session’s credentials to whoever connects, so bind beyond loopback only on a network you trust. Dropping the handle stops the tunnel.

def tunnel_async(
guest_port: SupportsIndex,
*,
bind: str | None = None,
verify_identity: TunnelIdentity | None = None,
max_connections: SupportsIndex | None = None,
) -> Tunnel

The awaitable twin of tunnel: binds the listener on the shared runtime and answers the same handle, whose loop runs there either way.

def upload_file(path: str, data: bytes, *, mode: str | None = None) -> None

Writes one file, creating parents. mode is an octal string ("0755"), which is the daemon’s shape — an integer here would be ambiguous between 0o755 and 755.

def upload_file_async(path: str, data: bytes, *, mode: str | None = None) -> None

The awaitable twin of upload_file.

def upload_tar(remote: str, archive: bytes) -> None

Extracts pre-built tar bytes under remote.

Bytes rather than a local path: packing a directory is the caller’s, because the symlink and permission decisions in a pack belong to whoever knows what the tree means.

def upload_tar_async(remote: str, archive: bytes) -> None

The awaitable twin of upload_tar.

def wait_until_ready(timeout: SupportsFloat | SupportsIndex = ...) -> Health

Polls health until the daemon reports bootstrapped.

Connection errors on the way are expected rather than exceptional: a VM that has just reached RUNNING commonly refuses a connection or two before the proxy path is wired up. A fatal error ends the wait at once, because retrying a 401 until the deadline is the mistake the retryable split exists to prevent.

def wait_until_ready_async(timeout: SupportsFloat | SupportsIndex = ...) -> Health

The awaitable twin of wait_until_ready.

def __repr__() -> str

This page is generated from bindings/microvms-py/microvms.pyi by Griffe 2.3.0, run from site/scripts/gen-reference.mjs on every pnpm run sync, so an edit made here is overwritten by the next run.

To change the page, change the doc comment in the binding’s Rust source, then regenerate the declarations with mise run stubs. mise run stubs:check fails when the committed file no longer matches the source.