Files
agentic-mobile-control/openspec/changes/host-agent-dependency-supervisor/tasks.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

6.0 KiB

1. Config

  • 1.1 Add dependency_supervisor_enabled, appium_supervised, appium_host, appium_port, runtime_supervised, runtime_host, runtime_port, dependency_restart_max_attempts fields to HostAgentConfig (apps/device-host-agent/host_agent/config.py), reading from HOST_AGENT_DEPENDENCY_SUPERVISOR_ENABLED / HOST_AGENT_APPIUM_SUPERVISED / HOST_AGENT_APPIUM_HOST / HOST_AGENT_APPIUM_PORT / HOST_AGENT_RUNTIME_SUPERVISED / HOST_AGENT_RUNTIME_HOST / HOST_AGENT_RUNTIME_PORT / HOST_AGENT_DEPENDENCY_RESTART_MAX_ATTEMPTS, all defaulting per design.md Decision 6.
  • 1.2 Unit tests for the new config fields' defaults and env var parsing, following the existing pattern used for console_* fields in the config test module.

2. Health probes and adoption

  • 2.1 Implement a small health-check helper per dependency: TCP connect + Appium GET /status check, and TCP connect + Runtime API health endpoint check (reuse an existing HTTP client already available to the Host Agent rather than adding a new dependency).
  • 2.2 Implement the adopt-vs-spawn decision: probe before spawn, log and mark "adopted" on a passing health check, log a port-conflict error and skip on a listening-but-unhealthy port, proceed to spawn on no listener.
  • 2.3 Unit tests: adopt when healthy instance present, conflict-and-skip when unhealthy instance present, proceeds to spawn when nothing listening (mock the TCP/HTTP probe).

3. Process supervisor core

  • 3.1 Create apps/device-host-agent/host_agent/dependency_supervisor.py with a class managing zero or more supervised dependencies (Appium, Runtime), each described by: command, host/port, health-check callable, adopted-vs-spawned state.
  • 3.2 Implement spawn via subprocess.Popen for Appium (appium --address <host> --port <port>) and Runtime (uvicorn api.rest:create_app --factory --host <host> --port <port>), capturing stdout/stderr and forwarding to Host Agent logging tagged by dependency name.
  • 3.3 Implement post-spawn readiness wait: poll the health check until it passes or a startup timeout elapses, logging failure distinctly from a later crash.
  • 3.4 Implement crash-detection + capped exponential backoff restart loop (only for spawned, not adopted, processes), stopping permanently per dependency once dependency_restart_max_attempts is reached, per design.md Decision 4.
  • 3.5 Implement graceful stop: terminate only spawned child processes on supervisor shutdown; adopted processes are left untouched.
  • 3.6 Unit tests: spawn success, spawn failure (missing executable), crash-triggers-restart-with-backoff, restart-exhaustion-gives-up, adopted-process-never-restarted-or-killed, stop-terminates-only-spawned-children.

4. Wiring into HostAgentApplication

  • 4.1 In apps/device-host-agent/host_agent/app.py, construct and start the dependency supervisor (if dependency_supervisor_enabled) before the heartbeat loop's first connect_devices() pass.
  • 4.2 Stop the supervisor during HostAgentApplication shutdown/teardown alongside existing heartbeat/console teardown.
  • 4.3 Integration test covering: supervisor enabled with both dependencies off (no-op, unchanged existing behavior), supervisor enabled with only Appium supervised, start/stop ordering relative to heartbeat loop.

5. Documentation

  • 5.1 Update docs/MACOS_IPHONE_SETUP.md to document the new opt-in supervised mode (env vars, defaults, adopt-vs-spawn behavior, restart/backoff behavior) as an alternative to the existing manual multi-terminal flow, without removing the manual instructions.

  • 5.2 Update .env.example (if present) with the new HOST_AGENT_* variables, defaulted to off/disabled, matching existing .env.example conventions for other opt-in Host Agent features.

    Note: the committed .env.example is scoped exclusively to compose.deploy.yaml Cloud-side variables (enforced by tests/test_deployment_config.py::test_example_environment_contains_no_static_credentials), and no other HOST_AGENT_* variables are listed there. Documenting the new variables in docs/MACOS_IPHONE_SETUP.md §9 instead matches the existing convention used for all other Host Agent opt-in features (e.g. HOST_AGENT_CONSOLE_*, AI_PLANNER_*), so no .env.example change was made.

6. Validation

  • 6.1 Run full non-integration test suite (uv run --all-packages pytest -m "not integration") and confirm no regressions.

    Result: 503 passed, 2 failed, 44 deselected. Both failures (tests/test_deployment_config.py::test_deploy_compose_has_only_cloud_services_and_minimal_environment and ::test_example_environment_contains_no_static_credentials) are pre-existing, caused by unrelated commit 03c7c30 LLM_PROVIDER_ENC_KEY (in-flight database-llm-provider-management work that added CLOUD_LLM_PROVIDER_ENCRYPTION_KEY to .env.example and compose.deploy.yaml without updating this test's allowlist). Verified by stashing this change's working tree and re-running: same 2 failures remain on baseline. The new tests/test_dependency_supervisor.py (19 tests) and updated tests/test_config.py / tests/test_app.py (38 combined) all pass.

  • 6.2 Ruff check/format and compileall on changed files.

    ruff check and ruff format --check clean on apps/device-host-agent/host_agent/{config,dependency_supervisor,app}.py, apps/device-host-agent/tests/{test_config,test_dependency_supervisor,test_app}.py. python -m compileall -q clean on the same set.

  • 6.3 openspec validate --strict for this change.

    Result: Change 'host-agent-dependency-supervisor' is valid.

  • 6.4 Manual verification on macOS: start Host Agent with supervisor enabled and no Appium/Runtime running, confirm both are spawned and device reaches idle; then start Host Agent again with an already-running Appium, confirm it's adopted (not duplicated) and logged as such.

    Deferred. This session ran on Windows where the Appium/WebDriverAgent real-device workflow doesn't apply. To be executed on a macOS host per docs/MACOS_IPHONE_SETUP.md §9 once available.