agent-ecosystem/docs/team-management/research-inbox.md
iliya e7d9e82ce8 feat(team-management): implement initial Team Management feature with Kanban support
- Added core documentation for the Team Management feature, including iteration goals, implementation plans, and design specifications.
- Introduced new files for team configuration, IPC channels, and UI components for team listing and Kanban board.
- Established a clear definition of done and scope for the first iteration, focusing on visible results and graceful degradation in the UI.
- Documented the messaging and task file formats, ensuring robust communication between team members.
- Implemented a Kanban design with defined states and transitions for task management.
2026-02-17 21:10:15 +02:00

242 lines
9.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Research: Inbox-файлы Claude Code
## Формат
**Путь**: `~/.claude/teams/{teamName}/inboxes/{memberName}.json`
**Структура**: JSON-массив объектов (весь файл = массив)
```json
[
{
"from": "contracts-cleaner",
"text": "Готово. Вот что было удалено из packages/core/...",
"timestamp": "2026-02-09T17:12:32.316Z",
"color": "blue",
"read": true,
"summary": "Cleanup complete"
},
{
"from": "team-lead",
"text": "{\"type\":\"shutdown_request\",\"from\":\"team-lead\",\"timestamp\":\"...\"}",
"timestamp": "2026-02-09T17:25:43.886Z",
"read": true
}
]
```
### Поля
| Поле | Обязательно | Тип | Описание |
|------|:-----------:|-----|----------|
| `from` | YES | string | Имя отправителя (зарегистрированный agent) |
| `text` | YES | string | Текст сообщения или JSON-строка |
| `timestamp` | YES | string | ISO 8601 |
| `read` | YES | boolean | Прочитано ли Claude Code |
| `summary` | NO | string | Краткое описание для UI |
| `color` | NO | string | Цвет агента (blue, green, red, yellow, purple, cyan, orange, pink) |
### text: plain vs structured
`text` может быть:
- **Plain text**: обычное сообщение
- **JSON-строка**: структурированное сообщение (парсится из text)
Типы структурированных:
```
idle_notification — агент ушёл в idle (idleReason: "available")
shutdown_request — запрос на завершение работы
shutdown_approved — подтверждение завершения
message — обычное DM (content, summary)
task_completed — задача завершена (taskId)
```
### read поведение
- Claude Code ставит `read: true` после обработки
- Сообщения НЕ УДАЛЯЮТСЯ — остаются навечно
- Нет автоматической чистки inbox
- 106 сообщений = ~256 KB
---
## Race Condition (КРИТИЧНЫЙ РИСК)
### Сценарий
```
T=0ms: App читает inbox [msg1, msg2]
T=1ms: Claude Code читает inbox [msg1, msg2]
T=5ms: App пишет [msg1, msg2, msg3_app]
T=6ms: Claude Code пишет [msg1, msg2, msg3_agent]
→ msg3_app ПОТЕРЯНО
```
### Почему происходит
Inbox — JSON-массив. Append = read whole array → add element → write whole file. Два процесса читают одну версию, каждый добавляет своё, последний перезаписывает первого.
### Вероятность
**НИЗКАЯ** — записи в inbox происходят нечасто:
- Юзер шлёт 1-2 сообщения в минуту
- Агенты шлют idle_notification раз в 5-30 секунд
- Коллизия = оба пишут в ОДИН файл в пределах ~10ms
### Митигация
1. **Atomic write** (предотвращает partial writes):
```typescript
const tmpPath = targetPath + '.tmp.' + process.pid;
fs.writeFileSync(tmpPath, JSON.stringify(messages, null, 2));
fs.renameSync(tmpPath, targetPath); // atomic на macOS/Linux
```
2. **Retry с проверкой** (обнаруживает потерю):
```typescript
// После записи — перечитать и проверить
const written = JSON.parse(fs.readFileSync(inboxPath, 'utf8'));
const found = written.some(m => m.messageId === ourMessageId);
if (!found) {
// Наше сообщение потеряно — retry
await appendToInbox(inboxPath, message);
}
```
3. **Уникальный messageId**: добавлять `messageId: uuid()` в наши сообщения
4. **Debounce**: не писать чаще раз в 500ms
### Что НЕ решает
Atomic write предотвращает corrupted JSON, но НЕ предотвращает overwrite race. Retry с проверкой — best effort, но не 100%.
---
## Доставка сообщений
### Между turns, НЕ real-time
```
Цикл тиммейта:
1. Читает inbox (видит новые сообщения с read: false)
2. Обрабатывает
3. Вызывает инструменты (Bash, Edit, Read...)
4. Turn заканчивается → шлёт idle_notification → IDLE
5. Ждёт...
6. Новый turn → читает inbox
```
**Задержка**: 1-30 секунд (зависит от длительности turn)
### Нельзя прервать mid-turn
Если агент уже вызвал Edit/Bash — инструмент будет выполнен. Сообщение "стоп, не трогай файл X" придёт ПОСЛЕ.
### Idle → Active
Сообщение idle-агенту пробуждает его при следующем цикле проверки inbox.
### Hard Interrupt (будущее)
Возможные подходы:
- `kill -SIGINT` процесса (жёсткое)
- Файловый flag `.interrupt-{member}`
- Ждать API от Anthropic
---
## from: "user" — валидация
### Факты
- В реальных inbox-файлах видны ТОЛЬКО зарегистрированные agent names
- Нет примеров `from: "user"` в 256+ KB данных
- Неизвестно, валидирует ли Claude Code поле `from` по config.json members
### Решение
Пробуем `from: "user"`. Если агент не получает сообщение → fallback на `from: "team-lead"` (всегда есть в config.json).
### Тест
При первой реализации:
1. Запустить команду с 1 тиммейтом
2. Записать сообщение с `from: "user"` в inbox тиммейта
3. Проверить — получит ли тиммейт сообщение
4. Если нет — повторить с `from: "team-lead"`
---
## Размер и масштабирование
| Метрика | Значение |
|---------|----------|
| Размер сообщения | ~2.4 KB |
| 100 сообщений | ~240 KB |
| 1000 сообщений | ~2.4 MB |
| Парсинг 1000 сообщений | <10ms |
| Реальный inbox (106 msgs) | 256 KB |
Проблема начнётся при 10000+ сообщений JSON.parse будет заметно медленнее. Для долгоживущих команд нужна архивация.
---
## Финальное решение (после 3 раундов ревью)
### Подход: Atomic write + messageId verify
Выбрана комбинация atomic write с постфактум-верификацией через `messageId`:
```typescript
// 1. Генерируем уникальный ID для нашего сообщения
const messageId = crypto.randomUUID();
const message: InboxMessage = {
from: 'user',
text,
timestamp: new Date().toISOString(),
read: false,
summary,
messageId,
};
// 2. Читаем текущий inbox
const existing = JSON.parse(fs.readFileSync(inboxPath, 'utf8'));
// 3. Добавляем сообщение
const updated = [...existing, message];
// 4. Atomic write (tmp + rename)
const tmpPath = inboxPath + '.tmp.' + process.pid;
fs.writeFileSync(tmpPath, JSON.stringify(updated, null, 2));
fs.renameSync(tmpPath, inboxPath);
// 5. Verify: перечитываем и проверяем messageId
const written = JSON.parse(fs.readFileSync(inboxPath, 'utf8'));
const found = written.some((m: InboxMessage) => m.messageId === messageId);
if (!found) {
// Потеря обнаружена — показать warning в UI, не silent fail
throw new Error(`Message ${messageId} lost during write`);
}
```
### Решения по итогам ревью
- **Полный CAS не нужен на MVP**: verify при следующем read достаточен для обнаружения потерь
- **messageId проверяется сразу после записи** (а не только при следующем read)
- **Не silent fail**: если сообщение потеряно UI показывает предупреждение пользователю
- **Retry не автоматический**: потеря крайне редка, ручная отправка достаточна на MVP
### Риски
| Риск | Вероятность | Митигация |
|------|-------------|-----------|
| Race condition: агент пишет одновременно | Низкая | Atomic write + verify |
| Потеря при race | Очень низкая | messageId verify warning в UI |
| Corrupted JSON | Практически 0 | Atomic write (tmp + rename) |
### Что не входит в MVP
- Автоматический retry при потере (добавить в Phase 2 при необходимости)
- Debounce записи (не нужен при редкой записи)
- Полный CAS с блокировкой (избыточно для данной частоты записей)