Перейти к содержимому

Воркфлоу

Воркфлоу в Moira — это направленный граф узлов, определяющий многошаговый процесс для выполнения AI-агентами.

Структура воркфлоу

Статический прогресс выполнения

Workflow может объявить необязательный top-level граф progress для краткого пользовательского представления. Определение может содержать шаблонизируемые title и goal, ограниченный набор общих facts и упорядоченные ноды. Нода содержит id, шаблонизируемый label, необязательный структурный plain-text content (summary, details, outcome, next) и статическую связь connections.default для отрисовки.

Граф progress — это представление воркфлоу как процесса: его ноды — блоки, а порядок массива — порядок процесса. Если progress присутствует, каждая нода основного графа, включая ноды маршрутизации, объявляет свой блок через progressNodeId; каждый блок несёт описание в content.summary; и каждая связь, которая выходит из блока либо возвращается к более раннему блоку или к своему же блоку, несёт запись connectionLabels с теми же ключами, что и connections: простую подпись или { "label": …, "cycle": { "cause": …, "exit": … } } для возврата. Переходы, циклы и блоки-хабы выводятся из основного графа и никогда не описываются дважды; отрисовочная связь connections.default этим выводом игнорируется. Шаблон результата ({{progress_*_outcome}}) стоит ровно на одном блоке, которому принадлежит нода, записывающая эту переменную. Валидация сообщает о каждом нарушении как об ошибке со стабильным кодом — unowned-node, unknown-block, empty-block, empty-description, unlabeled-edge, unexplained-cycle, outcome-duplicate, outcome-unowned, unconnected-block, — а moira-workflow <file> derive и GET /api/workflows/:id/process показывают выведенные блоки с теми же диагностиками.

Нода, на которой запуск останавливается (шаг agent-directive или другой останавливающий тип), также может объявить шаблонизируемый progressActiveLabel. Он заменяет отображаемую подпись её блока только пока именно эта нода является текущей, поэтому workflow может правдиво показывать unit, iteration, validation или repair detail без изменения стабильной подписи блока. Поле требует, чтобы нода принадлежала блоку, и не влияет на routing или сохранённое state.

Note запуска проецируется как название задачи. Та же нода может объявить progressActiveContent с теми же структурными полями. Пока эта нода активна, указанные поля заменяют соответствующие поля её блока, а пропущенные сохраняют базовые значения. Вложенные строки используют обычный реестр переменных и защиту шаблонов. Progress не хранит историю presentation: замена контекста, привязанного к revision, полностью заменяет следующую проекцию и не сохраняет устаревшие значения прежнего плана. После интерполяции текст повторно проверяется по тем же ограничениям. Слишком большое runtime-значение явно прерывает projection, а не молча обрезается до вводящей в заблуждение сводки.

Статус блока определяется записанным маршрутом запуска, а не порядком массива. Каждый запуск записывает по одному посещению на каждую выполненную ноду — ноду, связь, через которую она вышла (teleport для прыжка), изменённые переменные и факт паузы, — а session({ action: "progress" }) проецирует этот маршрут на процесс: блок последнего посещения — active или waiting, если запуск там ждёт; посещённый блок — done или repeated с числом проходов через его рабочие шаги; блок, чья работа не выполнялась или который запуск обошёл, — skipped; остальные — pending. Возврат к раннему блоку через repair, цикл или replan снова делает его активным и добавляет проход. Ничто непосещённое никогда не отмечается завершённым, а запуск, созданный до записи маршрутов, показывает только текущий блок активным с routeRecorded: false. Та же проекция содержит маршрут с отметками циклов и каждую переменную с историей; значения, заданные извне флоу, показаны как корректировки с их автором. Connections не маршрутизируют execution.

Если поле progress присутствует, каждая нода основного графа ссылается на существующий блок, как описано выше. Несколько нод основного графа могут ссылаться на один блок. Для активного блока фокусом является текущая нода основного графа, для остальных — первая связанная нода в порядке workflow. Pending- и skipped-блоки сохраняют summary, details и next, но скрывают outcome, чтобы результат прежней revision или unit не выглядел текущим во время engine-owned перехода.

Движок предоставляет общую содержательную visual model и ограниченный PNG-рендерер со светлой и тёмной темами. Полная задача, цель, facts, результаты завершённых блоков, текущее действие, детали и следующий шаг видны без hover. Текст переносится без обрезки, а блоки укладываются в детерминированные ряды слева направо. Агент получает короткоживущую, привязанную к revision и одноразовую ссылку через session progress-image-token; бинарные данные через MCP не передаются. Токен принимает необязательные theme (light|dark), viewportWidth (480–4096), viewcards (сетка карточек с содержимым, по умолчанию) или process (агрегированный вид блоков: блоки в порядке процесса с подписанными переходами, возвраты пунктирными дугами с подписью перехода, переходы в блоки-хабы записаны внутри исходного блока — как дорожки на странице запуска; причина и выход цикла есть на странице запуска, а не в картинке) — и hide / collapse: идентификаторы блоков или авторских нод (нода называет свой блок), которые исключаются из картинки с переносом их переходов на соседей или рисуются чипом только с названием. Неизвестные идентификаторы отклоняются при выпуске токена. Нода user-notification может задать attachProgressImage: true и использовать обычное сообщение как подпись к изображению. Такая нода обязана принадлежать существующему блоку. Устаревшая нода совместимости telegram-notification поддерживает то же вложение для provider-specific workflow.

Engine-интеграции, у которых есть workflow и execution, используют renderExecutionProgressImage(...). При отсутствии progress он возвращает null, иначе PNG buffer, MIME type, dimensions, workflow version и execution revision; ошибки rendering остаются errors.

