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

Выберите язык

Текущий язык: Русский

Как реализовать автоматический повтор для CLI-агентов, таких как Claude Code и Codex

HagiCode for Windows Microsoft Store artwork
HagiCode для Windows теперь в Microsoft Store
HagiCode для Windows официально доступен в Microsoft Store. Пользователи Windows могут установить его прямо из магазина и оставаться на управляемом магазином пути обновлений. Откройте страницу и взгляните.
Открыть Microsoft Store
Awesome Site banner: community-curated open-source collections
Awesome Site: тщательно отобранные коллекции с открытым исходным кодом
Найдите свой следующий инструмент, идею или источник вдохновения в коллекциях с открытым исходным кодом, которые отобрало сообщество. Выберите тему, читайте на своём языке и переходите по ссылке к первоисточнику.
Изучить Awesome Site
Awesome Design banner: a gallery of DESIGN.md design systems
Awesome Design: галерея DESIGN.md
Просматривайте отобранные дизайн-системы с живыми превью и ссылками на README, затем скопируйте DESIGN.md в свой проект и поручите ИИ-агенту создать подходящую страницу.
Открыть галерею дизайна
OpenSpec banner: agree on the spec, then build with AI
OpenSpec: согласуйте план, прежде чем ИИ напишет код
OpenSpec — лёгкий слой спецификаций для программирования с ИИ. Опишите изменение, изучите предложение, спецификации, дизайн и задачи, которые составит ваш ИИ, затем реализуйте его и заархивируйте в живые спецификации. Никаких жёстких этапов; подходит для существующих кодовых баз и ИИ-ассистентов, которыми вы уже пользуетесь.
Узнать об OpenSpec
OmniRoute Docs banner: one endpoint for many AI providers
OmniRoute Docs: одна конечная точка для множества ИИ-провайдеров
Узнайте, как OmniRoute маршрутизирует множество ИИ-провайдеров и моделей через одну конечную точку, с поддержкой бесплатных тарифов и автоматическим переключением на резерв, чтобы ваши инструменты для программирования продолжали работать.
Читать документацию OmniRoute

Как реализовать автоматический повтор для CLI-агентов, таких как Claude Code и Codex

Фраза «автоматический повтор» выглядит как небольшой переключатель, но когда вы помещаете его в реальную инженерную среду, всё оказывается совсем иначе. Всем привет, я Ю Кунь, создатель HagiCode. Сегодня я не хочу вести пустых разговоров. Я хочу рассказать, как на самом деле должен быть реализован автоматический повтор для CLI-агентов, таких как Claude Code и Codex, чтобы он мог как восстанавливаться после сбоев, так и не втягивать систему в бесконечное повторное выполнение.

Предпосылки

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

В обычном HTTP-запросе это часто означает лишь повторную отправку, возможно, с экспоненциальной задержкой. Но CLI-агенты работают иначе. Такие инструменты, как Claude Code и Codex, обычно выполняются в потоковом режиме, по частям выдавая результат. В процессе они также могут привязываться к потоку, сессии или токену возобновления. Иными словами, вопрос уже не в том, «Удался этот запрос или нет?», а в следующем:

  • Считается ли уже выданное содержимое действительным?
  • Может ли текущий контекст продолжить выполнение?
  • Должен ли этот сбой восстанавливаться автоматически?
  • Если да, сколько нужно подождать перед повторной попыткой, что следует отправить при повторе и нужно ли по-прежнему использовать исходный контекст?

Когда многие команды впервые реализуют эту часть, они инстинктивно пишут наивнейшую версию: если произошла ошибка — попробовать ещё раз. Эта идея совершенно естественна, но в реальном проекте один за другим начинают всплывать проблемы.

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

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

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

О HagiCode

Подход, которым мы делимся в этой статье, основан на реальной практике нашего проекта HagiCode. Цель HagiCode — не просто подключить одну модель и на этом закончить. Это объединение потоковых сообщений, вызовов инструментов, восстановления после сбоев и контекста сессий нескольких CLI-агентов в единую модель выполнения, которую можно поддерживать в долгосрочной перспективе.

Одно из того, что меня больше всего волнует — как сделать так, чтобы ИИ-программирование действительно приживалось в реальной инженерной работе. Написать демо несложно. Сложно превратить демо в инструмент, который команда действительно захочет использовать долгое время. HagiCode серьёзно относится к автоматическому повтору не потому, что эта функция выглядит изысканно, а потому, что если длительное, потоковое, возобновляемое выполнение CLI нестабильно, то пользователи видят не умного помощника, а обёртку над командами, которая обрывает соединение каждый второй раз.

Если вы хотите сначала посмотреть точки входа в проект, вот две ссылки:

Двигаясь дальше, HagiCode уже доступен в Steam. Если вы пользуетесь Steam, добавьте его в список желаемого:

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

