КЛАСТЕР 03 · ПАМЯТЬ И СОСТОЯНИЕ СОСТОЯНИЕ ЗАДАЧИ ~18 МИНУТ
День 13 · из 35

Task state
machine

До этого «состояние агента» — это туманный массив messages. Сегодня вводим строгую структуру: агент как конечный автомат. Каждый шаг — переход из явного состояния в другое. Это разница между «модель сама поймёт» и «модель работает в управляемом каркасе».

Суть урока

Когда задача сложная и многоэтапная, простой цикл «reason → act → observe» начинает разваливаться: агент путается между этапами, забывает, что уже сделал, повторяет одни и те же шаги. Решение — представить агента как машину состояний: явный список фаз, явные условия переходов, явный список действий в каждой фазе. Модель остаётся «мозгом», но работает в каркасе, который ты задаёшь как инженер.

Почему «свободного агента» мало

В Дне 6 ты познакомился с базовым циклом агента: думай → действуй → наблюдай → решай. Это работает для коротких задач: «найди новость, составь сводку», «ответь на письмо». 5-10 итераций, и готово.

Но как только задача становится структурно сложной — например, «помоги пользователю забронировать поездку: узнай даты → найди варианты → согласуй → оплати → подтверди» — свободный цикл начинает плыть:

  • Модель пытается оплатить, ещё не уточнив даты
  • Запрашивает у пользователя информацию, которую уже спросила
  • «Забывает», что уже выбран рейс, и начинает заново
  • В середине процесса теряется и не знает, что делать дальше

Это не проблема плохой модели — это проблема отсутствия структуры. У задачи есть естественные фазы, между ними есть зависимости («нельзя оплачивать без выбранного рейса»), а агент работает без знания об этой структуре.

Свободный агент

Без структуры состояний

В system prompt: «помоги забронировать поездку, узнай детали, найди варианты, оформи».

Модель сама решает, на каком шаге она сейчас. Через 10 ходов теряется: спрашивает то, что уже спросила, или пытается сделать то, для чего нет данных.

Машина состояний

С явными фазами

В system prompt: «задача проходит через 5 фаз: collect_dates, search_options, confirm, pay, finalize. Сейчас фаза: collect_dates. Действия в этой фазе: ...».

Модель работает только в текущей фазе, переход — по явному условию. Не теряется.

Свободный агент полагается на «модель сама разберётся, где она в процессе». Машина состояний даёт ей это знание явно — и снимает огромное количество ошибок.

Машина состояний — что это

Конечный автомат (FSM, finite state machine) — классическая концепция из computer science. Это формальная модель системы, у которой:

  • Есть конечный список состояний, в одном из которых она находится в каждый момент
  • Есть правила переходов между состояниями — что происходит при наступлении каких событий
  • В каждом состоянии определены допустимые действия

Когда мы применяем это к агенту: состояния — это фазы решения задачи. Переходы — это условия для перехода в следующую фазу. Действия — какие инструменты доступны и что модель пытается достичь в текущей фазе.

Реализация — простая. В состоянии агента (не в массиве messages, а в твоём коде) есть переменная current_state. Перед каждым вызовом LLM ты:

  1. Смотришь на текущее состояние
  2. В system prompt вставляешь описание только этой фазы и её допустимых действий
  3. После ответа модели — проверяешь, выполнено ли условие перехода в следующую фазу
  4. Если да — меняешь current_state и подкладываешь новые инструкции

В контекст модели на каждом шаге попадает описание только текущей фазы, а не «всех инструкций сразу». Это сильно фокусирует поведение модели и сокращает шум в промпте.

Какие бывают состояния

Тип 01

Стартовое

Точка входа в задачу. Обычно «понять, что нужно пользователю». Содержит первые уточняющие вопросы и условия для перехода в основную работу.

Тип 02

Рабочее

Фаза выполнения. Сбор данных, поиск, обработка, генерация. Обычно состояний этого типа несколько — это «основное тело» задачи.

Тип 03

Подтверждение