Страница флоу (см. руководство «Чтение и правка флоу») показывает выведенный процесс самого определения и позволяет владельцу править его на месте. На странице запуска (см. руководство «Чтение запуска») та же проекция показана как дорожки, карта, конспект и маршрут, а панель блока раскрывает шаги каждого блока и фокусирует технический граф нод. Страница и PNG содержат одинаковую существенную информацию. Воркфлоу без progress показывает вместо этого технический граф нод и панель переменных.

Каждый воркфлоу состоит из:

{
"id": "my-workflow",
"metadata": {
"name": "Мой воркфлоу",
"version": "1.0.0",
"description": "Описание того, что делает этот воркфлоу"
},
"variableRegistry": {
"project_name": { "type": "string", "description": "Название проекта" }
},
"nodes": [
// Массив определений узлов
]
}

Метаданные

ПолеОбязательноеОписание
nameДаЧеловекочитаемое имя воркфлоу
versionДаСтрока семантической версии
descriptionДаЧто выполняет воркфлоу

Реестр переменных

variableRegistry объявляет глобальные переменные воркфлоу один раз — единый источник истины для типа и описания каждой переменной. Каждая запись имеет ключ-имя переменной:

ПолеОбязательноеОписание
typeДаstring, number, boolean, object или array
descriptionДаЧто хранит переменная
defaultНетНачальное значение, заданное при старте воркфлоу

Глобальные переменные используются по короткому имени ({{project_name}}); узел записывает глобальную переменную, перечислив её имя в inputSchema.globalInputs.

Массив узлов

Узлы — это шаги вашего воркфлоу. Каждый узел имеет id и type, определяющий его поведение.

Выполнение воркфлоу

При запуске воркфлоу:

  1. Движок создаёт экземпляр выполнения с уникальным processId
  2. Находит start узел (type: start)
  3. Возвращает первую директиву и выданный сервером идентификатор попытки шага
  4. Агент выполняет работу и отправляет результат с Process ID и идентификатором попытки шага
  5. Движок атомарно сохраняет переход, точный ответ для повторов и следующую попытку
  6. Повторяет до достижения end узла (type: end)

Повтор той же попытки с теми же данными возвращает сохранённый ответ без повторного перехода. Каждое последующее приостановленное предъявление получает новый идентификатор попытки.

Контекст выполнения

Каждое выполнение поддерживает объект контекста:

{
variables: Record<string, unknown>; // Глобальные в variables[name]; локальные выводы узла в variables[nodeId]
nodeStates: Record<string, unknown>; // Состояние по узлам
executionId: string; // Уникальный ID выполнения
workflowId: string; // ID исходного воркфлоу
currentNodeId: string; // Текущая позиция
}

Глобальные переменные (объявленные в variableRegistry) хранятся в variables[name] и разрешаются по короткому имени ({{name}}). Выводы узла хранятся в variables[nodeId] и разрешаются как {{node-id.name}}.

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

Типы узлов

Ниже показаны распространённые встроенные типы узлов. Таблица содержит примеры, а не полный перечень; встроенные типы и их актуальные контракты приведены в разделе Узлы. Установленные расширения могут добавлять типы с пространством имён; их актуальные название, источник, версия и схема конфигурации поступают из каталога типов узлов текущей установки.

ТипНазначение
startТочка входа для выполнения воркфлоу
endТерминальный узел, отмечающий завершение
agent-directiveЗадача агента с директивой и условием завершения
conditionВетвление на основе структурированных условий
expressionВычисление значений с помощью арифметических выражений
subgraphДелегирование другому воркфлоу
user-notificationУведомление через настроенные каналы текущего пользователя
telegram-notificationУстаревшая Telegram-only нода совместимости

Соединения

Узлы связываются через объект connections, определяющий поток. Каждый тип узла имеет специфические типы соединений:

Agent Directive Node

{
"id": "analyze-task",
"type": "agent-directive",
"directive": "Проанализируй требования к задаче",
"completionCondition": "Анализ завершен",
"connections": {
"success": "next-step",
"error": "error-handler"
}
}

Condition Node

{
"id": "check-status",
"type": "condition",
"condition": {
"operator": "eq",
"left": { "contextPath": "status" },
"right": "success"
},
"connections": {
"true": "success-path",
"false": "retry-path"
}
}

Полный пример воркфлоу

{
"id": "simple-workflow",
"metadata": {
"name": "Простой воркфлоу задачи",
"version": "1.0.0",
"description": "Базовый воркфлоу, демонстрирующий соединения узлов"
},
"nodes": [
{
"id": "start",
"type": "start",
"connections": { "default": "main-task" }
},
{
"id": "main-task",
"type": "agent-directive",
"directive": "Выполни назначенную задачу",
"completionCondition": "Задача успешно выполнена",
"connections": { "success": "end" }
},
{
"id": "end",
"type": "end"
}
]
}

Видимость воркфлоу

Воркфлоу имеют настройки видимости:

  • private — Только владелец имеет доступ
  • public — Все пользователи могут запустить воркфлоу

Лучшие практики

  1. Начинайте со start — Каждый воркфлоу должен иметь start узел
  2. Заканчивайте end — Используйте end узлы для отметки завершения
  3. Четкие директивы — Пишите недвусмысленные инструкции
  4. Измеримые условия — Условия завершения должны быть проверяемы
  5. Обработка ошибок — Включайте error соединения для graceful failures
  6. Документация — Добавляйте описания к сложным узлам

Связанное

  • Узлы — Типы узлов и конфигурация
  • Шаблоны — Динамический контент в воркфлоу