После сбоя: почему журнал не гарантирует однократное действие

40 остановок тестовой программы с файловой памятью CapyAgent. Проверяем, что известно после восстановления, когда повтор создаёт лишнее действие и как влияет ключ операции.

Все исследования

Главный результат

После остановки процесса журнал CapyAgent может сообщать «результат неизвестен» в двух разных ситуациях: запрос ещё не отправлен или внешнее действие уже выполнено. В нашем опыте повтор второго случая создавал лишнее действие. Защита по ключу операции на стороне тестового сервиса предотвратила такой повтор.

Термины простыми словами
Журнал
Записи о начале и результате действия, которые агент сохраняет на диск.
Внешнее действие
Изменение за пределами памяти агента. В опыте это новая строка в базе тестового сервиса.
Ключ операции
Идентификатор одного намерения. При повторной попытке используется тот же ключ.
Идемпотентность
Свойство операции: повтор запроса не добавляет ещё один такой же эффект.
Контрольная точка
Момент, после которого определённая запись сохранена. Она не подтверждает события в другой системе автоматически.
SIGKILL
Сигнал принудительного завершения процесса без обычной процедуры выхода.

Аннотация

Исследована граница между файловым журналом CapyAgent и внешним эффектом. На закреплённом снимке разработки выполнены 40 случаев принудительной остановки отдельного процесса: четыре точки остановки, два режима внешнего сервиса, пять повторений. После каждой остановки хранилище открывалось двумя новыми процессами. Для случаев до отправки запроса и после внешнего эффекта без записи результата восстановление возвращало маркер неизвестного результата. Исследовательская политика повторяла запрос, если в журнале отсутствовала квитанция. В пяти случаях с обычным сервисом это создало второй эффект; в аналогичных пяти случаях с дедупликацией по ключу сохранился один эффект. Работа проверяет реальный компонент хранения и тестовую связку, но не основной цикл агента, текущую версию продукта или защиту от всех типов отказов.

1. Что нельзя узнать из одного журнала

Агент получил поручение создать запись во внешней системе. Сервис выполнил запрос, но процесс агента остановился раньше, чем сохранил ответ. После запуска у агента есть запись о начале работы и нет записи о результате. Повторять действие или нет? Точно такой же признак может остаться, если процесс остановился ещё до отправки запроса.

Ошибка здесь возникает не обязательно в языковой модели. Даже детерминированная программа не может узнать факт внешнего изменения только по отсутствующему локальному ответу. Мы проверяем это на файловом хранилище CapyAgent и отдельно считаем фактические изменения в тестовом сервисе. Слово «неизвестно» рассматривается как корректное описание неполной информации, а не как доказательство ошибки восстановления.

В предыдущей работе о состоянии задач изучались подготовленные состояния журнала и способы его открытия. Теперь добавлены настоящая остановка процесса сигналом SIGKILL, запуск новых процессов чтения и внешний счётчик действий. В исследовании Super-agent проверялась доставка сообщений в одном процессе. Текущий опыт касается другой границы: доставка входа ещё не доказывает однократное выполнение действия.

2. Где возникает неопределённость

  1. 01 / НамерениеСохранено начало действия
    Журнал знает, какой инструмент предполагалось вызвать. Внешнего изменения ещё может не быть.
  2. 02 / Внешний сервисИзменение уже произошло
    Сервис записал строку в свою базу и вернул квитанцию. В журнале агента ответа пока нет.
  3. 03 / ОстановкаПроцесс завершён принудительно
    Новый процесс восстанавливает историю. Без сохранённой квитанции результат остаётся неизвестным.
  4. 04 / ПовторРешение зависит от контракта сервиса
    Обычный запрос может создать второй эффект. Сохранённый сервисом ключ позволяет вернуть прежний результат.
Рисунок 1. Исследованный разрыв между внешним эффектом и локальной записью результата. Действие в опыте синтетическое; реальные платежи, письма и пользовательские данные не используются.

Локальная контрольная точка и запись внешнего сервиса - две разные операции. В нашем стенде они не объединены в общую транзакцию. Поэтому надёжное сохранение каждой из них по отдельности не делает всю последовательность неделимой. Проверка должна смотреть на обе стороны: что восстановил журнал и что фактически изменилось снаружи.

Ключ операции тоже не работает сам по себе. Его должен распознать и сохранить получатель запроса. Если агент просто добавляет строку operation_key в запрос, а сервис не использует её для обнаружения повторов, второй запрос остаётся вторым действием. Именно поэтому в опыт включены два режима одного и того же сервиса.

3. Какой код исследован

Использован тот же закреплённый снимок разработки CapyAgent 77ac9b72409fc2b6afc5741fd2469c662758a1b4, что и в предыдущем исследовании журнала. Это позволяет расширить проверку, сохраняя исходную реализацию. Рабочие исходники других участников не изменялись. Публичный stable на момент проверки 1 ноября 2026 года - v1.3.23; результаты этой статьи не приписываются ему автоматически.

