feat(host-agent): wire skill sync + inventory report into app lifecycle

Adds host_agent/skill_sync.py (HostAgentSkillSync) which constructs the
synced + local skill stores, the Cloud API sync client, and the runner,
then drives them on the host-agent lifecycle: incremental per-host pull
into the synced catalog, fork-on-revocation, and a best-effort local-
skill inventory report to the Cloud (design D7). Wired into
create_application + run_async start/stop. Host-agent suite green
(219 passed).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-07-15 08:02:31 +08:00
co-authored by Claude Opus 4.6
parent a8ba2312fc
commit 0d944ec97d
5 changed files with 214 additions and 3 deletions
+10
View File
@@ -24,6 +24,7 @@ from host_agent.local_account import LocalAccountStore
from host_agent.policy_cache import HostPolicyCacheStore
from host_agent.processor import AssignmentProcessingResult, AssignmentProcessor
from host_agent.retention import prune_task_history
from host_agent.skill_sync import HostAgentSkillSync
from host_agent.status import AgentStatusTracker
from host_agent.web.app import create_console_app
from host_agent.web.auth import SessionManager
@@ -42,6 +43,7 @@ class HostAgentApplication:
console_enrollment_client: HostAgentEnrollmentClient | None = None
dependency_supervisor: DependencySupervisor | None = None
instance_lock: InstanceLock | None = None
skill_sync: HostAgentSkillSync | None = None
def run(self) -> None:
asyncio.run(self.run_async())
@@ -60,6 +62,8 @@ class HostAgentApplication:
)
heartbeat_stop = asyncio.Event()
heartbeat_task = asyncio.create_task(self.heartbeat.run(heartbeat_stop))
if self.skill_sync is not None:
self.skill_sync.start()
console_task = (
asyncio.create_task(self.console_server.serve())
if self.console_server is not None
@@ -110,6 +114,8 @@ class HostAgentApplication:
finally:
if self.console_enrollment_client is not None:
self.console_enrollment_client.close()
if self.skill_sync is not None:
self.skill_sync.stop()
await self.client.aclose()
if self.instance_lock is not None:
self.instance_lock.release()
@@ -263,6 +269,9 @@ def create_application(
dependency_supervisor = DependencySupervisor.from_host_agent_config(
resolved_config
)
skill_sync: HostAgentSkillSync | None = None
if resolved_config.host_id and resolved_config.token:
skill_sync = HostAgentSkillSync(resolved_config)
return HostAgentApplication(
client=client,
heartbeat=heartbeat,
@@ -271,6 +280,7 @@ def create_application(
console_enrollment_client=console_enrollment_client,
dependency_supervisor=dependency_supervisor,
instance_lock=instance_lock,
skill_sync=skill_sync,
)
except BaseException:
instance_lock.release()