fix(smolvla2-runtime): allow task switching mid-run via 'task:' prefix

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: <new 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 <say> in one trained call.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Pepijn
2026-05-12 17:24:16 +02:00
parent a648da0455
commit d528078aca
@@ -776,8 +776,28 @@ def _run_autonomous(
lower = line.lower()
if lower in {"stop", "quit", "exit"}:
break
# ``task: <text>`` 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 + ``<say>`` 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(