deep-dive · 중급

장시간 실행 AI Agent의 재시작·보상·멱등성 설계

장시간 실행 Agent는 채팅 요청 하나가 아니라 영속 상태 머신으로 모델링하고, 각 외부 부작용에 안정적인 멱등성 키를 붙이며 완료된 단계의 결과와 체크포인트를 저장해야 한다. 일시 오류만 제한 재시도하고 취소는 협력적으로 전파하며, 되돌릴 수 없는 단계 전에는 승인하고 부분 완료는 도메인별 보상과 수동 복구 경로로 처리해야 한다.

30초 답변

장시간 실행 AI Agent는 긴 HTTP 요청이 아니라 영속 상태 머신으로 설계해야 한다. 요청을 접수하면 즉시 run_id와 상태 조회 URL을 반환하고, 검색·모델 호출·도구 실행·승인 대기를 각각 재개 가능한 단계로 나눈다. 완료된 단계의 결과와 외부 시스템의 작업 ID를 저장해 장애 후 다시 계산하지 않으며, 부작용 있는 호출에는 논리적 작업에서 파생한 동일한 멱등성 키를 재시도마다 사용한다.

일시 오류만 제한적으로 재시도하고, 업무 오류·권한 오류·안전 거절은 보류나 사람 검토로 보낸다. 취소는 실행기와 하위 작업에 협력적으로 전파하고, 이미 발생한 예약·전송·결제는 단순 DB rollback이 아니라 도메인별 보상 작업으로 다룬다. 사용자에게는 진행 중, 승인 대기, 취소 중, 부분 완료, 복구 필요를 구분해 보여준다.

이 글의 핵심

  • 채팅 세션과 업무 실행 상태는 다르다. 재개할 상태는 대화 요약이 아니라 영속 워크플로에 둔다.
  • “정확히 한 번 실행”을 기대하지 말고, 중복 실행돼도 결과가 하나가 되도록 멱등성을 만든다.
  • 체크포인트는 완료된 업무 단계와 외부 부작용의 영수증을 저장하는 지점이다.
  • 재시도, 재개, 취소, 보상은 서로 다른 상태 전이다.
  • 모델 응답도 완료된 활동 결과로 저장해 replay 때 무심코 다시 생성하지 않는다.

왜 일반적인 요청-응답 구조가 무너지는가

AI Agent는 한 요청 안에서 검색, 여러 모델 호출, 파일 처리, 외부 API, 사람 승인을 거칠 수 있다. 실행 시간이 길어지면 다음은 예외가 아니라 정상 조건이 된다.

  • 브라우저가 닫히거나 모바일 네트워크가 바뀐다.
  • 서버 배포와 worker 재시작이 일어난다.
  • 모델 공급자 호출이 timeout됐지만 실제 처리는 끝났을 수 있다.
  • 외부 API는 성공했는데 성공 응답이 유실된다.
  • 사람 승인이 몇 시간 뒤 도착한다.
  • 사용자가 중간에 취소한다.
  • 일부 단계만 완료돼 원상 복구가 불가능하다.
  • 프롬프트·모델·workflow 코드가 실행 중에 새 버전으로 배포된다.

이 상황을 메모리 속 current_step 하나로 처리하면 worker가 죽을 때 상태가 사라진다. 전체 함수를 처음부터 다시 돌리면 중복 메일, 중복 티켓, 중복 결제가 생긴다.

Temporal Workflow Execution 문서는 workflow의 이벤트 이력을 영속화하고 실패 후 마지막 기록 지점에서 replay해 진행을 재개하는 durable execution 모델을 설명한다. 특정 제품을 쓰든 직접 구현하든 필요한 성질은 같다. 진행 상태가 프로세스 수명과 분리되고, 무엇이 완료됐는지 재현 가능해야 한다.

동기 API 대신 작업 리소스를 반환한다

권장 외부 API는 다음과 같다.

