Python binding#
Python uses a PyO3 extension containing the canonical Rust runtime. No Node, Chromium or simulation server is required. CPython 3.9+ is supported by the abi3 binding. A compatible prebuilt wheel needs no Rust toolchain.
Install#
python -m venv .venv
# Linux/macOS: source .venv/bin/activate
# Windows PowerShell: .venv\Scripts\Activate.ps1
python -m pip install computerworld==0.2.0
python -c "import computerworld; print(computerworld.__version__, computerworld.engine_version)"
# Expected: 0.2.0 0.2.0
PyPI carries wheels for Linux x86-64 and arm64 (manylinux2014), macOS arm64 and x86-64,
and Windows x64, and no source distribution: on any other platform, build from source
as below. The same
wheels are assets of
v0.2.0 with a
SHA256SUMS; the release workflow publishes to PyPI the files it downloaded from that
release and checked against it, so the two are byte-identical. Wheels
contain the runtime, not an implicit company world. Download the tagged source
archive for example world definitions and runnable Python demos, or supply your
own definition. API and checkpoint compatibility may change between 0.x releases;
keep the exact version with your episode records.
Build from source#
A Rust toolchain is required for this path. Use the release tag for a reproducible
checkout (omit --branch to work on current development instead):
git clone --branch v0.2.0 https://github.com/JacobFV/computerworld.git
cd computerworld
python3 -m venv .venv
. .venv/bin/activate
python -m pip install .
python examples/python/smoke.py
python examples/python/computer_interaction.py
To build distributable wheels instead:
python -m pip install 'maturin>=1.7,<2'
maturin build --release --manifest-path crates/python/Cargo.toml
python -m pip install --force-reinstall target/wheels/computerworld-0.2.0-*.whl
python -c "import computerworld; print(computerworld.__version__, computerworld.engine_version)"
# Expected: 0.2.0 0.2.0
Install by exact version, not computerworld*.whl. target/wheels/ is a build
directory: it accumulates wheels from every revision you have built, and a glob can
silently install an older one. If engine_version does not match the source you
built, you installed a stale wheel — that is the usual cause of an example failing
with missing interactions or application module versions differ. CI builds
release candidates into target/release-wheels/ instead, and
scripts/smoke-bindings.sh uses target/python-wheel/ with an exact pin.
Run source examples from the checkout; example world JSON is not an implicit kernel resource. Your application supplies its own JSON-compatible definition:
import json
from computerworld import World
with open("worlds/company-2026/world.json") as source:
definition = json.load(source)
world = World(definition, seed=7)
machine = definition["computers"][0]
env = world.environment({
"actor": machine["user"], "machines": [machine["id"]],
"actions": ["terminal.v1"], "observations": ["terminal.v1"]
})
result = env.step([{
"family": "terminal.v1", "op": "execute", "machine": machine["id"],
"payload": {"command": "pwd"}
}])
assert result["outcomes"][0]["success"], result["outcomes"][0]
observation = env.observe()
checkpoint = world.snapshot()
branch = world.fork(checkpoint)
branch_env = branch.session(env.id)
world.restore(checkpoint)
Keep world and env alive for an episode; do not construct a process or runtime
per action. Configuration/actions/results are Python dictionaries and lists.
Python seeds are unsigned 64-bit integers. Invalid binding calls raise ValueError;
individual failed actions appear in result["outcomes"] and must also be checked. Codes
and refusal reasons are in Errors and refusals.
Threading#
World, Environment and Snapshot may be passed to other threads and called from
them. A World and every Environment minted from it share one lock around the Rust
world, so calls from several threads are serialized, not concurrent: a call that
arrives while another is running waits for it. Nothing is silently dropped and nothing
races.
import threading
frame = {}
t = threading.Thread(target=lambda: frame.update(env.render(320, 240)))
t.start(); t.join() # works: the render happens on that thread
What threads do not buy you is parallel simulation. One World is one state machine,
and the order its actions land in is the order the threads took the lock — which is not
deterministic across runs. Keep an episode on one thread, or give each thread its own
World (World(definition, seed), or world.fork(world.snapshot()), whose result is an
independent world). Threads are for keeping a UI responsive or for driving independent
worlds side by side, not for splitting one episode.
If a call panics inside the simulation, the lock is poisoned and every later call on that
world raises RuntimeError naming the situation. That is a deliberate change from
earlier alpha builds, where the objects were marked unsendable and a call from a second
thread aborted the interpreter instead of raising. Nothing here aborts the host
process. crates/python/tests/test_threading.py asserts both halves: a call from a
second thread succeeds, and a poisoned world raises rather than dying.
Environment exposes id, step, observe, scene(width=1024, height=768) and
render(width=1024, height=768). Scene access requires a semantic.v1 or pixels.v1
observation grant. scene returns layout/interaction data without rasterization.
render returns {"width": int, "height": int, "rgba": bytes}. Use an image library
to encode those bytes as PNG; no host image library participates in simulation.
See programmatic computer use for desktop launch, keyboard input, pointer dragging, transformed hit targets, PNG output and owner checkpoint examples. The runnable computer interaction demo is intended as a starting point for an agent integration.
World owns reset, snapshots, portable export_snapshot()/import_snapshot(text),
definition(), trajectory(), state_hash() and privileged inspect(). Retain it
in the harness; provide only the environment to actor code. world.session(env.id)
reattaches a restricted handle after fork/import. Python only converts values; it
contains no separate command, service or desktop semantics.
The owner also supports world.add_computer(computer, node, links) and
world.remove_computer(id). These preserve other devices and service state.
See device lifecycle and checkpoint rules.