Skip to main content
Summary

긴 작업은 한 번의 응답으로 끝나지 않는다

앞 장에서는 A2A 요청이 JSON-RPC over HTTP(S)로 오간다는 점을 살펴보았다. 짧은 작업이라면 message/send 한 번으로 충분하다. 예를 들어 "이 문장을 영어로 번역해 줘" 같은 요청은 클라이언트가 보내고, 원격 에이전트가 곧바로 답하면 된다.

하지만 현실의 에이전트 업무는 그렇게 단순하지 않다. 시장 조사, 코드 분석, 계약서 검토, 여러 시스템을 거치는 승인 업무는 몇 초가 아니라 몇 분, 몇 시간까지 이어질 수 있다. 중간에 추가 질문이 필요할 수도 있고, 일부 결과가 먼저 나올 수도 있다. 사용자는 "아무 반응 없이 기다리는 화면"을 신뢰하기 어렵다.

A2A는 이런 긴 작업을 위해 두 가지 흐름을 제공한다.

첫째는 스트리밍이다. 클라이언트가 연결을 열어 둔 상태에서 원격 에이전트가 진행 상황과 중간 결과를 계속 보내는 방식이다. 회의 중에 담당자가 "자료 수집 중입니다", "초안이 나왔습니다", "검토를 시작합니다"라고 계속 알려 주는 것과 비슷하다.

둘째는 비동기 푸시 알림이다. 클라이언트가 계속 연결을 붙잡고 있지 않아도, 원격 에이전트가 중요한 변화가 생겼을 때 지정된 주소로 알림을 보내는 방식이다. 세탁소에 옷을 맡긴 뒤 가게 앞에서 계속 기다리지 않고, 완료 문자를 받는 상황에 가깝다.

두 방식은 경쟁 관계가 아니다. 사용자에게 실시간 경험이 필요하면 스트리밍이 좋고, 오래 걸리는 백그라운드 업무라면 푸시 알림이 더 적합하다. 실제 시스템에서는 둘을 함께 쓰는 경우도 많다.

스트리밍과 비동기 알림 설계하기AI 생성 이미지

SSE 스트리밍의 기본 흐름

A2A의 스트리밍은 일반적으로 SSE(Server-Sent Events)를 사용한다. SSE는 HTTP 연결을 열어 둔 뒤 서버가 클라이언트에게 이벤트를 계속 밀어 보내는 방식이다. 웹소켓처럼 양방향 대화를 위한 범용 통로라기보다, 서버의 진행 상황을 클라이언트가 받는 데 초점이 있다.

A2A에서 스트리밍을 쓰려면 먼저 Agent Card를 확인해야 한다. 원격 에이전트가 capabilities.streaming: true를 표시해야 클라이언트는 스트리밍을 기대할 수 있다. 그다음 클라이언트는 message/stream 요청으로 작업 메시지를 보내면서 동시에 업데이트 구독을 시작한다. 서버가 이를 받아들이면 HTTP 200 OK와 Content-Type: text/event-stream으로 응답하고 연결을 유지한다.1

이때 중요한 점은 SSE가 "그냥 문자열 조각"을 흘려보내는 통로가 아니라는 것이다. A2A에서는 각 SSE 이벤트의 data 안에 JSON-RPC 응답 객체가 담긴다. 즉, 앞 장에서 본 JSON-RPC 구조가 스트리밍에서도 사라지지 않는다. 전송 방식만 일반 응답에서 이벤트 흐름으로 바뀐다.

스트림을 통해 올 수 있는 업데이트는 크게 세 가지로 볼 수 있다.

  • Task: 현재 태스크 전체 또는 중요한 스냅샷

  • TaskStatusUpdateEvent: 태스크 상태 변화와 중간 메시지

  • TaskArtifactUpdateEvent: 새로 생성되거나 갱신된 산출물 정보

예를 들어 리서치 에이전트에게 "A2A 도입 사례를 조사해 표로 정리해 줘"라고 맡겼다고 하자. 스트림은 다음처럼 사용자 경험을 바꾼다.

  1. 태스크가 submitted에서 working으로 바뀐다.

  2. 에이전트가 "공식 문서와 공개 사례를 확인 중입니다"라는 중간 메시지를 보낸다.

  3. 일부 표 데이터가 Artifact의 첫 조각으로 도착한다.

  4. 추가 검증이 끝난 뒤 최종 보고서가 완성된다.

  5. 태스크가 completed 상태가 되고 스트림이 닫힌다.

