Default-enable AI Planner in Host Agent; propose cloud-planner-proxy
Tests / Test passed: 626

- Host Agent now defaults AI_PLANNER_ENABLED=true (opt-out via env),
  scoped to apps/device-host-agent/host_agent/execution.py only; the
  shared runtime.planner_config default (disabled) is unchanged.
- Add openspec proposal for cloud-planner-proxy: centralize LLM
  provider config/credentials on the Cloud Control Plane and let the
  Host Agent proxy AI Planner decisions through it instead of holding
  provider API keys locally. Proposal only, no implementation yet.
This commit is contained in:
2026-07-13 20:50:35 +08:00
parent 78ce788e2f
commit 1107ace89c
9 changed files with 537 additions and 5 deletions
@@ -0,0 +1,92 @@
## 1. Cloud API: planner configuration
- [ ] 1.1 Add Cloud API-side planner configuration (provider, model, timeout,
provider API keys) analogous to `runtime/planner_config.py`, loaded
from the Cloud API's own process environment (e.g.
`AI_PLANNER_PROVIDER`/`AI_PLANNER_MODEL`/`AI_PLANNER_TIMEOUT_SECONDS`,
`ANTHROPIC_API_KEY`/`OPENAI_API_KEY`), living in
`packages/cloud-platform/cloud` or `apps/cloud-api`.
- [ ] 1.2 Add `anthropic`/`openai` as explicit dependencies of the package
that hosts this configuration (confirm whether `packages/cloud-platform`
or `apps/cloud-api` is the right home, matching where the new endpoint
handler will live).
## 2. Cloud API: planner-decision internal endpoint
- [ ] 2.1 Add request/response Pydantic models to `cloud.internal_api.models`
for a planner-decision call: request carries `system_prompt`,
`user_prompt`, optional base64 screenshot, tool specs, timeout;
response carries resolved `tool_name`/`arguments` or a structured
error.
- [ ] 2.2 Add the internal route (e.g. `POST /internal/v1/planner/decide`)
to the existing internal router, reusing the current host-scoped
bearer auth dependency used by heartbeat/claim/renew/result -- no new
scope.
- [ ] 2.3 Implement the route handler by constructing
`runtime.tool_calling_client.AnthropicToolCallingClient` or
`OpenAIToolCallingClient` (per D1) from the Cloud API's own planner
configuration, calling `.decide(...)`, and translating
`ToolCallDecision`/`ToolCallUnavailable` into the response model.
- [ ] 2.4 Ensure the handler does not log or persist raw prompt text or
screenshot bytes; only metadata (host id, resolved tool name,
latency, error class) may be logged.
## 3. Host Agent: cloud-proxy transport
- [ ] 3.1 Add `AI_PLANNER_TRANSPORT` (`direct` default | `cloud`) to
`apps/device-host-agent/host_agent/config.py`.
- [ ] 3.2 Add a `request_planner_decision(...)` method to
`host_agent/client.py::HostAgentClient` that calls the new Cloud API
endpoint using the existing authenticated `httpx` session, importing
the new request/response models from `cloud.internal_api.models`.
- [ ] 3.3 Add `host_agent/cloud_planner_client.py::CloudProxyToolCallingClient`
implementing the `runtime.tool_calling_client.ToolCallingClient`
Protocol structurally (no import of `host_agent`/`cloud` from
`runtime`), wrapping `HostAgentClient.request_planner_decision(...)`
and raising `ToolCallUnavailable` on any failure (network error, auth
rejection, non-2xx, provider error, timeout) -- matching D7.
## 4. Host Agent: wiring
- [ ] 4.1 Update `apps/device-host-agent/host_agent/execution.py`'s
`_host_agent_planner_config()`/`create_task_runner()` so that when
`AI_PLANNER_TRANSPORT=cloud`, the constructed `TaskRunner`'s
`AIPlanner` is built with a `CloudProxyToolCallingClient` instead of
the default local provider client, while leaving the existing
default-enabled/direct-transport behavior unchanged when
`AI_PLANNER_TRANSPORT` is unset or `direct`.
- [ ] 4.2 Confirm `apps/device-host-agent/tests/test_execution.py::test_runtime_owned_packages_do_not_import_host_or_cloud_concerns`-style
boundary checks still pass with the new module in place.
## 5. Tests
- [ ] 5.1 Unit tests for Cloud API planner configuration loading
(defaults, provider/model/timeout parsing) mirroring
`tests/test_planner_config.py`.
- [ ] 5.2 Unit tests for the planner-decision endpoint: authorized request
resolves a decision, unauthenticated/foreign-host request is
rejected, provider failure returns a structured error without
crashing.
- [ ] 5.3 Unit tests for `CloudProxyToolCallingClient`: successful decision
round-trip, and each failure mode raises `ToolCallUnavailable`.
- [ ] 5.4 Unit tests for Host Agent wiring: `AI_PLANNER_TRANSPORT=cloud`
constructs an `AIPlanner` using `CloudProxyToolCallingClient`;
`AI_PLANNER_TRANSPORT` unset or `direct` preserves existing
direct-to-provider construction (no regression to the
already-implemented default-enabled behavior).
- [ ] 5.5 Full non-integration suite (`uv run --all-packages pytest -m
"not integration"`) passes with no regressions.
## 6. Documentation
- [ ] 6.1 Update `docs/CLOUD_DEPLOYMENT.md`'s Runtime AI Planner section
with the `cloud` transport configuration path, its trade-offs
(latency, cloud-availability coupling, expanded data path for
screenshots/prompts), and the credential split (Cloud API holds
provider keys for `cloud` transport; Host Agent holds them for
`direct` transport).
## 7. Validation
- [ ] 7.1 `openspec validate --strict` passes for this change.
- [ ] 7.2 Ruff check/format and `compileall` pass for all touched packages.