시간 제한을 나타내는 중앙 타이머와 앞뒤 작업 블록을 연결한 AI Agent 파이프라인 일러스트

AI Agent 파이프라인 timeout 예산 설계: Planner 900초 상한과 단계별 실패 격리

Planner가 3600.104초 뒤 timeout됐다는 기존 관측 요약은 AI Agent 파이프라인 timeout 예산 설계를 단계 설정 문제가 아니라 전체 완료 문제로 바꾼다. Planner가 전역 3600초를 거의 모두 사용하면 그 단계가 마지막에 성공하더라도 Researcher, Writer, Reviewer, Publisher가 실행될 시간이 없다. 개별 Agent의 성공보다 승인 가능한 최종 산출물이 deadline 안에 나오는지가 먼저다.

이 글은 2026년 8월 15일까지 확인한 gRPC, Google SRE, AWS, Python, Kubernetes 공식 자료를 기존 관측 요약과 대조한 개념 설계다. 원 timeout 로그와 구현 코드는 입력 범위 밖이라 직접 확인하거나 재현하지 않았고, Planner의 900초 상한도 품질이 검증된 표준값이 아니다. 여기서는 이 숫자를 채택하기 전에 시간 장부, 상태 전이, SLO와 실패 주입을 어떻게 연결할지 정한다.

20초 핵심 요약

  • 무엇: 전역 hard deadline 안에서 Planner 단계 전체에 900초 상한을 두고, 남은 시간에서 후속 단계와 종료 여유를 먼저 보호하는 설계다.
  • 왜: Planner 하나가 3600초를 소진하면 후속 단계가 실행되지 못하고, 부분 산출물이나 무제한 재시도가 전체 run의 실패 비용을 키운다.
  • 어떻게: 전체 잔액 → 후속 예약 차감 → stage·attempt 예산 제한 → 산출물 gate → 상태별 중단으로 구현하고 지연 분포와 실패 주입으로 900초를 검증한다.

늦게 성공한 Planner도 전체 run에는 실패일 수 있다

순차 파이프라인에서 단계별 성공 여부만 보면 중요한 오류가 가려진다. Planner가 3590초에 결과를 내고 나머지 필수 단계에 10초만 남겼다면 Planner=SUCCEEDED여도 end-to-end 결과는 사실상 실패다. timeout을 한 작업의 최대 지속 시간, deadline을 결과를 더 기다리지 않을 시각으로 구분해야 하는 이유다.

Google SRE의 연쇄 실패 지침은 각 단계에 들어가기 전에 남은 deadline을 확인하고 상위 제한을 하위 호출에 전파하라고 설명한다. gRPC deadline 문서도 이미 흐른 시간을 차감해 다음 호출에 전달하는 방식을 다룬다. 이 원리를 Agent 파이프라인에 옮기면 각 단계 timeout의 합만 맞추는 설계로는 부족하다. 큐 대기, 재시도 backoff, 산출물 검증, 실행 종료, Publisher의 안전한 마감 시간도 같은 장부에서 빠져나간다.

이 글의 범위는 전체 stage budget이다. 단일 도구 호출의 프로세스 그룹 정리, LLM 출력 품질 평가 기준, 모든 입력에 통하는 900초 튜닝 값은 각각 별도 문제로 남긴다.

시간은 나눠 놓은 칸이 아니라 계속 줄어드는 잔액이다

전역 예산을 B_total, 파이프라인 시작의 monotonic clock 값을 t0, 현재 값을 t, 종료 여유를 G, 아직 시작하지 않은 필수 단계의 예약 시간을 R_future라고 두면 현재 단계가 쓸 수 있는 시간은 다음처럼 표현할 수 있다.

remaining_global(t) = B_total - (t - t0)
spendable_now(t)    = remaining_global(t) - R_future - G
attempt_budget      = min(stage_remaining, spendable_now, provider_cap)

attempt_budget <= 0이면 호출부터 시작한 뒤 timeout을 기다릴 일이 아니다. 비용이 발생하기 전에 BUDGET_EXHAUSTED로 끝낸다. 전역 deadline은 파이프라인 전체를 멈추는 최후의 안전망이고, stage budget은 한 단계가 입힐 수 있는 최대 손실이며, attempt budget은 지금 한 번의 호출에 실제로 내줄 잔액이다.

900초도 per_attempt_timeout이 아니라 Planner 단계의 모든 시도와 backoff가 공유하는 stage_budget 후보로 해석해야 한다.

planner_stage_budget = 900s
attempt_1 <= min(900s, spendable_now)
attempt_2 <= min(900s - attempt_1_elapsed - backoff, spendable_now)

