Files
agentic-mobile-control/openspec/changes/archive/2026-07-06-world-model-runtime/tasks.md
T
2026-07-06 23:52:53 +08:00

5.9 KiB

1. Package scaffolding

  • 1.1 Create the world/ package (__init__.py, models.py, model.py, config.py)
  • 1.2 Add world* to [tool.setuptools.packages.find].include in pyproject.toml (no new third-party dependency)
  • 1.3 Add World Runtime configuration in world/config.py: an enabled/disabled flag (default enabled) and a history-size bound (default 10), sourced from environment/config in one place world/ reads from
  • 1.4 Extend the project's smoke test (that imports every package) to import world

2. WorldState data model (capability: world-model)

  • 2.1 Implement world/models.py: WorldEvent (scene_summary: SemanticScene | Scene, action: str, success: bool, timestamp: datetime) and WorldState (current_app: str | None, current_page: str | None, variables: dict[str, Any], history: deque[WorldEvent]) dataclasses, with to_dict() mirroring the style of core/models.py (for future inspection/debugging use, not persistence in this change)
  • 2.2 Implement WorldState.history as a collections.deque(maxlen=<configured bound>) so oldest entries are evicted automatically once the bound is exceeded
  • 2.3 Write unit tests for WorldState/WorldEvent construction and for history's bounded-eviction behavior (append past the configured maxlen and assert the oldest entry is gone, length stays at the bound)

3. WorldModel update hook (capability: world-model)

  • 3.1 Implement world/model.py: WorldModel owning one WorldState per task, with observe(scene: Scene, semantic_scene: SemanticScene | None, step: PlannedStep, result: StepResult) -> None as the single update entry point
  • 3.2 Implement the current_page update rule: set current_page = semantic_scene.page when semantic_scene is not None and its page is non-empty; leave unchanged otherwise
  • 3.3 Implement the current_app update rule: on a successful StepResult for step.action in {"launch_app", "terminate_app"}, set/clear current_app from step.args (e.g. bundle_id/app_id); leave unchanged for any other action or a failed result
  • 3.4 Implement the variables update rule: merge step.args["remember"] (a dict) into WorldState.variables when present; leave variables unchanged when absent
  • 3.5 Implement the history update rule: append one WorldEvent per call to observe(), using semantic_scene when available and falling back to scene otherwise
  • 3.6 Make every update rule defensive: missing/malformed expected fields (e.g. no bundle_id on a launch_app step, non-dict remember value) are logged and skipped, never raised, so observe() never raises for any input shape
  • 3.7 Write unit tests for WorldModel.observe() covering: page update from SemanticScene, page unchanged when semantic_scene is None, app update on successful launch_app, app unchanged on failed launch_app or unrelated actions, variables merge via remember, variables unchanged without remember, and history append/eviction across repeated observe() calls

4. TaskContext and TaskRunner integration (capability: world-model)

  • 4.1 Add world: WorldState | None = None field to TaskContext in runtime/context.py
  • 4.2 Add an optional world_model: WorldModel | None = None constructor argument to TaskRunner in runtime/task.py; when None and World Runtime is enabled in config, construct a default WorldModel internally; when World Runtime is disabled in config, leave context.world as None and skip the update hook entirely
  • 4.3 In TaskRunner.run()'s step loop, call world_model.observe(scene, semantic_scene, step, result) once per executed step, immediately after the existing context.add_step_result(result) line, and refresh context.world from the model's current WorldState
  • 4.4 Confirm TaskRunner.run()'s existing control flow (retry/failure/max-steps handling) is unaffected by the new hook call — the hook must never change whether a step is treated as success/failure
  • 4.5 Write unit tests for TaskRunner covering: context.world populated after a step when World Runtime is enabled (default), context.world remaining None when explicitly disabled via config, and a task run completing normally (unchanged pass/fail outcome) whether World Runtime is enabled or disabled

5. Planner integration (capability: world-model)

  • 5.1 Add an optional world: WorldState | None = None keyword argument to Planner.plan() in runtime/planner.py; the existing stub Planner implementation accepts but does not use it
  • 5.2 Update TaskRunner.run()'s call to self.planner.plan(...) to pass world=context.world
  • 5.3 Write a unit test asserting the existing stub Planner.plan() call sites (with and without a world argument) both continue to return the same steps as before this change
  • 5.4 Write a unit test asserting TaskRunner passes the current context.world into Planner.plan()'s world argument by the second step of a multi-step task (using a custom test Planner subclass that records the world value it was given)

6. End-to-end validation

  • 6.1 Write an end-to-end test running a multi-step task through TaskRunner with a mocked Driver/Scene/SemanticScene sequence, asserting WorldState.current_app/current_page/history reflect the expected values after each step
  • 6.2 Write an end-to-end test confirming World Runtime tracking failure modes (missing SemanticScene, missing expected step args) never fail or interrupt the task loop, only leave the corresponding WorldState field unchanged
  • 6.3 Confirm World Runtime tracking is enabled by default after applying this change, and that disabling it via config fully restores pre-this-change TaskContext/Planner.plan() call behavior (no world state populated or passed)
  • 6.4 Run the full test suite (pytest) and confirm no existing test in tests/ needed a behavior change, only additive new tests