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

Узлы

Узлы — это строительные блоки воркфлоу Moira. Каждый узел представляет шаг в процессе со специфическим поведением, определяемым его типом.

Типы узлов

Ниже перечислены встроенные типы узлов Moira. Установленные расширения могут добавлять типы с пространством имён, например extension-name.node-name. Live-реестр расширений передаёт их схемы конфигурации валидатору, а каталог типов узлов Moira описывает их средству просмотра workflow.

Start

Точка входа для выполнения воркфлоу.

End

Терминальный узел, отмечающий завершение воркфлоу.

Agent Directive

Задача агента с директивой и условием завершения.

Condition

Ветвление выполнения на основе структурированных условий.

Expression

Вычисление значений с помощью арифметических выражений.

Subgraph

Делегирование другому воркфлоу.

User Notification

Отправка уведомления через все включённые каналы связи, настроенные текущим пользователем.

Telegram Notification (устаревшая)

Telegram-only нода совместимости для существующих provider-specific workflow.

Teleport

Цель перехода, доступная только через явный teleport.

Lock

PIN-блокировка с подтверждением через Telegram.

Materialize

Доставка файлов из реестра через пятиминутное разрешение, привязанное к ноде.

Start Node

Точка входа для выполнения воркфлоу. Каждый воркфлоу должен иметь ровно один start node.

{
"id": "start",
"type": "start",
"connections": {
"default": "first-task"
}
}
СвойствоОбязательноОписание
idДаСоглашение: должен быть “start”
typeДаДолжен быть "start"
connections.defaultДаID следующего узла

Start узел заполняет глобальные переменные воркфлоу из значений default в variableRegistry. Объявляйте глобальные переменные в variableRegistry уровня воркфлоу, а не на start узле.

End Node

Терминальный узел, отмечающий завершение воркфлоу. Нет исходящих соединений.

{
"id": "end",
"type": "end",
"finalOutput": ["result", "summary"]
}
СвойствоОбязательноОписание
idДаСоглашение: должен быть “end”
typeДаДолжен быть "end"
finalOutputНетКлючи контекста для включения в финальный результат

note: End узлы не имеют соединений — это терминальные узлы.

Agent Directive Node

Основной тип узла для задач агента. Содержит директиву (что делать) и условие завершения (когда готово).

{
"id": "analyze-requirements",
"type": "agent-directive",
"directive": "Проанализируй документ требований и определи ключевые фичи",
"completionCondition": "Фичи перечислены с приоритетами",
"inputSchema": {
"type": "object",
"globalInputs": ["analysis_done"],
"properties": {
"features": {
"type": "array",
"items": { "type": "string" }
}
},
"required": ["analysis_done", "features"]
},
"connections": {
"success": "next-step"
}
}
СвойствоОбязательноОписание
directiveДаЧто агент должен сделать
completionConditionДаКогда шаг завершен
inputSchemaНетJSON Schema для валидации ответа
inputSchema.globalInputsНетИмена глобальных переменных variableRegistry, которые пишет узел
inputSchema.propertiesНетЛокальные выводы узла (используются как node-id.name)
connections.successДаСледующий узел при успехе

В примере analysis_done — объявленная глобальная переменная (должна существовать в variableRegistry), которую пишет этот узел и которая доступна в других местах как {{analysis_done}}; features — локальный вывод узла, доступный как {{analyze-requirements.features}}. Возвращённый ключ, не являющийся ни объявленной глобальной, ни описанным локальным выводом, отклоняется.

Используйте inputSchema для структурированных ответов. globalInputs перечисляет глобальные переменные, которые пишет узел; properties описывает его локальные выводы. Движок автоматически валидирует и маршрутизирует ответ.

Если ответ не соответствует inputSchema, движок записывает ошибку и снова приостанавливается на том же узле. Сообщение объясняет ожидаемую схему и ошибки, но не повторяет отклонённый payload. Per-node retry-поля из старых definitions поддерживаются только для совместимости и не ограничивают и не перенаправляют этот цикл валидации; бизнес-логику retry и эскалации нужно явно моделировать в графе workflow.

