docs: обновлён WebSocket протокол, убраны адаптеры, capabilities на агентах

This commit is contained in:
Markov 2026-02-15 22:45:21 +01:00
parent 5061c60b9a
commit 7849a1fddb

374
IDEAS.md
View File

@ -61,9 +61,9 @@
| Компонент | Описание | | Компонент | Описание |
|-----------|----------| |-----------|----------|
| **Tracker** | Ядро: проекты, задачи, чаты, события. Внутренний, WebSocket сервер | | **Tracker** | Ядро: проекты, задачи, чаты, события. Внутренний, WebSocket сервер. Роутинг событий агентам |
| **Web Client** | Next.js + BFF (Python). Авторизация через Authentik. Единственное что торчит наружу | | **Web Client** | Next.js + BFF (Python). Авторизация через Authentik. Единственное что торчит наружу |
| **Агенты** | Отдельные сервисы. Подключаются к трекеру по WebSocket | | **Агенты** | Независимые приложения (любой язык). Подключаются к Tracker по WebSocket |
### Репозитории ### Репозитории
@ -221,48 +221,15 @@ Task A ──depends_on──→ Task B
--- ---
## Агенты и адаптеры ## Агенты
### Адаптер (провайдер) ### Принцип
Адаптер — это подключение к конкретной нейросети. Один адаптер может использоваться несколькими агентами. **Агент = независимое приложение.** Написано на чём угодно (Python, Go, Rust, JS — без разницы). Подключается к Tracker по WebSocket. Может быть даже человек с WebSocket-клиентом.
```yaml Tracker не знает и не заботится, что внутри агента — нейросеть, скрипт или человек.
Адаптер:
id: uuid
название: "Claude Opus"
провайдер: anthropic | openai | google | openclaw | cli
# Конфиг — JSON поле, разное для каждого провайдера ### Структура агента
config:
# Anthropic
api_key: "sk-ant-..."
model: "claude-opus-4-5"
max_tokens: 8192
# Или OpenAI
api_key: "sk-..."
model: "gpt-4"
organization: "org-..."
# Или OpenClaw
gateway_url: "http://localhost:18789"
gateway_token: "..."
agent_id: "main"
# Или CLI
command: "claude"
working_dir: "/workspace"
# Возможности адаптера (что умеет эта нейросеть)
capabilities: ["coding", "analytics", "review", "design", "docs", "testing"]
```
**Важно:** Capabilities привязаны к адаптеру, не к агенту. Потому что возможности определяются нейросетью.
### Агент
Агент — это "личность" с ролью, которая использует адаптер.
```yaml ```yaml
Агент: Агент:
@ -270,20 +237,21 @@ Task A ──depends_on──→ Task B
имя: "Кодер" имя: "Кодер"
slug: "coder" slug: "coder"
# Привязка к адаптеру # Capabilities — что умеет этот агент
адаптер_id: uuid capabilities: ["coding", "backend", "testing"]
# Роль
системный_промпт: "Ты — backend разработчик..."
# Подписка # Подписка
подписка: подписка:
режим: mentions # all | mentions | assigned режим: assigned # all | mentions | assigned
проекты: ["*"] # или конкретные проекты: ["*"] # или конкретные
# Ограничения # Ограничения
макс_параллельных: 2 макс_параллельных: 2
таймаут: 600 # секунд таймаут: 600 # секунд
# Статус (управляется Tracker)
status: online | offline | busy
last_heartbeat: timestamp
``` ```
### Лейблы ### Лейблы
@ -301,30 +269,20 @@ CREATE TABLE labels (
Примеры: `coding`, `design`, `analytics`, `testing`, `docs`, `backend`, `frontend`, `urgent`, `bug` Примеры: `coding`, `design`, `analytics`, `testing`, `docs`, `backend`, `frontend`, `urgent`, `bug`
### Связка: лейблы задач ↔ capabilities адаптеров ### Связка: лейблы задач ↔ capabilities агентов
``` ```
Задача с лейблом "coding" → только агенты с адаптером, у которого capability "coding" Задача с лейблом "coding" → только агенты с capability "coding"
Задача с лейблом "design" → только агенты с адаптером, у которого capability "design" Задача с лейблом "design" → только агенты с capability "design"
``` ```
Агент-дизайнер НЕ возьмёт задачу с лейблом `coding`у его адаптера нет такой capability. Агент-дизайнер НЕ возьмёт задачу с лейблом `coding`у него нет такой capability.
### Типы провайдеров
| Провайдер | Описание | Конфиг |
|-----------|----------|--------|
| `anthropic` | Claude API | api_key, model, max_tokens |
| `openai` | GPT, Codex | api_key, model, organization |
| `google` | Gemini | api_key, model |
| `openclaw` | OpenClaw Gateway | gateway_url, gateway_token, agent_id |
| `cli` | Claude Code CLI | command, working_dir |
### Подписка на события ### Подписка на события
| Режим | Агент получает | | Режим | Агент получает |
|-------|---------------| |-------|---------------|
| `all` | Все сообщения в подписанных проектах | | `all` | Все события в подписанных проектах |
| `mentions` | Только когда @упомянут | | `mentions` | Только когда @упомянут |
| `assigned` | Назначенные задачи + mentions | | `assigned` | Назначенные задачи + mentions |
@ -332,30 +290,108 @@ CREATE TABLE labels (
## WebSocket протокол ## WebSocket протокол
### Трекер → Клиент ### Подключение и Init Handshake
При подключении агент проходит аутентификацию и получает начальный контекст:
``` ```
task.created — задача создана 1. Агент подключается: ws://tracker:8100/ws
task.updated — задача обновлена 2. Агент → Tracker: auth
task.ready — задача готова к выполнению (перенесена из Backlog) {
"type": "auth",
"token": "agent-token-xxx"
}
3. Tracker → Агент: auth.ok + init (начальный контекст)
{
"type": "auth.ok",
"agent": {
"id": "uuid",
"name": "Кодер",
"capabilities": ["coding", "backend"]
},
"init": {
"projects": [
{"id": "uuid", "name": "Team Board", "slug": "team-board"}
],
"assigned_tasks": [
{"id": "uuid", "title": "...", "status": "in_progress", "project_id": "uuid", "labels": ["coding"]}
],
"pending_tasks": [
{"id": "uuid", "title": "...", "status": "ready", "project_id": "uuid", "labels": ["coding"]}
]
}
}
4. При ошибке:
{
"type": "auth.error",
"message": "Invalid token"
}
→ соединение закрывается
```
**init** содержит:
- **projects** — проекты, в которых участвует агент
- **assigned_tasks** — задачи, назначенные на этого агента (in_progress, review)
- **pending_tasks** — задачи, подходящие по capabilities и готовые к взятию (ready)
### Роутинг событий (Tracker → Агент)
Tracker фильтрует события по трём критериям:
**1. Capabilities** — лейблы задачи ∩ capabilities агента
- Задача с лейблом `coding` → только агентам с capability `coding`
- Нет совпадения → событие не отправляется
**2. Подписка агента** (subscription mode)
- `assigned` → только свои задачи + @mentions
- `mentions`@mentions в чатах и комментариях
- `all` → все события (фильтруются по capabilities)
**3. Чаты** — отдельная логика:
- **Чат задачи** → assignee + участники обсуждения
- **Чат проекта** → все агенты проекта (без фильтра по capabilities — обсуждение для всех)
- **Лобби** → все подключённые агенты
### Трекер → Клиент (события)
```
task.created — задача создана (с учётом роутинга)
task.updated — задача обновлена (статус, описание, лейблы)
task.ready — задача готова к выполнению
task.assigned — задача назначена агенту task.assigned — задача назначена агенту
task.blocked — задача заблокирована зависимостью task.blocked — задача заблокирована зависимостью
task.unblocked — зависимость выполнена, задача разблокирована task.unblocked — зависимость выполнена, задача разблокирована
chat.message — новое сообщение в чате chat.message — новое сообщение в чате (с учётом роутинга)
agent.status — статус агента изменился agent.status — статус агента изменился (online/offline/busy)
file.attached — файл прикреплён к задаче file.attached — файл прикреплён к задаче
``` ```
### Клиент → Трекер ### Клиент → Трекер (команды)
``` ```
auth — аутентификация (первое сообщение)
task.take — агент берёт задачу task.take — агент берёт задачу
task.complete — агент завершил задачу task.complete — агент завершил задачу
task.update — агент обновляет задачу (статус, описание)
task.comment — комментарий к задаче task.comment — комментарий к задаче
task.attach — прикрепить файл task.attach — прикрепить файл
chat.send — отправить сообщение в чат chat.send — отправить сообщение в чат
agent.register — регистрация агента agent.heartbeat — пульс (каждые 30 сек)
agent.heartbeat — пульс ```
### Heartbeat
```
Агент → Tracker (каждые 30 сек):
{
"type": "agent.heartbeat",
"status": "idle" | "busy",
"current_tasks": ["task-uuid-1"]
}
Если heartbeat не пришёл 90 секунд → agent.status = offline
``` ```
--- ---
@ -463,27 +499,18 @@ review_history:
## База данных (ключевые таблицы) ## База данных (ключевые таблицы)
```sql ```sql
-- Адаптеры (подключения к нейросетям) -- Агенты (независимые приложения)
CREATE TABLE adapters (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
provider TEXT NOT NULL, -- anthropic, openai, google, openclaw, cli
config JSONB NOT NULL, -- всё специфичное для провайдера
capabilities TEXT[] NOT NULL, -- ["coding", "analytics", "review", ...]
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Агенты (личности с ролями)
CREATE TABLE agents ( CREATE TABLE agents (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL, name TEXT NOT NULL,
slug TEXT UNIQUE NOT NULL, slug TEXT UNIQUE NOT NULL,
adapter_id UUID REFERENCES adapters(id), token TEXT UNIQUE NOT NULL, -- токен для WebSocket auth
system_prompt TEXT, capabilities TEXT[] NOT NULL DEFAULT '{}', -- ["coding", "analytics", ...]
subscription_mode TEXT DEFAULT 'mentions', -- all, mentions, assigned subscription_mode TEXT DEFAULT 'assigned', -- all, mentions, assigned
max_concurrent INT DEFAULT 1, max_concurrent INT DEFAULT 1,
timeout_seconds INT DEFAULT 600, timeout_seconds INT DEFAULT 600,
status TEXT DEFAULT 'idle', status TEXT DEFAULT 'offline', -- online, offline, busy
last_heartbeat TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW() created_at TIMESTAMPTZ DEFAULT NOW()
); );
@ -544,176 +571,59 @@ CREATE TABLE task_files (
### Принцип ### Принцип
**Агент = отдельный процесс.** Не привязан к OpenClaw или конкретному серверу. Может быть запущен локально, на другом сервере, в Docker — где угодно. Подключается к Tracker по WebSocket. **Агент = независимое приложение.** Написано на чём угодно. Подключается к Tracker по WebSocket. Tracker не управляет запуском — агент сам стартует и подключается.
### Архитектура
```
┌─────────────────────────┐
│ Agent Process │
│ ┌───────────────────┐ │
│ │ Agent SDK │ │ ← общая Python-библиотека
│ │ ┌──────────────┐ │ │
│ │ │ Adapter │ │ │ ← OpenClaw / OpenAI / Ollama / CLI
│ │ └──────────────┘ │ │
│ └─────────┬─────────┘ │
└────────────┼────────────┘
│ WebSocket (локально или через интернет)
┌──────┴──────┐
│ Tracker │
│ AgentManager │
└─────────────┘
```
### Agent SDK (Python-пакет)
Общая библиотека для всех агентов:
```python
from team_board.agent import Agent
from team_board.adapters import OpenClawAdapter
adapter = OpenClawAdapter(
gateway_url="http://localhost:18789",
gateway_token="...",
)
agent = Agent(
name="Кодер",
tracker_url="ws://localhost:8100",
adapter=adapter,
system_prompt="Ты — backend разработчик...",
)
agent.run() # подключается к Tracker, слушает задачи
```
Что делает SDK:
- **WebSocket клиент** → подключение к Tracker, переподключение при разрыве
- **Heartbeat** → автоматический пинг каждые N секунд
- **Обработка задач** → получил задачу → передал адаптеру → вернул результат
- **Файловые операции** → загрузка/скачивание файлов задачи
### Адаптеры (BaseAdapter)
```python
class BaseAdapter(ABC):
@abstractmethod
async def send(self, messages: list[Message]) -> Response:
"""Отправить сообщения нейросети и получить ответ."""
...
@abstractmethod
async def capabilities(self) -> list[str]:
"""Возвращает capabilities адаптера."""
...
```
Реализации:
| Адаптер | Подключение | Особенности |
|---------|-------------|-------------|
| `OpenClawAdapter` | Webhooks / sessions_spawn | Доступ к инструментам OpenClaw |
| `AnthropicAdapter` | Claude API напрямую | Чистый API, без инструментов |
| `OpenAIAdapter` | GPT / Codex API | |
| `OllamaAdapter` | Локальная модель | Бесплатно, офлайн |
| `CLIAdapter` | Claude Code CLI | Доступ к файловой системе |
### AgentManager (в Tracker) ### AgentManager (в Tracker)
Tracker управляет жизненным циклом агентов: Tracker отслеживает подключённых агентов:
```yaml ```yaml
Реестр агентов: Реестр агентов:
- id: uuid - id: uuid
name: "Кодер" name: "Кодер"
adapter: "Claude Opus" status: online | offline | busy
status: online | offline | busy | dead capabilities: ["coding", "backend"]
host: "localhost" # где запущен
pid: 12345 # PID процесса (если локальный)
last_heartbeat: timestamp last_heartbeat: timestamp
current_tasks: [task_id] current_tasks: [task_id]
``` ```
Функции AgentManager: Функции AgentManager:
1. **Запуск** — поднимает агента как отдельный процесс (локально: subprocess/systemd) 1. **Регистрация** — агент подключился → auth → попал в реестр
2. **Мониторинг** — следит за heartbeat, помечает dead если пропал 2. **Мониторинг** — следит за heartbeat, помечает offline если пропал
3. **Перезапуск** — dead агент → повторный запуск (макс 3 попытки) 3. **Назначение задач** — матчит задачу → подходящего агента по capabilities + загрузке
4. **Назначение задач** — матчит задачу → подходящего агента по capabilities + загрузке 4. **Роутинг событий** — фильтрация по capabilities + подписке + чатам
5. **Остановка** — graceful shutdown через WebSocket команду
### Запуск агентов
**Локально (MVP):**
```bash
# Tracker запускает как subprocess
python -m team_board.agent --name coder --tracker ws://localhost:8100
```
**Через systemd (production):**
```ini
[Unit]
Description=Team Board Agent: Coder
[Service]
ExecStart=/opt/team-board/venv/bin/python -m team_board.agent --name coder
Restart=on-failure
RestartSec=5
```
**Удалённо (будущее):**
- SSH: `ssh remote-host 'python -m team_board.agent --tracker ws://tracker:8100'`
- Docker: `docker run team-board-agent --tracker ws://tracker:8100`
- HTTP API на удалённом хосте
### Heartbeat и мониторинг
```
Агент → Tracker: agent.heartbeat (каждые 30 сек)
{
status: "idle" | "busy",
current_tasks: [...],
uptime: 3600,
memory_mb: 150
}
Если heartbeat не пришёл 90 секунд → статус = dead → перезапуск
```
### Таблица БД
```sql
-- Расширение таблицы agents
ALTER TABLE agents ADD COLUMN host TEXT DEFAULT 'localhost';
ALTER TABLE agents ADD COLUMN pid INT;
ALTER TABLE agents ADD COLUMN last_heartbeat TIMESTAMPTZ;
ALTER TABLE agents ADD COLUMN restart_count INT DEFAULT 0;
ALTER TABLE agents ADD COLUMN max_restarts INT DEFAULT 3;
```
--- ---
## Открытые вопросы ## Открытые вопросы
1. Как интегрировать CLI-агентов (Claude Code, Cline)? 1. Workspace для агентов — общий или изолированный?
2. Workspace для агентов — общий или изолированный? 2. Прогресс работы агента в реалтайме?
3. Прогресс работы агента в реалтайме? 3. Биллинг/учёт использования API?
4. Биллинг/учёт использования API?
5. Шаблоны агентов — как быстро создать нового?
--- ---
## Приоритеты реализации ## Приоритеты реализации
1. [ ] Tracker: базовый WebSocket сервер ### Сделано ✅
2. [ ] Tracker: проекты, задачи, подзадачи 1. [x] Tracker: REST API (проекты, задачи, агенты, лейблы)
3. [ ] Tracker: зависимости задач 2. [x] Tracker: базовый WebSocket (connection manager, heartbeat)
4. [ ] Tracker: чаты (лобби + проект) 3. [x] Tracker: Docker Compose (postgres + redis + tracker)
5. [ ] Tracker: файлы в задачах 4. [x] Web Client: канбан-доска с drag-and-drop
6. [ ] Web Client: авторизация (Authentik) 5. [x] Web Client: BFF с JWT авторизацией
7. [ ] Web Client: канбан-доска 6. [x] CI/CD: Gitea Actions auto-deploy
8. [ ] Web Client: чат
9. [ ] Шаблон агента ### В работе 🔧
10. [ ] Подключение первого агента (OpenClaw) 7. [ ] Web Client: детальный вид задачи (описание, комменты, assignee)
8. [ ] Tracker: WebSocket протокол v2 (init handshake, роутинг событий)
### Следующее 📋
9. [ ] Tracker: чаты (лобби + проект + задача)
10. [ ] Tracker: файлы в задачах
11. [ ] Web Client: чат
12. [ ] Web Client: управление агентами
13. [ ] Первый агент (OpenClaw → Tracker)
14. [ ] Web Client: Authentik OAuth
--- ---