AgentCanvas / Pages / Developer Guide / Nodesets / Env / Habitat
2026-07-04

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

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-trace trajectories.json.gz preprocessing (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

  1. Evaluate page calls GET /api/env-panels/env_habitat/state β†’ splits + current state from on_load()
  2. User selects episode from dropdown β†’ POST /api/env-panels/env_habitat/field/episode_index
  3. HabitatEnvPanel.on_field_change calls mgr.set_episode(…) and emits the episode_reset signal
  4. 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:

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
The framework detects 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
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

Performance Considerations

AgentCanvas docs