NavGPT on AgentCanvas — paper analysis + navgpt / navgpt_mp3d_tools port
NavGPT (Zhou et al., AAAI 2024; repo GengzeZhou/NavGPT) is the original "LLM as navigator" agent: a GPT-4 ReAct loop picks viewpoint IDs on the MP3D graph via action_maker — the released config's sole tool, which already teleports to any viewpoint in the scan (back_tracer rides the non-default tool-chain path) — over text observations built from offline BLIP-2 captions and object detections. This page documents the navgpt_mp3d_tools port driving env_mp3d in navgpt_mp3d.json (the paper-faithful mainline), plus the in-house NavGPT-CE variant (navgpt.py + navgpt_ce.json, continuous env — a derived design, not a port).
| At a glance | |
|---|---|
| Upstream | NavGPT (Zhou et al., AAAI 2024) — repo GengzeZhou/NavGPT pinned @ b3fc8a2 (MIT). Core: nav_src/agent.py (ReAct agent, scratchpad), nav_src/prompt/planner_prompt.py (VLN_GPT4_PROMPT et al.), nav_src/NavGPT.py. Author is a direct collaborator — treat as in-house-adjacent upstream. |
| Env consumed | env_mp3d — reset (metadata, n_headings 12), observe_navigable / observe_panorama, step_waypoint (viewpoint-ID teleport; success/error/turned_angle extras), evaluate (gym-like contract). CE variant: env_habitat step_discrete. |
| FM consumed | built-in llmCall — MP3D graph pins profile gpt-5-mini · temp 1.0 · max_tokens 2000 · stop: null (the gpt-5 reasoning-model convention — temp≠1.0 or a small max_tokens silently return empty, see platform/project_gpt5_llmcall_params.md); upstream ran GPT-4 · temp 0 — model swap is §3C. Prompt rides orchestrator_llm.config.template |
| Method nodesets | navgpt_mp3d_tools (11 nodes, local mode: paper caches, pure FM glue — base64 adapter / caption + detection formatters — legacy R-CNN alternate, format/parse/scratchpad) + navgpt (3 CE nodes: action format skill, keyword parse, history formatter). Online captioning/detection ride the generic model_blip2 / model_instructblip / model_grounding_dino wrappers since the TODO #56 extraction (2026-07-04) |
| State | MP3D graph: all agent memory rides the loop pivots (scratchpad + init_observation as iterOut persist ports); CE variant stores action_history in graph_state |
| Graphs | workspace/graphs/vln/unverified/navgpt_mp3d.json (22 nodes, 58 edges, step_budget 10 = upstream max_iterations) · navgpt_ce.json (25 nodes, 32 edges, step_budget 150) |
| Fidelity | faithful with justified deviations (inventory-first re-audit 2026-06-20 against b3fc8a2, walked cold from parser.py's released config): prompt skeleton = VLN_GPT4_PROMPT with recorded anti-revisit / back_tracer hardening (§3D); scratchpad mechanics mirror _construct_scratchpad incl. the 7000-char cap; step budget corrected 15→10 to match max_iterations; the GPT-4→gpt-5-mini model swap is a recorded cost/access divergence (§3C). E bucket now empty — the former CE-graph dead-planner_llm defect was removed this pass (§3E). |
| Status | Both graphs unverified since the 2026-06-09 gym migration. No eval run currently survives on disk — pre-May archives were pruned 2026-05-21 (§4). |
1. Upstream method analysis (Zhou et al. 2024)
Upstream NavGPT is a langchain ReAct agent (nav_src/agent.py) on the MatterSim viewpoint graph. The released default in parser.py is use_single_action=True, use_tool_chain=False, use_relative_angle=True, load_instruction=True — a single LLM with everything in one prompt (VLN_GPT4_PROMPT for gpt-4, else VLN_GPT35_PROMPT), tool set [action_maker] only, and max_iterations=10 (parser.py:42, :17, :21). The modular multi-LLM chain (PLANNER_PROMPT / HISTORY_PROMPT / BACK_TRACE_PROMPT / orchestrator variants in the same file, the action_chain / history_chain / plan_chain + back_tracer tool) only fires under use_tool_chain — it is dead code at the default eval. Per step:
- Offline perception (preprocessing, not in the loop): BLIP-2 ViT-G FlanT5-XL captions the egocentric views of every viewpoint; Faster R-CNN (Visual Genome vocabulary) detects objects within 3 m. The rollout reads these caches — no vision model runs at navigation time.
- Observation text: per-direction scene descriptions + object lists + navigable viewpoints with relative orientation ("left 90", …) and distance.
- ReAct prompt (
VLN_GPT4_PROMPT): instruction + initial observation + the running scratchpad; the LLM answersThought / Action / Action Inputwith toolsaction_makerorback_tracer, or stops withFinal Answer: Finished!(agent.py:44). - Parse:
Final Answerregex (agent.py:208) elseAction/Action Inputextraction; a missing Action Input is a format error fed back to the model. - Scratchpad (
_construct_scratchpad,agent.py:117–130): appends each step's log; only the latest step keeps the full observation — earlieraction_makersteps are demoted to one-line history summaries; the whole pad is tail-truncated toMAX_SCRATCHPAD_LENGTH = 7000chars (:46, :136);init_observationis swapped tohistory[0]after step 0 (get_full_inputs). - Act: the viewpoint ID teleports the agent via MatterSim;
back_tracermay target any previously visited viewpoint (non-adjacent allowed).
1.1 Upstream invariants worth marking
- No vision model runs at navigation time. Perception is an offline artifact; the loop is text-in, text-out. Any port that runs vision online is changing the method's cost profile, not just its plumbing.
- One prompt, several variants.
planner_prompt.pyships the single-LLMVLN_GPT4_PROMPT/VLN_GPT35_PROMPTand the modular planner/history/orchestrator chain. The paper's headline GPT-4 number is the single-prompt configuration — "which prompt" is the first question any port must answer. - The default tool set is
action_makeronly — and it already teleports anywhere. Underuse_single_actionthe agent is built withtools=[self.action_maker](agent.py:588);back_tracerexists only on theuse_tool_chainpath. And_make_actionvalidates the ID against the whole-scannavigable_dict.keys()(agent.py:397), soaction_makercan jump to any viewpoint in the scan, not just an adjacent one — back-tracing needs no separate tool. - Anti-revisit is advisory upstream. The GPT-4 prompt merely "encourages" avoiding revisits by comparing IDs in previous Action Inputs — there is no hard constraint and no visited list rendered for the model.
- The scratchpad is lossy by design: only the newest step keeps a full observation, and the pad tail-truncates at 7000 chars — long episodes silently forget their oldest reasoning.
- Stopping is a string protocol:
Final Answer: Finished!— there is no STOP action in the tool set.
2. The AgentCanvas port (navgpt_mp3d)
The mainline port reproduces the single-LLM VLN_GPT4_PROMPT configuration on env_mp3d, run on gpt-5-mini (upstream's GPT-4 is the reference; §3C). The ReAct prompt rides orchestrator_llm.config.template; the langchain agent loop becomes the graph loop (step_budget 10 = upstream max_iterations), with the scratchpad as an iterOut persist port and scratchpad_writer reproducing _construct_scratchpad's short-format demotion + 7000-char cap (_MAX_SCRATCHPAD_LENGTH, navgpt_mp3d_tools.py:1292). Perception is the paper's own offline artifacts, served by paper_objects_cache / paper_captions_cache nodes (1916-class object vocabulary, ~7.2 objects/viewpoint; BLIP-2 captions + summaries). The nodeset also ships online alternates (BLIP-2, InstructBLIP, COCO R-CNN, open-vocab detector) — none are instantiated in the reference graph.
2.1 Per-step flow
Drawn simplified: the init chain (reset → init_observe → init_format → init_observation, which builds the step-0 pad and history-0 line) and the duplicated step-side cache/observe/format chain are collapsed into single boxes; the caches gate the observe nodes via trigger wires so the formatter always sees cache text. Termination: step_waypoint.terminated → iter_out.stop (step budget 10 = upstream max_iterations); post-loop iter_out.final_stop → evaluate.trigger — the sound pivot-rooted shape. All agent memory crosses iterations through the two persist ports; this graph uses no graph_state container.
2.2 Node inventory (navgpt_mp3d_tools — 11 nodes)
| Node | Role | In reference graph? |
|---|---|---|
…__paper_objects_cache / …__paper_captions_cache | Serve the paper's offline artifacts per (scan, viewpoint): 1916-class objects (~7.2/viewpoint) and BLIP-2 captions + 1-line summary | Yes — ×2 each (init + step) |
…__observation_format | 8-compass observation text: per-direction descriptions, objects, navigable viewpoints with relative angle + distance | Yes — ×2 (init + step) |
…__init_observation | Step-0 pad: initial observation + history_0 + scratchpad seed (upstream history[0] / get_full_inputs swap) | Yes |
…__parse_action | Final Answer / Action Input extraction → viewpoint ID or stop (upstream agent.py:208 semantics) | Yes |
…__scratchpad_writer | Append Thought/Action/Observation; demote prior full obs to one-liners; rewrite the Visited Viewpoints line; 7000-char cap | Yes |
…__get_instruction | Instruction accessor (legacy) | No |
…__views_to_base64 → model_blip2__caption → …__format_captions | Online captioning alternate (replicates the offline BLIP-2 pass at runtime; generic wrapper + pure glue, byte-equal to the retired in-nodeset chain 3/3) | No — alternates only |
…__fasterrcnn_detect (legacy) / model_grounding_dino__detect (hf_tiny) → …__format_detections | Online detection alternates (COCO 91-class; open-vocab ≈ VG/BUTD via the generic wrapper + pure formatter) | No — alternates only |
2.3 State & memory
MP3D graph: scratchpad and init_observation are iterOut persist ports — the langchain agent's in-object state became pivot-carried wire values; nothing lives in a graph_state container. The cumulative visited-ID list is embedded in the scratchpad text itself (the "Visited Viewpoints" line), re-extracted and re-rendered each step precisely so it survives the 7000-char tail truncation (rationale recorded inline, navgpt_mp3d_tools.py:1671–1683). CE variant: parse_action appends {step, action, action_name, thought} to graph_state["action_history"]; format_history renders the last max_entries (default 15) with older steps elided.
2.4 Boundary contract
Local-mode nodesets (no server_python): the paper-cache and format/parse/scratchpad nodes are pure text plumbing; the optional online vision nodes lazy-load their models into the backend GPU. Env side: env_mp3d per ADR-server-003 (subprocess_per_worker, MatterSim batch 1) — step_waypoint keeps the navgpt-specific extras (success/error/turned_angle/new_viewpoint) that scratchpad_writer folds into the pad. The LLM is a plain llmCall; the ReAct prompt lives in graph config, not in a nodeset constant — prompt edits are graph edits.
2.5 Prompt assets
The system prompt is orchestrator_llm.config.template in navgpt_mp3d.json, derived from upstream VLN_GPT4_PROMPT (nav_src/prompt/planner_prompt.py). Verbatim-preserved: the embodied-agent framing, orientation conventions, the 3-m stop rule, the strict-viewpoint-ID warning, the full ReAct format block, and the closing scaffold (Thought: I should start navigation according to the instruction, {scratchpad}). Deliberately rewritten (recorded — §3D): the advisory anti-revisit sentence became a hard Visited Viewpoints contract, and back_tracer is reinstated with inline tool descriptions. CE variant prompts (5 llmCall templates) are in-house text with upstream echoes (its planner mirrors PLANNER_PROMPT's instruction-decomposition idea); the 3-node navgpt.py nodeset carries the CE action-format constant.
2.6 The NavGPT-CE variant (derived design, not a port)
navgpt_ce.json moves the idea to continuous VLN-CE — a setting upstream never shipped, so no fidelity verdict applies; it is audited only doc↔artifact. Structure: a 4-LLM pipeline per step — vlm_caption (panorama composite → scene text, temp 0.2), obs_summary_llm, orchestrator_llm (chooses FORWARD/LEFT/RIGHT/STOP; system prompt = the navgpt__action_output_format skill constant), history_update_llm, plus navgpt__parse_action (keyword regex, fallback FORWARD) into env_habitat.step_discrete; budget 150; termination step.terminated → iter_out.stop, post-loop final_stop → evaluate. It echoes upstream's modular chain (history LLM) rather than the GPT-4 single-prompt line. The previously-flagged dead planner_llm node was removed 2026-06-20 (§3E), trimming the graph to 25 nodes / 32 edges.
3. Delta vs upstream — five buckets (MP3D mainline)
Each row carries an equivalence icon (orthogonal to the bucket letter — the letter says why it differs, the mark says whether behaviour changed): 🟢 equivalent (identical or semantically identical behaviour) · 🟡 near-equivalent (same intent; a mechanism, library, or edge case differs — output usually matches) · 🟠 divergent (behaviour can differ — by design or, rarely, a defect). A genuine defect is a 🟠 row in bucket E with a severity — never ❌/🔴.
A. Preserved verbatim
| Equiv. | Element | Upstream anchor | How preserved |
|---|---|---|---|
| 🟢 | ReAct prompt skeleton (framing, orientations, 3-m rule, tool format block, scaffold seed) | VLN_GPT4_PROMPT, planner_prompt.py:206 | Same paragraphs in orchestrator_llm.config.template (re-diffed 2026-06-20; the only deltas are the anti-revisit / back_tracer region — §3D) |
| 🟢 | Stop protocol Final Answer: Finished! | agent.py:44, :208 | parse_action stop detection (tolerant superset: also bare STOP / FINISHED) |
| 🟡 | Scratchpad mechanics: latest-step-full / earlier-steps-demoted, 7000-char tail cap, init_observation → history_0 swap | _construct_scratchpad agent.py:117–130, :46, get_full_inputs :141 | scratchpad_writer (_demote_last_full_obs, _MAX_SCRATCHPAD_LENGTH=7000 at :1292), init_observation node. 🟡: the live step also front-loads the turned_angle+summary line (upstream carries that for demoted steps only) — the next-step demote pass collapses both to the same end-state |
| 🟢 | Observation format: 8-compass directions, relative left/right angles, objects-in-3 m dict, navigable-per-sector | modify_heading_angles agent.py:251–327 | _format_observation_compass (:1784) — byte-identical on matched-unit inputs |
| 🟢 | Step budget = upstream max_iterations | parser.py:21 (default 10) → AgentExecutor | step_budget: 10 (corrected 15→10 this pass — the prior 15 was langchain's own default, a silent drift) |
| 🟢 | Offline-perception regime (captions + ≤3 m objects, no runtime vision) | ImageObservationsDB, preprocessing scripts + caches | paper_objects_cache / paper_captions_cache serve the paper's own released artifacts |
B. Forced by substrate (script → typed graph)
| Equiv. | Upstream | Port | Why forced |
|---|---|---|---|
| 🟢 | langchain agent object + in-process loop | Graph loop; scratchpad/init_observation as iterOut persist ports | Cross-iteration state must ride pivots |
| 🟢 | Offline preprocessing run before eval | Cache nodes keyed by (scan, viewpoint) inside the loop | Perception must be a wire-visible producer; same artifacts, node-shaped |
| 🟢 | One Python call stack | 11-node split (format / parse / scratchpad / caches / init) | AAS search granularity; each stage independently swappable |
| 🟢 | MatterSim driven inline | env_mp3d.step_waypoint over a wire (ADR-server-003 subprocess) | Env/method process split |
C. Forced by environment / cost
| Equiv. | Upstream | Port | Reason |
|---|---|---|---|
| 🟠 | Planner LLM = gpt-4 · temperature 0 (deterministic greedy) · no max_tokens cap | orchestrator_llm = gpt-5-mini · temperature 1.0 · max_tokens 2000 · stop: null | GPT-4 at this access/cost tier is reserved for the paper reference; gpt-5-mini is the repo-standard accessible substitute (every sibling graph — mapgpt / discussnav / smartway / aoplanner — uses it). temp 1.0 / max_tokens ≥ ~2000 / no stop are forced, not chosen: a gpt-5 profile silently returns empty otherwise (platform/project_gpt5_llmcall_params.md; every working gpt-5 graph drops stop). 🟠 because temp 0 → 1.0 trades determinism for sampling — trajectories can differ. User decision, this session (2026-06-20). |
| 🟡 | VG/BUTD Faster R-CNN for fresh scans | Online alternates approximate it (COCO 91-class or open-vocab) — shipped, not in the reference graph | Original detector stack is impractical to rehost; the paper caches make it moot for R2R scans (reference graph uses the released caches, so this is dormant) |
| 🟢 | Episode-long ReAct session in one API conversation | mode: single_turn per step with the scratchpad re-sent | llmCall is stateless; equivalent context by construction |
D. Intentional divergences
| Equiv. | Upstream | Port | Rationale (recorded where) |
|---|---|---|---|
| 🟠 | Advisory anti-revisit ("encouraged … avoiding revisiting by comparing previous Action Inputs") — no visited list rendered | Hard contract: a Visited Viewpoints line is rendered at the pad's tail every step; the prompt forbids picking listed IDs as an action_maker input | Recorded inline in scratchpad_writer (navgpt_mp3d_tools.py:1676–1684 — incl. why the cumulative list must survive the 7000-char truncation) and in the template text itself. 🟠: a hard ban changes trajectories vs the advisory original. |
| 🟠 | back_tracer is absent from the released single-action tool set (tools=[action_maker], agent.py:588); it lives only on the dead use_tool_chain path | back_tracer reinstated as a second named tool, inlined into the template ("Move back to any previously visited viewpoint … Use this when you realize you have wandered off-track") | Functionally a prompt-level label: parse_action extracts one viewpoint ID regardless of tool name, and step_waypoint teleports to any visited ID — mirroring upstream makeAction (env.py:70), which already jumps to any in-scan viewpoint. Recorded in the template + this audit. |
| 🟢 | — | step_waypoint extras (success/error/turned_angle) folded into the pad | Failed-move feedback the upstream sim handled implicitly (agent.py:399–404); declared in node ports |
E. Unexplained / defects
| Equiv. | # | Finding | Detail | Severity |
|---|---|---|---|---|
| 🟠 | E1 | Fixed 2026-06-20 — dead planner_llm node in navgpt_ce.json | The CE graph instantiated a planner_llm (instruction-decomposition prompt, fed by episode_info.instruction) whose response had no outgoing edge — one wasted LLM call per episode, no behavioural effect. Removed this pass (node + its incoming edge + its graph_state binding; CE graph 26→25 nodes, 33→32 edges). | Closed. Deletion was behaviour-preserving (the only output had no consumer). |
E bucket empty for the MP3D port. Probed cold from the upstream release this pass: the parser.py released config (single-action, action_maker-only, max_iterations=10) → mapped the live ReAct loop, not the dead use_tool_chain path; prompt re-diffed against VLN_GPT4_PROMPT — deltas localize entirely to the recorded anti-revisit / back_tracer region (§3D); modify_heading_angles formatting re-checked against _format_observation_compass (byte-identical on matched units); _construct_scratchpad demote + 7000-char cap + get_full_inputs init-swap matched (the swap is realized by the iterIn pivot wiring init_history_0 → iter_out.init_observation); step budget reconciled to max_iterations (15→10); stop-string set is a tolerant superset of Final Answer; paper caches are the released artifacts, not re-generated approximations. No equivalence unit test exists for either nodeset — eval parity is the only executable check, and none is currently on disk (§4); the next gpt-5-mini run is the evidence baseline.
4. Evaluation
| What | Number | Evidence |
|---|---|---|
| Paper-reported (NavGPT, GPT-4, R2R val_unseen) | SR ≈ 0.34 | Paper headline as commonly cited; not re-verified against the PDF this audit — treat as provisional until a closure run exists |
| Our runs | none on disk | Earlier navgpt batches predate the 2026-05-21 archive prune (archive_outputs --prune); outputs/eval_runs/ currently holds no navgpt run. The next run is the evidence baseline. |
Current status: both graphs sit in workspace/graphs/vln/unverified/ — rewired in the 2026-06-09 gym migration (bundle nodes → observe_* + step_waypoint/step_discrete; termination via iter_out.stop; evaluate post-loop via final_stop, the sound shape). The MP3D graph now drives gpt-5-mini (temp 1.0 / max_tokens 2000 / step_budget 10), so the next run is the evidence baseline — run /experiment:run navgpt-mp3d navgpt_mp3d episode_count=10. R2R val_unseen stratification applies to any future headline number (front-loaded easy scans — use the MapGPT72 cut for paper-comparable figures).
5. Usage
# load — method nodesets are local-mode; env_mp3d is server-mode (per-worker MatterSim)
POST /api/components/nodesets/navgpt_mp3d_tools/load
POST /api/components/nodesets/navgpt/load # CE variant nodes
POST /api/components/nodesets/env_mp3d/load?mode=server
# graphs
workspace/graphs/vln/unverified/navgpt_mp3d.json
workspace/graphs/vln/unverified/navgpt_ce.json
# batch eval — graph-only form: /experiment:run <profile> <graph_name> [key=value ...]
/experiment:run navgpt-mp3d navgpt_mp3d episode_count=10
6. What this nodeset is NOT
- Not NavGPT-2 / VLN-MME. This is the original AAAI-2024 NavGPT; later work by the same author is independent prior art, not covered here.
- Not a runtime-vision method (in the reference graph). The online BLIP-2/InstructBLIP/R-CNN nodes exist as alternates; the mainline reads the paper's offline caches, matching the paper's cost profile.
- Not a faithful port on the CE side.
navgpt_ce.jsonis an in-house continuous-env derivation with its own prompts — no upstream counterpart, no fidelity verdict. - Not env logic. Viewpoint-graph mechanics, teleport execution and metrics live in
env_mp3d/env_habitat.
7. Source files
| File | Purpose |
|---|---|
workspace/nodesets/method/navgpt_mp3d_tools.py | The MP3D port — 11 nodes, 2126 lines (caches, format, parse, scratchpad, online alternates) |
workspace/nodesets/method/navgpt.py | CE variant nodes — action-format skill, keyword parse, history formatter (270 lines) |
workspace/nodesets/env/env_mp3d/ | Env side — MatterSim graph, step_waypoint + navgpt extras |
workspace/graphs/vln/unverified/navgpt_mp3d.json | Reference MP3D graph (§2.1 authority; ReAct template in orchestrator_llm.config) |
workspace/graphs/vln/unverified/navgpt_ce.json | CE variant graph (4-LLM pipeline, §2.6) |
workspace/nodesets/_upstream/navgpt/fetch_upstream.sh | Clones upstream @ b3fc8a2 (MIT) into a gitignored upstream/ |