v0.11.0 - Cross-module behaviors
A behavior-dispatch release. @when becomes a runtime decorator backed by
a content-addressed marshalled-code registry instead of a transpile-time
call-site rewrite. A behavior's code object is marshalled once, stored under a
hex content-hash key, and resolved by value inside each worker
sub-interpreter — so behaviors may now live in any importable module, not
just __main__. The transpiler shrinks to a bindings reducer that exposes the
defining module's imports, classes, functions, and constants to workers, and
captures are now declared explicitly as trailing default parameters.
New Features
- Cross-module behaviors — a
@whenbody defined in any importable
module now resolves on workers via the marshalled-code registry, lifting
the previous__main__-only restriction. A new worker-importable
bocpy_testfixtures package exercises cross-module resolution, dunder
handling, key collisions, and chained-global cases. - Behaviors in the REPL,
python -c, and piped stdin — a@whenbody
defined in a__main__with no source file on disk now runs on workers.
The runtime reduces the live interactive namespace to its imported modules
(each guarded so an environment module that cannot load in a
sub-interpreter, such asreadline, is skipped rather than fatal), and an
interactive behavior is validated at decoration to reference only builtins,
imported modules, and explicit captures. whencall(func, args, captures)— the lower-level escape hatch behind
@when: registers a behavior function against cowns and explicit capture
values without the decorator sugar.
Breaking Changes
-
@whenbehaviors must declare their captures explicitly. Implicit
capture of enclosing-scope variables as free variables is no longer
supported: a@whenbody may only reference its cown parameters, the
values it captures as trailing parameters, and names resolvable at the
defining module's scope (imports, module-level classes/functions,
constants, and builtins). Capture an enclosing local by adding a
trailing parameter with a same-named default — the canonical
name=namerecipe:# before — factor captured implicitly from the enclosing frame @when(x) def b(x): return x.value * factor # after — factor captured explicitly @when(x) def b(x, factor=factor): return x.value * factor
The loop-snapshot form
def b(c, i=i)and the rename form
def b(c, x=y)continue to work unchanged. This is the migration that
lets a behavior's code object be marshalled and resolved by value
across worker sub-interpreters.
Improvements
- Decoration-time capture validation —
@whennow rejects malformed
behaviors where the mistake is made, with actionable messages: a bare
trailing parameter (no default) raisesTypeErrornaming the cown/param
counts; a body closing over an enclosing-function local raises
SyntaxErrornaming the variable and suggesting thename=namefix; and
async def/ generator behaviors raiseSyntaxError. Computed defaults
(k=expensive()) are allowed and snapshotted once at schedule time. - Interactive traceback labels — behaviors defined interactively are
relabelled<behavior:hash>so two distinct interactive behaviors no
longer collide under a shared<stdin>/<string>filename.
Documentation
- Reworded the :doc:
c_abiandmessagingpages and the downstream
consumer template to describe the worker bindings module and bindings
reducer, replacing the retired transpile-and-rewrite vocabulary.
Tests
- New
test_registry.pycovering registry round-trips, key derivation,
capture validation, andResolverdispatch.test_transpiler.pyrewritten
for the bindings reducer; the dead constant-tracking machinery and its
tests were removed.
Internal
- New
boc_registry.c/boc_registry.hC subsystem storing marshalled code
objects under opaque hex keys. The transpiler is reduced to a bindings
reducer (MainBindings/bind_*); the legacy call-site rewriter, skeleton
fallback, andexport_module.pywere removed in a clean cut. Behavior keys
are content-addressed with length-prefixed framing and 128-bit truncation.