# Team Board — Backend API сервисы для Team Board. Python + FastAPI, микросервисная архитектура. ## Сервисы | Сервис | Порт | Описание | |--------|------|----------| | gateway | 8000 | API Gateway, аутентификация | | projects | 8001 | Проекты, Git интеграция | | tasks | 8002 | Задачи, канбан, подзадачи | | agents | 8003 | AI агенты, OpenClaw интеграция | | chat | 8004 | Чаты проектов | ## Структура ``` backend/ ├── services/ │ ├── gateway/ │ ├── projects/ │ ├── tasks/ │ ├── agents/ │ └── chat/ ├── shared/ │ └── openclaw_client.py ├── docker-compose.yml └── README.md ``` ## OpenClaw Integration ### Архитектура ``` Team Board (agents service) │ │ HTTP POST /hooks/agent ▼ OpenClaw Gateway (localhost:18789) │ │ sessions_spawn ▼ Субагенты (изолированные сессии) │ │ результат ▼ Callback → Team Board ``` ### Конфигурация ```env # OpenClaw OPENCLAW_URL=http://localhost:18789 OPENCLAW_TOKEN=team-board-secret-token ``` ### OpenClaw Client ```python # shared/openclaw_client.py import httpx from typing import Optional class OpenClawClient: def __init__(self, base_url: str, token: str): self.base_url = base_url self.headers = { "Authorization": f"Bearer {token}", "Content-Type": "application/json" } async def wake(self, text: str, mode: str = "now") -> dict: """Разбудить основную сессию""" async with httpx.AsyncClient() as client: response = await client.post( f"{self.base_url}/hooks/wake", headers=self.headers, json={"text": text, "mode": mode} ) return response.json() async def run_agent( self, message: str, session_key: str, agent_id: str = "main", deliver: bool = False, timeout: int = 300 ) -> dict: """Запустить задачу в изолированной сессии""" async with httpx.AsyncClient() as client: response = await client.post( f"{self.base_url}/hooks/agent", headers=self.headers, json={ "message": message, "name": "TeamBoard", "agentId": agent_id, "sessionKey": session_key, "deliver": deliver, "timeoutSeconds": timeout } ) return response.json() async def get_session_history(self, session_key: str) -> dict: """Получить историю сессии""" async with httpx.AsyncClient() as client: response = await client.get( f"{self.base_url}/api/sessions/{session_key}/history", headers=self.headers ) return response.json() ``` ## Agents Service ### Модели ```python # services/agents/models.py from sqlalchemy import Column, String, Integer, Text, Boolean from sqlalchemy.dialects.postgresql import UUID, TIMESTAMPTZ from shared.database import Base import uuid class Agent(Base): __tablename__ = "agents" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) name = Column(String(100), nullable=False) slug = Column(String(50), unique=True, nullable=False) # Настройки model = Column(String(100), default="claude-opus-4-5") system_prompt = Column(Text) max_concurrent = Column(Integer, default=1) # Статус status = Column(String(20), default="idle") current_tasks = Column(Integer, default=0) is_enabled = Column(Boolean, default=True) # Timestamps created_at = Column(TIMESTAMPTZ, server_default="now()") updated_at = Column(TIMESTAMPTZ, server_default="now()", onupdate="now()") ``` ### API Endpoints ```python # services/agents/routes.py from fastapi import APIRouter, Depends, HTTPException from .models import Agent from .schemas import AgentCreate, AgentUpdate, TaskAssignment from shared.openclaw_client import OpenClawClient router = APIRouter(prefix="/agents", tags=["agents"]) @router.get("/") async def list_agents(): """Список всех агентов""" pass @router.post("/") async def create_agent(data: AgentCreate): """Создать нового агента""" pass @router.put("/{agent_id}") async def update_agent(agent_id: str, data: AgentUpdate): """Обновить агента""" pass @router.post("/{agent_id}/assign") async def assign_task(agent_id: str, data: TaskAssignment): """Назначить задачу агенту""" agent = await Agent.get(agent_id) if not agent.is_enabled: raise HTTPException(400, "Agent is disabled") if agent.current_tasks >= agent.max_concurrent: raise HTTPException(400, "Agent is busy") # Запускаем через OpenClaw client = OpenClawClient(settings.OPENCLAW_URL, settings.OPENCLAW_TOKEN) result = await client.run_agent( message=data.prompt, session_key=f"task:{data.task_id}", agent_id=agent.slug ) # Обновляем статус agent.current_tasks += 1 agent.status = "working" await agent.save() return result @router.post("/callback") async def agent_callback(data: dict): """Callback от OpenClaw после выполнения задачи""" session_key = data.get("sessionKey") # Обновить задачу, уменьшить current_tasks агента pass ``` ## Запуск ```bash # Development cd services/tasks pip install -r requirements.txt uvicorn app:app --reload --port 8002 # Docker docker-compose up -d ``` ## Стек - Python 3.12 - FastAPI - PostgreSQL - Redis - SQLAlchemy - httpx (async HTTP client) ## Переменные окружения ```env # Database DATABASE_URL=postgresql://team_board:password@localhost:5432/team_board # Redis REDIS_URL=redis://localhost:6379 # Auth AUTHENTIK_CLIENT_ID=... AUTHENTIK_CLIENT_SECRET=... # OpenClaw OPENCLAW_URL=http://localhost:18789 OPENCLAW_TOKEN=team-board-secret-token ```