A2A 장기 태스크 미스터리: 2분 넘는 작업이 왜 자꾸 실패할까
Hermes 메시지 플랫폼 연동 제38편: A2A 장기 작업 타임아웃 문제의 세 가지 원인과 수정 방안
두 대의 컴퓨터에서 Hermes가 서로 “전화”를 걸어 일을 시키는데, 작업이 2분만 넘으면 실패한다—설정을 아무리 바꿔도 소용없다. 이건 미신이 아니라, 세 가지 작은 함정이 겹쳐진 것이다.
미스터리: 2분의 저주
상상해보자. 동료 A에게 파일 처리를 부탁하면서, 처리가 끝나면 전화로 알려달라고 했다. 그런데 매번 통화가 2분만 넘어가면, 내 쪽에서 “뚝” 끊겨버리고 “통화 실패”가 뜬다. 전화기도 바꿔보고, 회선도 바꿔보고, 심지어 사무실도 옮겨봤지만 문제는 그대로다.
이게 바로 Hermes 사용자들이 겪은 일이다. 두 대의 머신이 각각 Hermes를 실행 중이고, A2A 플러그인(에이전트 간 개방형 통신 프로토콜)으로 연결되어 있다. A 머신이 B 머신에 작업을 시키면, 작업 시간이 약 2분만 넘어가면 무조건 실패한다. 커뮤니티에서는 이걸 “long reply로 인한 진행 상황 추적 손상”이라고 부른다. 더 화나는 건, B 머신 쪽에서는 작업이 사실 끝났는데 A 머신은 결과를 영영 받지 못한다는 점이다.
원인 1: 전화 거는 사람이 너무 급하다
첫 번째 함정은 순전히 “성격 급한” 문제다.
Hermes 클라이언트에는 기본 타임아웃 설정이 있다—120초. 무슨 뜻이냐? 전화 거는 사람이 자기한테 알람을 맞춰놓은 거다: 120초 안에 상대가 응답하지 않으면 끊겠다. 그런데 서버 쪽은? 에이전트가 작업할 수 있도록 예약해 둔 응답 창은 300초, 즉 5분이다.
보시다시피, 전화 거는 사람은 2분이면 끊어버리는데, 받는 사람은 상대가 5분 인내심이 있다고 생각한다. 결과는? 작업이 아직 실행 중인데 전화가 먼저 끊긴다. 게다가 이 120초는 코드에 하드코딩되어 있어서, 일반 사용자는 어디서 바꿔야 할지 찾을 수도 없다.
원인 2: 옛날 회선에는 “타임아웃” 스위치가 없다
두 번째 함정은 역사적 유산 문제다.
Hermes는 A2A를 호출하는 두 가지 방식을 제공한다: 하나는 정식 “교환원 연결”(설정된 피어를 통한 호출), 다른 하나는 “직통 구식 회선”(원본 URL을 직접 호출)이다. 구식 회선은 초기 버전에서 남겨진 것으로, 내부에도 120초 타임아웃이 하드코딩되어 있고 설정 파일을 읽지 않는다.
즉, 타임아웃을 바꾸는 방법을 배웠더라도 구식 회선은 네 말을 듣지 않는다. 마치 옛날 전화기에 새 배터리를 넣었지만, 볼륨 조절 손잡이가 아예 없는 것과 같다.
원인 3: 시간이 되면 결과물을 버린다
세 번째 함정이 제일 악질이다: 서버가 시간이 되면 “청소”를 한다.
클라이언트 타임아웃을 드디어 늘려서 첫 두 함정을 넘겼다고 치자. 그런데 서버 쪽에서 또 사고를 친다: 300초 응답 창이 끝나면, 작업을 “실패”로 표시해버린다—에이전트가 여전히 열심히 일하고 있는데도 말이다. 더 심한 건, 이 “실패” 상태는 끈적끈적해서—마치 접착제처럼—떨어지지 않는다. 에이전트가 진짜로 일을 끝내고 결과를 가지고 돌아와도, 기다리는 사람이 아무도 없어서 결과가 그냥 버려진다.
이건 마치 택배기사가 정시에 퇴근해서, 네 소포가 배달됐는지와 상관없이 시스템에 먼저 “배달 실패”로 표시되는 것과 같다. 다음 날 소포를 받아도, 배송 정보는 영원히 “실패” 칸에 머물러 있고 되돌릴 수 없다.
해결 1단계: 알람을 늦춰라
세 가지 함정을 파악했으니, 수정은 쉽다.
첫 번째 수정은 아주 직관적이다: 클라이언트 기본 타임아웃을 120초에서 330초로 바꾼다. 330초 > 서버의 300초이므로, 전화 거는 사람이 받는 사람보다 더 인내심이 있어져서 작업이 서버의 응답 창을 넘길 수 있다. 동시에, a2a_call에 per-call 타임아웃 오버라이드 파라미터가 추가되었다—전역으로 바꾸지 않고 개별 작업마다 타임아웃을 따로 설정할 수 있다. 구식 회선도 드디어 “현대화”되어서, 이제 설정을 읽고 인증과 타임아웃 설정을 상속한다.
해결 2단계: 작업을 “실패”가 아닌 “분리”로 처리
두 번째 수정은 더 영리하다. 서버는 시간이 되면 작업을 실패로 표시하는 대신 “분리”한다. 무슨 뜻이냐? 마치 고객센터 전화 대기처럼, 너무 오래 기다려서 끊고 싶으면 끊어도 되지만, 네 티켓은 시스템에 남아 있고 처리가 끝나면 문자로 알려준다.
구체적으로: 응답 창이 만료되면, 호출자는 “작업 중”이라는 비종료 상태와 함께 안내를 받는다. 작업은 WORKING 상태를 유지하고, 시스템은 “유계 대기자(bounded waiter)“를 배치해서 계속 지켜보게 한다. 에이전트가 진짜로 일을 끝내면, 실제 결과가 기록된다. 이후 tasks/get이나 tasks/resubscribe로 조회하면, 관찰자는 최종 결과를 볼 수 있다. 늦게 도착한 결과는 더 이상 버려지지 않는다.
해결 3단계: 덤으로 고쳐진 함정들
본건을 해결하고 나니, 이웃집 함정 몇 개도 발견했다.
스트리밍 응답 잘림: 이전에는 스트리밍 응답이 호출자에게 마지막 조각만 반환할 수 있었다. 예를 들어 긴 event_id가 두 번째 세그먼트만 남도록 잘려서, 호출자는 불완전한 정보를 받았다. 수정 후에는 누적 응답이 완전히 보존되며, 테스트에서 152개 케이스가 모두 통과했다.
가짜 완료: 에이전트의 반복 예산이 소진되면, 이전에는 “완료된” 요약을 반환했지만, 피어는 “잘린 작업”과 “성공적으로 완료된 작업”을 구분할 수 없어서 부분 결과를 완전한 결과로 받아들일 수 있었다. 이제 잘린 작업은 TASK_STATE_FAILED로 명확히 보고하며, 더 이상 성공으로 위장하지 않는다.
외부 런처: 비-root 사용자도 이제 외부 에이전트 런처를 구성할 수 있다. 하위 프로세스나 버전 관리형 RPC 워커를 실행하고, 유계 출력, 프로세스 트리 수확, 취소, 정확히 한 번의 종료를 지원한다. 완료 가시성, 타임아웃 정렬, 다중 턴 연속성이라는 세 가지 오래된 문제를 해결했다.
당신에게 의미하는 것
이제 두 Hermes 사이에서 작업을 시킬 때, 2분이 넘는 작업도 더 이상 “무조건 실패”하지 않는다. 타임아웃을 조정할 수 있고, 구식 회선도 말을 듣는다. 더 중요한 건, 서버가 기다리지 못하더라도 작업이 “사형 선고”를 받지 않는다—작업 상태를 유지하면서 일을 끝내고 결과를 정상적으로 기록하므로, 언제든지 조회할 수 있다.
장기 작업은 이제 마라톤처럼, 누군가 시간을 재고, 누군가 함께 뛰고, 누군가 기록을 남긴다. 예전처럼 달리다가 심판이 호루라기를 불어서 기록이 무효가 되는 일은 없다.
📖 공식 문서
この記事は Hermes Agent の공식 문서に基づいています:공식 문서 › user-guide/messaging/a2a