Implementation verified on macOS (task 6.4 confirmed spawn + adoption behavior). Archives the change under openspec/changes/archive/2026-07-14-host-agent-dependency-supervisor/ and syncs the delta spec into a new main capability at openspec/specs/host-agent-dependency-supervisor/spec.md (5 baseline requirements covering opt-in default, adopt-don't-fight, spawn, bounded backoff restart, and lifecycle tied to Host Agent). openspec validate --strict passes on the synced spec. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
5.9 KiB
1. Config
- 1.1 Add
dependency_supervisor_enabled,appium_supervised,appium_host,appium_port,runtime_supervised,runtime_host,runtime_port,dependency_restart_max_attemptsfields toHostAgentConfig(apps/device-host-agent/host_agent/config.py), reading fromHOST_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 /statuscheck, 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.pywith 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.Popenfor 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_attemptsis 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 (ifdependency_supervisor_enabled) before the heartbeat loop's firstconnect_devices()pass. - 4.2 Stop the supervisor during
HostAgentApplicationshutdown/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.mdto 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 newHOST_AGENT_*variables, defaulted to off/disabled, matching existing.env.exampleconventions for other opt-in Host Agent features.Note: the committed
.env.exampleis scoped exclusively tocompose.deploy.yamlCloud-side variables (enforced bytests/test_deployment_config.py::test_example_environment_contains_no_static_credentials), and no otherHOST_AGENT_*variables are listed there. Documenting the new variables indocs/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.examplechange 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_environmentand::test_example_environment_contains_no_static_credentials) are pre-existing, caused by unrelated commit03c7c30 LLM_PROVIDER_ENC_KEY(in-flightdatabase-llm-provider-managementwork that addedCLOUD_LLM_PROVIDER_ENCRYPTION_KEYto.env.exampleandcompose.deploy.yamlwithout updating this test's allowlist). Verified by stashing this change's working tree and re-running: same 2 failures remain on baseline. The newtests/test_dependency_supervisor.py(19 tests) and updatedtests/test_config.py/tests/test_app.py(38 combined) all pass. -
6.2 Ruff check/format and
compileallon changed files.ruff checkandruff format --checkclean onapps/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 -qclean on the same set. -
6.3
openspec validate --strictfor 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.Verified by the user on a macOS host following
docs/MACOS_IPHONE_SETUP.md§9.