6.2 KiB
Why
agent-runtime's TaskRunner (from apex-agent-mvp, code-complete but unapplied) re-derives everything about a task from scratch every step: TaskContext accumulates a flat list of Scenes and StepResults, but nothing in the loop distills "what app am I in," "what page am I on," "am I already logged in," or "did I already navigate into the chat with Zhang San" into a queryable form. Even with semantic-scene-runtime's per-step SemanticScene (page identity, intents, widget purposes), that artifact is deliberately ephemeral and same-step only — it is discarded, not accumulated, so a real Planner (a later milestone) still cannot ask "have I already done this" without re-scanning raw scene/step history itself. This change adds a World Runtime: a WorldState that persists across a task's steps (current app, current page, a small variables dict, and a bounded history of recent semantic scenes/actions), updated incrementally by a hook in TaskRunner's step loop after each executed step, and exposed read-only to the Planner alongside the current SemanticScene so plans can skip redundant navigation or re-discovery. This is Milestone 6 (World) of the device-agnostic runtime roadmap established by device-agent-runtime-foundation, sitting directly on top of Milestone 5's semantic-scene capability.
What Changes
- Add a new
world/package that defines aWorldStatedataclass (current_app: str | None,current_page: str | None,variables: dict[str, Any], a boundedhistory: deque[WorldEvent]of recent(semantic_scene | scene, action)pairs) and aWorldModelthat owns oneWorldStateper task and knows how to update it. - Introduce an update hook (
WorldModel.observe(scene, semantic_scene, step, result)) called once per executed step fromruntime/task.py'sTaskRunner.run()loop, aftercontext.add_step_result(result), soWorldStateis derived incrementally from exactly the same per-step data the timeline already records — no new I/O, no new LLM call in this change. - Define the update rule set as a small, deterministic, rule-based derivation (not a second LLM call): app/page fields are refreshed from the current
SemanticScene.page(falling back to a heuristic derived from the rawScene/lastlaunch_appaction when semantic enrichment is disabled or unavailable),variablesare updated only via an explicitPlannedStep.args["remember"]convention a Planner can opt into, andhistoryis a fixed-size ring buffer (bounded, oldest evicted first) soWorldStatecannot grow unboundedly across a long-running task. - Expose
WorldStateread-only to the Planner: extendPlanner.plan()'s call signature with an optionalworld: WorldState | Nonekeyword argument (defaultNone, so the existing stubPlannerand any test constructingPlannedSteps directly keep working unmodified) that a future LLM-driven Planner (not built in this change) can read to decide "already there, skip this step." - Add
TaskContext.world(aWorldState | Nonefield, populated byTaskRunnerwhen aWorldModelis configured) so a single object continues to carry all per-run state the Planner/Executor need, matchingagent-runtime's existing "task context/memory available during a run" requirement instead of introducing a second parallel context object. - Add configuration to enable/disable World Runtime tracking globally (default enabled, since this is a pure derivation over data the loop already produces — unlike
semantic-scene's LLM call, there is no cost/latency reason to default it off) and to size the bounded history (default a small fixed window, e.g. 10 events). - BREAKING: none.
WorldState/WorldModelare additive;TaskContext.worlddefaults toNonewhen noWorldModelis configured,Planner.plan()'s newworldkwarg defaults toNone, andTaskRunner's constructor accepts an optionalworld_modelwithNonepreserving today's behavior exactly.
Capabilities
New Capabilities
world-model: AWorldStatestore (current app, current page, a variables dict, and a bounded history of recent semantic-scene/action pairs) for a single task, updated by a hook in the agent runtime's step loop after each executed step, and exposed read-only to the Planner as additional context alongside the currentSemanticScene, so plans can skip redundant navigation or re-discovery.
Modified Capabilities
(none — agent-runtime's Observe→Think→Act→Observe loop shape and semantic-scene's SemanticScene output are read-only inputs to this change; neither capability's existing requirements are altered. agent-runtime's "task context/memory available during a run" requirement is extended in spirit — TaskContext gains a world field — but this change does not itself modify agent-runtime's spec deltas since openspec/specs/ has no applied baseline for it yet; see Impact.)
Impact
- New package:
world/—models.py(WorldState,WorldEventdataclasses),model.py(WorldModel, the per-task owner +observe()update hook + rule-based derivation logic),config.py(enable/disable + history-size settings). - Modified:
runtime/context.py(TaskContextgains aworld: WorldState | None = Nonefield);runtime/task.py(TaskRunnergains an optionalworld_model: WorldModel | Noneconstructor argument, callsworld_model.observe(...)once per executed step inside the existing loop, and passescontext.worldintoself.planner.plan(...));runtime/planner.py(Planner.plan()gains an optionalworld: WorldState | None = Nonekeyword argument, unused by today's stubPlannerbut available to a future LLM-driven Planner). - No change to
core/models.py'sScene/Task/Step,semantic/'sSemanticSceneshape or enrichment logic,storage/timeline.py's persisted format, or any existing tool signature —WorldStateis derived in-memory per task run and is not persisted by this change (seedesign.mdOpen Questions for whether a later change should persist it). - Out of scope: no cross-task or cross-device world sharing (single task's world only, discarded when the task ends); no UI for viewing/editing world state (that is
web-console's domain, not touched here); no change toskill-catalog-subscription's orweb-console's pending capabilities.