Это очень практичный вопрос, поэтому перейдём сразу к сути: сложность автоматического повтора для CLI-агентов — не в «подождать несколько секунд и попробовать снова», а в «можно ли продолжить в исходном контексте?»

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

Конкретнее, существует четыре особенно типичные трудности.

1. Это потоковая передача

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

2. Обычно привязан к контексту сессии

Провайдеры вроде Codex привязываются к потоку, а реализации вроде Claude Code также имеют цель продолжения или эквивалентный возобновляемый контекст. Настоящее условие для автоматического повтора — не просто то, что ошибка похожа на временный сбой, но и то, что существует носитель, позволяющий этому выполнению продолжиться.

3. Не каждая ошибка стоит повторной попытки

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

4. Нужны границы

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

Из-за этих особенностей HagiCode в итоге не реализовал автоматический повтор в виде нескольких строк try/catch внутри конкретного провайдера. Вместо этого мы выделили его в общий уровень возможностей. В конечном счёте инженерные проблемы нужно решать инженерными методами.

Подход HagiCode: вынести повтор из провайдера

Текущая реальная реализация HagiCode может быть сжата до одного предложения:

Общий уровень единообразно управляет потоком повтора, а каждый конкретный провайдер отвечает только на два вопроса: стоит ли это конечное состояние повторять и может ли текущий контекст продолжиться?

Это не сложно, но критически важно. При таком разделении ответственности Claude Code, Codex и даже другие CLI-агенты могут использовать один и тот же каркас. Модели будут меняться, инструменты — развиваться, рабочие процессы — обновляться, но инженерный фундамент останется.

Уровень 1: Единый координатор для управления циклом повтора

Основной фрагмент реализации в проекте выглядит примерно так:

internal static class ProviderErrorAutoRetryCoordinator
{
public static async IAsyncEnumerable<CliMessage> ExecuteAsync(
string prompt,
ProviderErrorAutoRetrySettings? settings,
Func<string, IAsyncEnumerable<CliMessage>> executeAttemptAsync,
Func<bool> canRetryInSameContext,
Func<TimeSpan, CancellationToken, Task> delayAsync,
Func<CliMessage, bool> isRetryableTerminalMessage,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
var normalizedSettings = ProviderErrorAutoRetrySettings.Normalize(settings);
var retrySchedule = normalizedSettings.Enabled
? normalizedSettings.GetRetrySchedule()
: [];
for (var attempt = 0; ; attempt++)
{
var attemptPrompt = attempt == 0
? prompt
: ProviderErrorAutoRetrySettings.ContinuationPrompt;
CliMessage? terminalFailure = null;
await foreach (var message in executeAttemptAsync(attemptPrompt)
.WithCancellation(cancellationToken))
{
if (isRetryableTerminalMessage(message))
{
terminalFailure = message;
break;
}
yield return message;
}
if (terminalFailure is null)
{
yield break;
}
if (attempt >= retrySchedule.Count || !canRetryInSameContext())
{
yield return terminalFailure;
yield break;
}
await delayAsync(retrySchedule[attempt], cancellationToken);
}
}
}

То, что делает этот код, на самом деле очень просто, но при этом очень эффективно.

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

Вот почему я ранее постоянно подчёркивал, что автоматический повтор — это не просто «сделать запрос снова». Это не просто латание ветки обработки исключений. Это управление жизненным циклом выполнения. Звучит как язык продакт-менеджера, но в инженерных терминах это именно так.

Уровень 2: Снимок политики повтора

Ещё один очень легко упускаемый из виду вопрос: кто решает, включён ли автоматический повтор для данного запроса?

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

Основную структуру можно упростить до следующей:

public sealed record ProviderErrorAutoRetrySnapshot
{
public const string DefaultStrategy = "default";
public bool Enabled { get; init; }
public string Strategy { get; init; } = DefaultStrategy;
public static ProviderErrorAutoRetrySnapshot Normalize(bool? enabled, string? strategy)
{
return new ProviderErrorAutoRetrySnapshot
{
Enabled = enabled ?? true,
Strategy = string.IsNullOrWhiteSpace(strategy)
? DefaultStrategy
: strategy.Trim()
};
}
}

Затем на стороне выполнения политика маппится в объект настроек, фактически потребляемый провайдером. Ценность этого подхода очень прямая:

  • Бизнес-уровень решает, следует ли разрешить повтор
  • Среда выполнения решает, как выполнять повтор

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

Уровень 3: Провайдеры определяют только конечное состояние и жизнеспособность контекста

Когда мы добираемся до конкретного провайдера Claude Code или Codex, ответственность здесь становится очень узкой. Можно рассматривать это как дополнение, а не замену.

Возьмём Codex в качестве примера: при подключении к общему координатору ему нужно предоставить всего три вещи:

