AI 에이전트가 같은 일을 두 번 실행했다면? 외부 행동을 묶는 7칸 action ID 계약

읽는 시간 약 9분 · 외부 API를 실행하는 AI 에이전트 운영 카드

독자의 질문: 에이전트가 결제·배포·티켓 생성 뒤 타임아웃, 재시작, 웹훅 중복을 만났을 때 같은 일을 다시 실행해도 될까요?

짧은 답: 성공 여부가 불명확할 때는 새 요청을 만들지 말고, 처음 승인한 action ID로 결과를 조회해야 합니다. 그 ID를 업무 상태, 메시지 전달, 소비자 중복 차단, 감사 증거까지 연결해 두면 재시도는 ‘다시 실행’이 아니라 ‘같은 의도를 확인하는 과정’이 됩니다.

핵심 요약

  • 한 번만 실행은 에이전트 전체에 자동으로 생기는 약속이 아닙니다. 요청자, 큐, 소비자, 실제 업무 시스템이 각자 다른 실패 경계를 가집니다.
  • 외부 변경에는 승인된 action ID, 변경 내용을 대표하는 intent hash, 승인 버전, 업무 상태를 함께 남기세요.
  • 메시지 relay가 같은 이벤트를 다시 보낼 수 있으므로, 소비자도 독립적으로 중복을 막아야 합니다.
  • 응답이 사라졌다면 재호출보다 동일 action ID 조회가 먼저입니다.
  • 결과 조회 경로·중복 차단·보상 책임자 중 하나라도 없다면 자동 재시도를 멈추고 검토 큐로 넘기세요.

목차

  1. 성공 응답 하나로 안전을 판단할 수 없는 이유
  2. 외부 행동을 묶는 7칸 action ID 계약
  3. 응답 유실 배포 요청을 처리하는 예시
  4. 실패 주입 4종과 금지 동작
  5. 자동 재시도를 멈출 중지선
  6. 실행 전·후 체크리스트와 FAQ
계획과 승인부터 action ID, outbox, 소비자 처리, 결과 기록까지 이어지는 외부 행동 중복 방지 흐름도
모델의 계획과 실제 외부 변경 사이에 업무용 식별자와 조회 경로를 둡니다.

성공 응답 하나로 안전을 판단할 수 없는 이유

에이전트가 “배포가 완료되었습니다”라고 답했는데, 바로 뒤에 네트워크가 끊겼다고 가정해 보겠습니다. 배포 API가 실제로는 성공했는지, 요청이 도달하지 않았는지, 성공한 뒤 응답만 사라졌는지는 그 문장만으로 알 수 없습니다. 이때 새 요청을 보내면 같은 릴리스를 두 번 실행하거나, 같은 고객에게 두 번 메시지를 보낼 수 있습니다.

중요한 구분은 모델이 세운 계획과 되돌리기 어려운 외부 변경입니다. 전자는 다시 만들 수 있지만, 후자는 업무 시스템에 남습니다. 따라서 외부 변경은 대화 문맥이나 실행 로그만으로 관리하지 말고, 시스템이 재시작되어도 찾을 수 있는 업무 식별자로 묶어야 합니다.

Transactional outbox 패턴은 업무 데이터 변경과 전송할 이벤트를 함께 기록하는 방법을 설명합니다. 다만 relay가 같은 메시지를 두 번 발행할 수 있으므로, 받는 쪽의 idempotent 처리까지 필요하다는 점이 핵심입니다.

FLOWIT 7칸 action ID 계약

아래 일곱 칸은 특정 공급자 기능이 아니라, 결제·티켓·배포·고객 메시지에 공통으로 적용할 수 있는 최소 운영 계약입니다. 모든 칸이 거대한 시스템을 뜻하지는 않습니다. 작은 팀이라도 테이블 한 장과 조회 엔드포인트부터 시작할 수 있습니다.

