← На главную
Гайды· 29.05.2026· 4 мин чтения

Агентный пайплайн упал на шаге 4 из 7 — как не запускать всё заново

Агентный пайплайн упал на середине — как не начинать заново? Event Sourcing даёт иммутабельный лог и точечный resume. Разбираем на примере zymi.

Агентный пайплайн упал на шаге 4 из 7 — как не запускать всё заново
Материал подготовлен с помощью ИИ и проверен редактором

LLM-агенты ломаются не там, где вы ожидаете. Не на сложных задачах и не из-за плохой модели — а потому что каждый запуск чуть-чуть другой. Температура, тихий апдейт модели на стороне провайдера, изменившийся внешний API — и воспроизвести падение уже невозможно. Event Sourcing не убирает этот недетерминизм, но даёт конкретные инструменты: иммутабельный лог, точечный перезапуск и честный аудит.

Почему снапшот состояния не спасает

Стандартный подход — сохранять состояние агента как снапшот: JSON с текущим контекстом, промежуточными результатами, статусом шагов. Выглядит разумно, пока не нужно отлаживать.

Снапшот говорит «что есть сейчас», но не говорит «как мы сюда попали». Если агент на шаге 5 выдал странный результат, вы не знаете, какой именно LLM-вызов на шаге 2 его предопределил. Нет истории — нет отладки.

Event Sourcing переворачивает логику: состояние не хранится явно, оно выводится из журнала событий. Каждое действие — вызов инструмента, ответ LLM, решение агента — пишется в лог как иммутабельная запись. Состояние в любой момент — это проекция этого лога. Паттерн систематизировал Мартин Фаулер ещё в 2005-м, связку ES + CQRS закрепил Грег Янг в 2014-м. К LLM-агентам его прямо применили уже в 2026-м: работы ESAA (arXiv:2602.23193) и OpenKedge (arXiv:2604.08601).

Как выглядит ESA изнутри

Event-Sourced Architecture для агентов (ESA) — это event-driven шина с одним жёстким инвариантом: в лог можно только писать, никогда не редактировать.

Агенты на этой шине — обычные подписчики. Каждый ждёт нужных типов событий, забирает их в работу, результаты пишет туда же. Никакого прямого вызова между агентами — только через лог.

```python

Упрощённая схема: агент читает из лога и пишет в лог

def researcher_agent(stream_id: str, log: EventLog): events = log.read(stream_id, event_types=["TaskAssigned"]) for event in events: result = run_search(event.payload["query"]) log.append(stream_id, { "type": "ResearchCompleted", "payload": result, "prev_hash": log.last_hash(stream_id) }) ```

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

Перезапуск с произвольного шага

Допустим, у вас пайплайн: researcher собирает данные → writer готовит отчёт. Researcher отработал нормально, writer выдал мусор. Классический сценарий: переписать промпт writer'а и перезапустить — но не трогать дорогостоящий поиск.

В zymi это одна команда:

``bash zymi resume <stream_id> --from-step writer ``

Что происходит под капотом: создаётся новый стрим, в него физически копируется событийный префикс researcher'а — все его LLM-вызовы, результаты поиска, промежуточные артефакты. Writer перезапускается с обновлённым промптом из agents/writer.yml. Всё до точки resume — заморожено.

Отдельная проблема — внешние эффекты внутри переисполняемой части. Если writer на прошлом прогоне отправил email, повторно отправлять его нельзя. Для этого у инструментов есть флаг no_resume: true:

```yaml

agents/writer.yml

tools:

no_resume: true

no_resume: false ```

  • name: send_email
  • name: write_report

При resume инструмент с no_resume: true не вызывается. В журнал пишется:

``json { "type": "ToolCallCompleted", "tool": "send_email", "replayed": true, "result": "__skipped_on_resume__" } ``

Агент в следующем ходе видит это событие и знает: email уже был отправлен раньше, повторно не делал.

Лог как единственный источник правды

Выгода лога раскрывается только если все потребители и источники взаимодействуют исключительно через него. Никаких прямых вызовов между агентами, никаких side-channel обновлений состояния.

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

В zymi это проявляется буквально. Команда zymi verify проверяет целостность SHA-256 хэш-цепочки лога:

``bash zymi verify ``

Реализация — 76 строк, которые не импортируют рантайм вообще. Открывает тот же SQLite, перебирает стримы, валидирует хэши:

```python import sqlite3, hashlib

def verify_stream(db_path: str, stream_id: str) -> bool: conn = sqlite3.connect(db_path) rows = conn.execute( "SELECT hash, prev_hash, payload FROM events WHERE stream_id=? ORDER BY seq", (stream_id,) ).fetchall() for row in rows: expected = hashlib.sha256( (row[1] or "") + row[2] ).hexdigest() if expected != row[0]: return False return True ```

По тому же принципу работают zymi observe (TUI с живым графом пайплайна) и zymi runs (CLI-листинг прогонов) — каждая команда это самостоятельный потребитель поверх одного лога, в ядро никто не лезет.

Где это ломается

GDPR и право на удаление. Иммутабельный лог и «удалите мои данные» — прямое противоречие. Решения есть (crypto shredding, отдельный слой персональных данных), но они добавляют сложности и частично ломают гарантию единого источника правды.

Лог растёт бесконечно. Для долгоживущих агентов без компактизации лог становится узким местом по производительности. Нужна стратегия снапшотов или TTL на старые стримы — иначе проекция состояния начинает считаться секундами.

Недетерминизм никуда не девается. Fork-resume снижает его влияние, но не устраняет. Если вы перезапускаете writer с тем же промптом и той же моделью — результат всё равно может отличаться. Лог даёт воспроизводимость истории, но не детерминизм будущего.

Внешние события сложно вписать. Если агент реагирует на webhook от внешней системы, это событие нужно явно ввести в лог до того, как агент его обработает. Пропустили — и источник правды уже не единственный.

Что попробовать дальше

Если хотите глубже в теорию: ESAA (arXiv:2602.23193) — формализация Event Sourcing для агентов, OpenKedge (arXiv:2604.08601) — governance поверх event-sourced состояния.

Если интереснее production-решения: Temporal и Restate решают ту же задачу в словаре workflow/activity — «храним историю событий, реплеим детерминированно». Они старше и battle-tested, но требуют отдельной инфраструктуры.

Сам zymi — это Python-библиотека, которую можно попробовать как минимальную реализацию ESA для своих агентов. Команды resume, verify, observe работают поверх обычного SQLite — никакого Kafka для старта не нужно.

Источники

Автор: PLai AI