fix(world-model): scope WorldModel state per-task to prevent cross-task contamination
WorldModel routed all state through a single mutable _current_task_id, so concurrent tasks sharing one instance could corrupt each other's state. start_task() now returns a TaskWorldView handle scoped to that task; TaskRunner.run() threads it through as a local variable instead of reading self.world_model implicitly. Also extracts _start_world_view/ _record_step_result as reusable TaskRunner methods for composed runners. openspec: world-model capability, archived change world-model-runtime
This commit is contained in:
+37
-8
@@ -24,7 +24,7 @@ from storage.timeline import Timeline
|
||||
from tools.describe_screen import describe_screen
|
||||
from tools.screenshot import take_screenshot
|
||||
from world.config import WorldConfig, load_config as load_world_config
|
||||
from world.model import WorldModel
|
||||
from world.model import TaskWorldView, WorldModel
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -87,8 +87,9 @@ class TaskRunner:
|
||||
|
||||
def run(self, task: Task) -> Task:
|
||||
context = TaskContext(task_id=task.id, goal=task.goal)
|
||||
if self.world_model is not None:
|
||||
context.world = self.world_model.start_task(task.id)
|
||||
world_handle = self._start_world_view(task.id)
|
||||
if world_handle is not None:
|
||||
context.world = world_handle.state
|
||||
self._update_task(task, status="running")
|
||||
|
||||
for _ in range(self.config.max_steps):
|
||||
@@ -109,8 +110,7 @@ class TaskRunner:
|
||||
context=context,
|
||||
)
|
||||
context.add_step_result(result)
|
||||
self._update_world(context, scene, step, result)
|
||||
self._append_timeline(task, scene, step, result)
|
||||
self._record_step_result(world_handle, context, task, scene, step, result)
|
||||
if not result.success:
|
||||
self._update_task(
|
||||
task,
|
||||
@@ -128,6 +128,34 @@ class TaskRunner:
|
||||
)
|
||||
return task
|
||||
|
||||
def _start_world_view(self, task_id: str) -> TaskWorldView | None:
|
||||
"""Start (or resume) this task's isolated `WorldModel` handle, if enabled.
|
||||
|
||||
Shared by `run()` and by any other driver (e.g. `CollaborativeTaskRunner`)
|
||||
that composes this `TaskRunner` for its world-model bookkeeping.
|
||||
"""
|
||||
if self.world_model is None:
|
||||
return None
|
||||
return self.world_model.start_task(task_id)
|
||||
|
||||
def _record_step_result(
|
||||
self,
|
||||
world_handle: TaskWorldView | None,
|
||||
context: TaskContext,
|
||||
task: Task,
|
||||
scene: Scene,
|
||||
step: PlannedStep,
|
||||
result: object,
|
||||
) -> None:
|
||||
"""Record one executed step's bookkeeping: world-model observe + timeline append.
|
||||
|
||||
Shared by `run()` and by any other driver (e.g. `CollaborativeTaskRunner`)
|
||||
that executes steps outside of this class's own loop, so the two paths
|
||||
cannot drift apart on what gets recorded for a step.
|
||||
"""
|
||||
self._update_world(world_handle, context, scene, step, result)
|
||||
self._append_timeline(task, scene, step, result)
|
||||
|
||||
def _complete_task(self, task: Task) -> Task:
|
||||
self._update_task(task, status="completed", completed=True)
|
||||
self._notify_task_succeeded(task)
|
||||
@@ -194,20 +222,21 @@ class TaskRunner:
|
||||
|
||||
def _update_world(
|
||||
self,
|
||||
world_handle: TaskWorldView | None,
|
||||
context: TaskContext,
|
||||
scene: Scene,
|
||||
step: PlannedStep,
|
||||
result: object,
|
||||
) -> None:
|
||||
if self.world_model is None:
|
||||
if world_handle is None:
|
||||
return
|
||||
self.world_model.observe(
|
||||
world_handle.observe(
|
||||
scene,
|
||||
self._semantic_scene_from_result(result),
|
||||
step,
|
||||
result, # type: ignore[arg-type]
|
||||
)
|
||||
context.world = self.world_model.state
|
||||
context.world = world_handle.state
|
||||
|
||||
def _semantic_scene_from_result(self, result: object) -> SemanticScene | None:
|
||||
value = getattr(result, "result", None)
|
||||
|
||||
Reference in New Issue
Block a user