이 흐름 덕분에 클라이언트는 단순한 로딩 스피너 대신 실제 진행 상황을 보여 줄 수 있다. 사용자는 작업이 멈췄는지, 진행 중인지, 추가 입력이 필요한지 알 수 있다.

부분 산출물은 사용자 경험을 바꾼다

스트리밍의 가치는 "빨리 보이는 글자"에만 있지 않다. 더 중요한 것은 부분 산출물을 다룰 수 있다는 점이다.

긴 보고서를 만드는 에이전트가 있다고 하자. 최종 문서가 완성될 때까지 아무것도 보여 주지 않으면 사용자는 기다림의 이유를 알 수 없다. 반대로 목차, 주요 근거, 표 초안, 그래프 데이터가 순서대로 도착하면 사용자는 중간에 방향을 확인할 수 있다. 잘못된 방향이면 빨리 개입할 수 있다.

A2A의 TaskArtifactUpdateEvent는 이런 부분 산출물 전달에 사용된다. 산출물이 크거나 단계적으로 생성될 경우 조각 단위로 보낼 수 있다. 구현에서는 append, lastChunk 같은 필드를 활용해 "기존 산출물에 이어 붙이는 조각인지", "마지막 조각인지"를 표현할 수 있다.1

다만 부분 산출물은 설계가 필요하다. 중간 결과는 최종 결과와 다를 수 있다. 따라서 UI에는 "초안", "생성 중", "검토 전" 같은 상태를 분명히 표시하는 것이 좋다. 아직 확정되지 않은 정보를 최종 결과처럼 저장하거나 외부 시스템에 자동 반영하면 위험하다.

에이전트는 중간 메시지와 산출물을 구분해서 보내야 한다. "현재 검색 중입니다"는 메시지다. "검색된 후보 목록 JSON"은 산출물일 수 있다. "최종 분석 보고서 PDF"도 산출물이다. 이 구분이 명확해야 클라이언트가 화면 표시, 저장, 후속 작업 연결을 안정적으로 처리할 수 있다.

스트림은 언제 끝나는가

스트림은 영원히 열려 있지 않다. 태스크가 끝났거나, 더 이상 자동으로 진행할 수 없는 상태가 되면 서버는 스트림을 닫을 수 있다.

대표적인 종료 상태는 다음과 같다.

  • completed: 작업이 완료됨

  • failed: 작업이 실패함

  • canceled: 작업이 취소됨

  • rejected: 요청이 거부됨

  • input-required: 추가 입력이 필요해 멈춤

여기서 input-required가 흥미롭다. 이것은 실패가 아니다. 원격 에이전트가 일을 계속하려면 클라이언트나 사용자에게 더 물어봐야 한다는 뜻이다. 예를 들어 "어느 국가의 규정을 기준으로 검토할까요?"라는 질문이 필요할 수 있다. 이 경우 스트림은 일단 끝나지만, 클라이언트는 사용자의 답을 받아 같은 태스크 맥락에서 후속 메시지를 보낼 수 있다.

일부 사양 설명에서는 TaskStatusUpdateEvent에 final: true가 포함되어 현재 스트리밍 상호작용의 끝을 알릴 수 있다. 하지만 운영 관점에서는 특정 플래그 하나에만 의존하지 않는 편이 안전하다. 태스크 상태와 스트림 종료, 이후 tasks/get 조회 결과를 함께 보고 판단해야 한다.

연결이 끊어졌을 때: 재연결과 복구

스트리밍은 연결이 살아 있다는 전제를 가진다. 그러나 네트워크는 언제든 끊어진다. 브라우저 탭이 닫힐 수 있고, 모바일 네트워크가 바뀔 수 있으며, 프록시나 로드밸런서가 긴 연결을 종료할 수도 있다.

A2A에서는 스트림이 끊겼지만 태스크가 아직 진행 중이라면 tasks/resubscribe로 다시 구독할 수 있다.1 클라이언트는 이때 다음을 준비해야 한다.