POST   /agent-runs          → 202 Accepted + run_id + status_url
GET    /agent-runs/{id}     → 현재 상태·진행·결과 링크
DELETE /agent-runs/{id}     → 취소 요청 접수
POST   /agent-runs/{id}/approvals → 승인·거절

Azure Asynchronous Request-Reply 패턴은 긴 작업을 접수한 뒤 상태 endpoint를 제공하는 방식, 취소를 위한 상태 리소스, 중복 POST를 막는 idempotency key를 설명한다. 클라이언트는 최초 요청 전에 키를 생성하고 timeout 뒤에도 같은 키로 다시 요청한다. 서버는 새 작업을 만들지 않고 기존 run_id를 반환한다.

사용자가 동일 버튼을 두 번 누르는 것과 의도적으로 같은 업무를 두 번 실행하는 것은 다르다. 키 범위를 tenant + user + operation + client_request_id처럼 정의하고, 같은 키에 다른 payload가 오면 기존 결과를 재사용하지 말고 충돌로 처리한다.

상태 머신을 먼저 정의한다

ACCEPTED → RUNNING → WAITING_APPROVAL → RUNNING → SUCCEEDED
               ├→ RETRY_WAIT ───────────┘
               ├→ CANCEL_REQUESTED → CANCELLING → CANCELLED
               ├→ COMPENSATING → COMPENSATED
               ├→ PARTIALLY_COMPLETED
               └→ FAILED_NEEDS_REVIEW

상태명은 조직에 맞게 바꿔도 되지만 FAILED, CANCELLED, PARTIALLY_COMPLETED를 하나로 합치지 않는다. 사용자가 다시 실행해야 하는지, 일부 결과를 쓸 수 있는지, 담당자가 복구해야 하는지가 다르기 때문이다.

워크플로 레코드의 최소 필드는 다음과 같다.

구분필드 예시목적
식별run_id, tenant_id, business_key중복·테넌트 경계
소유권requester, effective_principal, approver권한·감사
상태status, current_step, state_version동시 변경·재개
계약workflow_version, prompt_version, policy_version실행 재현·업그레이드
시간created_at, deadline, next_retry_at, completed_attimeout·SLO
입력immutable input reference, input hash원본 추적·중복 비교
결과artifact IDs, output schema version큰 결과와 상태 분리
제어cancel_requested, point_of_no_return안전한 취소
관측trace_id, last_error_class진단

prompt 원문, 문서 전체, Tool 결과 전체를 상태 테이블에 무제한 복사하지 않는다. 암호화된 객체 저장소의 참조와 해시를 사용하고 데이터 분류·보존 정책을 적용한다.

멱등성: 여러 번 실행될 수 있지만 효과는 한 번만

분산 시스템에서는 “외부 API가 실행됐는가, 응답만 유실됐는가”를 호출자가 즉시 구분하지 못하는 구간이 생긴다. Temporal Activity의 멱등성 가이드는 Activity가 서버에 완료를 알리기 직전 worker가 죽으면 재시도될 수 있으므로 쓰기 작업을 멱등하게 만들 것을 권한다. Activity가 관측상 한 번 완료돼도 실제 함수는 여러 번 또는 부분적으로 실행될 수 있다.

안전한 패턴은 다음과 같다.

  1. 논리적 부작용마다 안정적인 operation_id를 만든다.
  2. 모든 재시도에 같은 ID와 같은 정규화 payload를 보낸다.
  3. 수신 서비스는 ID를 unique key로 저장한다.
  4. 이미 처리했다면 최초 결과 또는 현재 리소스를 반환한다.
  5. 같은 ID에 다른 payload가 오면 충돌로 거절한다.
idempotency_key = hash(tenant_id + run_id + step_id + business_action)

무작위 키를 재시도할 때마다 새로 만들면 중복 방지가 되지 않는다. 반대로 서로 다른 업무에 같은 키를 재사용하면 정상 실행이 막힌다. 결제, 메일, 티켓 생성처럼 외부 API가 멱등성 키를 지원하지 않으면 자체 outbox·deduplication table·업무 고유키 또는 조회 후 생성 패턴을 둔다. “조회 후 없으면 생성”은 동시성 경쟁이 있으므로 unique constraint나 원자적 조건부 쓰기가 필요하다.