두 번째 시도에서 다시 900초를 새로 지급하면 단계 상한은 이름만 남는다. Kubernetes Job의 activeDeadlineSeconds가 재시도 횟수와 무관하게 Job 전체 활성 시간을 제한하고 backoffLimit보다 우선한다는 점은 이 차이를 이해하는 데 유용한 대조군이다. Kubernetes를 도입하라는 뜻이 아니라, 재시도마다 시계를 초기화하지 않는 책임 모델을 참고한다는 뜻이다.

Coordinator는 시간뿐 아니라 산출물 상태를 전달해야 한다

Run coordinator가 관리할 최소 상태는 시간 필드와 산출물 필드로 나뉜다.

run_id
global_deadline_at
stage_name
stage_deadline_at
attempt_number
attempt_started_at / attempt_finished_at
remaining_global_seconds
remaining_stage_seconds
reserved_downstream_seconds
retry_backoff_seconds
termination_grace_seconds
status
failure_class
artifact_state

artifact_state는 적어도 NONE, PARTIAL_UNVERIFIED, VALIDATED를 구분해야 한다. timeout은 아무 일도 없었다는 뜻이 아니다. 파일 일부가 쓰였거나 원격 작업이 계속 중일 수 있다. AWS의 멱등 API 설계가 timeout 뒤에도 부작용이 이미 발생했을 가능성을 강조하는 이유다.

정상 흐름에서는 coordinator가 각 단계에 입력과 deadline_at을 전달한다. 단계는 산출물 참조, 종료 상태, 소비 시간, retry 가능 여부를 반환한다. Artifact gate가 스키마와 완결성을 확인해 VALIDATED로 바꾼 결과만 다음 단계가 받을 수 있다.

Cron scheduler
  -> Run coordinator: global deadline 생성
  -> Planner: 입력 + deadline 전달
  -> Artifact gate: 상태 + 검증 결과 반환
  -> Researcher -> gate
  -> Writer -> gate
  -> Reviewer: 승인 상태 반환
  -> Publisher: 승인된 결과만 외부 변경
  -> Run coordinator: terminal state 기록

여기서 화살표는 모두 같은 호출이 아니다. Coordinator에서 단계로 가는 화살표는 입력과 deadline 전파이고, 단계에서 gate로 가는 화살표는 산출물과 실행 상태 반환이다. Reviewer에서 Publisher로 가는 화살표는 외부 변경 권한의 승인이다. Publisher 이전 단계가 외부 시스템을 변경하지 않으면 timeout 재시도가 중복 발행으로 번질 면적도 줄어든다.

전역 잔액에서 Planner 예산을 계산하고 검증된 산출물만 후속 단계로 보내는 흐름도

Planner timeout은 부분 산출물과 의존 단계를 함께 격리한다

Planner의 local deadline이 지나면 TIMED_OUT만 기록하고 곧바로 다음 단계로 넘어가서는 안 된다. 취소를 요청하고 실제 종료를 확인한 뒤, 남은 산출물을 PARTIAL_UNVERIFIED로 격리해야 한다. gRPC cancellation 문서도 취소가 전달된 뒤 애플리케이션이 자신이 시작한 작업을 멈출 책임이 있다고 설명한다.

Python에서도 API에 따라 의미가 다르다. subprocess.run(timeout=...)은 timeout 시 자식을 종료하고 기다린 뒤 TimeoutExpired를 다시 발생시키지만, Popen.communicate(timeout=...)은 자동으로 자식을 죽이지 않는다. 따라서 timeout 예외 수신하위 실행 종료 확인은 별도 상태여야 한다. timeout 판단부터 모든 관련 실행이 멈출 때까지의 termination_lag_seconds도 측정해야 명목 900초와 실제 wall-clock 상한의 차이를 알 수 있다.

Planner 결과가 Researcher의 필수 입력이라면 실패 전파는 다음처럼 진행한다.

Planner attempt
  -> local deadline 초과
  -> 취소 요청 및 종료 확인
  -> PARTIAL_UNVERIFIED 격리
  -> transient + retry budget 있음: backoff 후 제한 재시도
  -> permanent 또는 예산 부족: Planner terminal state 기록
  -> Researcher/Writer: SKIPPED_DEPENDENCY
  -> 독립적인 정리·진단만 남은 예산 안에서 실행
  -> Publisher: 실행하지 않음