Перед необратимыми действиями. Показать пользователю, что собираешься сделать, дождаться явного «да». Об этом — Дни 14-15.

Тип 04

Конечное

Задача успешно выполнена. Финальный ответ пользователю, выход из автомата. Состояние без переходов наружу.

Тип 05

Ошибка

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

Тип 06

Пауза / ожидание

Ждём внешнего события: ответа пользователя, callback от внешней системы, истечения таймера. Агент не делает ничего, пока не пришло то, что ждём.

Пример: бот для бронирования

Возьмём ту самую задачу про бронирование поездки и распишем её как машину состояний. Это покажет, как абстракция выглядит в реальной задаче.

Машина состояний · бот бронирования
01
collect_intent Понять, что пользователь хочет забронировать (рейс, отель, аренду авто)
ask_user
→ collect_details
02
collect_details Собрать даты, города, число пассажиров, бюджет
ask_user
→ search_options
03
search_options Найти варианты по собранным параметрам
search_api
→ present_options
04
present_options Показать варианты, помочь выбрать
show_to_user
→ confirm_booking
05
confirm_booking Финальное подтверждение перед оплатой
explicit_yes_needed
→ pay
06
pay Провести платёж через платёжный шлюз
payment_api
→ finalize / error
07
finalize Отправить подтверждение, выдать билет, попрощаться
send_email
END

Что здесь происходит на каждом шаге?

Что в system prompt на каждом шаге

На каждой фазе твой код собирает system prompt из двух частей:

  • Стабильная часть — общая роль агента: «Ты ассистент бронирования. Профиль пользователя: ... Доступные инструменты: ...».
  • Динамическая часть — описание текущей фазы: «Сейчас фаза: collect_details. Твоя задача: собрать даты поездки и число пассажиров. Когда соберёшь — ничего не отвечай пользователю, я переведу в следующую фазу автоматически».

Это даёт фокус: модель знает, что от неё ждут сейчас, не пытается забегать вперёд.

Условия перехода в следующую фазу

Каждое условие — это проверка, которую делает твой код после очередного ответа модели. Примеры:

  • collect_details → search_options: в state есть dates, origin, destination, passengers
  • present_options → confirm_booking: пользователь выбрал один из вариантов
  • confirm_booking → pay: пользователь явно ответил «yes» или эквивалент

Проверка может быть простой (if dates and origin and destination: → next) или сложной (распарсить ответ пользователя, понять, какой вариант выбран). Часто для последнего нужен отдельный LLM-вызов на маленькой модели.

Принцип: переход — это решение твоего кода, а не модели. Модель только генерирует ответы; код смотрит на состояние и решает, куда идти.

State data — параллельно с массивом messages

Кроме current_state, у тебя есть state data — структурированные данные задачи, которые накапливаются:

  • intent: "flight"
  • dates: { start: "...", end: "..." }
  • origin: "Moscow", destination: "Yerevan"
  • passengers: 2
  • options_offered: [...]
  • chosen_option: {...}
  • payment_status: "pending"

Эти данные подмешиваются в system prompt каждой фазы — модель видит, что уже собрано, что ещё нет. Это лучше, чем «модель сама вычислит из истории сообщений», что часто ошибается.

Что даёт такой подход

  • Фокус. Модель не пытается решать всё сразу, работает с маленькой задачей текущей фазы.
  • Надёжность. Невозможны «прыжки через шаги»: оплата произойдёт только когда состояние confirm_booking явно завершено.
  • Прозрачность. Когда что-то идёт не так, ты точно знаешь, на какой фазе. Логи отлаживаются легко.
  • Управляемые переходы. Везде, где переход опасен (платёж, удаление), легко вставить подтверждение.
  • Cheaper. Стабильная часть system, инструкции фазы маленькие — токенов уходит заметно меньше, чем на «огромный универсальный промпт».

Решения AI-инженера

Развилка 1. Когда вообще нужна машина состояний