Вспомогательная Go-программа напрямую вызывает реальные методы NewFile, BeginTurn, CheckpointToolStart, CheckpointToolResult и History. После SIGKILL новый процесс открывает то же хранилище через NewFile, то есть проходит предусмотренное компонентом восстановление при старте. Основной цикл CapyAgent и его политика повторного вызова инструмента не запускаются.

Полный снимок исходников закрыт. В приложении опубликованы хеши исследованных файлов, исходник вспомогательной программы, тестовый сервис, данные и команды. Для повторной сборки требуется разрешённый доступ к этому снимку. Для проверки опубликованных подсчётов доступ к закрытому коду не нужен. Условия CapyAgent BSL 1.1 с Additional Use Grant и уведомления о сторонних компонентах сохранены; весь комплект не объявляется MIT.

4. Методика и контрольные варианты

Четыре точки остановки

До исполнения зафиксирован протокол. Для каждого случая создаётся новая папка памяти и отдельный идентификатор операции. Вспомогательный процесс доходит до барьера, сообщает о готовности и ждёт. Управляющая программа завершает только этот дочерний процесс сигналом SIGKILL и проверяет код завершения.

  • До записи начала инструмента. Ход начат, но вызов ещё не записан и запрос не отправлен.
  • До внешнего запроса. Начало инструмента записано, но сервис ещё не вызывался.
  • После внешнего эффекта. HTTP-запрос выполнен, сервис зафиксировал изменение и ответ получен, но результат ещё не записан в журнал CapyAgent.
  • После записи результата. Квитанция внешнего сервиса уже сохранена через метод контрольной точки.

Барьер после внешнего эффекта установлен после получения ответа клиентом. Потеря HTTP-ответа в сети здесь отдельно не моделируется. Это точечная инъекция отказа между завершёнными операциями, а не случайное прерывание внутри записи на диск.

Сервис со счётчиком

Локальный HTTP-сервис слушает случайный порт только на loopback. Каждое действие добавляет строку в SQLite. Транзакция фиксируется до отправки ответа. В обычном режиме каждый POST добавляет новую строку. В режиме с ключами сервис в одной транзакции проверяет таблицу ключей, сохраняет эффект и его квитанцию; повтор известного ключа возвращает прежнюю квитанцию.

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

Восстановление и повтор

После остановки память открывается отдельным новым процессом, затем ещё одним. Считаются строки истории, ответы инструмента, маркеры неизвестности и сохранённые квитанции. Два чтения сравниваются, чтобы заметить повторное добавление восстановленной истории. Внешний счётчик проверяется независимо от этих признаков.

Затем применяется исследовательская политика: если квитанция найдена, запрос не повторяется; если квитанции нет, отправляется одна попытка с прежним ключом. Эта намеренно простая политика принадлежит стенду. Она не описывает фактическое решение языковой модели или поведение текущего CapyAgent после аварии.

Матрица содержит 4 точки × 2 режима сервиса × 5 повторений = 40 случаев. Пять повторений проверяют воспроизводимость детерминированного сценария в отдельных папках и процессах. Они не являются выборкой реальных пользователей и не дают оценки вероятности сбоя в эксплуатации.

5. Результаты: одинаковая неизвестность, разное внешнее состояние

Таблица 1. Результат каждого из пяти повторений в соответствующей группе. Числа эффектов указаны на один случай.
Точка остановкиЭффекты до чтенияЖурналПосле повтора: обычныйПосле повтора: с ключом
До записи начала0Нет ответа инструмента11
До внешнего запроса0Результат неизвестен11
После внешнего эффекта1Результат неизвестен21
После записи результата1Квитанция сохранена1, повтор пропущен1, повтор пропущен

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

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

Все 40 дочерних процессов завершены именно SIGKILL. В каждом случае два последовательных запуска чтения дали одинаковые подсчитанные признаки истории. В опубликованной выгрузке сервиса всего 45 строк: 40 ожидаемых единичных эффектов и пять дополнительных. Отдельный Python-оценщик сгруппировал строки CSV по ключу и подтвердил совпадение с итоговыми результатами.

Нельзя превращать пять повторных действий в «12,5% ошибок CapyAgent». Мы сами выбрали точки остановки и политику повтора, которая провоцирует различие. Частоты в такой матрице описывают состав опыта, а не распространённость проблемы у продукта. Корректный вывод относится к достаточности локального признака и к поведению двух объявленных сервисных контрактов.

6. Как результат связан с известными подходами

RFC 9110 определяет идемпотентность через одинаковый предполагаемый эффект нескольких идентичных запросов и ограничивает автоматические повторы неидемпотентных запросов [1]. В нашем опыте обычный POST намеренно создаёт новую запись при каждом вызове. Сам HTTP не делает такую операцию однократной.

В инженерном разборе AWS Making retries safe with idempotent APIs рассматривается явный идентификатор клиентского запроса как основа повторяемого обращения [2]. Мы используем сходный принцип в минимальном контрольном сервисе, но не воспроизводим реализацию AWS и не переносим её гарантии на наш стенд.

