From d528078aca4a22c2a26c12f3b0957c5f3a39f8a3 Mon Sep 17 00:00:00 2001 From: Pepijn Date: Tue, 12 May 2026 17:24:16 +0200 Subject: [PATCH] fix(smolvla2-runtime): allow task switching mid-run via 'task:' prefix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both stdin handlers (autonomous mode and rich REPL) gated 'task:' to 'only if no task is set yet' — once the initial task existed, typing 'task: ' silently fell through to the interjection branch. Make 'task:' always override the active task and clear stale plan/memory/subtask so the next high-level pass regenerates context from scratch for the new task. For rephrasings within the same task, the interjection path (user_interjection_response recipe) is still the right channel — it refreshes the plan and emits a paired in one trained call. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../scripts/lerobot_smolvla2_runtime.py | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/src/lerobot/scripts/lerobot_smolvla2_runtime.py b/src/lerobot/scripts/lerobot_smolvla2_runtime.py index eadb1e2c2..a47929208 100644 --- a/src/lerobot/scripts/lerobot_smolvla2_runtime.py +++ b/src/lerobot/scripts/lerobot_smolvla2_runtime.py @@ -776,8 +776,28 @@ def _run_autonomous( lower = line.lower() if lower in {"stop", "quit", "exit"}: break + # ``task: `` always overrides the active task — both + # at first set and to switch tasks mid-run. Without the + # prefix and with a task already set, an utterance becomes + # either a VQA query (ends in ``?``) or an interjection + # (the user_interjection_response recipe — generates a + # fresh plan + ```` paired with the new instruction). + # Typing a rephrasing of the current task as an + # interjection is the trained way to redirect without + # resetting the high-level plan from scratch. + if lower.startswith("task:"): + new_task = line[5:].strip() + if new_task: + runtime.set_task(new_task) + # Clear stale plan/memory/subtask so the next + # high-level pass regenerates from the new task + # rather than carrying over context from the old. + runtime.state["current_plan"] = None + runtime.state["current_memory"] = None + runtime.state["current_subtask"] = None + continue if not runtime.state.get("task"): - runtime.set_task(line[5:].strip() if lower.startswith("task:") else line) + runtime.set_task(line) continue if lower.endswith("?"): runtime.state["recent_vqa_query"] = line @@ -1083,9 +1103,15 @@ def _run_repl(runtime: Any, *, initial_task: str | None, max_ticks: int | None) # Inject the user input as the right kind of event, # then run a single pipeline tick to consume it. - if not runtime.state.get("task"): - task = line[5:].strip() if lower.startswith("task:") else line - runtime.set_task(task) + if lower.startswith("task:"): + new_task = line[5:].strip() + if new_task: + runtime.set_task(new_task) + runtime.state["current_plan"] = None + runtime.state["current_memory"] = None + runtime.state["current_subtask"] = None + elif not runtime.state.get("task"): + runtime.set_task(line) elif lower.endswith("?"): runtime.state["recent_vqa_query"] = line runtime.state.setdefault("events_this_tick", []).append(