Не для каждой задачи. Простое правило:

  • Свободный агент — годится для одноходовых или коротких задач без жёсткой структуры: «ответь на вопрос», «найди информацию», «сгенерируй текст».
  • Машина состояний — для процессов, у которых есть устойчивая структура из фаз с зависимостями: онбординг, бронирование, продажа, диагностика, прохождение опроса.

Тест: если ты можешь нарисовать процесс как блок-схему — он будет машиной состояний. Если процесс открыт, ход решений не предсказуем — это свободный агент.

Развилка 2. Жёсткая или мягкая FSM

Можно делать машину состояний разной степени строгости:

  • Жёсткая: переходы только по чёткому условию в коде. Модель только говорит — переходы решает код. Максимум контроля, минимум гибкости.
  • Мягкая: модель сама может предложить переход («я думаю, что сейчас надо в фазу pay»), а код это утверждает или отклоняет. Больше гибкости, но и больше сюрпризов.

Для критичных операций (деньги, удаление, отправка) — жёсткая FSM. Для исследовательских задач (помощь в анализе) — мягкая.

Концептуальные грабли

«Раз это мощный паттерн — буду применять везде». Для задачи «помоги мне написать пост» машина состояний — это оверкилл и тормоз: процесс открытый, ход непредсказуем, любая «жёсткая» структура только мешает.

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

Спроектировал «солнечный путь»: все фазы по порядку до успешного финала. Не подумал, что: платёж может сорваться, пользователь может отменить в середине, инструмент может вернуть ошибку. Когда что-то идёт не по плану — система не знает, в каком она состоянии.

Хорошая FSM всегда включает: error state с возможностью graceful fallback, cancel state с возможностью пользователю выйти на любой фазе, timeout / retry для нестабильных переходов.

«Каждое маленькое действие — своё состояние». Получаешь 30 состояний с переходами «каждое в каждое» — это не FSM, это лапша.

Правило: состояний должно быть 5-10 на типичную задачу. Если больше — скорее всего, есть скрытые подзадачи, которые надо вынести во вложенные машины. Иерархические FSM существуют именно для этого.

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

State machine агента нужно сериализовать — сохранять current_state и state_data в базу. При возобновлении сессии — поднимать. Это, кстати, проще, чем восстанавливать «по контексту разговора» — у тебя структурированные данные, а не текстовый лог.

Практика

Эксперимент 01 · Разверни знакомый бот в FSM

Реверс-инженерь чат-бота поддержки

Открой любой чат-бот, который ты использовал (поддержка банка, бот заказа еды, бот техподдержки сервиса). Веди диалог по сценарию — записывай каждое «состояние», в котором ты находишься, и условие перехода. У большинства серьёзных ботов ты увидишь явную FSM «в действии». Это даст ощущение, как абстракция выглядит в реальном продукте.

Эксперимент 02 · Спроектируй FSM для своей задачи

На бумаге, 20 минут

Возьми задачу из своей работы или жизни, которую делал бы AI-агент: «приём заказа на доставку», «онбординг нового сотрудника», «составление резюме». Распиши: какие фазы (5-10)? Что собирается в state data на каждой? Какое условие перехода в следующую? Где error state? Где cancel? Где human-in-the-loop (об этом завтра)? Этот рисунок и есть архитектура агента.

Эксперимент 03 · Сравни два промпта

В плейграунде

Возьми условную задачу-процесс: «помоги пользователю составить план на выходные: спроси про настроение → спроси про время → предложи 3 варианта → уточни выбор». Сделай два теста: А. Промпт «свободный»: всё это написано в одном system prompt одной фразой. Б. Промпт «по фазам»: ты эмулируешь FSM руками. После каждого ответа модели вручную меняешь system prompt на «фаза 2 из 4: спроси про время». Сравни — на «А» модель часто скатывается в монолог, на «Б» она фокусирована и ведёт диалог как положено.

Что в Дне 14
У нас есть машина состояний. Но как сделать так, чтобы внутри неё всё было правильно? Что нельзя дважды списать деньги, что обязательно собран нужный набор данных, что после оплаты статус заказа не вернётся в «черновик». Это инварианты — правила, которые должны быть истинны всегда. И как их защищать.