← Back to Blog
AI · AX Article · 2026.10.23

에이전트의 중지 버튼은 어디까지 멈추는가

AI 에이전트에 중지를 요청해도 실행 중인 도구와 외부 작업은 남을 수 있다. Temporal의 취소 전달 방식과 Python의 비동기 작업 관리를 통해 중지 요청, 실행 종료, 외부 상태 확인을 나누는 업무 설계를 살펴본다.

AIai-agentworkflowcancellationautomation

갈라진 구리 선로의 청록색 차단막 앞에 모인 구슬과 이미 차단막을 지난 구슬을 따로 받는 그릇

GPT Image 2로 생성한 개념 이미지. 중지 요청이 전달되는 경계와 이미 진행된 작업을 추상적으로 표현했다.

행사 안내문을 만드는 에이전트를 가정해 보자. 자료를 읽고, 이미지를 만들고, 안내 페이지를 올리는 일을 나누어 실행한다. 담당자가 행사 날짜의 오류를 발견해 중지 버튼을 누른다. 대화창의 글자 생성은 멈췄지만 이미지 제작은 계속되고, 조금 뒤에는 잘못된 날짜가 적힌 페이지가 공개된다. 이 장면은 특정 서비스의 사고 사례가 아니라, 중지의 범위를 설계하기 위한 가정이다.

사용자가 멈추려 한 것은 업무 전체다. 화면이 멈춘 지점은 그 업무의 한 부분일 수 있다. 모델 응답, 비동기 작업, 별도 서버의 도구 실행, 이미 접수된 외부 요청은 서로 다른 수명을 가진다. 따라서 중지 버튼을 평가할 때는 누른 직후 화면이 조용해지는지에 더해, 남은 실행과 결과를 어디까지 확인하는지 살펴야 한다.

취소 요청이 도착할 길이 있어야 한다

Temporal은 오래 실행되는 업무 흐름과 그 안의 개별 작업을 관리하는 시스템이다. 이 시스템은 협력적 취소인 Cancellation과 강제 종료인 Termination을 구분한다. 취소 요청은 워크플로 코드에 전달되어 구현된 정리 절차를 실행할 기회를 준다. 강제 종료는 워크플로 실행을 즉시 닫으므로 그 코드가 정리할 기회를 얻지 못한다. 취소를 처리한 코드가 정상 반환하면 실행 상태가 Completed로 끝날 수도 있다. 화면에 표시되는 종료 상태를 업무 목적의 달성으로 곧바로 읽기 어려운 이유다. Temporal의 취소·종료 문서

개별 작업인 Activity에도 요청이 도착해야 한다. Temporal의 일반 Activity는 실행 중 보내는 Heartbeat를 통해 취소를 전달받는다. TypeScript 가이드는 Heartbeat와 Heartbeat Timeout을 설정하도록 안내한다. 같은 Worker 안에서 처리되는 Local Activity는 Heartbeat 없이도 취소를 받을 수 있는 예외다. 이 차이를 지우고 모든 작업이 같은 방법으로 멈춘다고 설명하면 실제로는 닿지 않는 중지 경로를 만들게 된다. TypeScript 취소 가이드

이 구현에서 업무 설계로 가져올 질문은 구체적이다. 지금 실행 중인 도구가 취소 신호를 받을 수 있는가. 받았다면 작업을 멈추고 자원을 정리하는가. 외부 서비스에 넘긴 요청까지 취소할 수 있는가. 도구마다 답을 확인해야 하며, 에이전트가 중지 메시지를 이해했다는 사실만으로 빈칸을 채울 수는 없다.

기다림을 끝내는 시점이 결과를 바꾼다

Temporal TypeScript SDK에서 TRY_CANCEL은 일반 Activity의 취소를 요청하면서 호출자 쪽에는 곧바로 취소를 알린다. 반면 WAIT_CANCELLATION_COMPLETED는 요청을 전파하고 Activity의 실행이 끝날 때까지 기다린다. 그 끝은 취소일 수도, 성공이나 실패일 수도 있다. ABANDON은 호출자에게 취소를 알리되 Activity에 취소 요청을 전달하지 않는다. 현재 기본 브랜치의 옵션 정의는 기다림을 먼저 끝낸 경우 호출자의 판단과 Activity의 실제 결과가 달라질 수 있음을 명시한다. 이 옵션의 동작은 해당 SDK의 일반 Activity에 한정한 설명이다. 공개 SDK의 옵션 정의

앞의 행사 안내 업무에 적용하면 차이가 드러난다. 취소를 요청한 뒤 곧바로 새 안내문 생성을 시작하면, 이전 실행의 게시 작업과 새 실행이 겹칠 수 있다. 기다리는 방식을 택해도 작업이 취소에 응답하지 않으면 기다림이 길어진다. 또 Activity의 종료 확인이 외부 게시 서비스의 취소나 삭제까지 증명하는 것은 아니다. 그 부분은 도구에서 외부 상태를 대조하도록 따로 구현해야 한다.

