Files
agentic-mobile-control/openspec/changes/host-agent-dependency-supervisor/proposal.md
T
q792602257andClaude Opus 4.6 75879c8a52
Tests / Test failed: 2, passed: 691
feat(host-agent): add optional Appium/Runtime supervisor
Adds an opt-in dependency supervisor inside the Host Agent that probes,
spawns, and restarts the two local processes the macOS single-machine
real-device workflow depends on: the Appium server (gates Driver.connect())
and the local Runtime API (local inspection). Default-off; gated by
HOST_AGENT_DEPENDENCY_SUPERVISOR_ENABLED plus per-dependency *_SUPERVISED
flags.

Mitigates the live-incident failure mode where forgetting to start Appium
silently keeps devices offline and tasks queued forever with no error
surfaced in Host Agent logs.

Behavior (per openspec change):
- Adopt-don't-fight: probe (TCP + dependency-specific HTTP health check)
  before spawn. Healthy listener → adopted (never killed/restarted).
  Unhealthy listener → port-conflict error, skip. No listener → spawn.
- Only supervisor-spawned processes are restarted on crash, with capped
  exponential backoff (1s/2s/4s/8s, capped at 30s) and a per-process-lifetime
  attempt ceiling (HOST_AGENT_DEPENDENCY_RESTART_MAX_ATTEMPTS, default 5).
- Spawn failures (e.g. missing executable) logged distinctly from crashes.
- Graceful stop terminates only spawned children; adopted processes untouched.
- Supervisor starts before the heartbeat loop's first connect_devices() pass
  and stops alongside existing heartbeat/console teardown.

Validation: ruff check + format clean, compileall clean, openspec validate
--strict valid. Non-integration suite 503 passed / 44 deselected / 2 failed
(both failures pre-existing from unrelated 03c7c30 LLM_PROVIDER_ENC_KEY;
verified by stashing this change). macOS real-device manual verification
(task 6.4) deferred to a macOS host.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-14 09:28:57 +08:00

29 lines
4.7 KiB
Markdown

## Why
On the macOS native real-device setup (`docs/MACOS_IPHONE_SETUP.md`), a device only reports `idle`/online to the Cloud Control Plane if `DeviceManager.connect()` (`device/manager.py:68-108`) can actually establish an Appium session — but the Host Agent process (`apps/device-host-agent/host_agent/app.py`) never starts Appium itself; the operator must run `appium --address 127.0.0.1 --port 4723` in a separate terminal before starting `device-host-agent`, and separately run the local Runtime API (`uvicorn api.rest:create_app --factory ...`) if they want to inspect device/task state during local debugging. Forgetting (or losing) either process is invisible from the Host Agent's own output: heartbeats keep succeeding, so the Host looks "connected," while the device silently stays `offline` and every assigned task stays queued forever with no error. This was diagnosed live from exactly that symptom. Having the Host Agent supervise these known local dependencies for the single-machine dev/real-device workflow removes an easy-to-miss manual step and makes the failure visible instead of silent.
## What Changes
- `HostAgentApplication` gains an optional local dependency supervisor that, when enabled, starts and monitors two external processes alongside the existing heartbeat/claim loop:
- **Appium server** (`appium --address <host> --port <port>`), whose reachability already gates real device `connect()` calls.
- **Local Runtime API** (`uvicorn api.rest:create_app --factory --host <host> --port <port>`), used for local inspection/debugging of device and task state.
- Before spawning either process, the supervisor probes the configured address: if something is already listening and answers the expected health check, it adopts the existing instance (log-only) instead of spawning a duplicate or rebinding the port. It only spawns a subprocess when the port is free.
- Crash handling: if a supervised subprocess it spawned exits unexpectedly, the supervisor restarts it with capped exponential backoff and a maximum retry count per run; it does not touch a process it did not spawn (an adopted, externally-managed instance is never restarted or killed by the Host Agent).
- New `HostAgentConfig` fields to control this, all opt-in and disabled by default (see design.md for the specific default-off rationale): enable/disable the supervisor as a whole, enable/disable each of the two dependencies independently, and host/port for each (defaulting to the existing documented conventions — Appium `127.0.0.1:4723`, Runtime `127.0.0.1:8000`).
- Documentation update to `docs/MACOS_IPHONE_SETUP.md` describing the new opt-in supervised mode as an alternative to the existing manual multi-terminal flow (the manual flow remains fully supported and is still what's documented as the default path).
- Explicitly out of scope: no change to WDA's own lifecycle (Appium continues to own WDA session management entirely unchanged), no Docker Compose changes (`compose.yaml`/`compose.deploy.yaml` are unaffected — this is a native-process-only capability), no change to the outbound cloud protocol.
## Capabilities
### New Capabilities
- `host-agent-dependency-supervisor`: optional, opt-in local process supervision inside the Host Agent for the Appium server and the local Runtime API — port/adoption probing, spawn, health-checked readiness, and rate-limited crash-restart, scoped to the macOS native single-machine real-device workflow.
### Modified Capabilities
(none — `host-agent-protocol` outbound behavior is unchanged; `driver-registry`/`device-pool` behavior for how a `Driver.connect()` reaches Appium is unchanged, this change only affects whether Appium happens to already be running when that connect attempt occurs)
## Impact
- Affected code: `apps/device-host-agent/host_agent/` (new supervisor module, `app.py` wiring to start/stop it alongside the heartbeat/claim loop, `config.py` new fields), `apps/device-host-agent/pyproject.toml` (no new runtime dependency expected — spawning uses stdlib `subprocess`; Runtime API is already invoked via its existing `uvicorn`/`api.rest` entry point).
- Not affected: `cloud.*`, `apps/cloud-api`, `compose.yaml`, `compose.deploy.yaml`, `driver/wda_driver.py` / `driver/android_driver.py` connection logic, WDA's own session lifecycle (still fully owned by Appium).
- Operational impact: opt-in only — a Host Agent with the supervisor disabled (the default) behaves exactly as it does today. When enabled, the Host Agent's own process lifecycle now indirectly affects two more local processes, which changes what "the Host Agent crashed" or "the Host Agent's log" means for local troubleshooting (see design.md for how adoption-vs-spawn is surfaced in logs to keep this legible).