Пересмотр процесса
Workflow часто несёт контракт собственного процесса: план, который он выполнит, чеклист, который пройдёт, корпус, который изучит, критерии, по которым примет результат. На середине работы реальность спорит с этим контрактом — пункт устарел, появилось требование, обесценивающее согласованный подход, выполненный шаг сделал остаток работы неверным. Этот паттерн даёт выполняющему агенту законный способ изменить контракт изнутри запуска, в момент, когда он видит проблему, вместо того чтобы доводить до конца процесс, о неверности которого уже знает. Изменение плана — самый известный случай, но не весь паттерн.
Структура
flowchart LR
A[Работа идёт] -.->|step teleportTo| T[teleport-вход]
T --> O[Владелец пересмотра]
O --> R[Существующая нода цикла]
R --> A
Три части: вход, владелец и возврат.
1. Вход — нода teleport
У ноды teleport нет обычных входящих связей, поэтому в неё не приходит ни один маршрут: агент
попадает туда только вызовом step({ processId, attemptId, teleportTo: "..." }) с идентификатором
попытки из текущего предъявления. Её hint добавляется к
каждому шагу запуска, и именно поэтому в hint формулируют и повод для прыжка, и границу
злоупотребления. Граница важнее: упавшая проверка, нестабильный тест, неудобное замечание ревью или
просто сложная задача относятся к своим владельцам исправления, а не к изменению процесса.
{ "id": "teleport-revise-tasks", "type": "teleport", "hint": "Jump here when the checklist itself no longer describes the work that must happen: remaining tasks turned out wrong, obsolete, missing, or in the wrong order. Not for a task that is merely hard, blocked, or failing — report that blocker to the user and stay on the task.", "directive": "You jumped here because the checklist no longer matches the work that must happen. Return the complete corrected checklist and the position to continue from.", "completionCondition": "The complete corrected checklist preserves already executed tasks in their original positions, and the position to continue from is returned", "inputSchema": { "type": "object", "additionalProperties": false, "properties": {}, "required": ["tasks", "resume_from_task"], "globalInputs": ["tasks", "resume_from_task"] }, "connections": { "success": "derive-revised-plan-state" }}2. Владелец собирает изменённый контракт типизированными глобалами
Пересмотр передаётся как объявленные глобальные переменные реестра, поэтому движок валидирует новое
состояние той же схемой, которая охраняла исходное. Поскольку teleport несёт собственную директиву и
валидируемую inputSchema, владельцем оказывается сам teleport, когда пересмотр — одна
типизированная подача: это пример выше из moira/todo-list. Когда пересмотр — содержательная работа
с собственным артефактом, владельцем становится нода, на которую teleport садится, а сам переход
только обосновывает изменение. Такова форма в moira/quick-task, где teleport-replan фиксирует,
почему согласованный план больше не подходит, и садится на существующую revise-plan, публикующую
следующую неизменяемую итерацию плана.
Не передавайте input при прыжке. Teleport покажет собственную директиву, когда переход
выполнится, а контекст запуска сохраняется целиком.
3. Возврат — существующая нода
После пересмотра управление возвращается в ноду, которая у флоу уже есть: в его проверку курсора или
независимое ревью, а не в частную копию этой логики. Тогда один контракт пересмотра обслуживает все
маршруты, которым он нужен. В moira/todo-list вычисление продолжается в существующую
check-tasks-remaining; в moira/quick-task переизданный план снова проходит plan-review, а в
режиме interactive — и согласование пользователем, поэтому пересмотренный план никогда не выполняется
без ревью.
Два инварианта
Производное состояние вычисляет движок, а не агент рядом с источником. Если агент вернёт и новый
список, и его длину, значения могут разойтись, и этого никто не заметит. Пусть агент возвращает
источник истины, а остальное выводит нода expression:
{ "id": "derive-revised-plan-state", "type": "expression", "expressions": ["total_tasks = tasks.length", "current_task = resume_from_task"], "connections": { "default": "check-tasks-remaining" }}Выполненное сохраняется, позиция возобновления названа явно. Пересмотр обязан сказать, что
происходит с прогрессом, иначе запуск либо повторяет сделанное, либо теряет своё место. Там, где
позиция служит идентификатором — нумерованный чеклист, упорядоченный план, — пересмотр оставляет
выполненные пункты на их местах и возвращает позицию, с которой продолжать, а движок проверяет её по
границам, объявленным в variableRegistry.
Почему не API, записывающий переменные напрямую
Вызов, записывающий любую переменную запуска, выглядит универсальным и стоит сразу трёх гарантий: он обходит реестр и схему ноды — единственные места, где состояние валидируется; он стирает границу полномочий, потому что тот же вызов, что чинит список задач, может поднять флаг права на коммит или переключить режим работы; и он отвязывает изменение состояния от перехода между нодами, поэтому история запуска перестаёт объяснять его поведение. Всё законное выражается через teleport и владельца, и эта форма сохраняет валидацию, границу полномочий и историю.
Когда workflow это нужно
Паттерн нужен workflow, который несёт контракт собственного процесса и может во время выполнения обнаружить, что контракт неверен. Workflow, всё состояние которого — текущий шаг, в нём не нуждается.
Связанное
- Паттерн Replan — случай плана с конкретными маршрутами
- Режим работы — что пересмотр всё равно проходит в каждом режиме
- Цикл валидации — обычное ревью, в которое возвращается пересмотр
- Антипаттерны — состояние и машинерия, которых пересмотр не добавляет