The ambient form
For code that has no handle to pass around — a training loop, a callback, a library three frames down:probe.init() takes everything client.run() does and returns the same Run handle, so with probe.init(...) as run: works and the rest of the API is one attribute away.
The binding is not a global. It is a contextvar backed by a process default, which means two things a bare global gets wrong:
- A worker thread finds the run. A plain contextvar would not — threads start with an empty context.
- A second
init()inside a thread or block shadows the outer one rather than hijacking it. That is the part ofwandb.init()’s global that silently corrupts concurrent runs.
probe.active_run() returns the current binding.finish() is closed at exit as completed, failed or canceled — KeyboardInterrupt is canceled, because stopping a run is a decision and not a defect.
The explicit form
No ambient state at all:run() resolves by default. question= is the one opt-in to creation.
The rest of a run
Writes never block your training loop
Data writes are fail-open by default. On failure they spool to disk and return — they do not raise and they do not retry inline.run.finish() (or probe flush) replays the spool. Appends and queue rewrites are fsynced and atomic.
Reading runs back
run_ids=[...], or select them with the same filters list_runs takes.
Runs of differing length keep
None holes rather than being cut to the shortest — differing length is usually what is being compared. More than 50 runs batches rather than truncating, because silently dropping runs 51 and up reads as “these are all of them”.There is no separate read client. wandb.Api() is a distinct object because W&B has two transports; one REST transport does not need the split.Full API reference
Every method on
Client and Run, with its endpoint.Framework integrations
Miles, passive push, and wiring Probe into a trainer that already has a tracker.