Replaces the stub Planner's fixed describe_screen/[] behavior with a real decision-maker: AIPlanner uses native tool/function calling (Anthropic or OpenAI, pluggable via AI_PLANNER_PROVIDER) to select exactly one grounded action per turn, with an explicit finish_task(success, reason) tool for completion/failure instead of an ambiguous "no tool call" signal. Default disabled (AI_PLANNER_ENABLED=false) and additive; TaskRunner falls back to the existing stub Planner unchanged when disabled. Amends CONSTITUTION.md's Perception Boundary with one narrow exception: only the AI Planner may receive the current step's raw screenshot bytes alongside Scene, for vision-grounded coordinate grounding. Also fixes a latent gap in TaskRunner.run(): observe/plan exceptions are now caught per iteration and turned into a failed task with a failure_reason, instead of propagating uncaught. openspec change: ai-planner-runtime. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
6.1 KiB
Why
runtime/planner.py::Planner — the Planner half of Milestone 3 (Agent
Runtime)'s agent-runtime capability, implemented by apex-agent-mvp and
still the only Planner in the codebase — is a stub: it always returns exactly
one hardcoded describe_screen step on a task's first call, then [] on
every call after any step has executed, regardless of the goal or the current
Scene. TaskRunner's Observe→Plan→Act→Observe loop, Executor's
retry/backoff, TaskContext's per-task memory, and the already-implemented
semantic-scene and world-model capabilities are all real and wired
end-to-end — every later milestone (Task Memory, Semantic Scene, World Model,
Skill Learning, Workflow Orchestration, Multi-Agent Runtime) has been built on
top of, or alongside, this stub without ever replacing it. Nothing in the
runtime actually decides what to do. This change gives the runtime its first
real decision-maker: an LLM-driven Planner that uses native tool/function
calling to choose exactly one grounded action per turn, completing what
Milestone 3 always intended the Planner role to be.
What Changes
- Add
runtime/ai_planner.py::AIPlanner, aPlannerimplementation that calls an LLM with native tool/function calling once perplan()invocation, translating the model's single chosen tool call into 0 or 1PlannedStep. Single-step, ReAct-style:TaskRunner.run()already re-observes and re-plans every iteration, soAIPlannernever attempts multi-step lookahead. - Add a pluggable dual-provider LLM abstraction
(
runtime/tool_calling_client.py): both Anthropic native tool use and OpenAI function calling are supported, selected via configuration (AI_PLANNER_PROVIDER), each forced to return exactly one tool call per turn so a response always resolves to a single, unambiguous decision. - Add a fixed, six-tool tool-face (
runtime/tool_specs.py):tap,swipe,input_text,launch_app,terminate_appfor action, plus an explicitfinish_task(success, reason)control tool the model calls to end the task (on success or failure) instead of relying on an ambiguous "no tool call" signal. - Amend the Perception Boundary invariant (
docs/CONSTITUTION.md) with one narrow, explicit exception: the AIPlanner(only that Planner) may additionally receive the current step's raw screenshot bytes alongsideScene, to support vision-grounded action grounding (precise tap/swipe coordinates). No other layer or LLM consumer gains access to raw screenshot bytes;Sceneremains the only perception artifact everywhere else. - Add configuration (
runtime/planner_config.py:AI_PLANNER_ENABLED,AI_PLANNER_PROVIDER,AI_PLANNER_MODEL,AI_PLANNER_TIMEOUT_SECONDS) defaultingAI_PLANNER_ENABLED=False, matching the existing enable/disable convention used by every other LLM-backed capability (semantic-scene,skill-learning).runtime/task.py::TaskRunnerbuilds anAIPlannerby default only when enabled; otherwise its existing stubPlannerbehavior is unchanged. - Fix a latent correctness gap in
TaskRunner.run()'s loop, exposed by giving the Planner a real chance to fail: observe/plan exceptions are now caught per iteration and turned into astatus="failed"task with afailure_reason, instead of propagating uncaught (previously harmless only because the stub Planner never raised). - BREAKING: none. Default-disabled; when disabled,
TaskRunner's constructedPlannerand control flow are unchanged from before this change.
Capabilities
New Capabilities
(none — this change fulfills the Planner role already scoped by Milestone
3's agent-runtime capability; see Impact for why no new capability is
declared)
Modified Capabilities
agent-runtime: the Planner requirement ("given a goal and the current Scene, produces steps") gains a real, LLM-backed implementation with native tool calling, a defined single-action-per-turn contract, an explicit task-completion/failure signal (finish_task), and — as a Planner-only exception to the Perception Boundary — optional access to the current step's screenshot.agent-runtime's base spec was never archived intoopenspec/specs/(a pre-existing gap fromapex-agent-mvp, out of scope for this change); this change's delta spec below uses## ADDED Requirementsagainst that as-yet-unarchived baseline, the same waysemantic-scene-runtimeandworld-model-runtimeeach layered their own delta on top of it without attempting to backfill it (seedesign.md).
Impact
- New files:
runtime/planner_config.py,runtime/tool_specs.py,runtime/tool_calling_client.py,runtime/planner_prompts.py,runtime/ai_planner.py. - Modified:
runtime/planner.py(basePlanner.plan()gains an optionalscreenshotparameter, stub behavior unchanged),runtime/task.py(TaskRunnerbuilds anAIPlannerwhen enabled; generalized its existing_planner_accepts_world()reflection helper to also conditionally injectscreenshot; wrapped the observe+plan step in try/except),docs/CONSTITUTION.md(Perception Boundary amendment described above). - No change to
api/rest.py—TaskRunner()'s existing zero-argument construction picks up the new behavior automatically onceAI_PLANNER_ENABLED=trueis set in the environment; no new request parameters, no MCP surface change (api/mcp.pyis a separate, curated tool-handler surface unaffected by this change). - No new dependencies:
pyproject.tomlalready declares bothanthropicandopenaiSDKs (the former added forsemantic-scene-runtime); this change is the first to actually construct an OpenAI client. - Out of scope: no
wait/no-op tool (v1.1 candidate, seedesign.md); no dynamic tool subset selection; no provider/model choice exposed as an API request parameter; no automatic retry of failed LLM calls insideAIPlanner(transient failures surface as a failed task; tool-execution retry remains solelyExecutor's concern, unchanged by this change); no backfill of the missingopenspec/specs/agent-runtime/base spec.