첫째, 태스크 ID를 반드시 저장해야 한다. 연결이 끊긴 뒤 "무슨 작업을 보고 있었는지" 모르면 복구할 수 없다.

둘째, 스트림 이벤트를 영구 저장소처럼 믿지 말아야 한다. 연결이 끊긴 동안 발생한 모든 이벤트를 서버가 반드시 다시 보내 준다고 가정하면 안 된다. 사양과 구현에 따라 누락 이벤트 처리 방식은 달라질 수 있다. 복구 시에는 tasks/get으로 현재 태스크 상태와 산출물을 다시 조회하는 흐름을 두는 것이 안전하다.

셋째, 중복 이벤트를 견딜 수 있어야 한다. 재연결 과정에서 이미 받은 상태나 산출물 조각이 다시 올 수 있다. 클라이언트는 이벤트 ID, 산출물 ID, 청크 순서, 마지막 반영 시점 등을 활용해 같은 내용을 두 번 표시하거나 두 번 저장하지 않도록 해야 한다.

넷째, 타임아웃을 설계해야 한다. 아무 이벤트가 오지 않는다고 해서 곧바로 실패로 처리하면 긴 작업을 잘못 중단할 수 있다. 반대로 무한정 기다리면 사용자 경험과 자원 사용이 나빠진다. 작업 유형별 예상 시간, 서버의 heartbeat 지원 여부, 사용자에게 보여 줄 대기 메시지를 함께 정해야 한다.

비동기 푸시 알림의 구조

스트리밍은 클라이언트가 연결을 유지할 수 있을 때 좋다. 하지만 모든 클라이언트가 그렇게 동작할 수 있는 것은 아니다. 배치 시스템, 모바일 앱, 사내 워크플로 엔진은 몇 시간 동안 연결을 열어 두기 어렵다. 이때 사용하는 방식이 푸시 알림이다.

A2A에서 푸시 알림을 사용하려면 Agent Card에 capabilities.pushNotifications: true가 표시되어 있어야 한다. 클라이언트는 태스크를 만들 때 또는 이후 설정 요청을 통해 PushNotificationConfig를 제공할 수 있다. 이 설정에는 보통 알림을 받을 HTTPS 웹훅 URL, 검증용 토큰, 인증 방식 정보가 포함된다.1

흐름은 단순하다.

  1. 클라이언트가 원격 에이전트에게 태스크를 요청한다.

  2. 요청 안에 "중요한 변화가 생기면 이 URL로 알려 달라"는 설정을 포함한다.

  3. 원격 에이전트는 백그라운드에서 작업을 계속한다.

  4. 상태가 completed, failed, input-required, auth-required처럼 중요하게 바뀌면 클라이언트의 웹훅으로 알림을 보낸다.

  5. 클라이언트는 알림을 검증한 뒤 tasks/get으로 최신 태스크 전체를 조회한다.

푸시 알림의 본문은 태스크, 메시지, 상태 업데이트, 산출물 업데이트 중 하나를 담을 수 있다. 그러나 알림 하나에 모든 정보를 싣는 방식은 권장하기 어렵다. 알림은 "변화가 생겼다"는 신호로 보고, 필요한 세부 정보는 다시 조회하는 편이 안정적이다.

웹훅은 보안 경계다

푸시 알림은 편리하지만 보안 위험도 함께 만든다. 클라이언트가 제공한 URL로 서버가 요청을 보내기 때문이다. 서버가 아무 URL이나 믿고 호출하면 내부망 주소를 찌르는 SSRF 공격이나, 특정 외부 시스템에 트래픽을 몰아주는 악용이 발생할 수 있다.

따라서 원격 에이전트 서버는 웹훅 목적지를 검증해야 한다. 허용된 도메인 목록, 소유권 확인, 외부 요청 제한, egress 방화벽 같은 장치를 둘 수 있다. 반대로 웹훅을 받는 클라이언트도 들어오는 알림이 진짜 원격 에이전트에서 온 것인지 확인해야 한다. 서명된 JWT, HMAC, OAuth, mTLS 같은 방식이 쓰일 수 있고, 재전송 공격을 막기 위해 타임스탬프와 고유 이벤트 ID를 검증하는 것이 좋다.2