이전 run이나 다른 topic의 산출물을 자동 fallback으로 가져오면 안 된다. 현재 run의 실패를 성공처럼 보이게 할 뿐 아니라 freshness와 격리 조건을 깨뜨린다. 선택적 enrichment는 SKIPPED_BUDGET으로 생략할 수 있지만 필수 품질 gate와 Publisher 전 승인은 생략 대상이 아니다.

3600초 전역 제한과 900초 단계 상한은 경쟁하지 않는다

두 제한은 서로 대체하는 옵션이 아니다. 전역 hard deadline은 전체 run의 끝을 정하고, Planner local cap은 그 안에서 한 단계의 최대 손실을 제한한다. 후속 단계 숫자는 아직 측정되지 않았으므로 아래 비교에서 2700초는 종료 지연을 제외한 명목 잔액일 뿐 운영 보장이 아니다.

판단 항목 전역 timeout 3600초만 사용 전역 3600초 + Planner 단계 전체 900초
Planner 단일 단계의 최대 명목 시간 최대 3600초 최대 900초 또는 더 작은 전역 가용 잔액
Planner timeout 뒤 명목 잔액 거의 0초일 수 있음 종료 지연 전 약 2700초
후속 단계 예약 보호 없음 시작 전에 차감할 때 가능
부분 산출물 처리 gate가 없으면 정상 입력으로 섞일 수 있음 PARTIAL_UNVERIFIED 격리 필요
재시도 시간 전역 시간을 독점할 수 있음 하나의 stage retry budget을 공유
주된 비용 늦은 실패와 후속 실행 상실 정상 장꼬리 요청의 false timeout 가능성

전역 timeout 하나만 두는 안은 구현이 단순하지만 이번 관측처럼 한 단계가 전체 시간을 소진할 수 있는 구조에는 맞지 않는다. 고정된 단계별 timeout만 두는 안도 backoff, 종료 지연, 이미 소비한 전역 시간을 반영하지 못한다. 입력 크기와 최근 지연 분포로 동적 배분하는 안은 장기 후보지만, 데이터가 적을 때 정상 작업을 잘못 자를 수 있다. 우선은 전역 deadline + 고정 stage cap + 후속 예약 + 제한된 retry로 telemetry를 쌓는 편이 판단할 근거를 더 잘 남긴다.

재시도는 횟수가 아니라 오류·시간·멱등성의 공동 결정이다

실행기가 오류를 분류하지 않으면 모델이나 각 라이브러리가 제각각 재시도해 호출 수가 불어난다. Google SRE가 든 예처럼 세 계층이 각각 네 번 시도하면 최하위 호출은 4^3=64번까지 늘 수 있다. 재시도 책임을 orchestration 계층 한 곳에 두고, 횟수와 backoff뿐 아니라 run 단위 retry quota를 함께 제한해야 한다.

실패 조건 기본 처리
명시적 rate limit, 일시적 5xx, 짧은 네트워크 단절 남은 stage·run budget이 있을 때만 jitter backoff 후 제한 재시도
잘못된 입력, 권한 없음, 스키마 불일치 같은 입력으로 회복되지 않으므로 fail-fast
timeout 뒤 외부 부작용 상태 불명 상태 조회와 reconciliation 전 재시도 금지
남은 시간이 최소 유효 시도 시간보다 짧음 BUDGET_EXHAUSTED
같은 오류와 입력이 반복됨 retry 중단 후 진단 상태로 전환
품질 gate 실패 timeout retry와 분리해 원인별 수정 경로 사용

AWS SDK 재시도 동작의 지수 backoff, jitter, retry quota 원리는 참고할 수 있지만 특정 SDK 기본값을 HuntLab 설정으로 복사해서는 안 된다. timeout 뒤 재시도가 안전하려면 동일 operation id로 결과를 조회하거나 중복 실행을 흡수할 멱등성도 필요하다.

900초는 지연 분포와 실패 주입을 통과해야 채택할 수 있다

평균 latency 하나로는 정상 장꼬리와 멈춘 작업을 구분하기 어렵다. Google SRE의 SLO 지침처럼 end-to-end 결과와 여러 지연 백분위를 함께 봐야 한다. 현재 데이터가 없으므로 p95나 timeout 허용률 같은 숫자를 임의로 정할 수는 없다.

