AgentCanvas / Pages / Developer Guide / Core / Decisions / Components / ADR-components-010
2026-07-04 01:34
Date
2026-07-04 01:34
Status
accepted
Field
components

Context

The two neural-policy nodesets (policy_vla, policy_vlnce) each bundled the whole 5-stage adapter pipeline โ€” env-side adapters (stages 1/5), model-side adapters + GPU policy (stages 2/3/4), and a singleton manager โ€” into one per-domain package, documented across three overlapping doc pages. This shape had accumulated recorded warts: the VLA robot dropdown lived on policy-side nodes although the principle says the env-side adapter's identity belongs to the graph's env ("env-locking", policy-vla ยง3.4); the VLN variant bundle implicitly pinned the env family; the env-side adapter code (pure numpy, no torch/habitat/model imports in either domain) was needlessly hosted inside GPU server subprocesses; and a latent wire bug sat on the VLA path โ€” make_canonical_obs stores a raw frozen CanonicalInfo dataclass, which the msgpack ANY-wire (app/server/serialization.py) cannot pack. A cross-domain "one policy adapter nodeset" was impossible: server_python is a nodeset-level ClassVar (app/components/bases.py), and Pi0/SmolVLA/RT-1-X (ac-vla-policy) cannot share an interpreter with CMA/Seq2Seq (ac-vlnce, Python 3.8).

Decision

Cut the pipeline along its own data contract โ€” the CanonicalDict lingua franca โ€” into three nodesets. (1) env/env_adapter/: ONE general env-side nodeset holding stages 1/5 for both domains (VLA robot adapters under robots/, VLN env-family adapters under envs/); pure numpy, server_python=None (in-process hub), no singleton manager โ€” the old ensure_robot/ensure_env_adapter slices became module-level lazy caches under a threading.Lock. (2) policy/policy_adapter_vla/ and (3) policy/policy_adapter_vlnce/: per-domain model-side nodesets holding stages 2/3/4 with their trimmed managers (two ensure_* slices each) on their existing conda envs. Node types re-prefixed accordingly (a Tier-2 graph-breaking rename; all 3 graphs swept). Config placement: VLA robot select moved to the env_adapter nodes โ€” duplicated on stage 5, because decode logic is robot-class-side; VLN variant moved to the pipeline's stage-2 chain entry, with a new note_canonical manager call preserving the pre-split load order (policy loads before the first canonical_to_model); the old implicit r2rce pin became an explicit env_family select on stage 1. The canonical info now always crosses a process boundary, so both domains flatten it to a plain dict at the env_adapter boundary (VLA stage 2 reconstructs the dataclass; VLN already stored asdict). Upstream's composed Adaptor and the model_to_env fused alias were deleted (dead in the canvas path; nothing left to compose cross-process). The legacy single-node policy_cma.py (zero graph/import references, superseded by the pipeline's cma variant) was deleted. The three method doc pages merged into one "General Policy Adapter" page (nodesets/method/policy-adapters.html).

Alternatives

(a) Rename-only (policy_adapter_vla/policy_adapter_vlnce as pure renames of the old bundles) โ€” rejected by the maintainer: the desired architecture separates policy-side and env-side adapters into different nodesets, matching the mental model "run VLN-CE = env + env adapter + policy adapter". (b) One cross-domain policy-adapter nodeset โ€” infeasible without framework surgery: one nodeset binds one server_python; per-node interpreter selection was explicitly scoped out. (c) Per-domain env-adapter nodesets (four nodesets total) โ€” unnecessary: env-side adapters have no interpreter constraint (pure numpy), so one shared in-process nodeset is strictly simpler and matches the "general" framing. (d) Shared _canonical helper module across the role directories โ€” rejected in favour of duplicating canonical.py per nodeset: the layout standard scopes shared _-modules to a single role directory, only plain dicts cross the wire, and self-contained nodesets survive independent deployment. (e) Keeping policy_cma.py as a fallback โ€” rejected: zero references, and the pipeline version carries the eval-parity evidence the single-node port never had.

Rationale

The nodeset boundary now coincides with the architectural boundary the code already enforced (env-side adapters never import models; model-side adapters never import envs), so deployment follows the contract instead of fighting it: env adapters run where they belong (the hub, for free) and each model family keeps its interpreter. The split structurally delivers half of the recorded env-locking wart โ€” the robot/env-family selection now lives on nodes beside the env they serve (the dropdown filter interlock remains open). It also forced the latent VLA msgpack bug into the open and fixed it at the node boundary, keeping canonical.py and all vendored model adapters verbatim. Correctness gate: the VLN path is pure code motion and was held to a bit-identical bar โ€” the 3-ep R2R-CE val_unseen reference slice reproduced success 0.333 / SPL 0.333 / nDTW 0.37831125435239743 / path 10.3326 / steps 56.7 exactly (run 20260704_004705, architect-cma profile), the same numbers as the 2026-06-29 pre-split baseline. The VLA path is not pure motion (boundary flatten/reconstruct + _cached_info) and its graphs stay in unverified/ (registration + static-resolution checks only; the Pi0 runtime path was already blocked by roadmap TODO #66). Composition cost is explicit and small: running a policy loads three nodesets instead of two, but the env_adapter is in-process and stateless, so no new subprocess, port, or VRAM is spent.

Affected docs

nodesets/method/policy-adapters.html (rewritten as the merged "General Policy Adapter" page; policy-vla.html + policy-cma.html deleted into it), nodesets/method/index.html, nodesets/index.html, nodesets/env/index.html, nodesets/status/vln-support-status.html, nodesets/status/vla-support-status.html, nodesets/status/eqa-support-status.html, nodesets/status/index.html, nodesets/env/habitat.html, nodesets/common/foundation-models/vlm-prismatic.html, design-docs/graph/graph-system.html, design-docs/graph/wire-types.html, design-docs/operations/server-mode.html, design-docs/components/nodesets.html, capabilities/isolated-runtime-environments.html, capabilities/customizable-node-system.html, core/architecture.html, core/roadmap.html (TODO #58/#66/#74/M5 bodies), community/credits.html, getting-started/installation.html, core/decisions/components/index.html + core/decisions/index.html (this ADR), .claude/PROJECT_OVERVIEW.md (refreshed).