Scatter-gather: параллель N задач + сборка
Параллельный fan-out на N однотипных задач, сборка результатов, partial failures, timeout, дедупликация.
Реализуй scatter-gather для {{unit_task}} с fan-out = {{fanout}}. Паттерн прост на бумаге и сложен в проде: вся боль — в partial failures и сборке.
1. Когда scatter-gather подходит
Идеально когда:
- N однотипных задач (один template, разный input)
- Задачи независимы друг от друга
- Результаты — composable (union, max, sum, list)
- Latency важна — sequential займёт N × t
Плохо когда:
- Задачи зависимы (выход одной = вход другой) → pipeline
- Нужен ранний exit при первом успехе → лучше race / quorum
- N маленькое (2-3) и t короткое → overhead spawn > выигрыша
2. Структура
scatter
supervisor ───────┬────► worker_1 (input_1) ─┐
├────► worker_2 (input_2) ─┤
├────► worker_3 (input_3) ─┤ gather
└────► worker_N (input_N) ─┘──► assembler
│
▼
final_output
3. Контракт unit-задачи
scatter_task:
task_id: scatter_2026-05-17_001
unit_template:
goal: "{{unit_task}} на одном элементе"
input_schema: { item_id, item_data, context }
return_schema:
status: "ok" | "partial" | "failed" | "timeout"
output: <по типу>
evidence: [...]
confidence: 0-100
inputs: [{ item_id: 1, ... }, { item_id: 2, ... }, ...]
budget_per_worker:
max_tokens: 5000
max_time_sec: 60
global_budget:
total_max_time_sec: 180 # gather не ждёт дольше
min_success_count: 7 # ниже — считаем scatter провалившимся
min_success_ratio: 0.7
Без min_success_count/min_success_ratio нельзя автоматически решить "достаточно ли результатов".
4. Обработка partial failures
В fan-out на 10+ workers что-то всегда провалится. План:
| Status | Что делать |
|---|---|
ok | Включить в merge |
partial | Включить с пометкой; снизить confidence итога |
failed | Логировать причину; retry если transient (rate-limit, network) |
timeout | Не ждать; пометить timed_out в evidence |
Решение об итоге:
success_count >= min_success_countИsuccess_ratio >= min_success_ratio→ продолжаем merge- Иначе → escalate: "scatter провалился, X из Y успешно"
- Никогда не маскируй неполный результат под полный
5. Дедупликация
Когда нужна:
- Workers могут вернуть пересекающиеся данные (одна и та же библиотека из двух источников)
- Один
item_idслучайно подан дважды - Retry создал две копии result'а
Стратегии:
- Pre-dedup: дедуп inputs по ключу перед dispatch (cheap)
- Post-dedup: дедуп outputs по signature после gather (надёжнее)
- Both: pre + post (если данные дорогие)
Signature = stable hash от ключевых полей (не от всего payload — иначе collision).
6. Timeout strategy
Два уровня:
- Per-worker: каждый worker не дольше
max_time_sec - Global gather: даже если worker ещё работает, после
total_max_time_secсобираем то, что есть
Реализация: Promise.allSettled + явный timeout race. Promise.all НЕ годится — упадёт целиком на первой ошибке.
results = await Promise.race([
Promise.allSettled(workers),
timeoutAfter(total_max_time_sec)
])
7. Merge / assembly
Зависит от типа output'а:
| Тип | Merge |
|---|---|
| List | concat → dedup by signature → sort |
| Set | union |
| Map | merge by key; conflict policy: first / last / max-confidence |
| Number aggregate | sum / avg / max / min (явно выбрать) |
| Text | summarise N → 1 (LLM call с явной инструкцией) |
Confidence итога = функция от individual confidences (например, mean × success_ratio).
8. Метрики
- success_ratio —
ok/ total (target ≥ 0.8) - p50 / p95 worker latency — распределение времени (длинный хвост = плохой sizing)
- timeout_rate — % timed-out (target ≤ 5%)
- dedup_rate — % дубликатов в outputs (≥ 10% → подумай о pre-dedup)
- gather_overhead — assembler time / total time (target ≤ 10%)
9. Anti-patterns
- ❌
Promise.allбезallSettled— один failed убивает весь scatter - ❌ Нет global timeout — gather зависает навсегда из-за одного worker'а
- ❌ Нет
min_success_count— merge на 1 из 10 результатов выдаётся как "готово" - ❌ Workers с shared state (общая БД на запись) — race и дубликаты
- ❌ Retry без exponential backoff — DDoS на источник
- ❌ Dedup по hash от всего payload — миссы при незначительных вариациях
- ❌ Workers возвращают разные schemas — assembler не сможет merge
- ❌ Все workers стартуют одновременно без throttle — rate-limit от внешнего API
- ❌ Маскирование timed-out как
okс пустым output — фейковый успех - ❌ Sequential scatter (последовательно): N × t latency — теряется весь смысл паттерна
Deliverable
- Schema scatter_task с примером на 3 inputs
- Алгоритм assembler'а под конкретный тип output'а
- Политика partial failures (правила решения)
- 5 метрик с порогами
- Bench: latency single vs scatter на реальных N
Декомпозиция задачи на агентов
Разбить большую задачу на параллельных независимых агентов с чёткими интерфейсами.
Последовательный пайплайн агентов
Когда параллель не подходит: цепочка агентов с явными контрактами между шагами.
Бюджет контекста для агента
Сколько токенов есть, как делить между инструкциями, контекстом и историей.