Habitat NodeSet
The Habitat NodeSet (EnvHabitatNodeSet) exposes the Habitat-Sim VLN-CE environment as a unified suite of canvas tools for navigation and observation. It manages the simulator lifecycle, handles both local and server-mode execution, and provides tools for stepping through episodes, observing the environment, and controlling episode state.
1. Overview
Purpose
The Habitat NodeSet wraps the Habitat-Sim 0.1.7 VLN-CE (Vision-and-Language Navigation, Continuous Embodied) simulator. It enables graphs to: - Step through navigation episodes with RGB + depth observation - Query agent position and orientation - Control episode lifecycle (switch episodes, change splits) - Render panoramic multi-view composites for scene understanding
Architecture
The nodeset is organized around a singleton HabitatEnvManager that:
- Maintains the single Habitat environment instance
- Enforces thread safety via locks
- Uses a dedicated single-threaded executor for GL/physics affinity
- Exposes blocking methods safe for async/sync code boundaries
Local vs Server Mode
| Mode | Execution | Deployment | When to use |
|---|---|---|---|
| Local | Same process | agentcanvas env |
Fast iteration, local graphs |
| Server | Separate process | ac-vlnce env (Python 3.8) |
Env isolation, production, complex setups |
| Auto-hosted | Separate process | Auto-detected via server_python |
Auto-routing; no manual ?mode=server |
When loaded via POST /api/components/nodesets/env_habitat/load?mode=server, the framework spins up a server subprocess using the path specified in server_python. The proxy nodes preserve the original node_type IDs (e.g., env_habitat__step), so graphs work identically regardless of deployment mode.
HabitatEnvManager Singleton
mgr = HabitatEnvManager.get() # Always returns the same instance
mgr.initialize(exp_config, split, gpu_id, max_steps)
obs = mgr.step(action) # Returns dict with rgb, depth, state, done
All public methods are blocking and thread-safe. Async code calls them via:
await asyncio.get_running_loop().run_in_executor(mgr.executor, mgr.step, action)
2. Canvas Nodes
User-Facing Tools
These nodes appear in the canvas sidebar and are safe to use in graphs:
| Node Type | Display Name | Category | Input Ports | Output Ports | Description |
|---|---|---|---|---|---|
env_habitat__reset |
Habitat: Reset | environment | trigger (ANY, optional) |
instruction (TEXT), episode_id (TEXT), scene_id (TEXT) |
Begin episode β emit metadata only, no observation. Episode placement is env panel-owned (set_episode); reset never re-runs env.reset(). Per-graph env-override config knobs (blank = YAML default; a mismatch rebuilds the env once per worker and re-seats the placed episode): rgb_resolution (Three-Step renders 1024), depth_resolution + max_episode_steps (AO-Planner renders 512/512 with a 5000-step cap, matching its upstream LLM_base_task.yaml). |
env_habitat__step_discrete |
Habitat: Step (discrete) | environment | action (ACTION, 0-3) |
reward (ANY), terminated (BOOL), truncated (BOOL), info (ANY) |
Advance one tick with a discrete action; returns control signals only (no observation β pull from observe_egocentric). |
env_habitat__step_pose |
Habitat: Step (pose) | environment | target (POSE) |
reward (ANY), terminated (BOOL), truncated (BOOL), info (ANY) |
Navigate toward a target SE(3) pose via shortest-path following. |
env_habitat__step_hightolow |
Habitat: Step (HIGHTOLOW) | environment | angle (TEXT), distance (TEXT) |
reward (ANY), terminated (BOOL), truncated (BOOL), info (ANY) |
Rotate by angle (rad) then walk distance (m) β Open-Nav HIGHTOLOW macro action. |
env_habitat__observe_egocentric |
Habitat: Observe (egocentric) | environment | trigger (ANY, optional) |
rgb (IMAGE), depth (DEPTH), pose (POSE), intrinsics (ANY), raw_obs (ANY), instruction_text (TEXT) |
Pull the current first-person observation; auto-resets if episode done. Pose + intrinsics folded in here. instruction_text is the current episode's raw instruction string (per-episode metadata) β feed it to env_adapter__vln_env_to_canonical.instruction raw; tokenization happens model-side in the policy adapter. |
env_habitat__observe_panorama |
Habitat: Observe (panorama) | environment | trigger (ANY, optional) |
views (ANY), directions (TEXT), n_views (ANY), composite (IMAGE) |
Multi-view panorama. Config representation: views_rgbd (aligned RGB-D views, default) or composite (stitched grid image). Expensive β pull on demand. |
env_habitat__evaluate |
Habitat: Evaluate | evaluation | trigger (TEXT, optional) |
metrics (METRICS), success (TEXT), spl (TEXT) |
Pull current task metrics (SR/SPL/nDTW/SDTW) without stepping; thin metric sink reading terminal info. |
This nodeset implements the gym-like env interface (4 verbs + pull perception, 2026-06-09). step_* nodes return no observation β perception is pulled via the observe_* family. The old step_native / observe / localize / episode_info / panorama_rgbd nodes and the two [Mock] nodes were retired; see the template's migration map.
Notable Output Ports
pose(POSE wire type):{"position": [x, y, z], "orientation": [qx, qy, qz, qw]}β emitted byobserve_egocentric.raw_obs(ANY): Raw Habitat observation dict; used by policy nodes that need full sensor data.terminated/truncated(BOOL): gym-style episode-end split β STOP/goal vs step-budget cutoff.info(ANY): per-step diagnostics + terminal metrics dict (populated when terminated/truncated).composite(IMAGE) /views(ANY): the two panorama representations fromobserve_panorama.
Internal Tools (Episode Control, ADR-legacy-002)
These tools are hidden from the sidebar (category="internal") but auto-proxied when the nodeset runs in server mode. Canvas evaluation env panels use them to manage episodes:
| Node Type | Display Name | Category | Input Ports | Output Ports | Purpose |
|---|---|---|---|---|---|
env_habitat__ep_metadata |
[Internal] Episode Metadata | internal | (none) | result (TEXT) |
Return splits, current index, episode counts, max steps |
env_habitat__ep_list |
[Internal] Episode List | internal | offset (TEXT), limit (TEXT) |
result (TEXT) |
Paginated episode list with instruction + scene_id |
env_habitat__ep_peek |
[Internal] Episode Peek | internal | index (TEXT) |
result (TEXT) |
Read episode info by index without switching |
env_habitat__ep_set |
[Internal] Set Episode | internal | index (TEXT) |
result (TEXT) |
Switch to episode by index within current split |
env_habitat__ep_set_split |
[Internal] Set Split | internal | split (TEXT) |
result (TEXT) |
Switch dataset split (val_unseen, val_seen, train) β heavyweight rebuild |
3. Configuration
Initialization Parameters
When loading the nodeset via POST /api/components/nodesets/env_habitat/load, pass these kwargs in the request body:
{
"dataset": "R2R-CE",
"split": "val_unseen",
"gpu_id": 0,
"max_steps": 500
}
| Parameter | Type | Default | Description |
|---|---|---|---|
dataset |
str | R2R-CE |
R2R-CE or RxR-CE. Selects the VLN-CE YAML and RxR language YAML at load time. Ignored when exp_config is supplied. |
split |
str | dataset-default | For R2R-CE: val_unseen, val_seen, train. For RxR-CE: the same base splits plus test_challenge, each suffixed with _en, _hi, or _te (e.g. val_unseen_en). |
exp_config |
str | auto | Explicit VLN-CE config path (relative to VLN-CE root). When supplied, overrides the datasetβYAML lookup. |
gpu_id |
int | 0 | CUDA device index for simulator rendering |
max_steps |
int | 500 | Max episode steps before auto-done |
Environment Variables
# Path to vlnce conda environment (Python 3.8)
export VLNCE_PYTHON="python"
# VLN-CE root directory (if not found via discovery)
export VLNCE_ROOT="/path/to/VLN-CE"
The nodeset auto-discovers VLN-CE in this order:
1. $VLNCE_ROOT/data if set
2. ../VLN-CE/data (relative to agentcanvas repo root)
3. third_party/VLN-CE/data
If found, the process cwd is changed to the VLN-CE root so Hydra configs resolve correctly.
Node-Level Config: Panorama View Count
The Panorama tool exposes a UI config field:
ConfigField("n_views", "select", label="Views", default=12,
options=[
{"value": 4, "label": "4 views (90Β°)"},
{"value": 8, "label": "8 views (45Β°)"},
{"value": 12, "label": "12 views (30Β°)"},
{"value": 24, "label": "24 views (15Β°)"},
])
Users can adjust view density in the node inspector before firing.
4. Datasets: R2R-CE and RxR-CE
The Habitat NodeSet supports two VLN-CE benchmarks under a single unified env panel:
| Dataset | Episodes dir | Splits | Splits with language suffix |
|---|---|---|---|
| R2R-CE | data/habitat/datasets/R2R_VLNCE_v1-3_preprocessed/ |
train, val_seen, val_unseen |
no |
| RxR-CE | data/habitat/datasets/RxR_VLNCE_v0/ |
train, val_seen, val_unseen, test_challenge |
yes β _en / _hi / _te |
4.1 Language-Suffixed Splits (RxR-CE)
RxR-CE episodes carry a BCP-47 language field per instruction (e.g. en-US, hi-IN, te-IN). The env panel exposes each base split three times, one per language group, following the same convention as the MP3D discrete nodeset:
val_unseen_en (matches en-US, en-IN)
val_unseen_hi (matches hi-IN)
val_unseen_te (matches te-IN)
Behind the scenes the suffix selects the matching rxr_cma_{en,hi,te}.yaml upstream config β each YAML already sets EVAL.LANGUAGES to the right BCP-47 list, which HabitatEnvManager.initialize threads into TASK_CONFIG.DATASET.LANGUAGES so RxRVLNCEDatasetV1's loader filters correctly at load time.
4.2 Episode Extras
Every episode info dict (from _get_episode_info_unlocked, get_episodes_list, and _peek_episode_sync) now carries:
| Field | Type | Notes |
|---|---|---|
language |
str \| None |
Full BCP-47 tag from ep.instruction.language (e.g. "en-IN"). None for R2R-CE. |
extras.language |
same | Mirrored under extras for cross-nodeset parity with MP3D. |
extras.instruction_id |
int \| str \| None |
From ep.instruction.instruction_id if present. |
extras.annotator_id |
str \| None |
From ep.instruction.annotator_id if present. |
extras.timed_instruction |
list \| None |
Word-level timestamps for RxR (only when loaded). |
extras.pose_trace_path |
None |
Reserved; CE pose-trace preprocessing is tracked as E8. |
The same extras schema is emitted by the MP3D nodeset (matterport3d.py::_load_rxr), so downstream agent graphs can consume extras.language / extras.instruction_id uniformly across simulators.
4.3 Data Download
Episodes for R2R-CE and RxR-CE land under data/habitat/datasets/ via scripts/data/fetch_episodes_vln.sh:
# RxR-CE episodes (~150 MB, via gdown from the upstream Google Drive)
bash scripts/data/fetch_episodes_vln.sh --rxr-ce
The flag is idempotent β if every expected {split}/{split}_guide.json.gz file is already on disk, it no-ops. Without gdown, it prints a clear manual-download hint pointing at the official RxR-Habitat Challenge zip.
Out of scope (tracked separately): pre-computed BERT instruction features (
E7) needed only for the CMA policy baseline, and pose-tracetrajectories.json.gzpreprocessing (E8) needed only for VLN-CE's recollect trainer.
5. Env Panel Integration
ADR-server-002: Episode Lifecycle Management
The Habitat NodeSet declares a HabitatEnvPanel (a BaseEnvPanel subclass) as a class-level attribute. This enables the canvas Evaluate page to manage episodes uniformly across all environments:
# On EnvHabitatNodeSet (registered by WorkspaceComponentRegistry on load)
env_panel = HabitatEnvPanel
The env panel provides:
- Metadata queries (splits, episode counts) via on_load() / get_options()
- Episode switching by index via on_field_change("episode_index", β¦)
- Split switching with full teardown + rebuild
- Run lifecycle buttons (play / pause / stop / reset) via on_action()
How It Works
- Evaluate page calls
GET /api/env-panels/env_habitat/stateβ splits + current state fromon_load() - User selects episode from dropdown β
POST /api/env-panels/env_habitat/field/episode_index HabitatEnvPanel.on_field_changecallsmgr.set_episode(β¦)and emits theepisode_resetsignal- Graph continues; agent navigates in the new episode
This abstraction allows the same Evaluate UI to work with Habitat, SAPIEN, or any future environment that ships a BaseEnvPanel subclass. In server mode the panel is hosted inside the env subprocess and bridged over /env-panel/* via RemoteEnvPanelProxy.
Three-Field Cascade (Dataset β Split β Episode)
The HabitatEnvPanel exposes a three-field cascade mirroring MP3DEnvPanel:
datasetβR2R-CEorRxR-CE. Changing resetssplitandepisode_index.splitβ dataset-conditional list via_dataset_splits(dataset). Changing triggers_switch_split, which resolves the(exp_config, base_split)pair via_resolve_dataset_config, shuts the simulator down, and re-initializes against the new YAML.episode_indexβ index into the currently loaded dataset/split.
Each field change emits a signal side-effect with signal_name="episode_reset", so any lifetime="episode" state container clears downstream. This matches the MP3D nodeset contract so agent graphs can bind state lifetimes without knowing which simulator is active.
6. Usage
Loading the NodeSet
Local mode:
POST /api/components/nodesets/env_habitat/load
Server mode (explicit):
POST /api/components/nodesets/env_habitat/load?mode=server
Auto-routed (default):
POST /api/components/nodesets/env_habitat/load
server_python != sys.executable and routes to server mode automatically.
Typical Graph Workflow
1. Load Habitat nodeset
β
2. Habitat: Episode Info β get instruction
β
3. Loop:
- Habitat: Observe β current RGB + depth
- [Policy or reasoning node] β decide action
- Habitat: Step β action=[0-3], get new obs + done
- [Iterate or break if done]
β
4. Final metrics available when done=True
Common Patterns
Panoramic Scene Understanding
Habitat: Observe β current RGB
β (optionally wire agent_state)
Habitat: Panorama (n_views=12) β composite grid + directions JSON
β
Vision model for multi-view analysis
Navigation with Shortest Path
Habitat: Episode Info β get current position
β
[Planner determines target] β target_state={position: [x,y,z]}
β
Habitat: Navigate To β done, final_state
Episode Switching in Evaluation
Use the Evaluate page's Episode dropdown. Behind the scenes:
1. Evaluator calls env_habitat__ep_metadata β get split + count
2. User selects episode index
3. Evaluator calls env_habitat__ep_set with index
4. Graph resets and continues in new episode
Error Handling
- "Environment not initialized": Habitat nodeset not loaded; load it first
- "Episode already done": Auto-reset on next Observe or explicitly call Episode Info β Set Episode
- "Index out of range": Requested episode index exceeds dataset size; check Episode List
- Split switch fails: Ensure VLN-CE is at the expected path or set
$VLNCE_ROOT
Performance Considerations
- Single simulator: All graph executions share one Habitat instance (thread-safe via locks)
- Thread affinity: GPU/physics operations run on dedicated executor thread
- Observation caching: Observe returns
raw_obsdict; extract specific keys (rgb, depth) as needed - Panorama rendering: N_views=24 takes ~2-3s per panorama on typical GPU; use 4 or 8 for quick iteration