Harness design для мультиагентов: как обвязка спасает длительные задачи | AiManual
AiManual Logo Ai / Manual.
22 Май 2026 Гайд

Мультиагентная архитектура для длительных задач: как обвязка (harness design) помогает AI-агентам дописывать приложения

Разбираем, как harness (обвязка) с паттерном генератор-оценщик помогает AI-агентам не терять контекст и дописывать сложные приложения. Пошаговый гайд и типичные

Вы запускаете мультиагентную систему. Генератор пишет код. Оценщик проверяет. Через час система возвращает 10 000 строк, завернутых в бесконечный цикл рефакторинга. Потому что контекст вытек, агенты забыли, что строили, и теперь они отчаянно переписывают модуль логина, хотя задача была "дописать платежный шлюз".

Знакомо? Это не баг LLM, это отсутствие обвязки. Если вы читали нашу статью про проклятие длинного контекста — вы знаете, что агенты деградируют на длинных сессиях. Но мультиагентная архитектура без жесткой смирительной рубашки — это гарантированный путь к хаосу.

В этой статье я расскажу про harness design — архитектурный паттерн, который превращает группу агентов в предсказуемый конвейер. Не очередной "умный промпт", а системный слой, управляющий итерациями, контекстом и валидацией.

Проблема: мультиагент не равен автоматизации

В статье про кейс Anthropic с +90% производительности мы видели, как разделение на роли резко улучшает результат. Но есть подвох: в исследовании Anthropic каждый агент работал с четкими границами контекста. В реальном проекте эти границы размываются.

Возьмем задачу "дописать приложение". У вас есть существующий код, тесты, требования. Задача длится не один вызов, а серию итераций. Классический паттерн генератор-оценщик (вдохновленный GAN) выглядит так:

  • Агент-генератор пишет код.
  • Агент-оценщик проверяет его логику, покрытие, стиль.
  • Оценщик отправляет замечания назад. Цикл повторяется.

Звучит логично. Но на практике:

  • Генератор переписывает целые файлы, хотя нужно было исправить одну строку.
  • Оценщик начинает галлюцинировать зависимости — требует импортировать несуществующие библиотеки.
  • После 5 итераций контекст становится огромным, агенты "забывают" первоначальный запрос.
  • Стоимость API вызовов взлетает, а качество падает.

Причина: нет развязки. Агенты напрямую общаются через общий контекст. Они не управляют памятью, не знают, когда остановиться, и не умеют восстанавливаться после сбоя.

Решение: Harness — обвязка, а не оркестратор

Оркестратор (типа LangGraph, AutoGen) просто вызывает агентов по очереди. Harness — это системный слой, который владеет состоянием. Он решает:

  • Когда запускать агента.
  • Что он видит (контекстная сегментация).
  • Что делать с его выводом (валидация, откат, merge).
  • Когда остановиться (критерий схождения).

Представьте, что вы — техлид, а агенты — джуны. Вы не даете им весь код проекта и не ждете, пока они сами разберутся. Вы выдаете таск с четким скоупом, проверяете пулл-реквест и откатываете, если тесты упали. Harness — это ваша роль, автоматизированная.

Архитектура включает три компонента:

  1. State Machine — управляет жизненным циклом задачи: инициализация, генерация, валидация, откат, завершение.
  2. Context Manager — режет контекст на порции, чистит историю, извлекает релевантные части из репозитория. Без него контекст распухает.
  3. Validation Gate — запускает юнит-тесты, линтеры, проверяет API-контракты. Не доверяет LLM-оценщику, а использует железные проверки.

Пошаговый план: как построить Harness для дописывания приложения

Допустим, у вас есть проект на Python с тестами. Нужно добавить новый эндпоинт. Вместо того чтобы отдать это одному агенту, вы создаете Harness.

1 Определите миссию и скоуп

Harness получает задачу и сразу создает JSON-спецификацию: цель, изменяемые файлы, ограничения (не трогать ядро, не менять API существующих методов). Это предотвращает рефакторинг половины проекта.

2 Контекстный бутстрап

Context Manager вычитывает только те файлы, которые относятся к задаче: модель, роутер, тесты. Он убирает лишнее: конфиги, документацию, логи. Это снижает шум и стоимость.

3 Цикл генерации и валидации с откатом

Генератор получает чистый контекст и задачу. Он производит diff. Harness применяет diff к временной ветке, запускает тесты. Если тесты упали — харнес откатывает изменения, логирует ошибку и отправляет генератору только текст ошибки (без всего контекста, который уже был). Это ключевая фишка: обратная связь минимальна, чтобы не засорять контекст.

4 Параллельные треки для больших задач

Если задача затрагивает несколько модулей, Harness может запустить параллельные циклы для каждого модуля, а затем автоматически смержить изменения, проверяя интеграционные тесты. Это как разделение на суб-агентов, о котором мы говорили в статье про суб-агентов — каждый отвечает за свой кусок.

Нюансы и ошибки, которые взорвут вашу архитектуру

Я видел, как команды копируют паттерн генератор-оценщик и получают не улучшение, а головную боль. Вот три грабли.