900초 상한을 평가할 SLI는 다음 질문에 답해야 한다.

  • end_to_end_completion_ratio: 전역 deadline 안에 승인 가능한 최종 산출물까지 도달했는가.
  • planner_latency_seconds: 입력 크기 구간별 p50, p95, p99는 어디에 놓이는가.
  • stage_timeout_ratio: 시작한 Planner 중 timeout된 비율은 얼마인가.
  • downstream_budget_preserved_seconds: Planner 종료 후 필수 후속 단계에 실제로 얼마가 남았는가.
  • retry_amplification: 최초 요청 한 건이 모델·도구 호출 몇 건으로 불어났는가.
  • termination_lag_seconds: timeout 판단 뒤 실제 종료까지 얼마나 걸렸는가.
  • partial_artifact_leak_ratio: 검증되지 않은 산출물이 후속 입력으로 들어갔는가. 목표는 0이다.
  • publisher_safety_violation: 승인·멱등성 조건 없이 외부 변경이 발생했는가. 목표는 0이다.

긴 상한은 후속 단계 시간과 compute·token을 소모하고 다음 Cron과 겹칠 위험을 키운다. 짧은 상한은 정상적인 긴 입력을 잘못 중단하고 이미 사용한 추론 비용을 버리며 재시도 비용을 늘릴 수 있다. 따라서 채택 실험에서는 정상 입력 회귀와 실패 격리를 같은 조건에서 본다.

  1. 대표 입력 크기별 정상 run의 목표 지연 백분위가 900초 안에 들어오는지 측정한다.
  2. hang, 느린 응답, rate limit을 주입해 900초와 허용된 종료 여유 안에 실제로 멈추는지 확인한다.
  3. Planner timeout 뒤 후속 예약 시간이 보존되고 의존 단계가 시작되지 않는지 확인한다.
  4. 여러 attempt와 backoff의 합이 Planner stage budget을 넘지 않는지 확인한다.
  5. 정상 run의 end-to-end 완료율과 산출물 품질이 기준선보다 허용 범위 이상 나빠지지 않는지 비교한다.

900초를 늘려야 할 수도 있다. 그 결정은 실패 한 건의 최댓값이 아니라 입력 크기별 지연 분포와 전체 완료 SLO에서 나와야 한다.

Cron 시작 기한과 run 실행 기한을 같은 설정으로 쓰지 않는다

Kubernetes CronJob의 startingDeadlineSeconds는 놓친 스케줄을 언제까지 시작할지 정한다. 반면 Job의 activeDeadlineSeconds는 이미 시작한 Job의 전체 활성 시간을 제한한다. 이 구분은 scheduler admission deadline과 run execution deadline이 서로 다른 책임임을 보여준다.

겹치는 Cron 실행 정책도 timeout budget과 별개다. 이전 run이 진행 중일 때 새 run을 허용하면 중복 작업이 생길 수 있고, 금지하면 새 스케줄을 놓칠 수 있으며, 교체하면 이전 run의 안전한 취소와 checkpoint가 필요하다. 어느 선택이든 run_id별 산출물 격리와 Publisher의 멱등성 키가 시간 제한을 보완해야 한다.

이 조건이 깨지면 900초 상한을 채택하지 않는다

HuntLab에서 먼저 검증할 구조는 전역 hard deadline + Planner 단계 전체 900초 cap + 후속 예약 + 종료 여유 + 재시도 한 계층이다. 다음 중 하나라도 나타나면 숫자만 적용한 것이지 실패 격리를 완성한 것이 아니다.

  • 정상 입력의 목표 지연 백분위가 900초를 자주 넘는데 입력 크기별 정책이나 fallback이 없다.
  • timeout 상태를 기록했지만 자식 프로세스나 원격 작업이 계속 실행된다.
  • retry 때 stage budget이 초기화되어 총 사용 시간이 900초를 넘는다.
  • Planner의 부분 산출물이 Researcher나 Writer의 정상 입력으로 들어간다.
  • 후속 예약과 종료 여유를 빼지 않아 Reviewer 또는 Publisher의 안전한 마감 시간이 사라진다.
  • 여러 계층의 retry 증폭을 측정할 수 없다.
  • 이전 run이나 다른 topic 산출물을 조용히 재사용한다.

900초 자체가 채택 기준은 아니다. 상한 안에서 실제 종료되고, 후속 예산이 보존되며, 정상 실행 회귀가 허용 범위 안이고, 부분 산출물 누출이 0일 때만 로컬 상한으로 유지할 수 있다. 데이터가 이를 지지하지 않으면 입력 크기별 budget이나 더 긴 cap을 검토하되 Planner가 전역 3600초를 다시 독점하도록 되돌리지는 않는다.

전체 stage budget을 정한 다음에는 AI Agent Evaluation Harness에서 실패 주입과 품질 gate를, AI Agent Tool Call Timeout에서 단일 호출 취소와 멱등성을 별도로 확인할 수 있다.

참고 링크

비슷한 글

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다