Files
openclaw-mission-control/backend/app/schemas/gateway_coordination.py

279 lines
10 KiB
Python
Raw Normal View History

"""Schemas for gateway-main and lead-agent coordination endpoints."""
from __future__ import annotations
from typing import Literal
from uuid import UUID
from pydantic import ConfigDict, Field
from sqlmodel import SQLModel
from app.schemas.common import NonEmptyStr
RUNTIME_ANNOTATION_TYPES = (UUID, NonEmptyStr)
def _lead_reply_tags() -> list[str]:
return ["gateway_main", "lead_reply"]
def _user_reply_tags() -> list[str]:
return ["gateway_main", "user_reply"]
class GatewayLeadMessageRequest(SQLModel):
"""Request payload for sending a message to a board lead agent."""
model_config = ConfigDict(
json_schema_extra={
"x-llm-intent": "lead_direct_message",
"x-when-to-use": [
"A board has an urgent tactical request that needs direct lead routing",
"You need a specific lead response before delegating work",
],
"x-when-not-to-use": [
"Broadcasting to many leads (use broadcast request)",
"Requesting end-user decisions (use ask-user request)",
],
"x-required-actor": "main_agent",
"x-response-shape": "GatewayLeadMessageResponse",
},
)
kind: Literal["question", "handoff"] = Field(
default="question",
description="Routing mode for lead messages.",
examples=["question", "handoff"],
)
correlation_id: str | None = Field(
default=None,
description="Optional correlation token shared across upstream and downstream systems.",
examples=["lead-msg-1234"],
)
content: NonEmptyStr = Field(
description="Human-readable body sent to lead agents.",
examples=["Please triage the highest-priority blocker on board X."],
)
# How the lead should reply (defaults are interpreted by templates).
reply_tags: list[str] = Field(
default_factory=_lead_reply_tags,
description="Tags required by reply templates when the lead responds.",
examples=[["gateway_main", "lead_reply"]],
)
reply_source: str | None = Field(
default="lead_to_gateway_main",
description="Reply destination key for the orchestrator.",
examples=["lead_to_gateway_main"],
)
class GatewayLeadMessageResponse(SQLModel):
"""Response payload for a lead-message dispatch attempt."""
model_config = ConfigDict(
json_schema_extra={
"x-llm-intent": "lead_direct_message_result",
"x-when-to-use": [
"Confirm lead routing outcome for a direct message request.",
],
"x-when-not-to-use": [
"Broadcast outcomes (use GatewayLeadBroadcastResponse)",
],
"x-required-actor": "gateway_main",
"x-interpretation": "Use to confirm handoff path and recipient lead context.",
},
)
ok: bool = Field(default=True, description="Whether dispatch was accepted.")
board_id: UUID = Field(description="Board receiving the message.")
lead_agent_id: UUID | None = Field(
default=None,
description="Resolved lead agent id when present.",
)
lead_agent_name: str | None = Field(
default=None,
description="Resolved lead agent display name.",
)
lead_created: bool = Field(
default=False,
description="Whether a lead fallback actor was created during routing.",
)
class GatewayLeadBroadcastRequest(SQLModel):
"""Request payload for broadcasting a message to multiple board leads."""
model_config = ConfigDict(
json_schema_extra={
"x-llm-intent": "lead_broadcast_message",
"x-when-to-use": [
"Multiple board leads need the same message",
"Coordinating cross-board operational alerts",
],
"x-when-not-to-use": [
"Single lead response required (use direct message)",
"Personalized board-level instruction from agent context",
],
"x-required-actor": "main_agent",
"x-response-shape": "GatewayLeadBroadcastResponse",
},
)
kind: Literal["question", "handoff"] = Field(
default="question",
description="Broadcast intent. `question` asks for responses; `handoff` requests transfer.",
examples=["question", "handoff"],
)
correlation_id: str | None = Field(
default=None,
description="Optional correlation token shared with downstream handlers.",
examples=["broadcast-2026-02-14"],
)
content: NonEmptyStr = Field(
description="Message content distributed to selected board leads.",
examples=["Board-wide incident: prioritize risk triage on task set 14."],
)
board_ids: list[UUID] | None = Field(
default=None,
description="Optional explicit list of board IDs; omit for lead-scoped defaults.",
examples=[["11111111-1111-1111-1111-111111111111"]],
)
reply_tags: list[str] = Field(
default_factory=_lead_reply_tags,
description="Tags required by reply templates when each lead responds.",
examples=[["gateway_main", "lead_reply"]],
)
reply_source: str | None = Field(
default="lead_to_gateway_main",
description="Reply destination key for broadcast responses.",
examples=["lead_to_gateway_main"],
)
class GatewayLeadBroadcastBoardResult(SQLModel):
"""Per-board result entry for a lead broadcast operation."""
model_config = ConfigDict(
json_schema_extra={
"x-llm-intent": "lead_broadcast_status",
"x-when-to-use": [
"Reading per-board outcomes for retries/follow-up workflows",
],
"x-when-not-to-use": ["Global summary checks should use parent broadcast response"],
"x-interpretation": "Use this result object as a transport status for one board.",
},
)
board_id: UUID = Field(description="Target board id for this result.")
lead_agent_id: UUID | None = Field(
default=None,
description="Resolved lead agent id for the target board.",
)
lead_agent_name: str | None = Field(
default=None,
description="Resolved lead agent display name.",
)
ok: bool = Field(default=False, description="Whether this board delivery succeeded.")
error: str | None = Field(
default=None,
description="Failure reason if this board failed.",
)
class GatewayLeadBroadcastResponse(SQLModel):
"""Aggregate response for a lead broadcast operation."""
model_config = ConfigDict(
json_schema_extra={
"x-llm-intent": "lead_broadcast_summary",
"x-when-to-use": [
"Inspect final counters after attempting a multi-board send.",
],
"x-when-not-to-use": [
"Single-board directed lead message (use GatewayLeadMessageResponse)",
],
"x-required-actor": "lead_agent_or_router",
"x-interpretation": "Use sent/failed counters before considering retry logic.",
"x-response-shape": "List of GatewayLeadBroadcastBoardResult",
},
)
ok: bool = Field(default=True, description="Whether broadcast execution succeeded.")
sent: int = Field(default=0, description="Number of boards successfully messaged.")
failed: int = Field(default=0, description="Number of boards that failed messaging.")
results: list[GatewayLeadBroadcastBoardResult] = Field(default_factory=list)
class GatewayMainAskUserRequest(SQLModel):
"""Request payload for asking the end user via a main gateway agent."""
model_config = ConfigDict(
json_schema_extra={
"x-llm-intent": "human_escalation_request",
"x-when-to-use": [
"Blocking decision requires explicit user input",
"Task flow requires preference confirmation or permission",
],
"x-required-actor": "lead_agent",
"x-response-shape": "GatewayMainAskUserResponse",
},
)
correlation_id: str | None = Field(
default=None,
description="Optional correlation token for tracing request/response flow.",
examples=["ask-user-001"],
)
content: NonEmptyStr = Field(
description="Prompt that should be asked to the human.",
examples=["Can we proceed with the proposed vendor budget increase?"],
)
preferred_channel: str | None = Field(
default=None,
description="Optional preferred messaging channel.",
examples=["chat", "email"],
)
# How the main agent should reply back into Mission Control
# (defaults interpreted by templates).
reply_tags: list[str] = Field(
default_factory=_user_reply_tags,
description="Tags required for routing the user response.",
examples=[["gateway_main", "user_reply"]],
)
reply_source: str | None = Field(
default="user_via_gateway_main",
description="Reply destination key for user confirmation loops.",
examples=["user_via_gateway_main"],
)
class GatewayMainAskUserResponse(SQLModel):
"""Response payload for user-question dispatch via gateway main agent."""
model_config = ConfigDict(
json_schema_extra={
"x-llm-intent": "human_escalation_result",
"x-when-to-use": [
"Track completion and main-agent handoff after human escalation request.",
],
"x-when-not-to-use": [
"Regular lead routing outcomes (use lead message/broadcast responses)",
],
"x-required-actor": "lead_agent",
"x-interpretation": "Track whether ask was accepted and which main agent handled it.",
},
)
ok: bool = Field(default=True, description="Whether ask-user dispatch was accepted.")
board_id: UUID = Field(description="Board context used for the request.")
main_agent_id: UUID | None = Field(
default=None,
description="Resolved main agent id handling the ask.",
)
main_agent_name: str | None = Field(
default=None,
description="Resolved main agent display name.",
)