refactor: streamline agent lifecycle management with new DB service helpers

This commit is contained in:
Abhimanyu Saharan
2026-02-11 01:13:10 +05:30
parent f4161494d9
commit f1038acf44
17 changed files with 377 additions and 350 deletions

View File

@@ -10,7 +10,6 @@ from __future__ import annotations
import asyncio
import json
import logging
import re
from dataclasses import dataclass, field
from datetime import UTC, datetime
@@ -22,7 +21,7 @@ from sqlalchemy import asc, func, or_
from sqlmodel import col, select
from sse_starlette.sse import EventSourceResponse
from app.core.agent_tokens import generate_agent_token, hash_agent_token, verify_agent_token
from app.core.agent_tokens import verify_agent_token
from app.core.time import utcnow
from app.db import crud
from app.db.pagination import paginate
@@ -50,6 +49,12 @@ from app.services.openclaw.constants import (
DEFAULT_HEARTBEAT_CONFIG,
OFFLINE_AFTER,
)
from app.services.openclaw.db_agent_state import (
mark_provision_complete,
mark_provision_requested,
mint_agent_token,
)
from app.services.openclaw.db_service import OpenClawDBService
from app.services.openclaw.gateway_rpc import GatewayConfig as GatewayClientConfig
from app.services.openclaw.gateway_rpc import (
OpenClawGatewayError,
@@ -120,17 +125,13 @@ class LeadAgentRequest:
options: LeadAgentOptions = field(default_factory=LeadAgentOptions)
class OpenClawProvisioningService:
class OpenClawProvisioningService(OpenClawDBService):
"""DB-backed provisioning workflows (bulk template sync, lead-agent record)."""
def __init__(self, session: AsyncSession) -> None:
self._session = session
super().__init__(session)
self._gateway = OpenClawGatewayProvisioner()
@property
def session(self) -> AsyncSession:
return self._session
@staticmethod
def lead_session_key(board: Board) -> str:
return f"agent:lead-{board.id}:main"
@@ -191,21 +192,16 @@ class OpenClawProvisioningService:
agent = Agent(
name=config_options.agent_name or self.lead_agent_name(board),
status="provisioning",
board_id=board.id,
gateway_id=request.gateway.id,
is_board_lead=True,
heartbeat_config=DEFAULT_HEARTBEAT_CONFIG.copy(),
identity_profile=merged_identity_profile,
openclaw_session_id=self.lead_session_key(board),
provision_requested_at=utcnow(),
provision_action=config_options.action,
)
raw_token = generate_agent_token()
agent.agent_token_hash = hash_agent_token(raw_token)
self.session.add(agent)
await self.session.commit()
await self.session.refresh(agent)
raw_token = mint_agent_token(agent)
mark_provision_requested(agent, action=config_options.action, status="provisioning")
await self.add_commit_refresh(agent)
# Strict behavior: provisioning errors surface to the caller. The DB row exists
# so a later retry can succeed with the same deterministic identity/session key.
@@ -220,13 +216,8 @@ class OpenClawProvisioningService:
deliver_wakeup=True,
)
agent.status = "online"
agent.provision_requested_at = None
agent.provision_action = None
agent.updated_at = utcnow()
self.session.add(agent)
await self.session.commit()
await self.session.refresh(agent)
mark_provision_complete(agent, status="online")
await self.add_commit_refresh(agent)
return agent, True
@@ -433,8 +424,7 @@ def _append_sync_error(
async def _rotate_agent_token(session: AsyncSession, agent: Agent) -> str:
token = generate_agent_token()
agent.agent_token_hash = hash_agent_token(token)
token = mint_agent_token(agent)
agent.updated_at = utcnow()
session.add(agent)
await session.commit()
@@ -692,28 +682,11 @@ class AgentUpdateProvisionRequest:
force_bootstrap: bool
class AgentLifecycleService:
class AgentLifecycleService(OpenClawDBService):
"""Async service encapsulating agent lifecycle behavior for API routes."""
def __init__(self, session: AsyncSession) -> None:
self._session = session
self._logger = logging.getLogger(__name__)
@property
def session(self) -> AsyncSession:
return self._session
@session.setter
def session(self, value: AsyncSession) -> None:
self._session = value
@property
def logger(self) -> logging.Logger:
return self._logger
@logger.setter
def logger(self, value: logging.Logger) -> None:
self._logger = value
super().__init__(session)
@staticmethod
def parse_since(value: str | None) -> datetime | None:
@@ -1013,17 +986,10 @@ class AgentLifecycleService:
data: dict[str, Any],
) -> tuple[Agent, str]:
agent = Agent.model_validate(data)
agent.status = "provisioning"
raw_token = generate_agent_token()
agent.agent_token_hash = hash_agent_token(raw_token)
if agent.heartbeat_config is None:
agent.heartbeat_config = DEFAULT_HEARTBEAT_CONFIG.copy()
agent.provision_requested_at = utcnow()
agent.provision_action = "provision"
raw_token = mint_agent_token(agent)
mark_provision_requested(agent, action="provision", status="provisioning")
agent.openclaw_session_id = self.resolve_session_key(agent)
self.session.add(agent)
await self.session.commit()
await self.session.refresh(agent)
await self.add_commit_refresh(agent)
return agent, raw_token
async def _apply_gateway_provisioning(
@@ -1078,11 +1044,7 @@ class AgentLifecycleService:
deliver_wakeup=True,
wakeup_verb=wakeup_verb,
)
agent.provision_confirm_token_hash = None
agent.provision_requested_at = None
agent.provision_action = None
agent.status = "online"
agent.updated_at = utcnow()
mark_provision_complete(agent, status="online", clear_confirm_token=True)
self.session.add(agent)
await self.session.commit()
record_activity(
@@ -1301,11 +1263,8 @@ class AgentLifecycleService:
@staticmethod
def mark_agent_update_pending(agent: Agent) -> str:
raw_token = generate_agent_token()
agent.agent_token_hash = hash_agent_token(raw_token)
agent.provision_requested_at = utcnow()
agent.provision_action = "update"
agent.status = "updating"
raw_token = mint_agent_token(agent)
mark_provision_requested(agent, action="update", status="updating")
return raw_token
async def provision_updated_agent(
@@ -1379,15 +1338,9 @@ class AgentLifecycleService:
if agent.agent_token_hash is not None:
return
raw_token = generate_agent_token()
agent.agent_token_hash = hash_agent_token(raw_token)
if agent.heartbeat_config is None:
agent.heartbeat_config = DEFAULT_HEARTBEAT_CONFIG.copy()
agent.provision_requested_at = utcnow()
agent.provision_action = "provision"
self.session.add(agent)
await self.session.commit()
await self.session.refresh(agent)
raw_token = mint_agent_token(agent)
mark_provision_requested(agent, action="provision", status="provisioning")
await self.add_commit_refresh(agent)
board = await self.require_board(
str(agent.board_id) if agent.board_id else None,
user=user,