From ab538513b03febd9ef27eb5677903d19d73f75b2 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Sun, 12 Jul 2026 18:50:21 +0800 Subject: [PATCH] feat(host-agent): compose execution factories --- .../device-host-agent/host_agent/execution.py | 48 +++++++++++++++++++ .../device-host-agent/tests/test_execution.py | 36 ++++++++++++++ .../cloud-control-plane-integration/tasks.md | 2 +- 3 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 apps/device-host-agent/host_agent/execution.py create mode 100644 apps/device-host-agent/tests/test_execution.py diff --git a/apps/device-host-agent/host_agent/execution.py b/apps/device-host-agent/host_agent/execution.py new file mode 100644 index 0000000..da0ee73 --- /dev/null +++ b/apps/device-host-agent/host_agent/execution.py @@ -0,0 +1,48 @@ +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass + +from device.manager import DeviceManager +from runtime.executor import Executor, default_tool_registry +from runtime.task import TaskRunner +from storage.task_metadata import TaskMetadataStore +from storage.timeline import Timeline +from workflow.runner import WorkflowRunner +from workflow.store import WorkflowStore + + +@dataclass(frozen=True) +class ExecutionFactories: + task_runner_factory: Callable[[], TaskRunner] + workflow_runner_factory: Callable[[], WorkflowRunner] + workflow_store: WorkflowStore + + +def create_execution_factories( + manager: DeviceManager, + *, + workflow_store: WorkflowStore | None = None, + metadata_store: TaskMetadataStore | None = None, + timeline: Timeline | None = None, +) -> ExecutionFactories: + shared_workflow_store = workflow_store or WorkflowStore() + + def create_task_runner() -> TaskRunner: + return TaskRunner( + executor=Executor(tools=default_tool_registry(manager=manager)), + metadata_store=metadata_store, + timeline=timeline, + ) + + def create_workflow_runner() -> WorkflowRunner: + return WorkflowRunner( + shared_workflow_store, + task_runner_factory=create_task_runner, + ) + + return ExecutionFactories( + task_runner_factory=create_task_runner, + workflow_runner_factory=create_workflow_runner, + workflow_store=shared_workflow_store, + ) diff --git a/apps/device-host-agent/tests/test_execution.py b/apps/device-host-agent/tests/test_execution.py new file mode 100644 index 0000000..0d39ffd --- /dev/null +++ b/apps/device-host-agent/tests/test_execution.py @@ -0,0 +1,36 @@ +from __future__ import annotations + +from pathlib import Path + +from device.manager import DeviceManager +from host_agent.execution import create_execution_factories +from runtime.task import TaskRunner +from workflow.runner import WorkflowRunner +from workflow.store import WorkflowStore + + +def test_execution_factories_compose_existing_runtime_and_workflow(tmp_path) -> None: + manager = DeviceManager() + workflow_store = WorkflowStore(tmp_path / "workflows.sqlite3") + + factories = create_execution_factories( + manager, + workflow_store=workflow_store, + ) + task_runner = factories.task_runner_factory() + workflow_runner = factories.workflow_runner_factory() + + assert isinstance(task_runner, TaskRunner) + assert isinstance(workflow_runner, WorkflowRunner) + assert workflow_runner.store is workflow_store + assert isinstance(workflow_runner.task_runner_factory(), TaskRunner) + + +def test_runtime_owned_packages_do_not_import_host_or_cloud_concerns() -> None: + root = Path(__file__).resolve().parents[3] + forbidden = ("import cloud", "from cloud", "import host_agent", "from host_agent") + + for package in ("core", "device", "driver", "runtime", "tools"): + for path in (root / package).rglob("*.py"): + source = path.read_text(encoding="utf-8") + assert not any(token in source for token in forbidden), path diff --git a/openspec/changes/cloud-control-plane-integration/tasks.md b/openspec/changes/cloud-control-plane-integration/tasks.md index 43780e9..a73538f 100644 --- a/openspec/changes/cloud-control-plane-integration/tasks.md +++ b/openspec/changes/cloud-control-plane-integration/tasks.md @@ -54,7 +54,7 @@ - [x] 7.1 Implement an authenticated Host Agent client for heartbeat, long-poll claim, renewal, and result operations with bounded retry/backoff. - [x] 7.2 Build complete device snapshots from the local `DeviceManager` and synchronize them at the configured interval. -- [ ] 7.3 Compose local `TaskRunner` and `WorkflowRunner` factories without importing cloud concerns into Runtime-owned packages. +- [x] 7.3 Compose local `TaskRunner` and `WorkflowRunner` factories without importing cloud concerns into Runtime-owned packages. - [ ] 7.4 Execute goal assignments through the configured Runtime Planner/Executor and workflow assignments through the existing workflow runner. - [ ] 7.5 Run lease renewal alongside active execution and stop further interruptible actions after confirmed lease loss. - [ ] 7.6 Normalize and report successful/failed terminal outcomes, including Runtime failure reasons, with idempotent retries after response loss.