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

@@ -33,10 +33,7 @@ from app.models.users import User
from app.schemas.board_group_memory import BoardGroupMemoryCreate, BoardGroupMemoryRead
from app.schemas.pagination import DefaultLimitOffsetPage
from app.services.mentions import extract_mentions, matches_agent_mention
from app.services.openclaw.shared import (
optional_gateway_config_for_board,
send_gateway_agent_message_safe,
)
from app.services.openclaw.gateway_dispatch import GatewayDispatchService
from app.services.organizations import (
is_org_admin,
list_accessible_board_ids,
@@ -206,6 +203,7 @@ def _group_header(*, is_broadcast: bool, mentioned: bool) -> str:
@dataclass(frozen=True)
class _NotifyGroupContext:
session: AsyncSession
dispatch: GatewayDispatchService
group: BoardGroup
board_by_id: dict[UUID, Board]
mentions: set[str]
@@ -226,7 +224,7 @@ async def _notify_group_target(
board = context.board_by_id.get(board_id)
if board is None:
return
config = await optional_gateway_config_for_board(context.session, board)
config = await context.dispatch.optional_gateway_config_for_board(board)
if config is None:
return
header = _group_header(
@@ -242,7 +240,7 @@ async def _notify_group_target(
f"POST {context.base_url}/api/v1/boards/{board.id}/group-memory\n"
'Body: {"content":"...","tags":["chat"]}'
)
error = await send_gateway_agent_message_safe(
error = await context.dispatch.try_send_agent_message(
session_key=session_key,
config=config,
agent_name=agent.name,
@@ -294,6 +292,7 @@ async def _notify_group_memory_targets(
context = _NotifyGroupContext(
session=session,
dispatch=GatewayDispatchService(session),
group=group,
board_by_id=board_by_id,
mentions=mentions,

View File

@@ -30,8 +30,8 @@ from app.schemas.pagination import DefaultLimitOffsetPage
from app.schemas.view_models import BoardGroupSnapshot
from app.services.board_group_snapshot import build_group_snapshot
from app.services.openclaw.constants import DEFAULT_HEARTBEAT_CONFIG
from app.services.openclaw.gateway_rpc import OpenClawGatewayError
from app.services.openclaw.provisioning import OpenClawGatewayProvisioner
from app.services.openclaw.shared import GatewayTransportError
from app.services.organizations import (
OrganizationContext,
board_access_filter,
@@ -273,7 +273,7 @@ async def _sync_gateway_heartbeats(
gateway,
gateway_agents,
)
except GatewayTransportError:
except OpenClawGatewayError:
failed_agent_ids.extend([agent.id for agent in gateway_agents])
return failed_agent_ids

View File

@@ -28,11 +28,8 @@ from app.models.board_memory import BoardMemory
from app.schemas.board_memory import BoardMemoryCreate, BoardMemoryRead
from app.schemas.pagination import DefaultLimitOffsetPage
from app.services.mentions import extract_mentions, matches_agent_mention
from app.services.openclaw.shared import (
GatewayClientConfig,
optional_gateway_config_for_board,
send_gateway_agent_message_safe,
)
from app.services.openclaw.gateway_dispatch import GatewayDispatchService
from app.services.openclaw.gateway_rpc import GatewayConfig as GatewayClientConfig
if TYPE_CHECKING:
from collections.abc import AsyncIterator
@@ -102,6 +99,7 @@ async def _send_control_command(
session: AsyncSession,
board: Board,
actor: ActorContext,
dispatch: GatewayDispatchService,
config: GatewayClientConfig,
command: str,
) -> None:
@@ -115,7 +113,7 @@ async def _send_control_command(
continue
if not agent.openclaw_session_id:
continue
error = await send_gateway_agent_message_safe(
error = await dispatch.try_send_agent_message(
session_key=agent.openclaw_session_id,
config=config,
agent_name=agent.name,
@@ -161,7 +159,8 @@ async def _notify_chat_targets(
) -> None:
if not memory.content:
return
config = await optional_gateway_config_for_board(session, board)
dispatch = GatewayDispatchService(session)
config = await dispatch.optional_gateway_config_for_board(board)
if config is None:
return
@@ -174,6 +173,7 @@ async def _notify_chat_targets(
session=session,
board=board,
actor=actor,
dispatch=dispatch,
config=config,
command=command,
)
@@ -206,7 +206,7 @@ async def _notify_chat_targets(
f"POST {base_url}/api/v1/agent/boards/{board.id}/memory\n"
'Body: {"content":"...","tags":["chat"]}'
)
error = await send_gateway_agent_message_safe(
error = await dispatch.try_send_agent_message(
session_key=agent.openclaw_session_id,
config=config,
agent_name=agent.name,

View File

@@ -33,6 +33,7 @@ from app.schemas.board_onboarding import (
BoardOnboardingUserProfile,
)
from app.schemas.boards import BoardRead
from app.services.openclaw.gateway_dispatch import GatewayDispatchService
from app.services.openclaw.onboarding_service import BoardOnboardingMessagingService
from app.services.openclaw.policies import OpenClawAuthorizationPolicy
from app.services.openclaw.provisioning_db import (
@@ -40,7 +41,6 @@ from app.services.openclaw.provisioning_db import (
LeadAgentRequest,
OpenClawProvisioningService,
)
from app.services.openclaw.shared import require_gateway_config_for_board
if TYPE_CHECKING:
from sqlmodel.ext.asyncio.session import AsyncSession
@@ -396,7 +396,7 @@ async def confirm_onboarding(
lead_agent = _parse_draft_lead_agent(onboarding.draft_goal)
lead_options = _lead_agent_options(lead_agent)
gateway, config = await require_gateway_config_for_board(session, board)
gateway, config = await GatewayDispatchService(session).require_gateway_config_for_board(board)
session.add(board)
session.add(onboarding)
await session.commit()

View File

@@ -39,8 +39,8 @@ from app.schemas.pagination import DefaultLimitOffsetPage
from app.schemas.view_models import BoardGroupSnapshot, BoardSnapshot
from app.services.board_group_snapshot import build_board_group_snapshot
from app.services.board_snapshot import build_board_snapshot
from app.services.openclaw.gateway_rpc import OpenClawGatewayError
from app.services.openclaw.provisioning import OpenClawGatewayProvisioner
from app.services.openclaw.shared import GatewayTransportError
from app.services.organizations import OrganizationContext, board_access_filter
if TYPE_CHECKING:
@@ -291,7 +291,7 @@ async def delete_board(
agent=agent,
gateway=config,
)
except GatewayTransportError as exc:
except OpenClawGatewayError as exc:
raise HTTPException(
status_code=status.HTTP_502_BAD_GATEWAY,
detail=f"Gateway cleanup failed: {exc}",

View File

@@ -40,12 +40,9 @@ from app.schemas.pagination import DefaultLimitOffsetPage
from app.schemas.tasks import TaskCommentCreate, TaskCommentRead, TaskCreate, TaskRead, TaskUpdate
from app.services.activity_log import record_activity
from app.services.mentions import extract_mentions, matches_agent_mention
from app.services.openclaw.shared import (
GatewayClientConfig,
GatewayTransportError,
optional_gateway_config_for_board,
send_gateway_agent_message_safe,
)
from app.services.openclaw.gateway_dispatch import GatewayDispatchService
from app.services.openclaw.gateway_rpc import GatewayConfig as GatewayClientConfig
from app.services.openclaw.gateway_rpc import OpenClawGatewayError
from app.services.organizations import require_board_access
from app.services.task_dependencies import (
blocked_by_dependency_ids,
@@ -305,11 +302,12 @@ def _serialize_comment(event: ActivityEvent) -> dict[str, object]:
async def _send_lead_task_message(
*,
dispatch: GatewayDispatchService,
session_key: str,
config: GatewayClientConfig,
message: str,
) -> GatewayTransportError | None:
return await send_gateway_agent_message_safe(
) -> OpenClawGatewayError | None:
return await dispatch.try_send_agent_message(
session_key=session_key,
config=config,
agent_name="Lead Agent",
@@ -320,12 +318,13 @@ async def _send_lead_task_message(
async def _send_agent_task_message(
*,
dispatch: GatewayDispatchService,
session_key: str,
config: GatewayClientConfig,
agent_name: str,
message: str,
) -> GatewayTransportError | None:
return await send_gateway_agent_message_safe(
) -> OpenClawGatewayError | None:
return await dispatch.try_send_agent_message(
session_key=session_key,
config=config,
agent_name=agent_name,
@@ -343,7 +342,8 @@ async def _notify_agent_on_task_assign(
) -> None:
if not agent.openclaw_session_id:
return
config = await optional_gateway_config_for_board(session, board)
dispatch = GatewayDispatchService(session)
config = await dispatch.optional_gateway_config_for_board(board)
if config is None:
return
description = _truncate_snippet(task.description or "")
@@ -361,6 +361,7 @@ async def _notify_agent_on_task_assign(
+ ("\n\nTake action: open the task and begin work. " "Post updates as task comments.")
)
error = await _send_agent_task_message(
dispatch=dispatch,
session_key=agent.openclaw_session_id,
config=config,
agent_name=agent.name,
@@ -415,7 +416,8 @@ async def _notify_lead_on_task_create(
)
if lead is None or not lead.openclaw_session_id:
return
config = await optional_gateway_config_for_board(session, board)
dispatch = GatewayDispatchService(session)
config = await dispatch.optional_gateway_config_for_board(board)
if config is None:
return
description = _truncate_snippet(task.description or "")
@@ -433,6 +435,7 @@ async def _notify_lead_on_task_create(
+ "\n\nTake action: triage, assign, or plan next steps."
)
error = await _send_lead_task_message(
dispatch=dispatch,
session_key=lead.openclaw_session_id,
config=config,
message=message,
@@ -470,7 +473,8 @@ async def _notify_lead_on_task_unassigned(
)
if lead is None or not lead.openclaw_session_id:
return
config = await optional_gateway_config_for_board(session, board)
dispatch = GatewayDispatchService(session)
config = await dispatch.optional_gateway_config_for_board(board)
if config is None:
return
description = _truncate_snippet(task.description or "")
@@ -488,6 +492,7 @@ async def _notify_lead_on_task_unassigned(
+ "\n\nTake action: assign a new owner or adjust the plan."
)
error = await _send_lead_task_message(
dispatch=dispatch,
session_key=lead.openclaw_session_id,
config=config,
message=message,
@@ -1029,8 +1034,11 @@ async def _notify_task_comment_targets(
if request.task.board_id
else None
)
config = await optional_gateway_config_for_board(session, board) if board else None
if not board or not config:
if board is None:
return
dispatch = GatewayDispatchService(session)
config = await dispatch.optional_gateway_config_for_board(board)
if not config:
return
snippet = _truncate_snippet(request.message)
@@ -1057,6 +1065,7 @@ async def _notify_task_comment_targets(
"thread but do not change task status."
)
await _send_agent_task_message(
dispatch=dispatch,
session_key=agent.openclaw_session_id,
config=config,
agent_name=agent.name,