- Agent Registry schema - OpenClaw Webhooks API documentation - Team Board → OpenClaw architecture - Python client example - Task execution flow
667 lines
21 KiB
Markdown
667 lines
21 KiB
Markdown
# Team Board — Research: Agent Integration
|
||
|
||
Исследование возможностей подключения AI-агентов к Team Board.
|
||
|
||
---
|
||
|
||
## Текущие возможности OpenClaw
|
||
|
||
### 1. sessions_spawn — спавн изолированных субагентов
|
||
|
||
```
|
||
Task → Spawn → Isolated Session → Result → Announce back
|
||
```
|
||
|
||
**Параметры:**
|
||
- `task` (required) — задача для агента
|
||
- `label` — метка для логов
|
||
- `agentId` — ID агента (если разрешён в allowlist)
|
||
- `model` — переопределение модели
|
||
- `runTimeoutSeconds` — таймаут
|
||
- `cleanup` — `delete|keep`
|
||
|
||
**Поведение:**
|
||
- Создаёт новую сессию `agent:<agentId>:subagent:<uuid>`
|
||
- Субагенты имеют полный набор инструментов минус session tools
|
||
- Не блокирует: возвращает `{ status: "accepted", runId, childSessionKey }`
|
||
- После завершения — announce в исходный чат
|
||
|
||
---
|
||
|
||
### 2. sessions_send — отправка между сессиями
|
||
|
||
```
|
||
Agent A → sessions_send → Agent B → Response
|
||
```
|
||
|
||
**Параметры:**
|
||
- `sessionKey` (required)
|
||
- `message` (required)
|
||
- `timeoutSeconds` — 0 = fire-and-forget
|
||
|
||
**Поведение:**
|
||
- Поддерживает ping-pong между агентами (до 5 итераций)
|
||
- Контекст сохраняется
|
||
- Провenance маркируется как `inter_session`
|
||
|
||
---
|
||
|
||
### 3. sessions_list / sessions_history
|
||
|
||
- Список активных сессий
|
||
- История сообщений сессии
|
||
- Фильтры по типу, времени, каналу
|
||
|
||
---
|
||
|
||
### 4. Multi-Agent Routing
|
||
|
||
Несколько агентов в одном gateway:
|
||
|
||
```yaml
|
||
agents:
|
||
list:
|
||
- id: main
|
||
workspace: ~/.openclaw/workspace
|
||
- id: coder
|
||
workspace: ~/projects/coder-workspace
|
||
- id: reviewer
|
||
workspace: ~/projects/reviewer-workspace
|
||
```
|
||
|
||
**Каждый агент имеет:**
|
||
- Свой workspace (файлы, AGENTS.md, SOUL.md)
|
||
- Свой state directory
|
||
- Своё хранилище сессий
|
||
- Свои auth profiles
|
||
|
||
---
|
||
|
||
### 5. ACP (Agent Client Protocol)
|
||
|
||
Стандартный протокол для подключения внешних агентов.
|
||
|
||
```
|
||
External Agent ↔ ACP ↔ OpenClaw Gateway
|
||
```
|
||
|
||
**Реализация в OpenClaw:**
|
||
- `src/acp/server.ts` — ACP сервер
|
||
- `src/acp/client.ts` — ACP клиент
|
||
- Использует `@agentclientprotocol/sdk`
|
||
|
||
**Dangerous tools требуют approval:**
|
||
- exec, spawn, shell
|
||
- sessions_spawn, sessions_send
|
||
- gateway
|
||
- fs_write, fs_delete, fs_move
|
||
|
||
---
|
||
|
||
## Варианты интеграции для Team Board
|
||
|
||
### Вариант 1: OpenClaw Multi-Agent (рекомендуется)
|
||
|
||
```
|
||
Team Board API
|
||
↓
|
||
OpenClaw Gateway
|
||
↓
|
||
┌─────────────────────────────────┐
|
||
│ Agent: main (Lead) │
|
||
│ Agent: coder (Developer) │
|
||
│ Agent: reviewer (QA) │
|
||
│ Agent: analyst (Research) │
|
||
└─────────────────────────────────┘
|
||
```
|
||
|
||
**Плюсы:**
|
||
- Готовая инфраструктура
|
||
- Сохранение контекста через сессии
|
||
- Единый API
|
||
- Безопасность (sandbox, permissions)
|
||
- Инструменты (exec, browser, files)
|
||
|
||
**Реализация:**
|
||
1. Добавить агентов в `openclaw.json`
|
||
2. Team Board вызывает `sessions_spawn` / `sessions_send` через Gateway API
|
||
3. Результаты через webhook или polling
|
||
|
||
**Конфиг:**
|
||
```json
|
||
{
|
||
"agents": {
|
||
"list": [
|
||
{
|
||
"id": "main",
|
||
"workspace": "~/.openclaw/workspace"
|
||
},
|
||
{
|
||
"id": "coder",
|
||
"workspace": "~/workspaces/coder",
|
||
"model": "anthropic/claude-sonnet-4-20250514"
|
||
}
|
||
],
|
||
"defaults": {
|
||
"subagents": {
|
||
"allowAgents": ["main", "coder", "reviewer"]
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### Вариант 2: ACP Bridge для CLI агентов
|
||
|
||
Для подключения Claude Code CLI без траты API токенов.
|
||
|
||
```
|
||
Claude Code CLI (Max subscription)
|
||
↓
|
||
ACP Client Wrapper
|
||
↓
|
||
OpenClaw Gateway
|
||
↓
|
||
Team Board
|
||
```
|
||
|
||
**Компоненты:**
|
||
|
||
1. **PTY Session Manager**
|
||
```python
|
||
import pexpect
|
||
|
||
class CliSession:
|
||
def __init__(self, command="claude"):
|
||
self.child = pexpect.spawn(command)
|
||
|
||
def send(self, message: str) -> str:
|
||
self.child.sendline(message)
|
||
self.child.expect(r'\n>') # wait for prompt
|
||
return self.child.before.decode()
|
||
```
|
||
|
||
2. **ACP Translator**
|
||
```python
|
||
class AcpCliBridge:
|
||
def handle_request(self, request):
|
||
response = self.cli_session.send(request.message)
|
||
return AcpResponse(content=response)
|
||
```
|
||
|
||
**Сложности:**
|
||
- CLI интерактивный, не headless
|
||
- Нужно парсить вывод
|
||
- Контекст внутри CLI сессии
|
||
|
||
---
|
||
|
||
### Вариант 3: Direct API + Context Store
|
||
|
||
Прямые вызовы API с хранением контекста в БД.
|
||
|
||
```
|
||
Team Board
|
||
↓
|
||
Agent Service
|
||
↓
|
||
┌──────────────────────────────────┐
|
||
│ Claude API │ Codex │ Gemini │
|
||
└──────────────────────────────────┘
|
||
↓
|
||
Context Store (PostgreSQL)
|
||
```
|
||
|
||
**Схема контекста:**
|
||
```sql
|
||
CREATE TABLE agent_contexts (
|
||
id UUID PRIMARY KEY,
|
||
agent_id UUID REFERENCES agents(id),
|
||
session_id UUID REFERENCES sessions(id),
|
||
messages JSONB, -- история сообщений
|
||
metadata JSONB, -- system prompt, tools, etc.
|
||
created_at TIMESTAMPTZ,
|
||
updated_at TIMESTAMPTZ
|
||
);
|
||
```
|
||
|
||
**При каждом запросе:**
|
||
1. Загрузить контекст из БД
|
||
2. Добавить новое сообщение
|
||
3. Вызвать API с полным контекстом
|
||
4. Сохранить ответ в БД
|
||
|
||
---
|
||
|
||
## Web Terminal Interface
|
||
|
||
### Архитектура
|
||
|
||
```
|
||
Browser (xterm.js)
|
||
↓ WebSocket
|
||
Team Board Frontend
|
||
↓ WebSocket
|
||
Team Board Backend (Session Manager)
|
||
↓ PTY / API
|
||
Agent (OpenClaw / CLI / API)
|
||
```
|
||
|
||
### Компоненты
|
||
|
||
**Frontend:**
|
||
- xterm.js — терминальный эмулятор
|
||
- WebSocket клиент
|
||
- Session selector UI
|
||
|
||
**Backend:**
|
||
- WebSocket сервер
|
||
- Session Manager
|
||
- PTY spawner (для CLI агентов)
|
||
- OpenClaw Gateway client (для OpenClaw агентов)
|
||
|
||
### Пример кода (Backend)
|
||
|
||
```python
|
||
from fastapi import WebSocket
|
||
import asyncio
|
||
|
||
class AgentSessionManager:
|
||
def __init__(self):
|
||
self.sessions = {}
|
||
|
||
async def create_session(self, agent_type: str, config: dict):
|
||
if agent_type == "openclaw":
|
||
return OpenClawSession(config)
|
||
elif agent_type == "cli":
|
||
return CliSession(config)
|
||
elif agent_type == "api":
|
||
return ApiSession(config)
|
||
|
||
async def handle_websocket(self, ws: WebSocket, session_id: str):
|
||
session = self.sessions[session_id]
|
||
|
||
async for message in ws.iter_text():
|
||
response = await session.send(message)
|
||
await ws.send_text(response)
|
||
```
|
||
|
||
---
|
||
|
||
## Сравнение вариантов
|
||
|
||
| Критерий | OpenClaw Multi-Agent | ACP CLI Bridge | Direct API |
|
||
|----------|---------------------|----------------|------------|
|
||
| Сложность | Низкая | Высокая | Средняя |
|
||
| Контекст | ✓ Автоматический | ✓ В CLI | Ручной |
|
||
| Инструменты | ✓ Полный набор | ✓ CLI tools | ✗ Нет |
|
||
| API токены | Нужны | Не нужны (Max) | Нужны |
|
||
| Готовность | Сейчас | Требует разработки | Требует разработки |
|
||
|
||
---
|
||
|
||
## Рекомендуемый план
|
||
|
||
### Фаза 1: OpenClaw Multi-Agent (быстрый старт)
|
||
|
||
1. Добавить агентов в конфиг OpenClaw
|
||
2. Team Board API вызывает Gateway
|
||
3. Использовать `sessions_spawn` для задач
|
||
4. Использовать `sessions_send` для коммуникации
|
||
|
||
**Результат:** Рабочая система с несколькими агентами
|
||
|
||
### Фаза 2: Web Terminal UI
|
||
|
||
1. xterm.js на фронте
|
||
2. WebSocket сервер
|
||
3. Прямое взаимодействие с агентами
|
||
|
||
**Результат:** Terminal-like опыт в браузере
|
||
|
||
### Фаза 3: ACP CLI Bridge (опционально)
|
||
|
||
1. PTY wrapper для Claude Code CLI
|
||
2. ACP транслятор
|
||
3. Интеграция с Gateway
|
||
|
||
**Результат:** CLI агенты без API токенов
|
||
|
||
---
|
||
|
||
## Открытые вопросы
|
||
|
||
1. **Headless mode в Claude Code CLI?**
|
||
- Нужно проверить `claude --help`
|
||
- Возможно есть `--message` или `--non-interactive`
|
||
|
||
2. **Как шарить файлы между агентами?**
|
||
- Общий workspace?
|
||
- Git как source of truth?
|
||
- Копирование через API?
|
||
|
||
3. **Rate limiting для API агентов?**
|
||
- Очередь задач
|
||
- Приоритеты
|
||
- Backoff при ошибках
|
||
|
||
4. **Мониторинг агентов?**
|
||
- Статус (idle/working/error)
|
||
- Использование токенов
|
||
- Время выполнения
|
||
|
||
---
|
||
|
||
## Agent Management System
|
||
|
||
### Agent Registry (БД)
|
||
|
||
```sql
|
||
CREATE TABLE agents (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
name TEXT NOT NULL, -- "Coder", "Analyst"
|
||
slug TEXT UNIQUE NOT NULL, -- "coder", "analyst"
|
||
|
||
-- Настройки
|
||
model TEXT, -- "claude-opus-4-5", "claude-sonnet"
|
||
system_prompt TEXT, -- роль/персона
|
||
max_concurrent INT DEFAULT 1, -- макс параллельных задач
|
||
|
||
-- Статус
|
||
status TEXT DEFAULT 'idle', -- idle, working, error, disabled
|
||
current_tasks INT DEFAULT 0,
|
||
|
||
-- Мета
|
||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||
);
|
||
```
|
||
|
||
### Web UI: Agent Config
|
||
|
||
```
|
||
┌─────────────────────────────────────────────┐
|
||
│ Agents [+ Add] │
|
||
├─────────────────────────────────────────────┤
|
||
│ 🟢 Coder claude-opus idle [⚙] │
|
||
│ 🟢 Reviewer claude-sonnet idle [⚙] │
|
||
│ 🟡 Analyst claude-opus working[⚙] │
|
||
│ ⚫ Tester claude-sonnet disabled[⚙] │
|
||
└─────────────────────────────────────────────┘
|
||
```
|
||
|
||
### Task Assignment Flow
|
||
|
||
```
|
||
1. User создаёт задачу в Web UI
|
||
2. Назначает агента (или Auto → Lead решает)
|
||
3. Team Board → POST /hooks/agent → OpenClaw
|
||
4. OpenClaw проверяет лимиты, спавнит субагента
|
||
5. Субагент работает, результат → callback
|
||
6. Team Board обновляет задачу в БД
|
||
```
|
||
|
||
---
|
||
|
||
## OpenClaw Webhooks API
|
||
|
||
### Конфигурация
|
||
|
||
```json
|
||
{
|
||
"hooks": {
|
||
"enabled": true,
|
||
"token": "${OPENCLAW_HOOKS_TOKEN}",
|
||
"path": "/hooks",
|
||
"defaultSessionKey": "hook:team-board",
|
||
"allowRequestSessionKey": true,
|
||
"allowedSessionKeyPrefixes": ["hook:", "task:"],
|
||
"allowedAgentIds": ["main", "coder", "reviewer"]
|
||
}
|
||
}
|
||
```
|
||
|
||
### Эндпоинты
|
||
|
||
#### POST /hooks/wake — Разбудить основную сессию
|
||
|
||
```bash
|
||
curl -X POST http://localhost:18789/hooks/wake \
|
||
-H "Authorization: Bearer $TOKEN" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"text": "Новая задача в Team Board", "mode": "now"}'
|
||
```
|
||
|
||
**Параметры:**
|
||
- `text` (required) — описание события
|
||
- `mode` — `now` (сразу) или `next-heartbeat` (при след. проверке)
|
||
|
||
#### POST /hooks/agent — Запустить изолированную сессию
|
||
|
||
```bash
|
||
curl -X POST http://localhost:18789/hooks/agent \
|
||
-H "Authorization: Bearer $TOKEN" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"message": "Напиши функцию сортировки на Python",
|
||
"name": "TeamBoard",
|
||
"agentId": "main",
|
||
"sessionKey": "task:abc123",
|
||
"deliver": true,
|
||
"channel": "telegram",
|
||
"timeoutSeconds": 300
|
||
}'
|
||
```
|
||
|
||
**Параметры:**
|
||
- `message` (required) — промпт для агента
|
||
- `name` — название хука для логов
|
||
- `agentId` — ID агента (main, coder, etc.)
|
||
- `sessionKey` — ключ сессии для отслеживания
|
||
- `deliver` — отправить ответ в канал
|
||
- `channel` — канал для ответа (telegram, discord, etc.)
|
||
- `model` — переопределение модели
|
||
- `thinking` — уровень размышлений (low, medium, high)
|
||
- `timeoutSeconds` — таймаут выполнения
|
||
|
||
### Аутентификация
|
||
|
||
```
|
||
Authorization: Bearer <token>
|
||
# или
|
||
x-openclaw-token: <token>
|
||
```
|
||
|
||
⚠️ Query-string токены (`?token=...`) запрещены.
|
||
|
||
---
|
||
|
||
## Team Board → OpenClaw Integration
|
||
|
||
### Архитектура
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ Team Board │
|
||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||
│ │ Web UI │ │ Tasks │ │ Agents │ │
|
||
│ │ (Next.js) │ │ Service │ │ Service │ │
|
||
│ └─────────────┘ └──────┬──────┘ └──────┬──────┘ │
|
||
│ │ │ │
|
||
│ └────────┬────────┘ │
|
||
│ │ │
|
||
│ ┌────────▼────────┐ │
|
||
│ │ OpenClaw │ │
|
||
│ │ Client │ │
|
||
│ └────────┬────────┘ │
|
||
└───────────────────────────────────┼─────────────────────────┘
|
||
│ HTTP
|
||
▼
|
||
┌───────────────────────────────────────────────────────────────┐
|
||
│ OpenClaw Gateway │
|
||
│ localhost:18789 │
|
||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||
│ │ /hooks/wake │ │/hooks/agent │ │ Sessions │ │
|
||
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
||
│ │ │
|
||
│ ▼ │
|
||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||
│ │ Agent: main (Марков) │ │
|
||
│ │ - sessions_spawn → субагенты │ │
|
||
│ │ - sessions_send → коммуникация │ │
|
||
│ │ - sessions_history → история │ │
|
||
│ └─────────────────────────────────────────────────────────┘ │
|
||
└───────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### OpenClaw Client (Python)
|
||
|
||
```python
|
||
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"):
|
||
"""Разбудить основную сессию"""
|
||
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
|
||
):
|
||
"""Запустить задачу в изолированной сессии"""
|
||
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()
|
||
```
|
||
|
||
### Task Execution Flow
|
||
|
||
```python
|
||
async def execute_task(task: Task, agent: Agent):
|
||
client = OpenClawClient(
|
||
base_url="http://localhost:18789",
|
||
token=settings.OPENCLAW_TOKEN
|
||
)
|
||
|
||
# Формируем промпт
|
||
prompt = f"""
|
||
Задача: {task.title}
|
||
|
||
Описание: {task.description}
|
||
|
||
Контекст проекта: {task.project.brief}
|
||
|
||
Выполни задачу и верни результат.
|
||
"""
|
||
|
||
# Запускаем агента
|
||
result = await client.run_agent(
|
||
message=prompt,
|
||
session_key=f"task:{task.id}",
|
||
agent_id=agent.slug,
|
||
timeout=600
|
||
)
|
||
|
||
# Обновляем статус
|
||
task.status = "in_progress"
|
||
task.openclaw_session = result.get("sessionKey")
|
||
await task.save()
|
||
|
||
return result
|
||
```
|
||
|
||
### Получение результатов
|
||
|
||
**Вариант 1: Polling**
|
||
```python
|
||
async def poll_task_result(task: Task):
|
||
client = OpenClawClient(...)
|
||
|
||
# Получаем историю сессии
|
||
history = await client.get_session_history(task.openclaw_session)
|
||
|
||
# Парсим последний ответ
|
||
last_message = history["messages"][-1]
|
||
if last_message["role"] == "assistant":
|
||
task.result = last_message["content"]
|
||
task.status = "completed"
|
||
await task.save()
|
||
```
|
||
|
||
**Вариант 2: Callback Webhook**
|
||
```python
|
||
# Team Board получает callback от OpenClaw
|
||
@app.post("/api/callbacks/openclaw")
|
||
async def openclaw_callback(data: dict):
|
||
session_key = data.get("sessionKey")
|
||
result = data.get("result")
|
||
|
||
# Находим задачу по session_key
|
||
task = await Task.find_by_session(session_key)
|
||
task.result = result
|
||
task.status = "completed"
|
||
await task.save()
|
||
```
|
||
|
||
---
|
||
|
||
## Текущий статус OpenClaw
|
||
|
||
```bash
|
||
# Gateway endpoint
|
||
http://localhost:18789
|
||
|
||
# Текущий токен
|
||
cat ~/.openclaw/openclaw.json | jq '.gateway.auth.token'
|
||
|
||
# Webhooks пока не настроены — нужно включить
|
||
```
|
||
|
||
### Для активации webhooks:
|
||
|
||
```json
|
||
// ~/.openclaw/openclaw.json
|
||
{
|
||
"hooks": {
|
||
"enabled": true,
|
||
"token": "team-board-secret-token",
|
||
"path": "/hooks"
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
*Документ обновляется по мере исследования.*
|