await foreach (var message in ProviderErrorAutoRetryCoordinator.ExecuteAsync(
prompt,
options.ProviderErrorAutoRetry,
retryPrompt => ExecuteCodexAttemptAsync(...),
() => !string.IsNullOrWhiteSpace(resolvedThreadId),
DelayAsync,
IsRetryableTerminalFailure,
cancellationToken))
{
yield return message;
}

Вы заметите, что специфичные для провайдера решения — это всего два:

  • IsRetryableTerminalFailure
  • canRetryInSameContext

Codex проверяет, может ли поток продолжаться, а Claude Code — существует ли цель продолжения. Стратегия задержки, количество повторов и последующие промпты не должны изобретаться каждым провайдером заново.

Когда этот уровень отделён, стоимость интеграции новых CLI в HagiCode значительно снижается. Не нужно дублировать всю конечный автомат повтора. Нужно только подключить граничные условия этого провайдера. Быстро написать — не то же самое, что написать надёжно. Подключить что-то — не то же самое, что подключить хорошо. Запустить — не то же самое, что сделать поддерживаемым в долгосрочной перспективе.

Лёгкая ошибка: не считайте каждую ошибку повторяемой

В этом анализе я больше всего хочу выделить не «как реализовать повтор», а «как избежать неправильных повторов».

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

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

  • Меньше вероятность полного сбоя из-за небольшого изменения формулировки в одном сообщении
  • Тестовое покрытие можно выстроить вокруг оболочки конечного состояния, а не одной захардкоженной строки текста
  • Логика повтора становится более согласованной внутри одного провайдера

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

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

Три самых ценных урока для практической работы

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

1. Бюджет повтора должен иметь границы

Текущий ритм задержек по умолчанию в HagiCode:

  • 10 секунд
  • 20 секунд
  • 60 секунд

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

2. Промпт продолжения должен быть единым

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

3. Общей библиотеке и слою адаптеров нужны зеркальные тесты

Об этом моменте я хочу сказать чуть подробнее. Многие команды напишут один слой тестов в общей среде выполнения и решат, что этого, наверное, достаточно. Этого недостаточно.

Причина, по которой я чувствую относительную уверенность в реализации HagiCode, — оба уровня покрыты тестами:

  • Общий провайдер проверяет, действительно ли произошло автоматическое продолжение
  • Слой адаптеров проверяет, были ли корректно сохранены итоговые ошибки и потоковые сообщения

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

Итоги

Если всю статью нужно сжать до одного предложения, то вот оно:

Для CLI-агентов, таких как Claude Code и Codex, автоматический повтор не следует реализовывать как локальный трюк, скрытый внутри одного провайдера. Он должен быть построен как комбинация общего координатора, снимка политики, проверок жизнеспособности контекста и зеркальных тестов.

Преимущества такого подхода весьма практичны:

  • Логика пишется один раз и повторно используется в нескольких провайдерах
  • Решение о том, разрешён ли повтор, может стабильно перемещаться вместе с цепочкой выполнения
  • Продолжать выполнение, когда контекст существует, и вовремя останавливаться, когда его нет
  • В конечном счёте внешний интерфейс видит стабильное завершённое состояние или состояние ошибки, а не кучу брошенного промежуточного шума

Это решение оттачивалось шаг за шагом, пока HagiCode интегрировал несколько CLI-агентов в реальных сценариях. Кто сказал, что ИИ-программирование — это не новая эра парного программирования? Модели помогают вам начать, дописать код и разветвить проект, но часто именно контекст, процессы и ограничения определяют верхний предел опыта.

Если эта статья была вам полезна, вы также можете ознакомиться с публичными точками входа HagiCode:

HagiCode уже доступен в Steam. Это не вапорвэр, и я помещаю ссылку прямо здесь. Если вы пользуетесь Steam, добавьте его в список желаемого. Кликнуть и посмотреть самому — прямолинейнее, чем слушать ещё десять строк от меня здесь.

На этом пока всё по этой теме. Увидимся в реальных проектах.

Ссылки

Уведомление об авторских правах

Спасибо за чтение. Если эта статья была вам полезна, вы можете поставить лайк, добавить в закладки и поделиться ей. Данный контент был создан с использованием ИИ-ассистированной совместной работы, и финальное содержимое было проверено и подтверждено автором.

开始使用 HagiCode

一次安装,几分钟上手

HagiCode for Windows 在 Microsoft Store 免费提供。打开商店即可安装并保持更新;也可以先对比各版本与定价,再决定从哪个渠道开始。

Превращайте идеи в готовое ПО.

HagiCode — агентная среда разработки: рабочие процессы OpenSpec планируют изменение, мультиагентное выполнение его реализует, а Hero Scenario делает долгий путь наглядным, так что идеи становятся готовым ПО.

Умный
Рабочие процессы OpenSpec превращают намерение в путь от предложения до готового изменения.
Эффективный
Мультиагентное выполнение ведёт исследование, реализацию и проверку параллельно.
Увлекательный
Удобный интерфейс упрощает планирование, работу агентов и проектные задачи.