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

Пересмотр процесса

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, всё состояние которого — текущий шаг, в нём не нуждается.

Связанное