action ID 계약을 구성하는 식별자, 범위, 변경 지문, 상태, outbox, 중복 차단, 감사 증거의 일곱 카드
일곱 칸은 ‘키 하나’가 아니라, 외부 변경을 다시 판단할 수 있게 만드는 기록 묶음입니다.
계약 칸 저장할 값 확인 질문 비어 있으면
1. action ID 승인 시 발급한 불변 ID 이번 요청은 이전 업무 의도와 같은가요? 새 외부 호출을 보류합니다.
2. actor·scope 요청 주체, 대상, 권한 범위 누가 무엇에 변경을 허용했나요? 권한 재검토로 보냅니다.
3. intent hash·승인 버전 정규화한 변경 내용의 지문과 승인본 대상·금액·환경이 처음 승인본과 같은가요? 새 승인을 받습니다.
4. 업무 상태 전이 준비·승인·요청·확인·보상 상태 지금 어느 단계에서 멈췄나요? 자동 재시도하지 않습니다.
5. outbox·event ID 업무 기록과 연결된 전달 이벤트 무엇을 언제 전달해야 하나요? 발행 근거를 먼저 복구합니다.
6. consumer 중복 차단 처리한 event/action ID와 결과 받는 시스템이 이미 적용했나요? 소비자 조회 전까지 보류합니다.
7. 재조회·보상·감사 증거 provider 조회 경로, 담당자, 영수 기록 불명확한 결과를 어떻게 판정하나요? 사람 검토로 넘깁니다.

Google Cloud의 transactional messaging 소개는 agentic workload에서도 데이터 변경과 메시지 전달의 경계를 다룰 필요가 있음을 보여 줍니다. 다만 이 글의 계약은 특정 제품 도입을 권하는 것이 아니라, 어떤 런타임과 큐를 쓰더라도 업무 의도를 조회 가능하게 남기자는 운영 원칙입니다.

예시: 승인된 배포 요청의 응답이 사라졌다면

운영자가 서비스 A의 릴리스 R을 운영 환경에 배포하도록 승인했습니다. 시스템은 act_demo_7f3a를 발급하고, 대상 환경·릴리스 식별자·승인본으로 만든 intent hash를 저장합니다. 이어서 업무 상태를 approved에서 requested로 바꾸고, 같은 변경 안에서 outbox 이벤트를 기록합니다.

배포 provider가 성공을 반환한 직후 응답 연결이 끊겼다면 상태는 outcome_unknown이 됩니다. 이때 금지할 행동은 “에러였으니 다시 배포”입니다. 대신 provider의 조회 경로에 act_demo_7f3a 또는 매핑된 요청 ID를 전달해 결과를 찾습니다. 결과가 확인되면 receipt를 남기고 confirmed로 전이합니다. 조회 자체가 불가능하거나 보상 담당자가 없다면, 자동화는 멈추고 사건 패킷을 사람에게 넘깁니다.

실패 주입 4종: 기대 동작과 금지 동작

응답 유실과 중복 웹훅, 승인 변경 상황에서 조회 또는 사람 검토로 갈라지는 외부 행동 판단 흐름
불명확한 결과는 재호출 버튼이 아니라 조회와 보류의 분기점입니다.
주입할 실패 기대 동작 금지 동작
외부 API 성공 뒤 응답 유실 같은 action ID로 provider 결과를 조회하고 상태를 확정합니다. 새 action ID로 즉시 재호출합니다.
relay 발행 뒤 프로세스 종료 outbox 이벤트와 consumer 기록을 대조해 재발행을 안전하게 처리합니다. 발행 횟수만 보고 성공으로 처리합니다.
같은 webhook 두 번 수신 event ID와 처리 결과를 저장해 두 번째 처리를 반환하거나 무시합니다. 수신 시각만 보고 새 업무로 만듭니다.
승인 뒤 대상·금액·환경 변경 intent hash와 승인 버전을 비교해 새 승인으로 돌립니다. 기존 action ID를 변경된 요청에 재사용합니다.

이 네 장면은 테스트 환경에서 의도적으로 재현해 보세요. 네트워크 응답을 끊고, relay를 발행 직후 종료하고, 같은 이벤트를 두 번 보내고, 승인본 뒤의 입력을 한 글자 바꿔 보시면 됩니다. 테스트의 합격 기준은 “오류가 없었다”가 아니라, 어떤 경우에도 외부 결과가 두 번 생기지 않고 불명확한 경우가 검토 가능한 상태로 남았는지입니다.

자동 재시도를 멈출 중지선

