"상태 증명 (Proof-of-State)"의 함정: 에이전트가 재시작할 때마다 죽는 이유
요약
에이전트 시스템 재시작 시 발생하는 '상태 증명(Proof-of-State) 함정'에 대해 분석합니다. 식별자 무효화, PID 재사용, 상태 불일치 등으로 인해 에이전트가 영구적인 오류 상태에 빠지는 네 가지 주요 사례와 근본 원인을 다룹니다.
핵심 포인트
- 재시작 시 상태 생명주기와 식별자 유효성 불일치 주의
- PID 재사용으로 인한 락(lock) 오작동 위험성
- 재시작 전 상태가 현재 생존 여부로 오인되는 문제
- 일회성 업데이트 작업의 keepalive 설정 오류 방지
지난 몇 주 동안 저는 일련의 에이전트 충돌(crash) 사례들을 분석(autopsy)해 왔습니다. 모델의 환각(hallucination) 문제가 아니라, 단순하게 재시작 후 인프라가 스스로에게 거짓말을 하는 문제였습니다. 이 사례들은 서로 관련이 없어 보였지만, 사실 그렇지 않았습니다.
이들은 하나의 근본 원인을 공유합니다. 저는 이를 **"상태 증명 (Proof-of-State) 함정"**이라고 부릅니다. "X가 여전히 살아있는가 / 여전히 당신의 것인가 / 여전히 현재 인스턴스인가"라는 판단이, 재시작 후에는 무효화되는 식별자(identifier)에 의존할 경우, 이는 소리 없이 영구적인 거부(veto)로 변질됩니다.
저를 확신하게 만든 네 가지 사례는 다음과 같습니다.
사례 1 — 세션 핸들(Session handle)의 은퇴, 새로운 핸들은 반환되지 않음 (#113434)
상위(upstream)의 generation이 은퇴(retired)되었습니다. 서버는 이미 새로운 핸들로 교체(rotate)되었지만, 클라이언트는 여전히 이전 핸들을 보유하고 있었습니다. 증상: 의문의 400 에러 / "세션 무효(session invalid)". 근본 원인: 재시작 시 상태 생명주기(state lifecycle)가 일치하지 않았음. 이 진단은 이후 병합된 상위 PR(#114056 / #114401 / #114478)을 통해 독립적으로 확인되었습니다.
사례 2 — 컨테이너 재사용으로 인한 PID 락(lock) 오작동 (#114234)
"락(lock)이 유지되고 있는가?"를 확인하는 과정에서 PID 생존 여부(liveness)를 사용했습니다. 컨테이너 재빌드 후 PID가 재사용되면서, 락이 "영구적으로 살아있는" 것처럼 보였고 캐시(cache)가 얼어붙었습니다. 근본 원인: 락 유효성에 대한 증명이 재시작 시 만료되는 식별자였음.
사례 3 — 게이트웨이(Gateway) 재시작 후 세션이 running 상태로 남음 (#114255)
게이트웨이 재시작 후, 세션의 상태 문자열(status string)이 running 상태에 멈춰 있었습니다. 이로 인해 하위(downstream) 코드는 "여전히 진행 중"이라고 결론짓고 영원히 차단(block)되었습니다. 근본 원인: 재시작 전의 상태가 현재의 생존 여부(liveness)로 취급됨.
사례 4 — LaunchAgent의 일회성 업데이트가 영구적인 재시작 루프가 됨 (#115326)
이것은 패턴을 부인할 수 없게 만든 사례입니다. 한 커뮤니티 개발자(robingutsche)는 자신의 '밤에 충돌하고 재시작하면 고쳐지지만 다시 충돌하는' 루프가 keepalive가 설정된 launchctl submit -l com.openclaw.beta-update-once 작업 때문임을 추적했습니다. 따라서 일회성 업데이트가 EACCES 오류로 실패하자, launchd는 이를 영원히 재시작했고, 관리되는 게이트웨이를 반복적으로 종료시키고 차단기(breaker)를 작동시켰습니다. 그는 또한 이 차단기가 절대 자동 해제되지 않으며, 채널이 실제로 not-running 상태일 때도 status --all은 OK로 보고한다는 것을 발견했습니다.
그는 이를 유지보수자 수준의 재현(reproduction)으로 작성했습니다. 저는 이를 세 가지 독립적인 버그로 정리했습니다:
- A (업데이트 오케스트레이션): 일회성 업데이트 작업은
keepalive를 포함해서는 안 되며, 실패할 경우 자신의 launchd 작업을 정리해야 합니다. - B (차단기 해제 불가): #114255와 같은 계열의 문제로, 재시작 이전에 기록된 상태가 현재 활성 상태(liveness)로 간주됩니다.
- C (상태 오보고): 유효한 설정(config) ≠ 건강한 런타임(runtime)입니다 (어떤 필드에 잘못된 종류의 증명(proof)을 로드했던 #99725와 같은 계열).
불변성 (The invariant)
네 가지 모두 하나의 실행 가능한 규칙으로 수렴됩니다:
상태 증명(Proof-of-state)은 인카네이션(incarnation, 구현체)에 묶여 있어야 합니다. PID, 오래된 핸들, 재시작 전의 상태 문자열, 오래된 차단기 잠금장치 등 재시작에 취약한 식별자를 기반으로 하는
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기