Files
agentic-mobile-control/openspec/changes/ai-planner-runtime/proposal.md
T
q792602257andClaude Sonnet 5 61ff3b425d feat(agent-runtime): add LLM-driven AI Planner with dual-provider tool calling
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>
2026-07-12 13:48:50 +08:00

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, a Planner implementation that calls an LLM with native tool/function calling once per plan() invocation, translating the model's single chosen tool call into 0 or 1 PlannedStep. Single-step, ReAct-style: TaskRunner.run() already re-observes and re-plans every iteration, so AIPlanner never 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_app for action, plus an explicit finish_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 AI Planner (only that Planner) may additionally receive the current step's raw screenshot bytes alongside Scene, to support vision-grounded action grounding (precise tap/swipe coordinates). No other layer or LLM consumer gains access to raw screenshot bytes; Scene remains 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) defaulting AI_PLANNER_ENABLED=False, matching the existing enable/disable convention used by every other LLM-backed capability (semantic-scene, skill-learning). runtime/task.py::TaskRunner builds an AIPlanner by default only when enabled; otherwise its existing stub Planner behavior 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 a status="failed" task with a failure_reason, instead of propagating uncaught (previously harmless only because the stub Planner never raised).
  • BREAKING: none. Default-disabled; when disabled, TaskRunner's constructed Planner and 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 into openspec/specs/ (a pre-existing gap from apex-agent-mvp, out of scope for this change); this change's delta spec below uses ## ADDED Requirements against that as-yet-unarchived baseline, the same way semantic-scene-runtime and world-model-runtime each layered their own delta on top of it without attempting to backfill it (see design.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 (base Planner.plan() gains an optional screenshot parameter, stub behavior unchanged), runtime/task.py (TaskRunner builds an AIPlanner when enabled; generalized its existing _planner_accepts_world() reflection helper to also conditionally inject screenshot; wrapped the observe+plan step in try/except), docs/CONSTITUTION.md (Perception Boundary amendment described above).
  • No change to api/rest.pyTaskRunner()'s existing zero-argument construction picks up the new behavior automatically once AI_PLANNER_ENABLED=true is set in the environment; no new request parameters, no MCP surface change (api/mcp.py is a separate, curated tool-handler surface unaffected by this change).
  • No new dependencies: pyproject.toml already declares both anthropic and openai SDKs (the former added for semantic-scene-runtime); this change is the first to actually construct an OpenAI client.
  • Out of scope: no wait/no-op tool (v1.1 candidate, see design.md); no dynamic tool subset selection; no provider/model choice exposed as an API request parameter; no automatic retry of failed LLM calls inside AIPlanner (transient failures surface as a failed task; tool-execution retry remains solely Executor's concern, unchanged by this change); no backfill of the missing openspec/specs/agent-runtime/ base spec.