Почему агентам, выполняющим длительные задачи, необходим машинный состояние: роли лидера, рабочего и верификатора
Четыре причины сбоев цикла одного агента при выполнении длительных задач, а также способы, с помощью которых команда из лидера, рабочего и верификатора, управляемая детерминистическим движком, их устраняет, и когда следует этого избегать.
Источники: MiniMax Agent Team Best Practice: An AI Agent Is a While Loop. A Reliable One Is a State Machine.
Переход от чат-бота к агенту требует использования цикла while, а не научного прорыва. Это верно, и именно здесь обычно прекращаются все объяснения и начинаются проблемы в реальных условиях. Более полезный вопрос заключается в том, почему этот цикл тихо разрушается при выполнении задач, которые длительнее десяти минут, и какая структура может его восстановить. MiniMax опубликовала два подробных материала именно на эту тему: учебное пособие Mini-Agent с открытым исходным кодом, в котором показан базовый цикл, и технический отчет их команды Agent Team, описывающий проблемы при масштабировании и разработанную в ответ систему выполнения задач. Оба материала служат основой для дальнейшего рассмотрения, причем этот подход применим к любой технологической стеку.
Один цикл — от начала до конца
Если упростить структуру агента, останутся четыре компонента: модель, список сообщений в реальном времени, реестр инструментов и цикл, обрабатывающий их. Пример работы Mini-Agent выглядит следующим образом: сначала проверяется, не достиг ли исторический записей предела, установленного контекстом, и при необходимости он сокращается. Затем сообщения и определения инструментов отправляются в модель. Если модель не запрашивает инструментов, цикл завершается. В противном случае запускаются запрошенные инструменты, их результаты добавляются к сообщениям, после чего процесс повторяется. Вот полная версия для любого конечного точки, совместимой с OpenAI, с двумя реальными инструментами и функцией подведения итогов:
# naive_loop.py -- a single-agent tool loop,
# Mini-Agent style: summarize if needed ->
# llm.generate(messages, tools) -> execute
# tool_calls -> append results -> repeat.
import json
import subprocess
from openai import OpenAI
client = OpenAI()
MODEL = "gpt-4o-mini"
MAX_HISTORY = 40 # crude context budget
def read_file(path: str) -> str:
with open(path) as f:
return f.read()[:4000]
def run_cmd(cmd: str) -> str:
p = subprocess.run(
cmd, shell=True, timeout=30,
capture_output=True, text=True)
out = p.stdout + p.stderr
return f"exit={p.returncode}\n{out[-2000:]}"
TOOLS = {"read_file": read_file,
"run_cmd": run_cmd}
def schema(name, desc, arg):
return {
"type": "function",
"function": {
"name": name,
"description": desc,
"parameters": {
"type": "object",
"properties": {
arg: {"type": "string"},
},
"required": [arg],
},
},
}
SCHEMAS = [
schema("read_file",
"Read a local text file.", "path"),
schema("run_cmd",
"Run a shell command.", "cmd"),
]
def summarize(msgs):
# Mini-Agent compresses old turns with an
# LLM summary call when the context nears
# its limit; hard truncation is the v0.
if len(msgs) <= MAX_HISTORY:
return msgs
return [msgs[0]] + msgs[-MAX_HISTORY:]
def run(task: str, max_steps: int = 20):
msgs = [{"role": "user", "content": task}]
for _ in range(max_steps):
msgs = summarize(msgs)
r = client.chat.completions.create(
model=MODEL,
messages=msgs,
tools=SCHEMAS,
)
msg = r.choices[0].message
msgs.append(msg)
if not msg.tool_calls:
return msg.content # model says done
for call in msg.tool_calls:
fn = TOOLS[call.function.name]
args = json.loads(
call.function.arguments)
try:
out = fn(**args)
except Exception as e:
out = f"tool error: {e}"
msgs.append({
"role": "tool",
"tool_call_id": call.id,
"content": out,
})
return "hit max_steps -- gave up"
if __name__ == "__main__":
print(run("Count the .py files here, then "
"summarize naive_loop.py."))
У цикла есть два способа завершения, и оба имеют значение. Успех определяется самой моделью посредством отсутствия дальнейших вызовов инструментов. Безопасность обеспечивается простым счётчиком — лимитом количества шагов. Именно эта асимметрия, при которой только модель решает, когда работа завершена, является тем фактором, который срабатывает первым.
Когда один цикл терпит неудачу при длительных задачах
Если запустить тот же цикл для задачи продолжительностью два часа вместо двух минут, начинаются сбои. Команда MiniMax составила список тех сбоев, с которыми они постоянно сталкивались. Ни один из них не является багом. Это свойства конкретного контекста, поэтому ни увеличение времени работы, ни улучшение запроса не решают этих проблем.
Сначала появляется тревога из-за контекста: агент останавливается, когда ему никто этого не просил. После выполнения семи подзадач он заканчивает три из них и сообщает, что выполнил задачи 1, 2 и 3, после чего спрашивает, следует ли продолжать. У моделей нет четкого представления о том, когда может закончиться длительная задача, а прямая настройка такого представления требует огромных затрат как в денежном отношении, так и с точки зрения алгоритмических усилий. Простой цикл перекладывает решение о остановке на наименее надежную его часть.
Затем происходит ухудшение качества работы: агент становится менее способным по мере увеличения объема контекста. Через пять минут его производительность высока, а через шестьдесят пять минут он ведет себя как рассеянный стажер. Одна небрежная ошибка, например, когда исследовательская задача превращается в маркетинговый текст, портит всё последующее выполнение, поскольку каждое последующее поколение формируется на основе уже «загрязненной» истории.
В-третьих, ничто не проверяет качество работы, поскольку судья одновременно является участником. Одинокий агент, формирующий окончательный результат, сам оценивает свою работу в том же контексте, где возникли его ошибки. Это не нечестность — это проверка сцены, которую сам модель только что создала.
В-четвертых, это молчание. Интерфейсы в стиле чата заставляют пользователей ожидать подтверждения в течение нескольких секунд, даже для задачи, занимающей час, но цикл, встроенный в вызов модели, не может сообщить, что он получил запрос и вернется с ответом. Согласно отчету MiniMax, вопрос «Почему мой агент не отвечает?» привлек больше отзывов пользователей, чем что-либо другое.
Решение: три роли и детерминистический двигатель
MiniMax отвечает с использованием структурированного подхода. Три вида ролей координируются обычным детерминистическим кодом, а не умным запросом, и этот код называется Team Engine. Один цикл выполнения задачи соответствует одной сессии, причем движение каждой сессии происходит через три состояния: генерация, проверка и завершение; при неудачной проверке движение снова начинается с этапа генерации.
- Лидер разбивает запрос пользователя на структурированный план, определяет, какие части выполняются одновременно, отвечает пользователю в течение нескольких секунд, в то время как остальные операции происходят незаметно для пользователя, и в конце объединяет результаты.
- Рабочие выполняют подзадачи, причем каждый из них работает в отдельном контексте с собственными инструментами, памятью и требуемым форматом вывода. Такое разделение важно, поскольку каждый рабочий видит только одну подзадачу, а не накопленные за несколько часов данные.
Ядро движка достаточно маленькое, чтобы его можно было полностью описать. В этой версии не требуется API-ключ, поскольку специальные «штубовые» компоненты заменяют вызовы больших языков моделей и могут быть заменены без изменения структуры автоматической машины состояний:
# team_engine.py -- the loop promoted to a
# state machine. One task lifecycle is one
# Session; deterministic code owns every
# transition: producing -> verifying -> done.
from dataclasses import dataclass, field
PRODUCING = "producing"
VERIFYING = "verifying"
DONE = "done"
FAILED = "failed"
@dataclass
class Session:
goal: str
state: str = PRODUCING
attempts: int = 0
artifact: str = ""
reviews: list = field(default_factory=list)
def run_session(s, worker, verifier,
max_retries: int = 3):
while s.state not in (DONE, FAILED):
if s.state == PRODUCING:
s.attempts += 1
s.artifact = worker(
s.goal, s.reviews)
s.state = VERIFYING
elif s.state == VERIFYING:
ok, report = verifier(
s.goal, s.artifact)
s.reviews.append(report)
if ok:
s.state = DONE
elif s.attempts >= max_retries:
s.state = FAILED
else:
# Reject: wake the worker with
# the review log in context.
s.state = PRODUCING
return s
def run_team(goals, worker, verifier):
# Leader's job: split the goal, run the
# sessions (a thread pool in real use),
# then merge N artifacts into 1 result.
done, failed = [], []
for g in goals:
s = run_session(Session(g),
worker, verifier)
if s.state == DONE:
done.append(s.artifact)
else:
failed.append(s.goal)
return done, failed
if __name__ == "__main__":
# Stub roles so the engine runs anywhere;
# swap in LLM calls for the real thing.
def worker(goal, reviews):
v = len(reviews) + 1
return f"draft v{v}: {goal}"
def verifier(goal, artifact):
ok = "v2" in artifact
return ok, f"review {artifact!r}: {ok}"
done, failed = run_team(
["intro", "benchmarks", "faq"],
worker, verifier)
print("delivered:", done)
print("failed:", failed)
При его запуске первая версия каждой сессии отклоняется, рабочий процесс запускается с журналом проверок в своем контексте, а вторая версия принимается. Если сравнить его с простым циклом, становятся заметны некоторые различия.
- Остановка: одиночный цикл заканчивается, когда модель прекращает отправку запросов к инструментам (метод fuzzy) или достигает лимита шагов, тогда как движок использует явные состояния «готово» и «не удалось» с лимитом попыток, заданным в коде.
- Проверка: в цикле сам контекст, в котором была выполнена работа, проверяет её, в то время как команда использует адверсарийского верификатора в отдельном контексте, основанном на командах.
- Параллелизм: цикл представляет собой один последовательный контекст, а команда запускает рабочие процессы в параллельных сессиях, причем лидер объединяет множество результатов в один.
- Персистентность: цикл сохраняет список сообщений до момента подведения итогов, а команда сохраняет состояние сессии, записи проверок, результаты работы и память отдельных агентов.
Лидер также не ограничивается однократными вызовами, которые генерируют подзадачу и возвращают текст. В конструкции MiniMax он считывает текущий статус движка и может отправлять дополнительные запросы в сессию, находящуюся в процессе выполнения или проверки, что позволяет использовать как проактивные отправки, так и запросы по мере необходимости. Это также решает проблему молчания: Лидер подтверждает действия в течение нескольких секунд, после чего пользователь получает отчеты на этапах выполнения, таких как «начато», «заблокировано», «требуется решение» и «завершено».
Обеспечение стабильности проверщика
Проблема самооценки исчезает только тогда, когда вердикт формируется вне любой модели. Что касается кода, MiniMax окружает его процесс выполнения специальным механизмом. Работа выполняется на ветках, в песочницах; изменения представляют собой различия между версиями, тесты можно повторять, ревью оставляют записи, а неудачи — возможность для повторной обработки. Самое важное: условие остановки должно быть связано с чем-то детерминированным и наблюдаемым извне, таким как тесты, инструменты проверки кода и процесс сборки, а никогда с ощущением модели о завершении работы.
# grounded_verifier.py -- evidence over vibes.
# The verdict comes from exit codes and logs,
# never from the model's self-assessment.
import subprocess
import sys
PY = sys.executable
# pytest exit codes: 0 = all passed,
# 1 = failures, 5 = no tests collected
# (acceptable for a docs-only change).
PYTEST_OK = (0, 5)
CHECKS = [
("diff", ["git", "diff", "--stat"], (0,)),
("build", [PY, "-m", "compileall",
"-q", "."], (0,)),
("tests", [PY, "-m", "pytest", "-q",
"--maxfail", "1"], PYTEST_OK),
]
def run_check(name, cmd, ok_codes, cwd):
p = subprocess.run(
cmd, cwd=cwd,
capture_output=True, text=True)
tail = (p.stdout + p.stderr)[-2000:]
return {
"check": name,
"cmd": " ".join(cmd),
"exit_code": p.returncode,
"ok": p.returncode in ok_codes,
"output_tail": tail,
}
def verify(repo: str):
evidence = []
for name, cmd, ok_codes in CHECKS:
e = run_check(name, cmd, ok_codes, repo)
evidence.append(e)
if not e["ok"]:
# Reject with logs attached: the
# worker retries against real
# errors, not "please improve".
return False, evidence
return True, evidence
if __name__ == "__main__":
ok, ev = verify(".")
for e in ev:
print(f"{e['check']:>6} exit "
f"{e['exit_code']} ok={e['ok']}")
print("verdict:", "pass" if ok else "fail")
Список доказательств одновременно служит записью обзора и является частью воспроизводимой, прозрачной трассы, которая отделяет то, что действительно можно предоставить, от того, что лишь кажется правдоподобным. MiniMax дополнительно разделяет процесс обзора. Тестировщик генерирует внешние доказательства путем выполнения тестов, сжатия логов с ошибками и добавления минимального примера воспроизведения проблемы. Рецензент задаёт другой вопрос: не о том, прошёл ли тест, а о том, следует ли изменять код именно так, учитывая границы абстракции, новые зависимости, расширенные права доступа и утечки конфиденциальной информации в логи. Рецензенты могут работать параллельно с специалистами в таких областях, как общие вопросы, безопасность и конкретные домены.
Сколько стоит команда и когда её можно пропустить
Команда — это не бесплатный параллелизм. Три затраты возникают сразу при совместной работе участников, и расширение окна контекста не устраняет ни одной из них.
Стоимость передачи: одна и та же информация должна быть переорганизована между агентами, поэтому страницы с исследованиями превращаются в краткое описание задачи для автора, которое затем используется для проверки формата. Решением является передача читаемых файлов с краткими резюме по определенным путям вместо прямой передачи больших объемов контекста.
Стоимость обмена: за каждый глобально общий токен платят все участники в каждом раунде. Работоспособная структура позволяет сохранять данные в закрытом виде у каждого агента, обеспечивает возможность взаимодействия агентов через прерывания по командной строке и предоставляет общую доску для записей, к которой агенты обращаются только тогда, когда это необходимо.
Третьей стоимостью является стоимость агрегации. Собрать десять частичных черновиков — просто. Однако объединить их в один файл с согласованными фактами, цитатами и тоном — самая сложная задача для лидера, и для этого требуется этап слияния, а не дополнительные сотрудники.
У консенсуса тоже есть своя цена. Свободные дискуссии между агентами могут потребовать в несколько раз больше токенов, чем процесс изолированной самокоррекции, и при этом не улучшить точность. Ценность заключается в структуре: ограниченные сессии, четко определенные элементы и возможность для верификатора отклонять результаты.
Избегайте использования команды, когда задача небольшая, локальная и ее легко проверить, поскольку один цикл обходится дешевле. Используйте команду тогда, когда работа можно четко разделить, ошибки имеют серьезные последствия, и независимая проверка может предоставить доказательства того, что модель не сможет обмануть саму себя. Хороший критерий: добавляйте роль только в том случае, если она покрывает сценарий сбоя, который не охватывается другими ролями; в противном случае дополнительные затраты на сотрудничество не принесут никакой пользы.
Что создавать в первую очередь
Начните с самой простой автоматической системы состояний, которой можно доверять. Каждая сессия имеет цель, результаты работы, журнал проверок и лимит попыток, причем каждый переход четко описан. Разместите инструмент проверки за интерфейсом, ответы которого поступают от тестов, инструментов анализа кода, процессов сборки или подобных механических проверок. Параллельные задачи можно добавить позже: параллелизм — это оптимизация, тогда как критерии корректности являются частью архитектуры. Сохраните видимым шаг объединения данных, выполняемый лидером группы, оставьте результаты передачи данных в доступном для проверки виде и задокументируйте причины завершения каждой сессии.
В результате не появляется автономный разум. Это контролируемый рабочий процесс: модели предлагают варианты, инструменты выполняют действия, а обычный код определяет, завершена ли работа. Сначала напишите простой цикл вручную, прежде чем использовать фреймворк. Ожидайте, что длительные задачи могут нарушить его структуру из-за тревоги, отклонений, самооценки и молчания, и никакие указания не смогут этого исправить. Рассматривайте системы с несколькими агентами как среду выполнения с плоскостью управления состоянием, событиями, разрешениями, возможностями наблюдения и восстанавливаемыми объектами, а не как ролевую игру в рамках указаний. Также учтите необходимость передачи данных, их обмена и агрегации перед формированием команды, и никогда не создавайте команду из-за опечатки.
Связанные материалы
- Практические заметки: Claude Code Agent Teams: практическое руководство по созданию команды — Подробное руководство по использованию Практических заметок: Claude Code Agent Teams: практическое руководство по созданию команды, включающее информацию о контрактах, проверках и местах для вставки кода для команд, применяющих эту модель.
- Почему логи агента не смогут соответствовать требованиям статьи 12: записи решений, причинно-следственные связи и происхождение данных — Треки не являются доказательством ответственности. Как записывать решения агента в виде устойчивых объектов с причинно-следственными связями, цепочками хэшей для отслеживания происхождения и детерминистичными правилами работы с помощью Semantica.