tractor/.claude/notes/rt_vars_lift_plan.md

4.3 KiB
Raw Blame History

RuntimeVars env-var lift — design plan

Status: draft, awaiting user edits

Goal

Consolidate the sprawl of pytest CLI flags + ad-hoc env vars + hardcoded fixture defaults into a single env-var-encoded runtime-vars envelope, with a typed in-memory representation (tractor.runtime._state.RuntimeVars) as the sole source of truth.

Why now

  • --tpt-proto, --spawn-backend, --diag-on-hang, --diag-capture-delay and (soon) TRACTOR_REG_ADDR etc. are proliferating. Each adds a parsing seam.
  • tests/devx/test_debugger.py invokes example scripts as separate subprocesses; they currently cant see the fixture-allocated reg_addr at all (root cause of why parametrizing devx scripts on reg_addr is on your TODO).
  • Concurrent pytest sessions on the same host collide on shared defaults (the registry@1616 race we just fixed is one symptom; per-session unique addr is the structural fix).
  • tractor.runtime._state.RuntimeVars: Struct is already defined and unused — its docstring even says it “should be utilized as possible for future calls.”

Design

Module: tractor/_testing/_rtvars.py

Lifted from modden.runtime.env, ~50 LOC, no new deps.

_TRACTOR_RT_VARS_OSENV: str = '_TRACTOR_RT_VARS'

def dump_rtvars(rtvars: RuntimeVars|dict) -> tuple[str, str]:
    '''str-serialize via `str(dict)` — ast.literal_eval-able'''

def load_rtvars(env: dict) -> RuntimeVars:
    '''ast.literal_eval the env-var value, hydrate to struct'''

def get_rtvars(proc: psutil.Process|None = None) -> RuntimeVars:
    '''read the var from a target proc's env (or current)'''

def update_rtvars(
    rtvars: RuntimeVars|dict|None = None,
    update_osenv: bool|dict = True,
) -> tuple[str, str]:
    '''mutate + re-encode + (optionally) write to os.environ'''

Encoding choice: str(dict) + ast.literal_eval

Pros: - stdlib only - handles all the types tractors tests need: str, int, float, bool, None, list, tuple, dict - human-readable in the env (greppable, inspectable via cat /proc/<pid>/environ | tr '\0' '\n')

Cons: - non-stdlib types (msgspec Structs, Path, custom classes) must be lowered first — fine for the test fixture set - not stable across Python versions for esoteric repr cases (we dont hit any)

Alternatives considered: - msgpack: adds a dep + binary form is ungreppable - json: doesnt preserve tuples (becomes lists), which is a common type for reg_addr - toml/yaml: heavier deps, no real benefit

RuntimeVars becomes the single source of truth

The legacy _runtime_vars: dict[str, Any] global in runtime/_state.py becomes a cached view of a RuntimeVars singleton instance:

  • get_runtime_vars() returns either the struct or a .to_dict() view depending on callers preference
  • set_runtime_vars(...) validates against the struct schema
  • spawn-time SpawnSpec sends the struct (already does conceptually — just gets typed)
  • __setattr__ breakpoint() debug instrumentation gets removed (unrelated cleanup, mentioned in conversation)

Migration path

Phase 0 (prep): strip the stray breakpoint() from RuntimeVars.__setattr__.

Phase 1: land _rtvars.py as a leaf module, used only by test infra. Subprocess-spawned scripts in tests/devx/ read _TRACTOR_RT_VARS on startup → reconstruct RuntimeVars → call tractor.open_root_actor(**rtvars.as_kwargs()). Concurrent runs become deterministic-isolated because each session writes a unique _registry_addrs into the env.

Phase 2: migrate runtime callers (_state.get_runtime_vars, spawn SpawnSpec, Actor.async_main) to operate on the struct directly, with the dict as a compat view that gets deprecated.

Phase 3 (structural): per-session bindspace subdir /run/user/<uid>/tractor/<session_uuid>/ — encoded in the rt-vars envelope, picked up by every subactor automatically. Obsoletes the entire bindspace-leak warning class.

Open design questions (user input wanted)

  • (placeholder for your edits)
  • (placeholder)
  • (placeholder)

Out-of-scope for this lift

  • Anything in modden.runtime.env related to Spawn, WmCtl, Wks — thats a workspace orchestration layer, not an env-var helper. We only lift the four utility functions + the var name constant.
  • Switching to msgpack/json — explicitly chosen against above.