체크포인트: 대화가 아니라 완료된 업무 사실을 저장한다

체크포인트는 임의의 코드 줄마다 찍는 snapshot이 아니다. 다시 시작해도 이미 완료된 부작용을 반복하지 않을 안정적인 업무 경계다.

예를 들어 보고서 생성 workflow는 다음을 저장할 수 있다.

단계체크포인트에 저장할 것재개 시 행동
입력 확정입력 파일 ID·해시·권한 snapshot원본 변경 여부 확인
추출 완료구조화 결과 artifact ID·schema version모델 재호출 없이 검증부터
검증 완료validator 결과·정책 버전승인 단계로 이동
승인 완료approver·payload hash·만료같은 payload만 실행
외부 전송 완료provider message ID·수신자·시간재전송하지 않고 영수증 조회

모델 호출도 외부 Activity다. 완료된 응답을 artifact로 저장하고 hash·모델·prompt version을 연결한다. replay 때 모델을 다시 호출하면 비결정적 결과와 추가 비용이 생기고 이후 경로도 달라질 수 있다. 재생이 필요한 경우에는 기존 결과를 읽고, 의도적인 재생성은 새 attempt나 새 workflow로 기록한다.

큰 파일 처리처럼 단계 내부가 오래 걸리면 진행 cursor, 완료한 chunk ID, 부분 artifact를 heartbeat/checkpoint로 남긴다. 단, 부분 결과를 최종 성공처럼 노출하지 않고 원자적으로 publish하는 단계를 둔다.

재시도: 오류 분류와 예산이 먼저다

오류 종류재시도다음 상태
연결 단절·일시적 5xx·rate limit지수 백오프·지터·상한 내 재시도RETRY_WAIT
인증·권한 오류자동 재시도 금지FAILED_NEEDS_REVIEW
입력·스키마·업무 규칙 오류입력 수정 또는 사람 검토WAITING_INPUT/REVIEW
모델 안전 거절정책상 안내·업무 재설계REFUSED/REVIEW
deadline 초과남은 작업 중지, 필요 시 보상TIMED_OUT
중복·버전 충돌기존 결과 조회 또는 조정RUNNING/CONFLICT

재시도 횟수만 두지 말고 최대 경과 시간, 전체 deadline, 업무별 retry budget을 함께 둔다. Temporal Retry Policy 문서는 재시도 간격·계수·최대 간격·최대 시도 같은 정책 요소를 설명한다. 구체 수치는 외부 서비스의 공식 지침과 실제 오류 분포로 정한다.

workflow 내부와 SDK, Gateway, queue에서 동시에 재시도하면 폭주한다. 어느 계층이 최종 책임을 갖는지 정하고 attempt 번호와 오류 분류를 trace에 남긴다. 일시 오류가 아닌데 prompt를 바꾸지 않고 모델만 반복 호출하는 것은 복구가 아니라 비용 증가다.

취소: 요청, 중단, 정리, 보상을 구분한다

취소 버튼을 눌렀다고 이미 실행 중인 외부 작업이 즉시 사라지지는 않는다. 취소를 협력적 프로토콜로 설계한다.

  1. API는 CANCEL_REQUESTED를 영속화한다.
  2. worker는 단계 경계와 heartbeat에서 취소를 확인한다.
  3. 가능한 하위 모델·Tool 호출에 취소 신호를 전달한다.
  4. 새 단계와 재시도 예약을 막는다.
  5. 이미 완료된 부작용을 목록화한다.
  6. 정책에 따라 유지, 보상, 사람 판단 중 하나를 선택한다.
  7. 최종 상태와 남은 영향 범위를 사용자에게 알린다.