한 프로세스 안에서도 작업의 묶음 방식이 중요하다. Python 3.14의 asyncio.TaskGroup은 구성 작업에서 취소 이외의 예외가 발생하면 나머지 작업을 취소하고 종료를 기다린다. 기본 설정의 asyncio.gather는 첫 예외를 호출자에게 전달하면서 다른 작업은 계속 실행할 수 있다. 공식 문서는 정리에 try/finally를 사용하고, CancelledError를 직접 잡았다면 정리 후에는 대체로 다시 전파하도록 안내한다. 예외를 삼켜 버리면 취소를 내부적으로 사용하는 구조가 오작동할 수 있다. 다만 이러한 작업 묶음이 외부 서버의 실행까지 자동으로 지배한다고 확대해서는 안 된다. Python 3.14 비동기 작업 문서

중지 뒤에 남은 일을 화면에 드러낸다

여기서부터는 특정 제품의 기능 설명이 아니라 업무 자동화를 위한 설계 제안이다. 먼저 사용자가 기대하는 중지 범위를 적어 둔다. 행사 안내 업무라면 새 게시를 막고, 실행 중인 제작을 취소하며, 이미 공개된 페이지의 존재를 확인하는 범위가 될 수 있다. 페이지를 비공개로 돌릴 권한까지 주어진 것인지도 구분해야 한다.

하나의 완료 표시로 묶기보다, 운영 기록에는 다음 상태를 나누어 남길 수 있다. 아래 표현은 표준 상태 코드가 아닌 제안이다.

표시할 상태필요한 근거아직 남을 수 있는 일
중지 요청 접수대상 실행과 요청 시각 기록이미 실행 중인 작업
실행 정리 중작업별 취소 요청과 응답응답하지 않은 도구, 정리 작업
실행 종료 확인관리하는 작업들의 최종 상태외부 서비스에서 이미 발생한 효과
외부 상태 확인대상 서비스 조회와 결과 대조별도 승인이나 복구가 필요한 변경

이미 게시된 안내문은 실행을 멈춘 것만으로 사라지지 않는다. 이 부분은 보상 트랜잭션을 다룬 앞선 글과 이어진다. 이번 중지로 새 행동을 차단하는 일과, 이미 일어난 행동을 수습하는 일에는 각각 근거와 권한이 필요하다.

특히 취소 확인 직전과 게시 직전 사이의 경합을 고려해야 한다. 실행이 게시 가능 여부를 한 번 읽은 뒤 그 결과만 믿고 나중에 게시하면, 그사이에 들어온 중지 요청을 놓칠 수 있다. 게시를 허용하는 지점에서 현재 실행의 유효성을 확인하고, 가능한 범위에서는 확인과 반영을 같은 제어 아래 처리하는 설계가 필요하다. 외부 서비스가 그런 제어를 제공하지 않는다면 그 한계를 숨기지 말고, 접수된 작업 ID와 실제 게시 결과를 대조하는 절차를 두어야 한다. 모든 외부 API에 통하는 원자적 중지 기능이 있다고 가정해서는 안 된다.

중지도 하나의 업무 시나리오로 시험한다

자동화의 검수에는 끝까지 성공한 실행 외에 중간에 멈춘 실행도 필요하다. 행사 안내 예시라면 자료를 읽는 동안, 외부 제작 요청을 보낸 직후, 게시 응답이 오기 직전에 각각 중지를 요청해 볼 수 있다. 그리고 중지 요청을 접수한 시각, 관리하는 작업이 모두 끝난 시각, 외부 결과를 확인한 시각을 따로 비교한다. 이는 실제 측정 결과가 아닌 시험 방법의 제안이다.

정리 자체가 실패하는 경우도 시험할 필요가 있다. 외부 작업의 응답이 오지 않을 때 화면이 계속 정리 중이라고만 표시하면 담당자는 다음 행동을 결정하기 어렵다. 기다릴 기한과 확인할 대상, 사람이 이어받을 기록을 정해 두고 미확인 상태를 그대로 보여 주는 편이 낫다. 중지 직후 같은 업무를 다시 시작하는 시험은 이전 실행의 결과가 뒤늦게 새 결과를 덮는지도 드러낼 수 있다.

사람이 에이전트에 일을 맡기는 범위가 넓어질수록, 도중에 판단을 바꿀 수 있는 능력도 함께 커져야 한다. 잘 설계된 중지 버튼은 사용자의 요청을 접수하고, 남은 실행을 추적하며, 실제로 바뀐 세계를 확인할 때까지 책임의 경로를 유지한다. 버튼을 누른 뒤 무엇이 남았는지 설명할 수 있어야, 다음 일을 안심하고 맡길 수 있다.