feat: enhance agent and board APIs with role-based tags and improved documentation

This commit is contained in:
Abhimanyu Saharan
2026-02-13 02:08:30 +05:30
parent 5695c0003d
commit a3148baca9
13 changed files with 550 additions and 280 deletions

View File

@@ -2,7 +2,9 @@
from __future__ import annotations
from enum import Enum
from typing import TYPE_CHECKING, Any
from typing import cast
from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, Query, status
@@ -77,6 +79,11 @@ TASK_STATUS_QUERY = Query(default=None, alias="status")
IS_CHAT_QUERY = Query(default=None)
APPROVAL_STATUS_QUERY = Query(default=None, alias="status")
AGENT_LEAD_TAGS = cast("list[str | Enum]", ["agent-lead"])
AGENT_MAIN_TAGS = cast("list[str | Enum]", ["agent-main"])
AGENT_BOARD_TAGS = cast("list[str | Enum]", ["agent-lead", "agent-worker"])
AGENT_ALL_ROLE_TAGS = cast("list[str | Enum]", ["agent-lead", "agent-worker", "agent-main"])
def _coerce_agent_items(items: Sequence[Any]) -> list[Agent]:
agents: list[Agent] = []
@@ -142,12 +149,20 @@ def _guard_task_access(agent_ctx: AgentAuthContext, task: Task) -> None:
OpenClawAuthorizationPolicy.require_board_write_access(allowed=allowed)
@router.get("/boards", response_model=DefaultLimitOffsetPage[BoardRead])
@router.get(
"/boards",
response_model=DefaultLimitOffsetPage[BoardRead],
tags=AGENT_ALL_ROLE_TAGS,
)
async def list_boards(
session: AsyncSession = SESSION_DEP,
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
) -> LimitOffsetPage[BoardRead]:
"""List boards visible to the authenticated agent."""
"""List boards visible to the authenticated agent.
Board-scoped agents typically see only their assigned board.
Main agents may see multiple boards when permitted by auth scope.
"""
statement = select(Board)
if agent_ctx.agent.board_id:
statement = statement.where(col(Board.id) == agent_ctx.agent.board_id)
@@ -155,23 +170,34 @@ async def list_boards(
return await paginate(session, statement)
@router.get("/boards/{board_id}", response_model=BoardRead)
@router.get("/boards/{board_id}", response_model=BoardRead, tags=AGENT_ALL_ROLE_TAGS)
def get_board(
board: Board = BOARD_DEP,
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
) -> Board:
"""Return a board if the authenticated agent can access it."""
"""Return one board if the authenticated agent can access it.
Use this when an agent needs board metadata (objective, status, target date)
before planning or posting updates.
"""
_guard_board_access(agent_ctx, board)
return board
@router.get("/agents", response_model=DefaultLimitOffsetPage[AgentRead])
@router.get(
"/agents",
response_model=DefaultLimitOffsetPage[AgentRead],
tags=AGENT_ALL_ROLE_TAGS,
)
async def list_agents(
board_id: UUID | None = BOARD_ID_QUERY,
session: AsyncSession = SESSION_DEP,
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
) -> LimitOffsetPage[AgentRead]:
"""List agents, optionally filtered to a board."""
"""List agents visible to the caller, optionally filtered by board.
Useful for lead delegation and workload balancing.
"""
statement = select(Agent)
if agent_ctx.agent.board_id:
if board_id:
@@ -195,14 +221,23 @@ async def list_agents(
return await paginate(session, statement, transformer=_transform)
@router.get("/boards/{board_id}/tasks", response_model=DefaultLimitOffsetPage[TaskRead])
@router.get(
"/boards/{board_id}/tasks",
response_model=DefaultLimitOffsetPage[TaskRead],
tags=AGENT_BOARD_TAGS,
)
async def list_tasks(
filters: AgentTaskListFilters = TASK_LIST_FILTERS_DEP,
board: Board = BOARD_DEP,
session: AsyncSession = SESSION_DEP,
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
) -> LimitOffsetPage[TaskRead]:
"""List tasks on a board with optional status and assignment filters."""
"""List tasks on a board with status/assignment filters.
Common patterns:
- worker: fetch assigned inbox/in-progress tasks
- lead: fetch unassigned inbox tasks for delegation
"""
_guard_board_access(agent_ctx, board)
return await tasks_api.list_tasks(
status_filter=filters.status_filter,
@@ -214,13 +249,16 @@ async def list_tasks(
)
@router.get("/boards/{board_id}/tags", response_model=list[TagRef])
@router.get("/boards/{board_id}/tags", response_model=list[TagRef], tags=AGENT_BOARD_TAGS)
async def list_tags(
board: Board = BOARD_DEP,
session: AsyncSession = SESSION_DEP,
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
) -> list[TagRef]:
"""List tags available to the board's organization."""
"""List available tags for the board's organization.
Use returned ids in task create/update payloads (`tag_ids`).
"""
_guard_board_access(agent_ctx, board)
tags = (
await session.exec(
@@ -240,14 +278,18 @@ async def list_tags(
]
@router.post("/boards/{board_id}/tasks", response_model=TaskRead)
@router.post("/boards/{board_id}/tasks", response_model=TaskRead, tags=AGENT_LEAD_TAGS)
async def create_task(
payload: TaskCreate,
board: Board = BOARD_DEP,
session: AsyncSession = SESSION_DEP,
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
) -> TaskRead:
"""Create a task on the board as the lead agent."""
"""Create a task as the board lead.
Lead-only endpoint. Supports dependency-aware creation via
`depends_on_task_ids` and optional `tag_ids`.
"""
_guard_board_access(agent_ctx, board)
_require_board_lead(agent_ctx)
data = payload.model_dump(exclude={"depends_on_task_ids", "tag_ids"})
@@ -343,14 +385,21 @@ async def create_task(
)
@router.patch("/boards/{board_id}/tasks/{task_id}", response_model=TaskRead)
@router.patch(
"/boards/{board_id}/tasks/{task_id}",
response_model=TaskRead,
tags=AGENT_BOARD_TAGS,
)
async def update_task(
payload: TaskUpdate,
task: Task = TASK_DEP,
session: AsyncSession = SESSION_DEP,
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
) -> TaskRead:
"""Update a task after board-level access checks."""
"""Update a task after board-level authorization checks.
Supports status, assignment, dependencies, and optional inline comment.
"""
_guard_task_access(agent_ctx, task)
return await tasks_api.update_task(
payload=payload,
@@ -363,13 +412,17 @@ async def update_task(
@router.get(
"/boards/{board_id}/tasks/{task_id}/comments",
response_model=DefaultLimitOffsetPage[TaskCommentRead],
tags=AGENT_BOARD_TAGS,
)
async def list_task_comments(
task: Task = TASK_DEP,
session: AsyncSession = SESSION_DEP,
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
) -> LimitOffsetPage[TaskCommentRead]:
"""List comments for a task visible to the authenticated agent."""
"""List task comments visible to the authenticated agent.
Read this before posting updates to avoid duplicate or low-value comments.
"""
_guard_task_access(agent_ctx, task)
return await tasks_api.list_task_comments(
task=task,
@@ -380,6 +433,7 @@ async def list_task_comments(
@router.post(
"/boards/{board_id}/tasks/{task_id}/comments",
response_model=TaskCommentRead,
tags=AGENT_BOARD_TAGS,
)
async def create_task_comment(
payload: TaskCommentCreate,
@@ -387,7 +441,10 @@ async def create_task_comment(
session: AsyncSession = SESSION_DEP,
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
) -> ActivityEvent:
"""Create a task comment on behalf of the authenticated agent."""
"""Create a task comment as the authenticated agent.
This is the primary collaboration/log surface for task progress.
"""
_guard_task_access(agent_ctx, task)
return await tasks_api.create_task_comment(
payload=payload,
@@ -400,6 +457,7 @@ async def create_task_comment(
@router.get(
"/boards/{board_id}/memory",
response_model=DefaultLimitOffsetPage[BoardMemoryRead],
tags=AGENT_BOARD_TAGS,
)
async def list_board_memory(
is_chat: bool | None = IS_CHAT_QUERY,
@@ -407,7 +465,10 @@ async def list_board_memory(
session: AsyncSession = SESSION_DEP,
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
) -> LimitOffsetPage[BoardMemoryRead]:
"""List board memory entries with optional chat filtering."""
"""List board memory with optional chat filtering.
Use `is_chat=false` for durable context and `is_chat=true` for board chat.
"""
_guard_board_access(agent_ctx, board)
return await board_memory_api.list_board_memory(
is_chat=is_chat,
@@ -417,14 +478,17 @@ async def list_board_memory(
)
@router.post("/boards/{board_id}/memory", response_model=BoardMemoryRead)
@router.post("/boards/{board_id}/memory", response_model=BoardMemoryRead, tags=AGENT_BOARD_TAGS)
async def create_board_memory(
payload: BoardMemoryCreate,
board: Board = BOARD_DEP,
session: AsyncSession = SESSION_DEP,
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
) -> BoardMemory:
"""Create a board memory entry."""
"""Create a board memory entry.
Use tags to indicate purpose (e.g. `chat`, `decision`, `plan`, `handoff`).
"""
_guard_board_access(agent_ctx, board)
return await board_memory_api.create_board_memory(
payload=payload,
@@ -437,6 +501,7 @@ async def create_board_memory(
@router.get(
"/boards/{board_id}/approvals",
response_model=DefaultLimitOffsetPage[ApprovalRead],
tags=AGENT_BOARD_TAGS,
)
async def list_approvals(
status_filter: ApprovalStatus | None = APPROVAL_STATUS_QUERY,
@@ -444,7 +509,10 @@ async def list_approvals(
session: AsyncSession = SESSION_DEP,
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
) -> LimitOffsetPage[ApprovalRead]:
"""List approvals for a board."""
"""List approvals for a board.
Use status filtering to process pending approvals efficiently.
"""
_guard_board_access(agent_ctx, board)
return await approvals_api.list_approvals(
status_filter=status_filter,
@@ -454,14 +522,17 @@ async def list_approvals(
)
@router.post("/boards/{board_id}/approvals", response_model=ApprovalRead)
@router.post("/boards/{board_id}/approvals", response_model=ApprovalRead, tags=AGENT_BOARD_TAGS)
async def create_approval(
payload: ApprovalCreate,
board: Board = BOARD_DEP,
session: AsyncSession = SESSION_DEP,
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
) -> ApprovalRead:
"""Create a board approval request."""
"""Create an approval request for risky or low-confidence actions.
Include `task_id` or `task_ids` to scope the decision precisely.
"""
_guard_board_access(agent_ctx, board)
return await approvals_api.create_approval(
payload=payload,
@@ -471,14 +542,21 @@ async def create_approval(
)
@router.post("/boards/{board_id}/onboarding", response_model=BoardOnboardingRead)
@router.post(
"/boards/{board_id}/onboarding",
response_model=BoardOnboardingRead,
tags=AGENT_BOARD_TAGS,
)
async def update_onboarding(
payload: BoardOnboardingAgentUpdate,
board: Board = BOARD_DEP,
session: AsyncSession = SESSION_DEP,
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
) -> BoardOnboardingSession:
"""Apply onboarding updates for a board."""
"""Apply board onboarding updates from an agent workflow.
Used during structured objective/success-metric intake loops.
"""
_guard_board_access(agent_ctx, board)
return await onboarding_api.agent_onboarding_update(
payload=payload,
@@ -488,13 +566,16 @@ async def update_onboarding(
)
@router.post("/agents", response_model=AgentRead)
@router.post("/agents", response_model=AgentRead, tags=AGENT_LEAD_TAGS)
async def create_agent(
payload: AgentCreate,
session: AsyncSession = SESSION_DEP,
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
) -> AgentRead:
"""Create an agent on the caller's board."""
"""Create a new board agent as lead.
The new agent is always forced onto the caller's board (`board_id` override).
"""
lead = _require_board_lead(agent_ctx)
payload = AgentCreate(
**{**payload.model_dump(), "board_id": lead.board_id},
@@ -506,7 +587,11 @@ async def create_agent(
)
@router.post("/boards/{board_id}/agents/{agent_id}/nudge", response_model=OkResponse)
@router.post(
"/boards/{board_id}/agents/{agent_id}/nudge",
response_model=OkResponse,
tags=AGENT_LEAD_TAGS,
)
async def nudge_agent(
payload: AgentNudge,
agent_id: str,
@@ -514,7 +599,10 @@ async def nudge_agent(
session: AsyncSession = SESSION_DEP,
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
) -> OkResponse:
"""Send a direct nudge message to a board agent."""
"""Send a direct nudge to one board agent.
Lead-only endpoint for stale or blocked in-progress work.
"""
_guard_board_access(agent_ctx, board)
_require_board_lead(agent_ctx)
coordination = GatewayCoordinationService(session)
@@ -528,13 +616,16 @@ async def nudge_agent(
return OkResponse()
@router.post("/heartbeat", response_model=AgentRead)
@router.post("/heartbeat", response_model=AgentRead, tags=AGENT_ALL_ROLE_TAGS)
async def agent_heartbeat(
payload: AgentHeartbeatCreate,
session: AsyncSession = SESSION_DEP,
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
) -> AgentRead:
"""Record heartbeat status for the authenticated agent."""
"""Record heartbeat status for the authenticated agent.
Heartbeats are identity-bound to the token's agent id.
"""
# Heartbeats must apply to the authenticated agent; agent names are not unique.
return await agents_api.heartbeat_agent(
agent_id=str(agent_ctx.agent.id),
@@ -544,14 +635,21 @@ async def agent_heartbeat(
)
@router.get("/boards/{board_id}/agents/{agent_id}/soul", response_model=str)
@router.get(
"/boards/{board_id}/agents/{agent_id}/soul",
response_model=str,
tags=AGENT_BOARD_TAGS,
)
async def get_agent_soul(
agent_id: str,
board: Board = BOARD_DEP,
session: AsyncSession = SESSION_DEP,
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
) -> str:
"""Fetch the target agent's SOUL.md content from the gateway."""
"""Fetch an agent's SOUL.md content.
Allowed for board lead, or for an agent reading its own SOUL.
"""
_guard_board_access(agent_ctx, board)
OpenClawAuthorizationPolicy.require_board_lead_or_same_actor(
actor_agent=agent_ctx.agent,
@@ -565,7 +663,11 @@ async def get_agent_soul(
)
@router.put("/boards/{board_id}/agents/{agent_id}/soul", response_model=OkResponse)
@router.put(
"/boards/{board_id}/agents/{agent_id}/soul",
response_model=OkResponse,
tags=AGENT_LEAD_TAGS,
)
async def update_agent_soul(
agent_id: str,
payload: SoulUpdateRequest,
@@ -573,7 +675,10 @@ async def update_agent_soul(
session: AsyncSession = SESSION_DEP,
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
) -> OkResponse:
"""Update an agent's SOUL.md content in DB and gateway."""
"""Update an agent's SOUL.md template in DB and gateway.
Lead-only endpoint. Persists as `soul_template` for future reprovisioning.
"""
_guard_board_access(agent_ctx, board)
_require_board_lead(agent_ctx)
coordination = GatewayCoordinationService(session)
@@ -589,14 +694,21 @@ async def update_agent_soul(
return OkResponse()
@router.delete("/boards/{board_id}/agents/{agent_id}", response_model=OkResponse)
@router.delete(
"/boards/{board_id}/agents/{agent_id}",
response_model=OkResponse,
tags=AGENT_LEAD_TAGS,
)
async def delete_board_agent(
agent_id: str,
board: Board = BOARD_DEP,
session: AsyncSession = SESSION_DEP,
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
) -> OkResponse:
"""Delete a board agent as the board lead."""
"""Delete a board agent as board lead.
Cleans up runtime/session state through lifecycle services.
"""
_guard_board_access(agent_ctx, board)
_require_board_lead(agent_ctx)
service = AgentLifecycleService(session)
@@ -609,6 +721,7 @@ async def delete_board_agent(
@router.post(
"/boards/{board_id}/gateway/main/ask-user",
response_model=GatewayMainAskUserResponse,
tags=AGENT_LEAD_TAGS,
)
async def ask_user_via_gateway_main(
payload: GatewayMainAskUserRequest,
@@ -616,7 +729,10 @@ async def ask_user_via_gateway_main(
session: AsyncSession = SESSION_DEP,
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
) -> GatewayMainAskUserResponse:
"""Route a lead's ask-user request through the dedicated gateway agent."""
"""Ask the human via gateway-main external channels.
Lead-only endpoint for situations where board chat is not responsive.
"""
_guard_board_access(agent_ctx, board)
_require_board_lead(agent_ctx)
coordination = GatewayCoordinationService(session)
@@ -630,6 +746,7 @@ async def ask_user_via_gateway_main(
@router.post(
"/gateway/boards/{board_id}/lead/message",
response_model=GatewayLeadMessageResponse,
tags=AGENT_MAIN_TAGS,
)
async def message_gateway_board_lead(
board_id: UUID,
@@ -637,7 +754,7 @@ async def message_gateway_board_lead(
session: AsyncSession = SESSION_DEP,
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
) -> GatewayLeadMessageResponse:
"""Send a gateway-main message to a single board lead agent."""
"""Send a gateway-main control message to one board lead."""
coordination = GatewayCoordinationService(session)
return await coordination.message_gateway_board_lead(
actor_agent=agent_ctx.agent,
@@ -649,13 +766,14 @@ async def message_gateway_board_lead(
@router.post(
"/gateway/leads/broadcast",
response_model=GatewayLeadBroadcastResponse,
tags=AGENT_MAIN_TAGS,
)
async def broadcast_gateway_lead_message(
payload: GatewayLeadBroadcastRequest,
session: AsyncSession = SESSION_DEP,
agent_ctx: AgentAuthContext = AGENT_CTX_DEP,
) -> GatewayLeadBroadcastResponse:
"""Broadcast a gateway-main message to multiple board leads."""
"""Broadcast a gateway-main control message to multiple board leads."""
coordination = GatewayCoordinationService(session)
return await coordination.broadcast_gateway_lead_message(
actor_agent=agent_ctx.agent,

View File

@@ -4,9 +4,11 @@ from __future__ import annotations
import asyncio
import json
from enum import Enum
from dataclasses import dataclass
from datetime import UTC, datetime
from typing import TYPE_CHECKING
from typing import cast
from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
@@ -68,6 +70,7 @@ ACTOR_DEP = Depends(require_admin_or_agent)
IS_CHAT_QUERY = Query(default=None)
SINCE_QUERY = Query(default=None)
_RUNTIME_TYPE_REFERENCES = (UUID,)
AGENT_BOARD_ROLE_TAGS = cast("list[str | Enum]", ["agent-lead", "agent-worker"])
def _parse_since(value: str | None) -> datetime | None:
@@ -402,14 +405,21 @@ async def create_board_group_memory(
return memory
@board_router.get("", response_model=DefaultLimitOffsetPage[BoardGroupMemoryRead])
@board_router.get(
"",
response_model=DefaultLimitOffsetPage[BoardGroupMemoryRead],
tags=AGENT_BOARD_ROLE_TAGS,
)
async def list_board_group_memory_for_board(
*,
is_chat: bool | None = IS_CHAT_QUERY,
board: Board = BOARD_READ_DEP,
session: AsyncSession = SESSION_DEP,
) -> LimitOffsetPage[BoardGroupMemoryRead]:
"""List memory entries for the board's linked group."""
"""List shared memory for the board's linked group.
Use this for cross-board context and coordination signals.
"""
group_id = board.board_group_id
if group_id is None:
return await paginate(session, BoardGroupMemory.objects.by_ids([]).statement)
@@ -426,7 +436,7 @@ async def list_board_group_memory_for_board(
return await paginate(session, queryset.statement)
@board_router.get("/stream")
@board_router.get("/stream", tags=AGENT_BOARD_ROLE_TAGS)
async def stream_board_group_memory_for_board(
request: Request,
*,
@@ -434,7 +444,7 @@ async def stream_board_group_memory_for_board(
since: str | None = SINCE_QUERY,
is_chat: bool | None = IS_CHAT_QUERY,
) -> EventSourceResponse:
"""Stream memory entries for the board's linked group."""
"""Stream linked-group memory via SSE for near-real-time coordination."""
group_id = board.board_group_id
since_dt = _parse_since(since) or utcnow()
last_seen = since_dt
@@ -463,14 +473,18 @@ async def stream_board_group_memory_for_board(
return EventSourceResponse(event_generator(), ping=15)
@board_router.post("", response_model=BoardGroupMemoryRead)
@board_router.post("", response_model=BoardGroupMemoryRead, tags=AGENT_BOARD_ROLE_TAGS)
async def create_board_group_memory_for_board(
payload: BoardGroupMemoryCreate,
board: Board = BOARD_WRITE_DEP,
session: AsyncSession = SESSION_DEP,
actor: ActorContext = ACTOR_DEP,
) -> BoardGroupMemory:
"""Create a group memory entry from a board context and notify recipients."""
"""Create shared group memory from a board context.
When tags/mentions indicate chat or broadcast intent, eligible agents in the
linked group are notified.
"""
group_id = board.board_group_id
if group_id is None:
raise HTTPException(

View File

@@ -2,7 +2,9 @@
from __future__ import annotations
from enum import Enum
from typing import TYPE_CHECKING, Literal
from typing import cast
from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, Query, status
@@ -56,6 +58,7 @@ BOARD_GROUP_ID_QUERY = Query(default=None)
INCLUDE_SELF_QUERY = Query(default=False)
INCLUDE_DONE_QUERY = Query(default=False)
PER_BOARD_TASK_LIMIT_QUERY = Query(default=5, ge=0, le=100)
AGENT_BOARD_ROLE_TAGS = cast("list[str | Enum]", ["agent-lead", "agent-worker"])
async def _require_gateway(
@@ -393,7 +396,11 @@ async def get_board_snapshot(
return await build_board_snapshot(session, board)
@router.get("/{board_id}/group-snapshot", response_model=BoardGroupSnapshot)
@router.get(
"/{board_id}/group-snapshot",
response_model=BoardGroupSnapshot,
tags=AGENT_BOARD_ROLE_TAGS,
)
async def get_board_group_snapshot(
*,
include_self: bool = INCLUDE_SELF_QUERY,
@@ -402,7 +409,10 @@ async def get_board_group_snapshot(
board: Board = BOARD_ACTOR_READ_DEP,
session: AsyncSession = SESSION_DEP,
) -> BoardGroupSnapshot:
"""Get a grouped snapshot across related boards."""
"""Get a grouped snapshot across related boards.
Returns high-signal cross-board status for dependency and overlap checks.
"""
return await build_board_group_snapshot(
session,
board=board,

View File

@@ -38,6 +38,36 @@ if TYPE_CHECKING:
configure_logging()
logger = get_logger(__name__)
OPENAPI_TAGS = [
{
"name": "agent",
"description": (
"Agent-scoped API surface. All endpoints require `X-Agent-Token` and are "
"constrained by agent board access policies."
),
},
{
"name": "agent-lead",
"description": (
"Lead workflows: delegation, review orchestration, approvals, and "
"coordination actions."
),
},
{
"name": "agent-worker",
"description": (
"Worker workflows: task execution, task comments, and board/group context "
"reads/writes used during heartbeat loops."
),
},
{
"name": "agent-main",
"description": (
"Gateway-main control workflows that message board leads or broadcast "
"coordination requests."
),
},
]
@asynccontextmanager
@@ -56,7 +86,12 @@ async def lifespan(_: FastAPI) -> AsyncIterator[None]:
logger.info("app.lifecycle.stopped")
app = FastAPI(title="Mission Control API", version="0.1.0", lifespan=lifespan)
app = FastAPI(
title="Mission Control API",
version="0.1.0",
lifespan=lifespan,
openapi_tags=OPENAPI_TAGS,
)
origins = [o.strip() for o in settings.cors_origins.split(",") if o.strip()]
if origins:

View File

@@ -55,6 +55,7 @@ DEFAULT_GATEWAY_FILES = frozenset(
{
"AGENTS.md",
"SOUL.md",
"LEAD_PLAYBOOK.md",
"TASK_SOUL.md",
"SELF.md",
"AUTONOMY.md",