취소 가능한 지점과 point_of_no_return을 workflow에 표시한다. 예를 들어 메일 발송 전에는 취소할 수 있지만 발송 후에는 “메일을 없애는” 것이 아니라 정정 메일 같은 별도 보상만 가능하다. 결제·배포·외부 공개처럼 영향이 큰 단계는 직전에 승인과 재검증을 배치한다.

보상: rollback이 아니라 업무적으로 되돌리는 새 작업

Azure Compensating Transaction 패턴은 여러 시스템에 걸친 eventually consistent 작업이 실패했을 때, 완료한 단계의 효과를 도메인별 규칙으로 되돌리는 workflow를 설명한다. 보상 자체도 실패하고 재시도될 수 있으므로 진행을 기록하고 보상 작업도 멱등하게 만들어야 한다.

완료된 작업가능한 보상완전 복구가 아닌 이유
캘린더 일정 생성같은 event ID 취소참석자가 이미 알림을 봤을 수 있음
티켓 발행상태를 취소로 변경티켓 번호와 감사 기록은 남음
결제 승인승인 취소·환불수수료·정산 시점·정책이 다름
파일 외부 공유링크 폐기·권한 회수이미 다운로드됐을 수 있음
이메일 발송정정·회수 요청수신 서버에서 삭제 보장 불가

모든 단계를 무조건 역순으로 되돌릴 필요도 없고, 되돌릴 수 없는 단계도 있다. 보상 함수, 필요한 원본 데이터, 실행 기한, 담당자, 수동 복구 절차를 사전에 정의한다. 애매하거나 영향이 큰 경우 자동 보상 전에 사람 판단을 둔다.

모델의 background 실행과 workflow 내구성은 같은 것이 아니다

OpenAI Background mode는 오래 걸리는 모델 응답을 비동기로 시작하고 상태를 polling하는 기능을 제공한다. 이는 클라이언트 연결 timeout을 줄이는 데 유용하다. 하지만 여러 공급자, 사내 DB, 승인, 메일 전송까지 포함한 업무 workflow의 멱등성·보상·취소·상태 전이를 대신하지는 않는다.

모델 provider의 background response ID를 workflow step의 외부 작업 ID로 저장하고 다음처럼 다룬다.

  • 시작 요청에는 workflow step과 연결된 고유키를 사용한다.
  • polling worker가 바뀌어도 같은 response ID를 조회한다.
  • 완료 결과를 저장한 뒤 다음 단계로 원자적으로 전이한다.
  • workflow 취소 시 provider 취소 가능 여부를 확인하고, 불가능하면 결과를 폐기 대상으로 표시한다.
  • provider 보존 정책과 사내 workflow 보존 정책을 별도로 확인한다.

관측성과 버전 변경

한 run 안에서 API, queue, worker, 모델, Tool이 같은 인과관계로 연결돼야 한다. OpenTelemetry Context Propagation은 trace context를 프로세스·네트워크 경계에 전달해 분산된 작업을 하나의 trace로 연결하는 방법을 설명한다. 비동기 메시지에는 traceparent뿐 아니라 run_id, step_id, attempt, idempotency_key hash를 민감하지 않은 형태로 연결한다.

진행 중 workflow에 새 코드를 바로 적용하면 과거 이벤트와 새 분기 로직이 맞지 않을 수 있다. workflow definition과 prompt·schema·policy 버전을 저장하고, 실행 중 인스턴스는 기존 버전으로 마치거나 명시적 migration을 거친다. replay test와 worker 교체·queue 중복·응답 유실·중간 취소를 포함한 장애 주입 시험을 배포 전에 실행한다.

사용자 경험과 실패 시나리오

완료 버튼을 두 번 눌렀더니 메일이 두 통 발송됐다

클라이언트 재요청과 외부 전송 단계에 안정적인 멱등성 키가 없었던 경우다. 최초 접수부터 같은 client request key로 기존 run을 반환하고, 메일 단계에는 run·step 기반 key와 provider message ID를 저장한다.