1. Контекстный бюджет глобальный, а не локальный

Часто харнес передает агенту всю историю итераций. Через 10 шагов контекст становится гигантским, агент начинает видеть в коде то, чего нет. Как надо: каждый вызов агента — это фиксированный контекст: скоуп задачи + текущий diff + одно сообщение от валидатора. История хранится только в State Machine, но не закидывается агенту.

2. Синхронные блокировки

Если валидатор ждет, пока генератор закончит, а генератор ждет валидатора — вы получили deadlock. Как надо: харнес запускает генератор, получает результат, отправляет его валидатору, но не блокирует поток. Может быть параллельная очередь задач. Вспомните архитектуру без роутинга — иногда лучше убрать посредников и сделать прямой пайплайн с очередями.

3. Доверие LLM-оценщику

Оценщик часто пропускает баги, потому что его промпт недостаточно строгий. Как надо: валидация должна включать исполнение кода — юнит-тесты, type checker, линтер. Оценщик-LLM используется только для проверки соответствия требованиям ("использовал ли правильный паттерн?"). Без железных проверок ваш харнес — это просто дорогая переписка агентов.

💡
Совет: Начните с малого. Сделайте харнес для одной задачи — добавить тест для существующей функции. Когда научитесь контролировать цикл, расширяйте на целые фичи. Иначе вы построите космический корабль, который взорвется на старте.

Пример: Harness-класс на Python

Ниже — упрощенный, но рабочий скелет. Он не зависит от конкретного LLM-провайдера. Суть в архитектуре.

import subprocess, json, tempfile, os
from enum import Enum

class Step(Enum):
    BOOTSTRAP, GENERATE, VALIDATE, ROLLBACK, DONE = range(5)

class Harness:
    def __init__(self, repo_path: str, max_iterations: int = 10):
        self.repo_path = repo_path
        self.state = Step.BOOTSTRAP
        self.iteration = 0
        self.max_iterations = max_iterations
        self.diff_stack = []

    def context_for_generator(self, task_spec: dict) -> str:
        # загружаем только релевантные файлы
        files = task_spec['input_files']
        context = '\n\n'.join(open(os.path.join(self.repo_path, f)).read() for f in files)
        return context + '\nЗадача: ' + task_spec['instruction']

    def apply_diff(self, diff_text: str):
        with tempfile.NamedTemporaryFile(mode='w', suffix='.diff') as f:
            f.write(diff_text)
            f.flush()
            result = subprocess.run(['git', 'apply', f.name], cwd=self.repo_path,
                                    capture_output=True, text=True)
            if result.returncode != 0:
                raise RuntimeError(f'Patch failed: {result.stderr}')
        self.diff_stack.append(diff_text)

    def rollback_last(self):
        if self.diff_stack:
            subprocess.run(['git', 'checkout', '.'], cwd=self.repo_path, check=True)
            self.diff_stack.clear()

    def run_tests(self) -> bool:
        result = subprocess.run(['pytest', '--tb=short', '-x'], cwd=self.repo_path,
                                capture_output=True, text=True)
        if result.returncode == 0:
            return True
        # записываем только ошибки, не весь контекст
        self.last_error = result.stderr[-2000:]
        return False

    def run(self, task_spec: dict):
        self.state = Step.GENERATE
        while self.iteration < self.max_iterations:
            if self.state == Step.GENERATE:
                context = self.context_for_generator(task_spec)
                # вызов LLM — заглушка
                diff_text = self.llm_generate(context, task_spec)
                try:
                    self.apply_diff(diff_text)
                except RuntimeError as e:
                    self.state = Step.ROLLBACK
                    continue
                self.state = Step.VALIDATE

            elif self.state == Step.VALIDATE:
                if self.run_tests():
                    self.state = Step.DONE
                    break
                else:
                    self.state = Step.GENERATE
                    self.iteration += 1
                    self.rollback_last()
            # ROLLBACK и прочее опущено для краткости
        if self.state == Step.DONE:
            print('Задача выполнена. Итераций:', self.iteration)
        else:
            print('Не удалось за ' + str(self.max_iterations) + ' итераций')

Обратите внимание: генератор получает только сырой контекст, а ошибки проходят через отдельный канал. Это предотвращает перегрузку контекста. Такой подход перекликается с идеей stateful memory из статьи про проектирование AI-агента — память должна быть внешней, а не встроенной в промпт.

Когда Harness — это overkill

Не пихайте харнес в каждую задачу. Если генерация кода занимает один вызов LLM и не требует валидации — простой агент справится лучше. Как я писал в статье "Один против всех", мультиагент — это инструмент для сложных задач, а не модная игрушка.

Правило: Если задача занимает меньше 10 минут ручного кодинга — не стройте харнес. Но если вы планируете автоматизировать целые эпики — обвязка окупится после первой же недели работы без перегоревших токенов.

В итоге: без харнеса мультиагент — это просто дорогой хаос. С харнесом он становится предсказуемым конвейером, где каждый шаг контролируется кодом, а не промптом. И помните: лучше один хорошо спроектированный харнес, чем десять агентов с божественным промптом.

Подписаться на канал