State Schema Versioning в 2026: как менять агентные workflow без поломки старых checkpoints

State schema versioning в 2026: backward compatibility, migrations, pending approvals и почему старые snapshots нельзя десериализовать вслепую.

State schema versioning в 2026 становится обязательным, как только у вас появляются durable agent runs, pause/resume и long-running approvals. В этот момент state больше не является временным внутренним объектом. Он становится persisted contract между разными моментами времени: до и после deploy, до и после approval, до и после incident recovery.

Именно поэтому старое правило "просто поменяем структуру dict и задеплоим" для агентных систем больше не работает.

Представьте, что агент поставил workflow на паузу вчера, а сегодня вы поменяли код и структуру state. Если новый код не умеет читать старый snapshot, resume может сломаться в самый неподходящий момент. State schema versioning нужен именно для этого.
Самый опасный anti-pattern - десериализовать старые snapshots в новую модель данных без явной версии и migration logic. Особенно больно это бьёт по pending approvals, nested tools и workflows, которые жили дольше одного deploy-а.

Короткая версия

Хорошее state schema versioning в 2026 обычно требует:

  1. Явный schema_version
  2. Backward-compatible changes by default
  3. Migration path для старых snapshots
  4. Особые правила для pending approvals и paused runs
  5. Rollback-safe reading

Что особенно важно

  • хранить version рядом со snapshot, а не угадывать по полям;
  • избегать резких breaking changes в control state;
  • мигрировать лениво или через read-time adapters;
  • сохранять связь между state version и code version.
Без техники
Команда переименовала `pending_action` в `proposal`, задеплоила новый код и попыталась резюмировать вчерашний paused run.
С техникой
Каждый snapshot хранит `schema_version`, код умеет применить migration adapter, а long-lived approvals резюмируются только на совместимой ветке logic.
ПромптVersioning intuition
Почему versioning особенно важен для human approvals?
Ответ модели

Потому что approval может прийти спустя часы или дни после pause. К этому моменту код уже мог измениться, и resume нужно либо безопасно мигрировать, либо направить в совместимую ветку исполнения.

1. Persisted state становится контрактом

Когда state живёт дольше одного process lifetime, он уже не просто внутренняя структура.

Он связывает:

  • предыдущую версию кода;
  • текущую версию кода;
  • pending decisions;
  • внешние side effects;
  • observability and audit.

Поэтому любое изменение state schema нужно рассматривать как API change внутри собственной системы.

2. Что особенно чувствительно к versioning

Pending approvals

Самый коварный случай. Решение человека может прийти позже, когда код уже изменился.

Nested agent/tool runs

Вложенные execution paths часто сериализуют больше metadata, чем кажется.

Commit markers

Нельзя потерять понимание, какие side effects уже были зафиксированы.

State-machine statuses

Переименование статусов или шагов легко ломает resume logic.

3. Изменения не все одинаково опасны

Безопаснее всего

  • добавить новое optional поле;
  • ввести новый derived field;
  • добавить metadata, не влияющее на transitions.

Осторожно

  • менять enum/status values;
  • переименовывать поля, участвующие в routing/resume;
  • менять shape approval payload;
  • убирать commit markers;
  • менять meaning existing fields.

Именно второй класс требует явной migration logic.

Если поле влияет на resume, approval, side effects или state transitions, считайте его control-plane полем. Такие изменения должны проходить через versioning discipline, а не через "потом поправим".

4. Read-time migration часто практичнее write-all migration

Для агентных систем полезны два подхода:

Read-time adapter

При чтении старого snapshot-а код преобразует его в новый shape.

Плюсы:

  • быстрее внедрять;
  • не нужно массово переписывать storage сразу.

Batch migration

Все snapshots заранее обновляются до новой версии.

Плюсы:

  • меньше complexity в runtime;
  • проще support после полной миграции.

На практике read-time adapter часто удобнее для paused runs и long-lived approvals.

5. State version и code version лучше хранить рядом

Идеальный минимум:

  • schema_version;
  • workflow_version;
  • created_at;
  • last_updated_at.

Это помогает понять:

  • какой код изначально создал snapshot;
  • какую migration ветку применять;
  • можно ли resume-ить safely;
  • не нужен ли quarantine path для слишком старых runs.

6. Что чаще всего ломают команды

Version inferred from shape

Код пытается угадать версию по наличию полей.

No approval compatibility policy

Long-lived pending tasks резюмируются на новом коде без защит.

Hidden semantic changes

Поле называется так же, но означает уже другое.

No downgrade strategy

Старый код не умеет читать state, записанный новой версией.

Mixed snapshots in one queue

Review or resume workers не знают, с какими версиями state они вообще работают.

7. Какие метрики полезны

Минимальный versioning dashboard обычно включает:

  • snapshot count by schema version;
  • migration success rate;
  • paused-run resume failures by version;
  • incompatible approval resume rate;
  • time-to-retire old versions;
  • percent of queue on deprecated schema.

Если старые версии не видны, технический долг по state быстро становится невидимой operational миной.

Плюсы

  • Versioning делает paused runs и approvals переживаемыми через deploy-ы
  • Явный `schema_version` упрощает migrations и debugging
  • Read-time adapters часто позволяют безопасно эволюционировать без массовых переписей
  • Связка state version и workflow version делает resume управляемым

Минусы

  • Появляется дополнительная дисциплина и runtime-слой migrations
  • Слишком много живых версий усложняют поддержку
  • Скрытые semantic changes всё равно трудно ловить автоматически
  • Без retirement policy старые snapshots будут жить бесконечно

Пример versioned snapshot

{
  "schema_version": 3,
  "workflow_version": "refund_flow_v7",
  "workflow_id": "wf_2041",
  "status": "paused_for_approval",
  "proposal": {
    "type": "refund",
    "amount": 129.0
  },
  "commit_markers": []
}

Простой migration adapter

def migrate_snapshot(snapshot):
    version = snapshot.get("schema_version", 1)

    if version == 1:
        snapshot["proposal"] = snapshot.pop("pending_action")
        snapshot["schema_version"] = 2

    if snapshot["schema_version"] == 2:
        snapshot["commit_markers"] = snapshot.get("commits", [])
        snapshot["schema_version"] = 3

    return snapshot

Практический совет: если approval или pause может пережить хотя бы один deploy, state schema versioning уже не опционален. В этот момент snapshot становится частью вашего production API, даже если он никогда не покидает внутренний storage.

Проверьте себя

1. Почему state schema versioning особенно важен для paused runs?

2. Какой anti-pattern особенно опасен?

3. Что чаще всего стоит считать control-plane полем?