장애 후 재개했는데 보고서 내용이 바뀌었다

완료된 모델 호출을 replay하면서 다시 생성한 경우다. 모델 응답 artifact와 버전을 체크포인트에 저장하고 재개 시 재사용한다. 사용자가 최신 데이터로 다시 생성하기를 원하면 기존 run을 덮지 말고 새 run으로 시작한다.

취소했는데 결제는 이미 처리됐다

취소 요청과 실행 완료가 경쟁했을 수 있다. 상태를 “취소됨”으로 즉시 표시하지 말고 취소 요청됨 → 정리 중 → 취소됨/부분 완료로 보여준다. 결제 provider의 거래 ID를 조회해 실제 상태를 확인한 뒤 멱등한 취소·환불 보상으로 이동한다.

작업이 승인 대기인지 멈춘 것인지 알 수 없다

내부 current step만 저장하고 사용자 상태를 설계하지 않은 경우다. 승인 필요, 승인 대상·기한·영향, 마지막 진행 시간, 취소 가능 여부, 예상 다음 단계를 보여준다. 기술적 retry 대기는 사용자에게 오류 폭주 로그가 아니라 “일시 지연, 자동 재시도 중”으로 설명한다.

구현 체크리스트

  • 긴 작업을 동기 HTTP 연결이 아닌 작업 리소스로 모델링했는가?
  • 최초 POST에 클라이언트 생성 idempotency key를 받는가?
  • 상태 머신에 승인 대기·재시도 대기·취소 중·부분 완료가 구분되는가?
  • workflow, prompt, schema, policy 버전을 실행과 함께 저장하는가?
  • 각 외부 부작용에 안정적인 멱등성 키와 unique constraint가 있는가?
  • 같은 키에 다른 payload가 오면 충돌로 처리하는가?
  • 완료된 모델·Tool 결과와 외부 작업 ID를 체크포인트로 저장하는가?
  • replay가 완료된 모델 호출과 부작용을 다시 실행하지 않는가?
  • 일시 오류와 영구·업무 오류의 재시도 정책이 분리됐는가?
  • SDK·Gateway·workflow의 중복 재시도를 제거했는가?
  • 취소 신호를 단계 경계·heartbeat·하위 작업에 전파하는가?
  • 되돌릴 수 없는 단계와 point of no return이 표시됐는가?
  • 보상 동작도 멱등하고 실패 후 재개 가능한가?
  • run·step·attempt를 trace로 연결하고 민감정보를 제외했는가?
  • worker crash, 응답 유실, 중복 메시지, 취소 경쟁, 보상 실패를 시험했는가?

자주 묻는 질문

Q1. queue에 넣으면 Durable Execution이 완성되나?

아니다. queue는 작업 전달과 재전송을 돕지만 업무 상태, 완료 단계, 멱등성, 승인, 취소, 보상까지 자동으로 정의하지 않는다. 메시지는 중복 전달될 수 있다고 가정하고 소비자를 멱등하게 만들며, workflow 상태와 step ledger를 영속화해야 한다.

Q2. 데이터베이스 transaction으로 전체 Agent 실행을 묶으면 안 되나?

모델 API, 메일, 결제, 사람 승인처럼 긴 외부 작업을 하나의 DB transaction으로 묶을 수 없고 그렇게 하면 lock과 장애 범위가 커진다. 짧은 로컬 상태 전이는 transaction으로 보호하고, 시스템 간 일관성은 outbox, 멱등성, 상태 머신, 보상으로 관리한다.

Q3. exactly-once를 지원하는 workflow 엔진이면 멱등성 키가 필요 없나?

필요하다. workflow 엔진이 이벤트 처리와 관측 가능한 완료를 강하게 보장해도, 외부 서비스가 작업을 수행한 직후 응답이 유실되는 경계는 남는다. 외부 부작용은 여러 번 호출될 수 있다고 보고 수신 측 deduplication이나 업무 고유키를 둔다.

근거 자료