python sdk

RL pools

Thousands of live environments behind one batched step. Environments stay warm and resident between steps — nothing is reloaded, re-imported or re-serialised — and step() is a single request carrying every action and returning every result. This is the one part of the SDK with no CLI equivalent.

why it stays fast (and lean)

Each environment is a long-lived sandboxed process. Code and interpreters are mounted read-only and shared across the pool, so memory stays low as N grows. Steps are fanned out in parallel on the worker. There is no snapshot store and no per-step container boot.

  • Soft resetpool.reset() / reset(where=…) calls the env’s own reset in-process. Fast; state lives in your Python variables.
  • Hard resetpool.reset(hard=True) kills and respawns the sandboxes and wipes the workspace. Slower; the clean start when you need episode N+1 byte-identical to episode 1.
  • Not snapshots — we do not checkpoint process memory. Warm pools give you speed without holding a full image of each env in RAM.

the environment

my_env/env.py
from boltzlabs.env import serve

state = {"t": 0}

def reset(seed):
    state["t"] = 0
    return {"t": 0}

def step(action):
    state["t"] += 1
    return {"t": state["t"]}, float(action), state["t"] >= 100, {}

serve(reset=reset, step=step)

serve() owns the three things that silently break this channel: buffering, stray output on stdout, and exceptions. It is shipped into the sandbox with your code automatically — you never copy a file.

the training loop

train.py
from boltzlabs import RLPool

with RLPool("./my_env", 1000) as pool:
    obs = pool.reset(seed=0)

    for t in range(max_steps):
        actions = policy(obs)
        obs, rewards, dones, infos = pool.step(actions)   # ONE request
        if dones.any():
            obs = pool.reset(where=dones)                 # only the finished ones

    print(pool.timing)          # worker time and round trip, never conflated
    print(pool.envs_per_gb())   # the number that decides the bill

rewards is np.float32[n] and dones is np.bool_[n], so it drops into a trainer as it is. obs and infos stay as they came — they are your JSON, and coercing them into an array would be a guess about your observation space.

the rest of the pool

pool.timing.worker_ms

the batch, measured on the worker — excludes the network

pool.timing.roundtrip_ms

your process's wall clock around the whole call

pool.timing.stragglers

environments that missed their deadline; returned done with zero reward

pool.reset(where=dones)

partial reset — returns all n observations, spliced back in place

pool.reset(hard=True)

respawn the sandboxes: byte-identical starts, slower

BoltzLabsVecEnv(pool)

Gymnasium vectorized adapter — pip install boltzlabs[gym]

Pools whose reward is a timing measurement should be created with serialize_measurement=True: it funnels each step through a single gate so exactly one environment's code runs at a time. Rollout stays concurrent — only the measurement funnels.