В научной работе RIFL сохранённые результаты RPC позволяют отличить повтор от нового вызова и вернуть прежний результат без повторного исполнения [3]. Эта работа также рассматривает управление метаданными и их сроком жизни. Наш опыт существенно уже: один сервис, последовательные обращения и таблица ключей без очистки. Мы не измеряем линейризуемость распределённой системы и не повторяем производительность RIFL.

Официальная документация Stripe показывает, почему необходимо читать конкретный контракт API: правила повторов различаются между API v1 и v2; для v1 описаны сохранение ответа, проверка параметров и возможность удаления ключей после установленного периода [4]. Это пример требований, которые простой локальный стенд не покрывает. Платёжный API в исследовании не вызывался.

7. Что проверить перед применением в агенте

Практический сценарий - агент создаёт заявку, отправляет документ или меняет запись. Перед автоматическим повтором нужно выяснить, есть ли у сервиса идентификатор операции и способ проверить её состояние. Если такой возможности нет, неизвестный результат требует отдельного решения: сверки внешнего состояния, участия человека или заранее предусмотренной компенсации. Компенсация тоже является действием и может потребовать собственных прав и проверки.

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

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

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

При выборе интеграции следует проверять свежие случаи, желательно за последние полгода: меняются модели, инструменты и способы защиты данных. Старые работы дают метод, но не заменяют проверку актуального API. Ответственность за правила исполнения, доступ и последствия действий остаётся у людей; корректный журнал не гарантирует правильный выбор инструмента агентом.

8. Материалы и повторение

Опыт выполнен 1 ноября 2026 года на KotikTurbo до ночного ограничения нагрузки, с общим замком тестовой очереди, CPUQuota 100% и MemoryMax 1500 МБ. Компиляция и исполнение не выполнялись на production-сервере. Версии Go, Python и ОС сохранены в results.json.

Приложение к исследованию содержит предварительный протокол, Go-программу, Python-сервис и управляющий сценарий, исходные результаты 40 случаев, 45 строк внешних эффектов и независимый пересчёт. Для проверки чисел достаточно распаковать архив и выполнить python3 audit.py.

Для нового полного запуска нужен разрешённый доступ к снимку исходников 77ac9b72. В отдельной копии репозитория поместите main.go в core/cmd/research-crash/, соберите вспомогательную программу в папку комплекта и запустите run.py. Сценарий завершает только порождённые им процессы. Его следует запускать в отдельной тестовой среде; не подставляйте рабочий каталог памяти агента.

# Из core закреплённого снимка
 BUNDLE=/path/to/bundle
 go build -o "$BUNDLE/probe" \
   ./cmd/research-crash
# Из отдельной папки комплекта
 python3 run.py
 python3 export_effects.py
 python3 audit.py

При новом запуске создаётся папка run-data; уже существующая папка приводит к остановке, чтобы не смешать серии. Доступ к закрытому коду ограничивает полную независимую воспроизводимость. Проверка выгрузки и логики подсчёта открыта полностью. Ни внешнее рецензирование, ни DOI для этой работы не заявляются.

9. Ограничения и вывод

Мы останавливаем клиентский процесс в выбранных точках, но не выключаем компьютер и не повреждаем файловую систему. Внешний сервис продолжает работать. Поэтому результат не является проверкой потери питания, частично записанных секторов, восстановления SQLite после аварии или сетевого разделения.

Нет параллельных клиентов, истечения ключа, смены параметров при том же ключе, прав доступа и очередей с отложенной доставкой. Не запускается языковая модель. Не проверяется, как настоящий агент интерпретирует восстановленную историю и выбирает дальнейшее действие. Также не доказано, что все пути записи в CapyAgent проходят через исследованные методы.

Следующий эксперимент должен включать реальный исполнительный цикл с детерминированной моделью, сохранение ключа между запусками и отдельную проверку внешнего состояния перед повтором. Для оценки защиты нужны случаи истечения полномочий и ключей, а для оценки надёжности - другие типы отказа. Эти проверки запланированы, но в текущую статью как выполненные не включены.

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

Источники и данные

Проверены 1 ноября 2026 года. Работа подготовлена с применением ИИ. Внешнее рецензирование и DOI не заявляются.

  1. Fielding R., Nottingham M., Reschke J. RFC 9110: HTTP Semantics, §9.2.2. 2022.
  2. Featonby M. Making retries safe with idempotent APIs. Amazon Builders’ Library.
  3. Lee C., Park S. J., Kejriwal A., Matsushita S., Ousterhout J. Implementing Linearizability at Large Scale and Low Latency. SOSP 2015.
  4. Stripe. Idempotent requests. Официальная документация, проверена 01.11.2026.
  5. Снимок CapyAgent 77ac9b72: исходный манифест предыдущего исследования. Закрытый код, исторический срез версий.
  6. Манифест нового опыта: закреплённый снимок, хеши компонента и границы доступа.
  7. Что остаётся от задачи после остановки. Предыдущее компонентное исследование центра, 01.11.2026.
  8. Данные новой серии: протокол, 40 случаев, внешние эффекты, исходники стенда и независимый пересчёт.