Condition Node

Ветвление выполнения на основе структурированных условий:

{
"id": "check-result",
"type": "condition",
"condition": {
"operator": "eq",
"left": { "contextPath": "status" },
"right": "success"
},
"connections": {
"true": "success-path",
"false": "retry-step"
}
}
СвойствоОбязательноОписание
conditionДаОбъект структурированного условия
connections.trueДаСледующий узел когда условие true
connections.falseДаСледующий узел когда условие false

Структурированные условия

Условия используют структурированный формат (не строковую оценку):

{
"operator": "and",
"conditions": [
{
"operator": "gt",
"left": { "contextPath": "score" },
"right": 80
},
{
"operator": "eq",
"left": { "contextPath": "validated" },
"right": true
}
]
}

Поддерживаемые операторы:

  • eq, neq — равно, не равно
  • gt, gte, lt, lte — операторы сравнения
  • contains — вхождение в строку/массив
  • exists — проверка существования
  • and, or, not — логические операторы

Expression Node

Вычисление значений с помощью арифметических выражений. Полезно для счётчиков, расчётов и трансформации переменных:

{
"id": "increment-counter",
"type": "expression",
"expressions": ["counter = counter + 1", "result = counter * multiplier"],
"connections": {
"default": "next-step",
"error": "error-handler"
}
}
СвойствоОбязательноОписание
expressionsДаМассив выражений присваивания
connections.defaultДаСледующий узел после успешного вычисления
connections.errorНетСледующий узел при ошибке вычисления

Синтаксис выражений

Выражения поддерживают базовые арифметические операции:

  • Арифметика: +, -, *, /
  • Скобки: (a + b) * c
  • Присваивание: result = a + b
  • Пути в контексте: step.index, plan.items[0].value, tasks[current_index].action
{
"expressions": ["total = price * quantity", "tax = total * 0.1", "final_price = total + tax"]
}

Выражения вычисляются собственным изолированным парсером, а НЕ через JavaScript eval. Чтение использует только собственные свойства; постоянный или переменный индекс массива должен быть целым, неотрицательным и находиться в границах. Цель присваивания — безопасное простое имя. При наличии реестра присваивание обязано обращаться к объявленной глобальной переменной и соответствовать её JSON Schema; при ошибке нода ничего не публикует.

Обработка ошибок

Expression node может завершиться с ошибкой в двух случаях:

  • Деление на ноль
  • Некорректное, небезопасное, неразрешимое или выходящее за границы чтение пути
  • Необъявленная цель присваивания или значение вне схемы реестра

При ошибке выполнение переходит к connection error если он определён, иначе воркфлоу завершается с ошибкой.

Subgraph Node

Делегирование выполнения другому воркфлоу:

{
"id": "run-tests",
"type": "subgraph",
"graphId": "test-workflow",
"inputMapping": {
"codeDir": "projectPath"
},
"outputMapping": {
"testResults": "results"
},
"connections": {
"success": "next-step"
}
}
СвойствоОбязательноОписание
graphIdДаID ссылаемого воркфлоу
inputMappingДаРодительский контекст -> контекст подграфа
outputMappingДаКонтекст подграфа -> родительский контекст
connections.successДаСледующий узел при успехе

Подграфы обеспечивают композицию и повторное использование воркфлоу. Агент видит это как непрерывный воркфлоу.

connections.error необязателен и в текущем runtime не используется для исключений выполнения дочернего workflow или ошибок mapping. Такие ошибки записываются, а выполнение приостанавливается на subgraph node, чтобы агент исправил причину и повторил шаг.

User Notification Node

Отправляет сообщение через все корректные включённые каналы пользователя выполнения. Workflow не может выбирать provider, получателя или credential:

