ADR-platform-008
Nodeset role-directory layout: workspace/nodesets/ organized by role (env/ method/ policy/ model/ common/ other/); server/ eliminated β deployment expressed only by server_python
Context
Pre-2026-06-11 workspace/nodesets/ had exactly one organizing axis: deployment. The root held nodesets that run inside the backend's agentcanvas interpreter; server/ (a scanner "bucket" β a directory without __init__.py) held nodesets that run in their own conda env via auto_host. That axis was pure convention: the engine's actual routing reads the server_python ClassVar (ADR-server-001 / auto-routing), and WorkspaceComponentRegistry._scan_subdir assigns no semantic meaning to directory names.
The layout had accreted real problems: (a) server/ mixed four roles β environments (habitat.py, libero/β¦), policies (policy_*), generic model servers (vlm_*, ram_perception.py), and even pure method logic (explore_eqa.py, tooleqa_explore.py) parked there only because it needed the hmeqa interpreter; (b) one method's parts scattered across the tree by interpreter β smartway lived in four places (root smartway/, smartway_mono/, server/smartway_perception/, server/smartway_waypoint/); (c) file stems didn't match nodeset names for six envs (habitat.py β name="env_habitat"); (d) loose non-nodeset files sat at scanner-visible levels (hmeqa_renderer.py, test_matterport3d_path_resolution.py) and got imported on every scan; (e) grab-bags existed (others.py β "tools that don't belong to a specific system"). The doc-site nodeset catalog was meanwhile organized by role β two taxonomies for one corpus. Tracked as TODO #40.
Decision
Directory encodes role; deployment is not a tree concern at all. Codified in .claude/standard/nodeset-layout.md; the full tree migrated the same day.
- Seven top-level directories:
env/(gym-contract simulators + EnvPanel),method/(one paper/system's reasoning core and its owned parts),policy/(trained-policy inference),model/(generic foundation/perception servers),common/(deliberately cross-method tools),other/(quarantine for the not-yet-classifiable, entry requires a docstring rationale),_upstream/(frozen vendored reference, ADR-platform-007). All role dirs are scanner buckets β never an__init__.pyin them. server/is gone. A nodeset needing its own interpreter declaresserver_pythonand lives wherever its role puts it.- Stem =
nameClassVar, full role prefix included (env/env_habitat.pyβname="env_habitat"). The directory+prefix redundancy is deliberate: the registry andnode_typenamespaces are flat, so the name must be self-identifying outside its directory (editor tabs, grep hits, AAS diffs). - Ownership beats modality for the method-owned vs generic-model boundary (TODO #56 made operational): a second unrelated method could load it unchanged β
model/(vlm_/model_prefix); tuned to one method βmethod/as{method}_{part}. Method families share the{method}prefix, so same-directory alphabetical sorting reunites scattered parts physically. - Sidecars live inside their owner's folder package; a
_-prefixed module may sit loose in a role bucket only when shared by 2+ nodesets there (method/_explore_eqa_tsdf.py, imported byexplore_eqaandtooleqa_explore).
Alternatives
(a) Keep the deployment axis, tighten naming only (the first draft of nodeset-layout.md, written hours earlier) β rejected on review: it codifies the symptom. The dominant queries ("which envs exist", "where are smartway's parts") still require knowing each nodeset's interpreter history, and the doc-site/code taxonomy split persists.
(b) Ownership-first tree (each method directory owns everything it uses, envs duplicated or symlinked) β rejected: envs are shared infrastructure with no single owner; the scanner's one-bucket depth can't express it; the auto_host unit is a file, not a directory closure.
(c) Role directory without name prefix (env/habitat.py deriving env_habitat) β rejected: reintroduces "open the file to learn the name", and flat node_type strings still need the prefix anyway, so derivation saves nothing downstream.
(d) Role encoded in names only, directories stay deployment-split β rejected for the same reason as (a); also keeps method families physically scattered.
Rationale
The tree has two readers and both think in roles. Humans browse by "what envs do we have"; the coding agents of Paper #1 (AAS implementer) and Paper #2 (porting agents) use paths as retrieval keys β env/ answers the env-enumeration query in one hop instead of a scan-plus-classify. The doc-site catalog was already role-organized; this collapses two taxonomies into one.
Deployment is a private implementation detail. The engine reads server_python; encoding the same fact in the path put private information in the public interface while crowding out the semantic axis. Relocation is now free (a role reclassification is a git mv that touches no behavior), and the contested classification axis (method-owned vs generic, TODO #56) lives in the cheap medium β names and one-line judgments β rather than directory structure.
Zero engine cost. _scan_subdir already supports any number of top-level buckets with packages inside (precedent: server/policy_vla/); the migration was git mv + reference sweep. Migration verification: registry-scan diff before/after (36 discovered, set-identical modulo the three intended renames samβmodel_sam, ram_perceptionβmodel_ram, othersβgeometry+mock_frontier), 117 backend tests, and a libero end-to-end smoke (run 20260611_152007, 1390 steps β bit-parity with pre-migration baselines). The scan diff caught the one real breakage (smartway's cross-package import of smartway_mono) before any runtime did.
Accepted costs: (i) historical architect design_runs overlays reference pre-migration relative paths and can no longer replay β explicitly waived by the user ("ζ§ηι½δΈη¨δΊ"); (ii) ~107 vendored absolute self-imports (policy_vla, policy_vlnce) and nine __file__-relative path chains needed mechanical depth fixes, each programmatically verified to resolve to the repo root.
Affected docs
.claude/standard/nodeset-layout.mdβ the standard itself (role grammar, naming, decision table, migration map)..claude/standard/component-creation.md,.claude/tutorials/skill-nodeset.md,.claude/tutorials/skill-env-nodeset.md,CLAUDE.mdβ guidance aligned; standard registered in the index table..claude/PROJECT_OVERVIEW.mdβ repo map + nodeset catalog repointed.core/codebase-map.html,core/glossary.html,design-docs/components/nodesets.html,design-docs/operations/plugin-servers.html,nodesets/env/*.html,nodesets/method/*.html,tutorials/habitat-nodeset.htmlβ path references updated to role layout (same session, /commit doc pass).workspace/nodesets/β 35 nodesets relocated;workspace/graphs/vln/unverified/discussnav_mp3d.jsonβ one node_type updated.
Changelog
2026-06-11 15:37: Initial draft; standard written and full tree migration executed + verified in the same session.