feat(boards): add max_agents field to board models and enforce limits
This commit is contained in:
@@ -43,5 +43,6 @@ class Board(TenantScoped, table=True):
|
||||
require_review_before_done: bool = Field(default=False)
|
||||
block_status_changes_with_pending_approval: bool = Field(default=False)
|
||||
only_lead_can_change_status: bool = Field(default=False)
|
||||
max_agents: int = Field(default=1)
|
||||
created_at: datetime = Field(default_factory=utcnow)
|
||||
updated_at: datetime = Field(default_factory=utcnow)
|
||||
|
||||
@@ -7,7 +7,7 @@ from typing import Self
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import model_validator
|
||||
from sqlmodel import SQLModel
|
||||
from sqlmodel import Field, SQLModel
|
||||
|
||||
_ERR_GOAL_FIELDS_REQUIRED = "Confirmed goal boards require objective and success_metrics"
|
||||
_ERR_GATEWAY_REQUIRED = "gateway_id is required"
|
||||
@@ -33,6 +33,7 @@ class BoardBase(SQLModel):
|
||||
require_review_before_done: bool = False
|
||||
block_status_changes_with_pending_approval: bool = False
|
||||
only_lead_can_change_status: bool = False
|
||||
max_agents: int = Field(default=1, ge=0)
|
||||
|
||||
|
||||
class BoardCreate(BoardBase):
|
||||
@@ -76,6 +77,7 @@ class BoardUpdate(SQLModel):
|
||||
require_review_before_done: bool | None = None
|
||||
block_status_changes_with_pending_approval: bool | None = None
|
||||
only_lead_can_change_status: bool | None = None
|
||||
max_agents: int | None = Field(default=None, ge=0)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def validate_gateway_id(self) -> Self:
|
||||
|
||||
@@ -922,6 +922,49 @@ class AgentLifecycleService(OpenClawDBService):
|
||||
|
||||
return payload
|
||||
|
||||
async def count_non_lead_agents_for_board(
|
||||
self,
|
||||
*,
|
||||
board_id: UUID,
|
||||
) -> int:
|
||||
"""Count board-scoped non-lead agents for spawn limit checks."""
|
||||
statement = (
|
||||
select(func.count(col(Agent.id)))
|
||||
.where(col(Agent.board_id) == board_id)
|
||||
.where(col(Agent.is_board_lead).is_(False))
|
||||
)
|
||||
count = (await self.session.exec(statement)).one()
|
||||
return int(count or 0)
|
||||
|
||||
async def enforce_board_spawn_limit_for_lead(
|
||||
self,
|
||||
*,
|
||||
board: Board,
|
||||
actor: ActorContextLike,
|
||||
) -> None:
|
||||
"""Enforce `board.max_agents` when creation is requested by a lead agent.
|
||||
|
||||
The cap excludes the board lead itself.
|
||||
"""
|
||||
if actor.actor_type != "agent":
|
||||
return
|
||||
if actor.agent is None or not actor.agent.is_board_lead:
|
||||
return
|
||||
|
||||
worker_count = await self.count_non_lead_agents_for_board(board_id=board.id)
|
||||
if worker_count < board.max_agents:
|
||||
return
|
||||
|
||||
noun = "agent" if board.max_agents == 1 else "agents"
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail=(
|
||||
"Board worker-agent limit reached: "
|
||||
f"max_agents={board.max_agents} (excluding the lead); "
|
||||
f"cannot create more than {board.max_agents} {noun}."
|
||||
),
|
||||
)
|
||||
|
||||
async def ensure_unique_agent_name(
|
||||
self,
|
||||
*,
|
||||
@@ -1484,6 +1527,7 @@ class AgentLifecycleService(OpenClawDBService):
|
||||
user=actor.user if actor.actor_type == "user" else None,
|
||||
write=actor.actor_type == "user",
|
||||
)
|
||||
await self.enforce_board_spawn_limit_for_lead(board=board, actor=actor)
|
||||
gateway, _client_config = await self.require_gateway(board)
|
||||
data = payload.model_dump()
|
||||
data["gateway_id"] = gateway.id
|
||||
|
||||
Reference in New Issue
Block a user