Skip to content

Websocket Client

A pool of remote monty workers reached over a WebSocket instead of local subprocesses — the intended peer is monty-server. See running monty-server.

AsyncMontyWebsocket

Async context manager owning a pool of remote monty workers reached over a WebSocket. The intended peer is monty-server (the production server: one monty subprocess child per connection, plus capacity/timeout policy and graceful drain), but any server that accepts the connection and bridges to a worker fits — a relay pairing it with a child that dialed in from the other end, or the dev relay scripts/websocket_relay.py.

Like AsyncMonty, but instead of spawning local subprocesses each checkout dials the configured URL. There is no sync counterpart — remote turns are network-bound. checkout() yields the same AsyncMontySession.

A monty-server enforces its own policy on top of the pool’s. On SIGTERM drain it answers the session’s next request with MontyShutdown, whose dump restores the session onto another server; every other server-side drop (idle, session or turn timeout, capacity) closes the connection and raises MontyDisconnectError.

async with AsyncMontyWebsocket('ws://127.0.0.1:8799') as pool:
    async with pool.checkout() as session:
        result = await session.feed_run('1 + 1')

Methods

__new__
def __new__(
    cls,
    url: str,
    *,
    max_processes: int | None = None,
    checkout_timeout: float | None = None,
    request_timeout: float | None = 10.0,
) -> Self

Configure a remote worker pool; connections are made by async with and each checkout (no workers are pre-warmed).

Returns

Self

Parameters

url : str

ws:///wss:// URL to dial — a relay, or any server that bridges to a worker. Dialed verbatim; any session/rendezvous routing the URL needs (e.g. a /<uuid>/parent path for a relay) must already be in it.

max_processes : int | None Default: None

Cap on concurrent connections (defaults to the CPU count); checkouts beyond it wait.

checkout_timeout : float | None Default: None

Seconds checkout() waits for capacity before raising TimeoutError. None waits forever.

request_timeout : float | None Default: 10.0

Hard per-turn deadline in seconds (default 10.0) — a worker that exceeds it has its connection killed and the call raises MontyCrashedError with timed_out=True. This also bounds the wait when a relay accepts the connection but never produces a worker. Pass None to wait indefinitely.

Note that install_dependencies is a turn too, so the default 10.0 is often too low for it — a real uv pip install can exceed it. Raise request_timeout (or pass None) when installing dependencies over the WebSocket transport.

checkout
def checkout(
    *,
    script_name: str = 'main.py',
    limits: ResourceLimits | None = None,
    type_check: bool = False,
    type_check_stubs: str | None = None,
    type_check_format: TypeCheckFormat | None = None,
    type_check_color: bool = False,
    assert_message_annotations: bool | int = ...,
) -> AsyncMontySession

Prepare a REPL session served by a dedicated remote connection.

Identical to AsyncMonty.checkout; the connection is opened by async with on the returned session.

Returns

AsyncMontySession