쉽게 말해 푸시 알림의 웹훅 URL은 "알림 받을 주소"이면서 동시에 "외부에서 호출 가능한 문"이다. 문을 만들었다면 누가 들어오는지 확인해야 한다.

취소와 중단도 정상 흐름이다

긴 작업에서는 취소가 중요하다. 사용자가 잘못 요청했거나, 더 이상 결과가 필요 없거나, 비용이 큰 작업을 중단해야 할 수 있다. A2A에서는 tasks/cancel 같은 취소 요청을 통해 진행 중인 태스크를 멈추는 흐름을 설계할 수 있다.

취소는 즉시 강제 종료와 다를 수 있다. 원격 에이전트가 외부 API 호출, 파일 생성, 다른 에이전트와의 하위 작업을 진행 중이라면 안전한 지점에서 멈춰야 한다. 따라서 클라이언트는 취소 요청을 보낸 뒤 최종 상태가 canceled인지, 이미 completed가 되었는지, 취소에 실패했는지 확인해야 한다.

스트리밍 중 취소가 발생하면 서버는 상태 업데이트를 보낸 뒤 스트림을 닫을 수 있다. 푸시 알림을 사용하는 경우에는 취소 완료 또는 실패를 웹훅으로 알려 줄 수 있다. 어느 방식이든 취소를 "오류"로만 취급하지 말고 정상적인 사용자 의사 표현으로 다루는 것이 좋다.

어떤 방식을 선택할 것인가

동기 호출, 스트리밍, 푸시 알림은 작업 성격에 따라 선택해야 한다.

방식적합한 경우주의할 점
동기 호출짧고 즉시 끝나는 작업오래 걸리면 타임아웃과 사용자 불안이 커진다
스트리밍진행 상황, 중간 메시지, 부분 산출물이 중요한 작업연결 끊김, 재구독, 중복 이벤트 처리가 필요하다
푸시 알림오래 걸리는 백그라운드 작업, 클라이언트가 대기하기 어려운 작업웹훅 보안, 알림 검증, 후속 조회 설계가 필요하다

간단한 기준은 다음과 같다.

  • 사용자가 화면을 보고 기다리는가? 그렇다면 스트리밍을 우선 고려한다.

  • 작업이 몇 초 안에 끝나는가? 그렇다면 동기 호출이 단순하다.

  • 작업이 몇 분 이상 걸리거나 클라이언트가 오프라인일 수 있는가? 그렇다면 푸시 알림이 필요하다.

  • 중간 결과를 검토하며 방향을 바꿔야 하는가? 그렇다면 스트리밍이 적합하다.

  • 최종 완료 여부만 알면 되는가? 그렇다면 푸시 알림과 tasks/get 조합이 실용적이다.

가장 견고한 설계는 세 방식을 배타적으로 보지 않는다. 예를 들어 클라이언트는 먼저 message/stream으로 실시간 진행 상황을 받는다. 사용자가 화면을 닫거나 연결이 끊기면 tasks/resubscribe를 시도한다. 그래도 장시간 작업이 이어지면 푸시 알림을 통해 완료 시점을 받는다. 마지막으로 tasks/get으로 확정된 태스크와 산출물을 조회한다.

이 장의 핵심은 단순하다. 에이전트 협업에서 긴 작업은 예외가 아니라 기본 상황에 가깝다. A2A의 스트리밍과 비동기 알림은 그 긴 시간을 사용자와 시스템이 견딜 수 있게 만드는 장치다. 그러나 연결 복구, 중복 처리, 타임아웃, 취소, 웹훅 보안은 프로토콜이 대신 완성해 주지 않는다. 좋은 A2A 시스템은 업데이트를 잘 보내는 것만큼, 업데이트가 끊기고 늦고 중복될 때도 안전하게 동작하도록 설계되어야 한다.

참고

1A2A Streaming and Asynchronous Operations

2A2A streaming-and-async documentation on GitHub

책 목록

Agent2Agent: Google A2A로 배우는 멀티 에이전트 상호운용성

전체 목차 보기

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기