tracker/README.md
Markov bbbc65bc48 docs: add OpenClaw integration architecture
- OpenClawClient Python class
- Agent model with status tracking
- Task assignment flow
- Callback endpoint
2026-02-15 15:41:37 +01:00

6.4 KiB

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

Конфигурация

# OpenClaw
OPENCLAW_URL=http://localhost:18789
OPENCLAW_TOKEN=team-board-secret-token

OpenClaw Client

# 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

Модели

# 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

# 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

Запуск

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

Переменные окружения

# 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