Загадка долгих задач A2A: почему всё, что дольше двух минут, стабильно падает
Статья 38 в серии платформ обмена сообщениями Hermes: три причины проблемы тайм-аута в длительных задачах A2A и их исправление.
Два экземпляра Hermes на разных машинах «звонят» друг другу, чтобы раздать задачи, — и всё, что выполняется дольше двух минут, стабильно падает. Никакие танцы с бубном вокруг конфигов не помогают. Это не магия, а три мелкие грабли, сложенные в одну кучу.
Загадка: проклятие двух минут
Представь: ты просишь коллегу А обработать файл, и он обещает перезвонить, когда закончит. Но каждый раз, когда разговор затягивается дольше двух минут, у тебя «обрывается» линия, и на экране — «сбой вызова». Ты менял телефон, менял линию, даже переезжал в другой офис — проблема никуда не делась.
Именно это и случилось с пользователями Hermes. Две машины, на каждой крутится свой Hermes, связаны через плагин A2A (открытый протокол для общения между агентами). Машина А раздаёт задачу машине Б, и как только выполнение занимает больше ~2 минут — гарантированный провал. В сообществе это называют «long reply ломает отслеживание прогресса». Самое обидное: на стороне Б задача-то выполняется, но А так и не получает результат.
Причина первая: у звонящего просто нет терпения
Первые грабли — чисто вопрос «торопыги».
У клиента Hermes есть таймаут по умолчанию — 120 секунд. Что это значит? Звонящий ставит себе будильник: если за 120 секунд собеседник не ответил — вешаю трубку. А что на стороне сервера? Там для работы агента зарезервировано окно ответа в 300 секунд, то есть 5 минут.
Вот и получается: звонящий вешает трубку через 2 минуты, а тот, кто принимает звонок, думает, что у тебя терпения на 5 минут. Итог: задача ещё выполняется, а связь уже оборвана. Причём эти 120 секунд захардкожены прямо в коде — обычный пользователь просто не найдёт, где это поменять.
Причина вторая: у старой линии нет «тумблера» таймаута
Вторые грабли — историческое наследие.
Hermes поддерживает два способа вызова A2A: «официальный» — через сконфигурированных пиров (как через коммутатор), и «прямую старую линию» — вызов напрямую по исходному URL. Старая линия осталась с ранних версий, и внутри неё тоже зашиты те же 120 секунд, причём конфиг она не читает.
То есть даже если ты разобрался, как поменять таймаут, старая линия просто не обратит на это внимания. Как если бы ты поставил новую батарейку в старый дисковый телефон — но у него физически нет регулятора громкости.
Причина третья: результат выбрасывают по расписанию
Третьи грабли — самые подлые: сервер «зачищает поле» по таймеру.
Допустим, ты наконец увеличил таймаут на клиенте и пережил первые два пункта. И тут сервер выкидывает новый фортель: как только окно ответа в 300 секунд истекло, он помечает задачу как «проваленную» — даже если агент всё ещё пашет как проклятый. Хуже того: этот статус «провала» липкий — как суперклей, не отодрать. Когда агент наконец доделывает работу и возвращается с результатом, оказывается, что его уже никто не ждёт, — и результат просто выбрасывают.
Это как если бы курьер ушёл домой по расписанию, и система показала «доставка не удалась», не дожидаясь, доехала ли посылка. А когда ты на следующий день получаешь посылку, в трекинге навсегда остаётся статус «неудача», и ничего не исправить.
Шаг первый в расследовании: замедлить будильник
Когда понял все три грабли, починить уже несложно.
Первое исправление — прямое: меняем таймаут клиента по умолчанию со 120 секунд на 330. 330 > 300 (серверных), так что звонящий теперь терпеливее принимающего, и задача спокойно переживает окно ответа на сервере. Заодно в a2a_call добавлен параметр переопределения таймаута для конкретного вызова — можно задать таймаут для отдельной задачи, не трогая глобальные настройки. И старая линия наконец «модернизировалась»: теперь она читает конфиг и наследует настройки аутентификации и таймаута.
Шаг второй: задача «отвязывается», а не «проваливается»
Второе исправление — поумнее. Сервер больше не помечает задачу как проваленную по истечении окна ответа — вместо этого он её «отвязывает». Что это значит? Как с очередью в колл-центре: если ждать надоело, можно положить трубку, но твоя заявка остаётся в системе, и тебе пришлют SMS, когда её обработают.
Если конкретнее: когда окно ответа истекает, вызывающая сторона получает нефинальный статус «в работе» с инструкциями. Задача остаётся в статусе WORKING, и система назначает «ограниченного ожидающего», который продолжает следить за ней. Когда агент реально заканчивает работу, финальный результат фиксируется. И теперь — через tasks/get или tasks/resubscribe — наблюдатель всегда увидит итоговый результат. Опоздавший результат больше не выбрасывают.
Шаг третий: заодно прибили соседские баги
Пока чинили главное, всплыли и другие болячки по соседству.
Обрезанные потоковые ответы: раньше потоковый ответ мог вернуть вызывающей стороне только последний фрагмент. Например, длинная цепочка event_id обрезалась до второго сегмента, и вызывающий получал обрубок. Теперь накопленный ответ сохраняется целиком — в тестах все 152 кейса прошли.
Фальшивое завершение: когда у агента заканчивался бюджет итераций, раньше возвращалось «завершённое» резюме, но пир не мог отличить «обрубленную работу» от «успешно завершённой» — и мог принять частичный результат за полный. Теперь обрезанная задача честно сообщает TASK_STATE_FAILED и больше не притворяется успешной.
Внешний лаунчер: не-root пользователи теперь могут настраивать внешний лаунчер агентов — запускать подпроцессы или версионированные RPC-воркеры с ограниченным выводом, сбором дерева процессов, отменой и ровно одним финальным завершением. Это закрыло три старые проблемы: видимость завершения, выравнивание таймаутов и непрерывность многораундовых диалогов.
Что это значит для тебя
Теперь, когда два экземпляра Hermes раздают друг другу задачи, всё, что дольше двух минут, больше не «гарантированно падает». Таймаут настраивается, старая линия слушается. А главное — даже если сервер не дождался, задачу больше не «приговаривают»: она остаётся в работе, результат записывается как обычно, и ты в любой момент можешь его получить.
Долгие задачи наконец-то как длинный забег: есть кто считает время, есть кто бежит рядом, есть кто фиксирует результат. А не как раньше — когда на полпути судья свистит, и результат аннулируют.
📖 Официальная документация
Эта статья основана на официальной документации Hermes Agent :Официальные документы › user-guide/messaging/a2a