{
"id": "notify-complete",
"type": "user-notification",
"message": "Воркфлоу {{workflowName}} успешно завершен",
"format": "markdown",
"silent": false,
"connections": {
"default": "next-step",
"error": "notification-failed"
}
}
СвойствоОбязательноОписание
messageДаТекст уведомления с поддержкой шаблонов
formatНетПереносимый формат: plain, markdown или html
silentНетЗапрос тихой доставки, если provider её поддерживает
attachProgressImageНетПриложить текущий ограниченный PNG прогресса workflow
attachmentНетОдно bounded base64-вложение image или document с именем/MIME
connections.defaultДаПуть при полной, частичной доставке или отсутствии подходящих каналов
connections.errorНетПуть при полном провале попыток; иначе используется default

attachment и включённый attachProgressImage взаимоисключающие. Имя вложения должно соответствовать безопасному переносимому allowlist, а MIME — форме type/subtype. До provider delivery сервис применяет общие per-user/provider rate limits, ограничения concurrency пользователя и provider, deadlines, пределы текста и байтов. Настроенный канал Telegram поддерживает текст, PNG/JPEG-изображения и документы.

Результат ноды хранится под её ID. userNotificationStatus принимает delivered, partial, no_configured_channels или all_failed; configuredChannels, deliveredChannels и channels содержат счётчики и очищенные per-channel status/reason. Настроенный канал, который не поддерживает тип запрошенного вложения, получает статус unsupported и пропускается; если другого подходящего канала или ошибки проверки доступности нет, общий результат — no_configured_channels, а не all_failed. Credentials, получатели, тело сообщения и байты вложения в результаты и ошибки не попадают.

Устаревшая Telegram-specific нода

telegram-notification остаётся исполняемой для существующих workflow. Она отправляет только через Telegram, а заданный chatId сохраняет точное provider-specific значение получателя. Для новых обычных уведомлений используйте user-notification; переход explicit legacy recipient к generic fan-out требует намеренной миграции workflow.

Teleport Node

Цель перехода, доступная только через явный teleport, а не через обычные соединения. Работает как agent-directive: приостанавливается для ввода и валидирует его схему, но достигается только по явному запросу агента.

{
"id": "teleport-replan",
"type": "teleport",
"directive": "Перепиши план разработки",
"completionCondition": "Новый план создан и проверен",
"hint": "Используй, когда текущий план нужно перестроить",
"inputSchema": {
"type": "object",
"properties": {
"reason": { "type": "string" }
},
"required": ["reason"]
},
"connections": { "success": "plan-node" }
}
СвойствоОбязательноОписание
hintДаПонятное человеку описание, когда нужен teleport
directiveДаИнструкция, показанная агенту после teleport
completionConditionДаКритерии успешного завершения шага teleport
inputSchemaНетJSON Schema для валидации ответа агента
connections.successДаСледующий узел после ввода для teleport
connections.errorНетУзел обработки ошибки

У teleport-узлов не должно быть входящих соединений от других узлов. При валидации они исключены из предупреждений о недостижимых узлах.

Использование Teleport во время выполнения

Если воркфлоу содержит teleport-узлы, их подсказки добавляются к каждому ответу шага в разделе «Available Teleport Jumps». Для перехода передайте параметр teleportTo в step():

step({ processId: "abc123", attemptId: "attempt-current", teleportTo: "teleport-replan" })
  • Целью может быть только узел типа teleport
  • Используйте идентификатор попытки шага из текущего предъявления
  • Не передавайте input при переходе: teleport-узел сначала покажет собственную директиву
  • Контекст выполнения сохраняется целиком
  • После ввода для teleport-узла выполнение продолжается через connections.success

Lock Node

PIN-блокировка выполнения. Она доставляет PIN в настроенный Telegram-чат текущего пользователя с inline-клавиатурой Approve и приостанавливает workflow только после успешной доставки.

