Roadmap
Open work — what's queued, in flight, and explicitly deferred. Done items live in roadmap-done; paradigm-level direction is in major-versions.
F) · Env (environment/simulator integration, prefix E) · Method (research method ports, prefix M) · Planned / Deferred. Click a chip to filter; IDs are stable and never reused.
§1TODO —
Active maintenance, refactors, and docs work. IDs are stable numeric (highest assigned: 75) and never reused; completed items move to roadmap-done.
tooleqa__step + workspace/graphs/eqa/unverified/tooleqa_hmeqa.json: episodes where the 3B agent never calls final_answer run to the env step-limit and emit no metrics port, so they're silently excluded from the SR aggregate (harness reported 1.0 = 1/1 metric-producing episode, true result 1/3). Three fixes: (1) the max-iter fallback keys off env step_index, which only advances on go_next — switch it to the ReAct step count so non-terminating episodes still emit a (failed) answer + metrics; (2) metrics.num_steps reports 0 — populate with the ReAct step count; (3) count env-done-without-answer episodes as success=0 rather than dropping them — partially covered since 2026-06-11: the batch runner now flips an eval episode with no graphOut snapshot to status=error (no more silent exclusion), but emitting an explicit failed verdict is still the right fix. Also investigate why the 3B keeps exploring instead of answering (prompt/format adherence vs upstream 7B).workspace/nodesets/method/navgpt.py + navgpt_mp3d_tools.py, graph JSONs); remaining: agent manifest, skills module, consolidate two graph variants, clean organization (merged with former #27).TSDFPlanner objects cannot cross the server-mode JSON IPC boundary (numba-JIT'd volumes aren't pickle-friendly), so explore-eqa nodeset ended up with subprocess-local module globals keyed by episode_id instead of state containers. Load-bearing design constraint for future container work — the container system needs an in-subprocess addressing mode.0fd2df90 broker + RemoteContainerProxy prototype graduated — broker forwarding made async, the container payload path switched to msgpack, the executor callback URL now resolves from Settings (config.resolve_executor_url) instead of a hardcoded localhost:8000, and silent injection / batched-grant drops now raise loudly. Residual: cross-nodeset access to a replicated nodeset's containers under worker_count>1 is still not routable (the home registry resolves an untagged URL; the broker can't disambiguate N executors in the eval topology) — surfaced as a load-time warning in registry._check_container_ownership rather than a silent 404, so #17 stays open for that piece. Indirectly validated by a 10ep/10-worker explore-eqa run (SR 0.4 ≈ 0.42 baseline; explore_eqa_tsdf_map is owner-local so unaffected).StatePanel.tsx: add/remove named states, reducer type). Remaining: decompose HistoryTracker into composite graph with visible state, implement container checkpoint persistence to disk.workspace/ subdirectory conventions, evaluate whether agentcanvas/backend/app/ structure scales, align folder naming with documented concepts. Define a canonical layout doc.workspace/nodesets/ is now organized by kind (common/ env/ method/ model/ policy/ other/) with placement/naming rules codified in the nodeset-layout standard; graphs are bucketed {vln,eqa,vla}/{verified,unverified}/; codebase-map serves as the backend layout reference. Remaining: the agentcanvas/backend/app/ scaling audit + a decision on the residual top-level dirs.habitat.py, sam.py, policy_cma.py, others.py currently only sets NodeUIConfig(color="..."). Audit which nodes should expose inline editable config_fields and display_fields. Per node: color-only is fine, or upgrade to block/strip/viewer layout. One-line classification per node, then edits for ones that need the richer form.NodeUIConfig-bearing nodeset files now set config_fields (e.g. env_habitat reset: rgb/depth res + max steps; model_sam: 10 fields; the 2026-07-05 FM standardization wave). Remaining: display_fields and custom layout= are used by zero workspace nodesets, and a tail of nodes is still color-only — the per-node classification pass hasn't been done.viewpoint_id TEXT + cand_vpids_json; Habitat-continuous: 2-vector or Discrete(4) ACTION int; VLN-CE: discrete Discrete(4); future AI2-THOR: parameterized actions with object IDs), which forces every agent graph to be env-specific. Proposal (deferred): every env's reset/step emits a single action_manifest (TEXT, JSON-serialized) with a first-field type discriminator describing the currently valid action set. For now: keep env-native shapes; revisit once a third env nodeset lands and we can see the real minimal common schema._self_log("error", ...) is not carried back over the proxy HTTP response; (b) many built-in tools swallow soft failures into empty default outputs without exposing an error output port.ErrorBus + error_event WS frame + Report tab) shipped Phase 1 — per-node executor catches, graph-level catches, uncaught FastAPI exceptions, HTTP 4xx/5xx, network failures, React crashes now all surface as toasts + Report-tab entries with full traceback/scope. Still missing: (1) server-mode subprocess proxy doesn't yet relay envelopes published inside the child to the parent's bus; (2) no per-node "explicit error output port" pattern + canvas red-ring decoration yet./api/internal/events (api/execution/internal_events.py), which republishes them on the ErrorBus → error_event WS frame, so server-node logs/errors now surface on the canvas. Handler exceptions are first-classed (pushed with node/execution scope + traceback via server/event_push.py) instead of being demoted to a swallowed {"error": ...} value; a WARNING+ logging bridge forwards general subprocess logs. Still open: gap (2) — per-node explicit error output port + canvas red-ring decoration.error-output-port pattern is in the engine (graph_executor._declares_error_output + routing: an undeclared bare error key is convicted as failure, a declared port passes as data; unit-tested) and adopted by real nodesets (env_libero, env_mp3d, model_detany3d, spatialnav). Remaining: the live-canvas red-ring — the error outline exists only in the post-hoc LogCanvasView replay; BlockLayout (the default live renderer) shows no error state.workspace/{graphs,graph_nodes,nodesets,policies} discovery, outputs/eval_runs/ vs data/outputs/{eval,eval_mock,eval_native}/ vs legacy outputs/runs/ JSONL persistence, overlapping ownership with TODO #40 — clarify what "file management" covers before scheduling).worker_count > 1 with a batched=True node hosted in a parallelism="shared" server-mode subprocess, _BatchQueue._delayed_flush raises RuntimeError: got Future attached to a different loop (observed 2026-05-07 on policy_vlnce__predict at K=10; 88/100 episodes returned metrics={}). Workaround in tree: policy_adapter_vlnce__predict.batched = False (node renamed from policy_vlnce__predict in the 2026-07-04 split). Real fix: make _BatchQueue loop-agnostic or per-worker.flatten_graph — leaky abstraction. Cleaner: composite stays as a real runtime entity, executor walks composite boundaries natively. Trade-off: flatten = simple executor + complex compile-time transform with leaky patches; composite-native = composite-aware executor + simple boundary resolution. Trigger: third leak shows up, OR multi-scope authoring becomes the dominant pattern. Until then, the conditional-strip middle option keeps things working.install_*.sh in scripts/install/ plus 11 conda envs on disk. Naming inconsistent (agentcanvas-X vs bare vlnce/hmeqa/vlaws*), env→nodeset mapping implicit, several install scripts duplicate boilerplate. Audit goals: document canonical env↔nodeset table; decide which envs can merge; extract shared install boilerplate; clarify install_all variants or collapse them; align naming.install_ac_*.sh with envs/ac_*.yaml + .lock files; the shared ac-fm env consolidated the FM-nodeset interpreters (2026-07-05); clean-room install verify passed on a fresh Ubuntu 20.04 host (2026-07-09, PRs #32/#34/#36). Remaining: the documented env↔nodeset table, shared-boilerplate extraction, merge decisions.docs/pages/developer-guide/tutorials/ showing a minimal worked example: DAG / cyclic loop / ReAct hidden / ReAct router / Plan-and-Execute / bounded multi-agent / embodied sense-act / FM pipeline. Canonical list lives in major-versions.html §1 "Agent forms v1 covers".BaseServer.start() to a thread, fixing the framework hole where server-mode startup blocks the entire event loop for up to startup_timeout (1800s); (b) run the ensure endpoint as a token-keyed asyncio.Task + add POST /nodesets/ensure/cancel, where server-mode cancel kills the subprocess; (c) auto-promote heavy local nodesets to server mode so they gain hard-cancel; lightweight local stays on client-side fetch abort (soft cancel).docs/pages/developer-guide/design-docs/graph/graph-executor.html landed 2026-06-08 (Part I concept SVGs + flowchart; Part II verbatim run()/_fire_node/helpers with ◀── annotations). Committed as a checkpoint — refine later: tighten prose, check dark-mode contrast and flowchart arrow routing in-browser, consider trimming the full-verbatim listings if they prove too long to maintain. 2026-06-11 (evening): the listing problem is structurally resolved — Part I split into graph-view/engine-view, §4.5 redrawn around the ready-queue, §4.6 now a turn-by-turn table; Part II slimmed to run() only (_fire_node/helpers read in source) and the listing is synced from source by docs/_lib/_sync_run_listing.py (comments are the annotations; Pygments two-stage highlighting per .claude/standard/code-highlighting.md). Remaining: in-browser pass — dark-mode contrast, flowchart arrow routing, the new GitHub-palette listing.evaluate node (triggered by step_*.info) does not fire after the agent's final STOP step — the loop terminates (e.g. parse_response.is_stop → termination) before evaluate re-fires — so the harvested output_port__metrics graphOut holds the pre-STOP value (success=0). Confirmed on smartway_ce (20 ep, gpt-5-mini, step_budget=15, run 20260610_091657): habitat computes success=1.0 on the STOP step (visible in step.info.metrics) but 5/20 episodes that stopped within the 3 m radius (ep 9/10/11/14/17, dist 0.43–2.61 m) recorded success=0 → reported SR 0.000 instead of ≈0.25 (baseline 0.28). Mechanism, not agent regression (nDTW 0.50 ≥ baseline 0.40; oracle 0.40). Structural fix landed 2026-06-11 (two-sided iterOut refactor): the termination node was removed, evaluate moved to the after-loop band fed once at termination via the iterOut's final_stop handle, and the validator now rejects loop-body-fed metrics/success graphOuts — the engine-level regression test asserts the after-loop consumer receives the terminal iteration's value. Remaining work = re-verification: vln/verified/), vln/verified/); 2026-07-05 wave: smartway_ce), 20260705_194214, full metric harvest via final_stop), 20260705_193758) — all promoted to verified/; still open: navgpt_ce (llmCall action/summary port drift) and opennav_habitat (llmCall template drift) — the rest of the wave has landed (threestepnav_ce and octo_simpler now sit in verified/; discussnav_mp3d / spatialnav_mp3d are tracked by #73 / M13, not this mechanism bug). See env template.vla_policy_libero.json. The graph's ✅-verified datapoint (2026-05-02, 5/5) came from scripts/smoke/smoke_vla_libero.py against the external vlaworkspace; the canvas-graph path through the policy_adapter_vla server has been broken since the 2026-05-04 adapter split. Debt chain, outermost first — first three fixed 2026-06-11: (1) graph checkpoint_path pointed at a deleted vlaworkspace slurm dir → repointed to vendored data/vla_policy/checkpoints/pi0_libero_pytorch; (2) LiberoRobot lost its RobotAdaptor base in the adapter split, so ensure_robot's _find_subclass never matched → re-based; (3) adapters/models/pi0_model.py kept a one-level-short relative import (..models.openpi → resolves to adapters.models.openpi) → ...models.openpi; (3b) ac-vla-policy env had pip nvidia-nvjitlink-cu12 12.5 vs 12.9-era CUDA libs → upgraded to ~=12.9. Remaining blocker: Pi0Policy.__init__ with DEFAULT_KWARGS use_pretrained_weight=True tries a JAX→PyTorch conversion via python -m vlaworkspace.model.convert_jax_model_to_pytorch (module not vendored; cache_dir 'data/models' also doesn't match the vendored layout data/vla_policy/checkpoints/pi0_base_pytorch) — even though the finetuned safetensors is already local. Fix = make the pretrained-base path resolve to the vendored pi0_base_pytorch (or skip base-init when a full finetune checkpoint is given), then smoke vla_policy_libero 1 ep. Companion engine hole (cousin of the silent-episode-completion memory): a server proxy node whose response is {"x": null} gets logged as outputs={} with error=null and the episode "completes" at step_count=0 — three of today's four failures were silent for this reason.env_habitat.encode_depth_raw_base64)env_habitat's panorama path (observe_panorama, env_habitat.py:536-537) emits two depth products per view: depth_base64 (8-bit min-max-normalised PNG via encode_depth_base64, viewer-only, lossy / relative) and depth_raw_base64 (16-bit-millimetre PNG via encode_depth_raw_base64 env_habitat.py:798, metric-faithful, capped at 65.535 m to fit uint16). This base64-PNG encoding is an application-layer contract consumed by downstream method nodesets (opennav_perception._decode_depth_raw, aoplanner._decode_depth_m, smartway_waypoint) — distinct from, and predating, the Move-1 msgpack transport (#67), which can now carry a raw ndarray across the env boundary as a blob ExtType with no base64 at all. Not a bug — kept intentionally because (1) the consumers' wire contract is a base64 string; (2) 16-bit PNG compresses the depth map (a raw float32 480×640 panorama set is large); (3) the "16-bit / mm / cap 65.535 m" metric semantic is carried by the I;16 PNG format choice; (4) it survives the JSON legacy path too. Cleanup question: once the JSON legacy path is retired (transport msgpack-only) and the consumers can be migrated together, depth_raw_base64 could collapse to a raw depth ndarray + a units flag (is_mm/metres), letting msgpack own compression/encoding and removing the bespoke encode_depth_raw_base64 / _decode_depth_raw pair. Scope: weigh the bandwidth win of PNG compression against the simplification before scheduling; relates to #67 (transport contract) and the viewer-vs-metric depth split.vln-/eqa-/vla-support-status.html) drifted while being converted onto the VLN two-part template (2026-06-30). Divergences to reconcile: VLN's §1 matrix uses CSS-styled ours-* badge spans plus an Env column, while EQA/VLA use plain-text status glyphs (✅/❓/❌/🅼/⏳/▫); status-badge vocabulary, column schemas (Method · Env · Status · Graph/roadmap), and Part-I section ordering (env / tool / method / datasets / eval-infra / gaps) are not yet uniform. Standardize the three onto one shared shape — likely a .claude/standard/support-status-style.md defining the column conventions + a single badge legend — then normalize all three pages (including VLN) against it.{vln,eqa,vla}/unverified/ graphs to a trustworthy end-to-end datapoint (SR/SPL/LLM-Match + run_id) and promote the ones that pass to verified/, the way voxposer_libero_decomposed went (2026-06-30, libero_object 0.88). Overlaps the gym-migration re-verification wave (#64: smartway_ce, smartway_mono_ce, navgpt_ce, opennav_habitat, straightforward, + mp3d/simpler) and the VoxPoser/Pi-0 blockers (#65, #66). Progress 2026-07-15: verified/ now holds 13 graphs — vln 7 (incl. threestepnav_ce, promoted since), eqa 4, vla 2. Still pending a datapoint: discussnav_mp3d (100-ep faithful-config run; the 5-ep smoke is SR 0.2), tooleqa_hmeqa (also gated on #63), vla_policy_libero/_simpler (#66), the two pyslam_* probe graphs; blocked on port drift: navgpt_ce, opennav_habitat (#64), spatialnav_mp3d (M13, SSG CSV). Track progress against the §1 support matrices on the three support-status pages.policy_adapter_vlnce (then policy_vlnce) refactor (env→canonical standardizes only; instruction tokenize + obs_transforms moved to canonical_to_model; _full_raw_obs leak removed; tokenize_instruction node retired; verified 3-ep bit-identical to baseline). Remaining: (1) explicit typed rgb/depth/instruction ports at the env→adapter boundary — retire the raw_obs ANY passthrough and rewire the obs iterIn/iterOut loop; (2) land the unified canonical.py v0 (flat schema + plain-dict info + action_space; scratchpad draft) as a shared policy module and migrate policy_adapter_vla onto it; (3) verify the RxR / feat path (rxrce + Seq2Seq) — aligned but not eval'd; (4) carry the standardize/process split into policy_adapter_vla. Mental model + done-state in policy-adapters.html §1.2/§8.§2Feature TODO —
Significant new capabilities that change what AgentCanvas can do. Prefix F.
naive_memory_nodeset in workspace/nodesets/memory.py — 3 canvas nodes: memory_save (persist episode learnings), memory_search (semantic search over saved memories), memory_get (read specific memory entry). Storage: markdown files in workspace/memory/. Search: embedding-based vector similarity. Inspired by OpenClaw's memory system.GraphDefinition into a self-contained Python script importing only workspace/ components — no FastAPI, no React, no WebSocket. Enables headless batch eval, single-file sharing, integration into other codebases. Covers: topological code generation, inlining node configs, IterIn/IterOut → Python while-loop, composite flattening, CLI entry point.graph_to_code() / Graph.to_code() (app/graph_sdk_codegen.py) compiles any GraphDefinition into a self-contained builder script — topological node/edge emission, inlined configs, composite sub-builders, CLI entry — round-trip semantically exact on MapGPT-MP3D. Differs from the original spec: the emitted script rebuilds the graph via the Graph SDK and runs it through the real GraphExecutor (preserving exact loop semantics) rather than transpiling IterIn/IterOut into a bare Python while-loop. The item's motivation (headless batch eval, single-file sharing, codebase integration) is met by the broader Graph SDK capability; the literal while-loop transpile remains open.set_episode()/reset() over HTTP (ADR-server-002), ✓ env reset between episodes (ADR-eval-002 PB), ✓ proxy nodes preserve node_type, ✓ episode replay from persisted step data (Replay page over /api/replay, reading per-episode log.jsonl + assets). Remaining: run comparison UI (side-by-side metrics for 2+ runs — RunHistory is single-select today), WS bandwidth throttling toggle for live viewer images (only the hardcoded 256px/JPEG-q70 thumb cap in wire_types.py exists).query() loop and the ReAct pattern. Subsumes F2.subprocess.Popen boundary in WorkspaceComponentRegistry._load_nodeset_as_server is now the only spawn site; container spawn is a one-file swap. Provide official Docker images for Habitat and Matterport3D nodesets. Eliminates habitat-sim / CUDA dependency from agentcanvas host; enables cloud deployment; makes the nodeset usable on machines without GPU drivers.model_pyslam runs its GPL-isolated SLAM stack in a rootless-Docker container (self-managed: initialize() → docker run via PySlamContainerClient, FastAPI shim inside; Dockerfiles in-tree). The pattern is proven, but it bypasses the framework spawn site and no env nodeset uses it yet. Remaining: route container spawn through _load_nodeset_as_server (or bless self-managed as the pattern) and ship Habitat / MP3D images._load_nodeset_as_server: server_image on the nodeset selects ContainerServer (foreground docker run, ro repo mount, stock auto_host — no bridge code). First user model_orbslam3 verified end-to-end (registry load + canvas graph run). Remaining: migrate model_pyslam off its private bridge; ship Habitat / MP3D env images.model_pyslam now declares server_image (default agentcanvas/pyslam:cuda, weights + /opt/out handle drop via server_mounts, PYSLAM_* via server_env); the private _client.py/_server.py bridge (~870 lines) is deleted. Verified: registry load through ContainerServer + pyslam_tum_slam end-to-end on TUM fr1_xyz — first batch-eval datapoint for the graph: ATE RMSE 0.011 m / RPE RMSE 0.006 m over the full 400-pair sequence (run 20260820_113615; the graph also gained numeric ate_rmse/rpe_rmse graphOuts so the eval runner can harvest a verdict). Fix en route: the pyslam image entrypoint now preserves the injected PYTHONPATH across pyenv-activate. Remaining: ship Habitat / MP3D env images.§3Env TODO —
Environment setup, Habitat versions, conda envs, data pipeline, infra tasks. Prefix E.
env_mp3d loads REVERIE episodes (_load_reverie(): reverie_id + per-viewpoint bbox_path) and the data is on disk (data/mp3d/tasks/REVERIE/, 10,567 BBox JSONs + all four splits). Remaining: a live grounding observation (today only the GT-bbox path is surfaced), an object-selection action, and the continuous-env (Habitat) side.gen_skybox_rgb_mp3d.sh that generates trajectories.json.gz from raw RxR speaker pose traces. Only needed by VLN-CE's recollect trainer; the main eval path does not consume this.§4Method TODO —
VLN methods, agent architectures, research implementations to build or port. Prefix M.
LLMCall node under PortBench rules.
workspace/nodesets/policy/ alongside the policy-adapter nodesets. Requires pretrained weights download. Not a v1 PortBench task (single-box trained transformer).lifetime="episode" accumulator with an explicit advance rule), a sharp state-container advance-condition probe. New work: constraint-parser node + value-mapper + advance-condition state, wired against the Habitat-CE waypoint surface. Not a v1 PortBench task (post-cut); v2 candidate. Lineage: vln-methods §2.5.14.spatialnav_mp3d.json (vln/unverified/) + workspace/nodesets/method/spatialnav.py / ssg.py, code restored + import-verified. PAUSED 2026-06-20: blocked on the upstream SSG category-mapping CSV (unreleased; the standard MP3D category_mapping.tsv would substitute). Known trap: with the mapping data missing, the SSG silently degrades to an empty-graph NavGPT. Remaining before eval: orchestrator LLM params + eval profile. Added retroactively.Also representative but intentionally not listed: LM-Nav (22.07, 702 cites) — the first "compose 3 foundation models" VLN agent, but it navigates real-world topological graphs, not MP3D / Habitat, so it needs an env nodeset AgentCanvas doesn't ship (out of current scope). Open-Nav already has a graph (opennav_habitat.json, §1 unverified) — tracked under TODO #6 / §1, not as a new M-item.
§5Planned
Backlog of items intentionally not yet on the critical path.
NodeShell has the status-dot component (used by composites), nav_step/viewer_data WS events already flip status for the agent + viewer nodes, and the post-hoc LogCanvasView replays the full firing sequence — but BlockLayout (the default renderer for most nodes) shows no live firing state. Remaining: wire per-node firing events into the live canvas.§6Deferred
Explicitly out-of-scope items kept here so we don't keep re-evaluating them.
GenericBlockRenderer template_editor field type.tsx (complex: needs variable chip insertion at cursor position).flatten_graph verification).