다음 네 가지 중 하나라도 빠지면 자동 재시도 권한을 주지 않는 편이 안전합니다.

  1. 처음 승인한 action ID가 없습니다.
  2. provider 또는 업무 시스템에서 결과를 조회할 경로가 없습니다.
  3. consumer가 같은 이벤트를 다시 처리하지 않도록 막지 못합니다.
  4. 되돌릴 수 없을 때 누가 어떤 증거로 보상 결정을 할지 정해지지 않았습니다.

검토 큐로 넘기는 사건 패킷에는 action ID, 요청 주체와 범위, intent hash, 승인본, 마지막 상태 전이, outbox/event ID, provider 조회 응답, 이미 관찰된 외부 결과를 담으세요. 이렇게 하면 다음 담당자가 대화 기록을 추측하지 않고 같은 사실에서 판단할 수 있습니다.

에이전트 런타임의 세션과 복구 기능은 유용하지만, 업무 시스템의 중복 방지 책임까지 대신하지는 않습니다. OpenAI Agents API 공식 가이드처럼 런타임이 제공하는 상태 관리와, 실제 외부 시스템이 받아들이는 업무 식별자를 분리해 설계하세요.

외부 행동 전·후 체크리스트

호출 전

  • 승인 시점에 새 action ID를 발급했나요?
  • 대상·변경 내용·권한 범위를 정규화해 intent hash와 승인 버전을 남겼나요?
  • 업무 상태와 outbox 기록을 연결했나요?
  • 같은 ID로 provider 결과를 조회할 수 있나요?

호출 후

  • consumer가 event/action ID를 기준으로 중복 처리를 막나요?
  • 성공·실패·불명확 상태를 구분해 기록했나요?
  • 불명확 상태에서 재호출보다 조회를 먼저 했나요?
  • 보상 불가 또는 승인 불일치 사건을 검토 큐로 넘겼나요?

결과가 없거나 누락됐는지를 사후에 찾는 방법은 business receipt 카드에서, 쓰기 작업의 재시도를 어디서 멈출지는 재시도 중단 카드에서 이어서 확인하실 수 있습니다. 모델의 계획과 결정적 실행을 분리하는 기준은 추론·결정적 함수 경계, 실패 사례를 다음 검증으로 바꾸는 방법은 trace→eval 원장에 정리했습니다.

자주 묻는 질문

idempotency key 하나만 있으면 충분한가요?

아닙니다. 키는 같은 요청을 구분하는 출발점일 뿐입니다. 누가 승인했는지, 변경 내용이 같은지, 어느 상태인지, 결과를 어디서 조회하는지, 보상이 가능한지는 별도로 남겨야 합니다.

SQS FIFO를 쓰면 전체 흐름이 한 번만 처리되나요?

아닙니다. FIFO의 중복 제거는 해당 큐와 설정 범위의 기능입니다. 소비자, 외부 provider, 업무 데이터 변경은 각자 중복 방지와 조회 규칙을 가져야 합니다.

응답이 없으면 같은 action ID로 다시 호출해도 되나요?

먼저 그 ID로 결과 조회가 가능한지 확인하세요. provider가 같은 ID 재호출을 명시적으로 안전하게 처리한다고 문서화했더라도, 승인본과 intent hash가 일치하는지 확인한 뒤에만 진행해야 합니다.

웹훅 이벤트 ID가 action ID를 대신할 수 있나요?

대신하기보다 연결해야 합니다. 웹훅 이벤트 ID는 전달 중복을 가르는 데 쓰고, action ID는 처음 승인된 업무 의도를 가르는 데 씁니다.

작은 내부 자동화에도 이 구조가 필요한가요?

되돌리기 어렵거나 사람에게 영향을 주는 변경이라면 최소한 action ID, 상태, 결과 조회 경로부터 두는 편이 좋습니다. 단순 읽기 작업은 더 가볍게 시작할 수 있습니다.

참고 자료

이 글이 마음에 드세요?

RSS 피드를 구독하세요!

FLOWIT on YouTube

영상으로도 FLOWIT을 이어서 보세요

AI 자동화, Claude Code, n8n, 데이터 분석 흐름을 블로그와 영상으로 함께 정리하고 있습니다.

YouTube 채널 보기