{
"type": "lock",
"id": "approval-gate",
"reason": "Деплой на продакшен для {{workflow_name}}",
"connections": {
"unlocked": "proceed-node"
}
}
СвойствоОбязательноОписание
reasonДаПричина блокировки (поддерживает шаблоны {{var}})
connections.unlockedДаСледующий узел после разблокировки

Поведение:

  1. Для запуска любого workflow с lock node текущему пользователю нужны корректные bot token и chat ID. Ни skipNotificationCheck, ни его устаревший alias skipTelegramCheck не обходят это требование.
  2. При первом посещении Moira сохраняет только хеш ожидающего доставки PIN, отправляет открытый PIN в настроенный chat, активирует блокировку, сохраняет _lockId и приостанавливает выполнение.
  3. При отсутствующих или некорректных настройках и при ошибке доставки не возникает доступной активной блокировки или context reference. Повторное посещение запускает новую попытку доставки с новым PIN.
  4. Следующие посещения проверяют активную блокировку или введённый пользователем PIN и после разблокировки переходят через connections.unlocked.

MCP-ответы и ответы workflow никогда не содержат сгенерированный PIN. Пользователь может передать PIN отдельно или подтвердить блокировку в Telegram.

Materialize Node

Доставляет объявленные автором файлы в файловую систему агента, удерживая их отрендеренное содержимое вне контекста агента. Сервер создаёт пятиминутное разрешение на tar-загрузку, привязанное к ноде; агент выполняет или повторяет точную команду из сгенерированной директивы и завершает шаг значением null или {}. Хост, который не может выполнить эту команду, использует указанный в директиве fallback-маршрут доставки в контекст: он расходует контекст и завершает шаг так же.

{
"id": "materialize-standards",
"type": "materialize",
"basePath": "{{workspace_path}}",
"files": [
{ "path": "standards/planning.md", "from": "planning_standards" },
{ "path": "plans/.keep", "content": "" }
],
"connections": {
"success": "create-plan",
"error": "materialize-failed"
}
}
СвойствоОбязательноОписание
basePathДаРезультат рендеринга должен быть непустым и не содержать NUL
filesДаОт 1 до 100 записей архива
files[].pathДаБезопасный шаблонизируемый путь относительно basePath
files[].fromОдно изСтроковая запись variableRegistry, чей текущий default служит исходником
files[].contentОдно изДолжен быть ровно ""; создаёт каркасный файл
connections.successДаСледующая нода после пустого completion input
connections.errorНетПуть при ошибке валидации, конфигурации или выдачи разрешения

from и content взаимоисключающие. from должен ссылаться на объявленную строковую переменную со строковым default; произвольный inline-текст отклоняется. При показе шага сервер рендерит basePath и сводку путей файлов в директиве. Отрендеренный basePath становится частью выданной команды. При HTTP-запросе архива сервер заново загружает текущее определение workflow и рендерит каждый путь записи архива и содержимое из реестра с привязанным контекстом выполнения. Поэтому изменение workflow после выдачи команды может изменить скачанные пути или содержимое, но не каталог назначения в уже выданной команде. Повторный показ приостановленного шага использует актуальное определение и выдаёт новые команду и разрешение вместо снимка, сохранённого при старте выполнения.

Сгенерированная POSIX-команда имеет следующую форму; все аргументы уже безопасно заключены в кавычки для shell:

Terminal window
mkdir -p -- '<basePath>' && curl -sSf -- '<reusable-url>' | tar -x -C '<basePath>'

Копируйте выданную команду без изменений. Записи архива относительны к basePath; сам basePath в tar не входит. Пути должны быть нормализованными, относительными, непустыми и уникальными после рендеринга; запрещены пустые сегменты, ., .., абсолютный или начинающийся с обратной косой черты путь и NUL. Лимиты: 100 файлов, 1 MiB UTF-8-содержимого на файл и 10 MiB несжатого содержимого суммарно.

