Антипаттерны
Антипаттерны — это повторяющиеся ошибки проектирования workflow, которые приводят к сломанным, неэффективным или неподдерживаемым workflow. Workflow Management Flow (WMF) автоматически проверяет их при создании и редактировании.
Каталог антипаттернов
Заголовок раздела «Каталог антипаттернов»#1: Кругооборот данных
Заголовок раздела «#1: Кругооборот данных»Директива говорит агенту собрать данные, затем inputSchema просит агента вернуть эти же данные как структурированный вывод. Агент работает как оператор ввода данных, копируя из вывода инструмента в поля схемы.
Обнаружение: inputSchema повторяет структуру данных, которые агенту было поручено собрать.
❌ Directive: "Get project info from package.json" inputSchema: { project_name: string, version: string, description: string } // Агент копирует данные из файла в поля схемы — бессмысленный кругооборот
✅ Use expression node to extract data, or capture agent's ANALYSIS in inputSchema inputSchema: { architecture_assessment: string, risks_identified: string[] }#2: Пустая InputSchema с побочными эффектами
Заголовок раздела «#2: Пустая InputSchema с побочными эффектами»У узла нет inputSchema, но директива указывает агенту выполнить побочные эффекты (создать файлы, запустить команды). Никаких свидетельств о выполненном не сохраняется.
Обнаружение: Узел agent-directive без inputSchema, где директива описывает действия с наблюдаемыми результатами.
❌ Directive: "Create test files for auth module" (no inputSchema) // Workflow не знает, что было создано и было ли выполнено успешно
✅ inputSchema: { files_created: string[], tests_passing: boolean } // Свидетельства завершения сохраняются в контексте workflow#3: Необъявленная переменная / Неявное продвижение
Заголовок раздела «#3: Необъявленная переменная / Неявное продвижение»Ссылка на переменную по голому имени, которая не объявлена в variableRegistry workflow, или ожидание, что локальный вывод узла станет глобальной переменной за счёт совпадения имён. Глобальные переменные должны объявляться явно; узел записывает глобальную переменную только когда указывает её имя в inputSchema.globalInputs.
Обнаружение: Шаблонная ссылка по голому имени (не локальная node-id.name и не системная переменная вроде executionId), для которой нет соответствующей записи в variableRegistry. Или workflow, который полагается на продвижение возвращаемого ключа узла в глобальную переменную по совпадению имён.
❌ Директива ссылается на переменную по голому имени, не объявленную в variableRegistry // Нет записи в реестре → значение undefined во время выполнения
❌ Узел возвращает ключ и ожидает, что он станет глобальным из-за совпадения имени // Неявного продвижения нет — значение остаётся локальным выводом узла
✅ Объявляйте каждую глобальную переменную в variableRegistry Узел записывает глобальную переменную только через inputSchema.globalInputs (указав имя) Ссылайтесь на локальные выводы узла как node-id.name#4: Цикл ревью через InputSchema вместо файлов
Заголовок раздела «#4: Цикл ревью через InputSchema вместо файлов»Обратная связь при ревью передаётся через текстовые поля inputSchema вместо персистентных файлов. Агент теряет контекст между итерациями.
Обнаружение: Цикл ревью/исправления, где узел исправления получает обратную связь только через текст inputSchema, а не через персистентный файл.
❌ Review node → inputSchema: { feedback: string } → Fix node reads feedback from input // Обратная связь эфемерна, теряется при повторе
✅ Review node writes review.md → Fix node reads review.md // Результаты сохраняются между итерациями и повторами#5: Неявные циклы исправлений
Заголовок раздела «#5: Неявные циклы исправлений»Директива говорит «исправляй пока не будет готово» без явной структуры цикла в графе. Агент повторяет попытки внутренне без контроля workflow.
Обнаружение: Слова вроде «retry», «keep trying», «fix until» в директиве без соответствующего условного узла, создающего цикл в графе.
❌ Directive: "Keep fixing tests until they all pass" // Нет структуры графа, агент зацикливается внутренне без контроля workflow
✅ [fix] → [run-tests] → condition(passed?) → [fix] / [next] // Явный цикл в графе с видимостью каждой итерации для workflow#6: Неограниченные циклы
Заголовок раздела «#6: Неограниченные циклы»Циклы валидации без лимитов итераций или путей эскалации. Могут выполняться бесконечно, если условие никогда не будет выполнено.
Обнаружение: Цикл в графе без переменной-счётчика итераций или без условия проверки лимита итераций.
❌ Loop: [action] → [check] → [fix] → [action] (бесконечно, если качество никогда не пройдёт)
✅ Expression node increments counter → Condition: iterations > max → escalation path // Агент не может застрять — после N попыток эскалация пользователю#7: Динамические данные в переменных workflow
Заголовок раздела «#7: Динамические данные в переменных workflow»Хранение динамических данных, генерируемых в ходе выполнения workflow (содержимое файлов, ответы API, HTML-вывод, результаты извлечения) в переменных workflow. Эти переменные внедряются в каждую директиву через шаблоны, раздувая контекст потенциально неограниченным содержимым.
Обнаружение: Переменные, получающие динамический контент через inputSchema во время выполнения, особенно контент >1KB или контент, растущий неограниченно.
Область применения: Этот антипаттерн относится к данным, передаваемым между шагами workflow — НЕ к статической конфигурации, хранящейся как значение default в variableRegistry. См. паттерн Статическая конфигурация workflow для понимания различия.
❌ inputSchema: { html_content: string } Next directive: "Publish {{step.html_content}}" // Целая HTML-страница хранится в переменной, внедряется в каждую последующую директиву
❌ inputSchema: { extraction_results: object } Next directive: "Analyze {{extraction_results}}" // Потенциально большие данные извлечения передаются туда-обратно через переменные
✅ Directive: "Save HTML to {{workspace_path}}/report.html" inputSchema: { file_path: string } Next directive: "Publish file at {{step.file_path}}" // В переменной хранится только путь, агент читает файл по необходимостиИсключение: Статический инструктивный контент, хранящийся как значение default в variableRegistry (правила, стандарты, чек-листы), — это правильный паттерн, а не злоупотребление переменными. Они намеренно размещены, чтобы агент видел их на каждом соответствующем шаге. См. Статическая конфигурация workflow.
#8: Модификация SystemReminder
Заголовок раздела «#8: Модификация SystemReminder»Системное соображение. systemReminder статичен и применяется ко ВСЕМ шагам. Инструкции для конкретных шагов должны быть в директивах узлов, а не в systemReminder.
#9: Ручное управление индексами агентом
Заголовок раздела «#9: Ручное управление индексами агентом»Агент вручную отслеживает индексы массивов, счётчики или пагинацию через inputSchema. Подвержено ошибкам и ломается при повторе.
Обнаружение: inputSchema с полями вроде current_index, next_item_number или значениями счётчиков, которые агент должен вычислять.
❌ inputSchema: { current_index: number, next_item: string } // Агент выполняет арифметику, подвержен ошибкам
✅ Expression node: current_index = {current_index} + 1 Directive: "Process {{items[{current_index}]}}" // Движок workflow обрабатывает арифметику детерминированно#10: Загрузка файла через контент
Заголовок раздела «#10: Загрузка файла через контент»Передача всего JSON workflow через параметры MCP-инструмента для больших workflow. Упирается в лимиты размера.
Обнаружение: manage({ action: "create", workflow: <large JSON> }) с workflow >50KB.
❌ manage({ action: "create", workflow: <50KB JSON> }) // Может упереться в лимиты размера параметров
✅ token({ action: "upload" }) → HTTP PUT with file content // Загрузка по токену обрабатывает любой размер#11: Дублирование контекста
Заголовок раздела «#11: Дублирование контекста»Копирование информации, уже имеющейся в контексте агента, в переменные workflow. Избыточно и раздувает контекст.
Обнаружение: inputSchema, захватывающая данные, которые агент уже имеет в контексте разговора (например, списки директорий, информация о проекте из предыдущего анализа).
❌ variable: project_structure = "<output of tree command>" // Агент уже имеет это в контексте; повторное хранение тратит токены
✅ Let agent re-read current data when needed. Store only stable references (paths, IDs).#12: Вынесение критических инструкций в файлы
Заголовок раздела «#12: Вынесение критических инструкций в файлы»Перемещение важных инструкций и стандартов агента из значения default в variableRegistry во внешние файлы с ожиданием, что агент будет читать их каждый раз. Это создаёт риск галлюцинаций и добавляет лишний ввод-вывод.
Обнаружение: Директива говорит «прочитай правила из файла X» для контента, который должен быть стабильно доступен. Инструкции, критичные для поведения агента, хранятся вне workflow.
❌ Directive: "Read planning standards from ./standards.md before creating plan" // Агент может пропустить чтение, вспомнить из обучающих данных или прочитать выборочно // Дополнительная операция ввода-вывода на каждом шаге, требующем этих правил
✅ Declare as a variableRegistry default: planning_standards = "1. Each step must... 2. Tests built-in..." Directive: "Create plan following {{planning_standards}}" // Движок workflow гарантирует доставку; нет ввода-вывода; нет риска галлюцинаций#13: Ложный отчёт о зацикливании
Заголовок раздела «#13: Ложный отчёт о зацикливании»Агент видит одну и ту же директиву ревью/валидации дважды и сообщает пользователю, что workflow зациклился или застрял, после чего останавливается.
Обнаружение: Обратная связь повторной валидации к идентичному узлу без счётчика раундов, без подсказки «ожидаемо, не баг» и зачастую без ограничения.
❌ [validate] → condition(issues?) → true → [fix] → [validate] // Та же директива validate появляется снова; агент сообщает «workflow застрял в цикле»
✅ expression(validation_round = validation_round + 1) → [validate] (рендерит счётчик и подсказку) condition(validation_round < max_validation_rounds) → true → [fix] → expression(...) → [validate] → false → [ask-user-limit]Директива входа в цикл рендерит счётчик раундов и сообщает, что цикл ожидаем:
Re-validation pass {{validation_round}} of {{max_validation_rounds}} — a normal quality loop,expected to converge. This is NOT a bug and NOT a stuck flow; do not report a loop to the user.Счётчик инкрементируется узлом expression (автоматически потоком), объявляется в variableRegistry с числовым default (счётчик по умолчанию 0, max по умолчанию, например, 5). Счётчик, инкрементируемый агентом, сам является антипаттерном (см. #9).
#14: Объявленная переменная без default
Заголовок раздела «#14: Объявленная переменная без default»{{var}} рендерится как [[UNDEFINED_VARIABLE]] во время выполнения, хотя var объявлена в variableRegistry; выражение вроде var = var + 1 даёт NaN.
Обнаружение: Объявление в variableRegistry без default, где переменная не инициализирована в start initialData и не записывается ни одним вышестоящим узлом через globalInputs.
❌ variableRegistry: { validation_round: { type: "number" } } // нет default Directive: "Pass {{validation_round}} ..." // рендерится [[UNDEFINED_VARIABLE]] Expression: validation_round = validation_round + 1 // NaN
✅ variableRegistry: { validation_round: { type: "number", default: 0 } } // Счётчик начинается с реального значения; инкременты и шаблоны разрешаютсяЗадавайте default каждому счётчику и флагу. Валидатор выдаёт предупреждение «объявлена без default», когда переменная, на которую ссылаются, не имеет default и нигде не записывается выше по потоку.
#15: Шаблон в данных
Заголовок раздела «#15: Шаблон в данных»{{name}}, который агент записывает в данные, возвращаемые через step(), рендерится как [[UNDEFINED_VARIABLE]], когда это значение позже интерполируется в директиву.
Обнаружение: Возвращаемые значения inputSchema, содержащие синтаксис шаблона {{...}} по голому имени.
❌ step() returns: { summary: "Report for {{project_name}}" } Next directive: "Publish {{summary}}" // {{project_name}} рендерится [[UNDEFINED_VARIABLE]]
✅ step() returns: { summary: "Report for acme-api" } // Подставленные значения данных считаются литеральным текстом, не парсятся как шаблоныНикогда не записывайте голый {{...}} в данные step(); шаблоны принадлежат только статическим полям узла (directive, completionCondition, message). Движок считает подставленные значения данных литеральными.
#16: Внедрение шаблона (SSTI)
Заголовок раздела «#16: Внедрение шаблона (SSTI)»Недоверенный ввод, содержащий синтаксис шаблона, интерполированный в директиву, может попытаться выгрузить набор переменных ({{context.variables}}), выполнить хелпер each/if или обойти область видимости другого узла через node-id.field.
Обнаружение: Директива интерполирует переменную, значение которой исходит из недоверенного ввода, способного содержать синтаксис {{...}}.
❌ Directive: "Summarize the user's note: {{user_note}}" где user_note = "{{context.variables}}" // попытка выгрузить все переменные
✅ Интерполированные ЗНАЧЕНИЯ литеральны — движок нейтрализует синтаксис фигурных скобок, исходящий из подставленных данных, поэтому {{context.variables}} в user_note рендерится как обычный текст. Не отражайте недоверенный синтаксис шаблона в директивах; предпочитайте явные именованные переменные полным выгрузкам {{context.variables}}.Интерполированные значения никогда не парсятся повторно как шаблоны. Шаблоны живут в статических полях, контролируемых автором.
Правильные паттерны
Заголовок раздела «Правильные паттерны»Паттерн динамического индекса массива
Заголовок раздела «Паттерн динамического индекса массива»Правильный способ итерации по элементам массива:
1. Store array: items = ["a", "b", "c"]2. Store index: current_index = 03. Directive: "Process item {{items[{current_index}]}}"4. Expression: current_index = {current_index} + 15. Condition: {current_index} < items.length → loop / continueПаттерн ревью через файлы
Заголовок раздела «Паттерн ревью через файлы»[implement] → [review-to-file] → [check-issues] → [fix-from-file] → [review-to-file] ↓ no issues → [next]