feat(boards): add max_agents field to board models and enforce limits

This commit is contained in:
Abhimanyu Saharan
2026-02-14 19:43:16 +05:30
parent d241455da6
commit ae711909ff
10 changed files with 204 additions and 1 deletions

View File

@@ -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)

View File

@@ -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:

View File

@@ -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