URL живёт пять минут и привязан к текущим пользователю, выполнению и ноде. Его можно скачивать повторно, пока выполнение ожидает на этой ноде; сразу после перехода выполнения URL становится недействительным. Сгенерированная директива объясняет окно повторов и напоминает, что доставка не доказывает чтение; каждый последующий потребитель всё равно должен явно требовать чтения нужных ему файлов. Вызов session({ action: "current_step" }) на паузе выдаёт свежий URL без продвижения графа. Текстового fallback намеренно нет.

Полный контракт архива, путей, разрешений и ошибок, а также применение в Workflow Management Flow описаны в разделе Материализация файлов.

Ноды расширений

Установленные расширения добавляют типы в собственном пространстве имён, например corporate-messenger.send. Манифест задаёт форму конфигурации и схемы валидации; браузер строит по ним универсальный редактор и не загружает frontend-код расширения. Успешный результат сохраняется под ID ноды. Ошибки входа, обработчика, срока, runner и выхода переходят по connections.error, если эта связь есть; иначе Moira записывает диагностику и приостанавливается на ноде для повтора.

Живой каталог типов отличает недоступный runner от доступного реестра, в котором не установлено конкретное расширение. Контракты манифеста, SDK и исполнения описаны в Написании расширения.

Input Schema

Определение ожидаемой структуры ответа с использованием JSON Schema:

{
"inputSchema": {
"type": "object",
"properties": {
"summary": {
"type": "string",
"description": "Краткое резюме находок"
},
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": { "type": "string" },
"priority": { "type": "number" }
}
}
}
},
"required": ["summary"]
}
}

Автоматические типы узлов

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

Read Note Node

Читает заметки по фильтру в переменную контекста:

{
"type": "read-note",
"id": "load-notes",
"outputVariable": "projectNotes",
"filter": {
"tag": "{{projectTag}}",
"keyPattern": "project-"
},
"singleMode": false,
"connections": {
"default": "next-node",
"error": "error-handler"
}
}
СвойствоОбязательноОписание
outputVariableДаПеременная контекста для результатов
filter.tagНетФильтр по точному тегу
filter.keyPatternНетФильтр по префиксу ключа
filter.keySearchНетПоиск подстроки в ключе
singleModeНетВернуть объект вместо массива
connections.errorНетУзел обработки ошибки

Write Note Node

Записывает данные из контекста в заметки:

{
"type": "write-note",
"id": "save-results",
"key": "results-{{timestamp}}",
"source": "analysisResults",
"tags": ["analysis"],
"connections": {
"default": "next-node"
}
}
СвойствоОбязательноОписание
keyНет*Ключ заметки, обязательный в одиночном режиме
sourceДаПеременная контекста со значением
tagsНетНазначаемые теги
batchModeНетОбрабатывать массив заметок

Upsert Note Node

Ищет существующую заметку или создаёт новую:

{
"type": "upsert-note",
"id": "upsert-config",
"search": { "tag": "config" },
"keyTemplate": "{{projectId}}-config",
"value": "configData",
"connections": {
"default": "next-node"
}
}
СвойствоОбязательноОписание
search.tagНетПоиск по тегу
search.keyPatternНетПоиск по префиксу ключа
keyTemplateДаКлюч новой заметки, если она не найдена
valueДаПеременная контекста со значением

Все параметры фильтра и ключа поддерживают шаблонные выражения {{ variable }}.

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

  1. Начинайте со start — Каждый воркфлоу должен иметь ровно один start узел
  2. Заканчивайте end — Используйте end узлы для отметки точек завершения
  3. Четкие директивы — Будьте конкретны в том, что агент должен сделать
  4. Проверяемые условия — Условия завершения должны быть объективно измеримы
  5. Валидация схемы — Используйте inputSchema для структурированных ответов
  6. Пути ошибок — Используйте error connection только там, где его маршрутизирует документированное поведение конкретного типа узла

Связанное