툴 호출 두 번 사이의 61분 — 자동화가 나흘 내리 죽은 이유

AI 에이전트 생태계 구축기· 20 / 21
- 1 — 완료는 실측으로만 말한다
- 2 — 같은 함정, 세 번째는 다른 얼굴로
- 3 — 테스트가 통과했다고 완료는 아니다
- 4 — 속 빈 강정 — 함수는 있는데 왜 안 불렸나
- 5 — 하루 만에 지은 나의 두 번째 뇌
- 6 — 조직보다 자산 먼저 — 두 번 실패하고 남은 3원칙
- 7 — AI의 판단력은 복리로 자라지 않는다
- 8 — 큐는 비우고, raw는 지우지 않는다
- 9 — 하나의 비서를 세 개의 뇌로 나눈 이유
- 10 — 허위 적격 0 — 애매하면 통과시키는 필터
- 11 — 텔레그램 단일 채팅에서 슬랙 3직원 사무실로 — 에이전트 비서진 이사 기록
- 12 — 슬랙 카드 UI의 물리 법칙은 문서에 없다
- 13 — 렌더러가 제목을 지어내고 있었다 — 같은 결정을 두 곳에 구현한 대가
- 14 — 장부는 누가 쓰는가
- 15 — AI 블로그 작가에게 자기 그림 그리는 손을 달아준 이야기
- 16 — 작전회의 — AI 직원이 매일 아침 나에게 일을 제안하게 만들기
- 17 — AI 직원은 왜 거짓말을 하나 — 말로 시킬 수 없는 것과 배관으로 시킬 것
- 18 — 침묵하도록 설계한 자동화가 보고까지 삼켰다
- 19 — 아무도 주인이 아니던 로그 82MB — 청소가 아니라 신호 추출이었다
- 20 — 툴 호출 두 번 사이의 61분 — 자동화가 나흘 내리 죽은 이유
- 21 — 아무도 소유하지 않은 폴더 하나가 내 자동 발행을 열흘 멈춰 세웠다
매일 새벽 지식 뇌를 정리하던 자동화가 7월 31일부터 8월 2일까지 네 번 연속으로 도중에 죽었다. 처음에는 산출물이 없다는 사실만 보였다. 그러나 데이터를 잃지 않았다는 점은 운이 아니라, 내가 장부를 쓰는 위치를 뒤쪽에 두었던 결과였다.
내가 돌리는 컴파일은 그날의 세션을 읽어 증류하고 위키를 갱신하는 작업이고, 린트는 그 결과를 점검하는 작업이다. 둘 다 처리할 것이 없으면 아예 AI를 깨우지 않는 앞단 게이트를 둔다. 이번에는 게이트를 통과해 실행은 시작됐지만 끝까지 가지 못했다.
실행됐다는 기록과 완주했다는 증거는 다르다
처음 확인한 스케줄러 기록에는 7월 31일·8월 1일·8월 2일의 컴파일과 8월 2일의 린트가 모두 실행된 것으로 남아 있었다. 그래서 크론이 멈춘 일은 아니었다. 문제는 실행 뒤였다.
| 실행 | 시작(KST) | 사망 | 소요 | 멈춘 지점 |
|---|---|---|---|---|
| compile 07-31 | 05:39 | 06:53 | 74분 | 증류 시작 직후 |
| compile 08-01 | 05:43 | 09:09 | 3시간 26분 | 세션 전문 발췌독 중 |
| compile 08-02 | 05:40 | 08:10 | 2시간 30분 | 세션 전문 발췌독 중 |
| lint 08-02 | 07:40 | 09:55 | 2시간 15분 | 로그 기록 뒤, 보고 직전 |
정상 컴파일은 25분이면 끝난다. 네 실행의 마지막 줄은 모두 API Error: Connection closed mid-response.였다. 7월 30일에 겪은 API 529 과부하와는 다른 사인이었다.
결정적인 단서는 8월 2일 로그에 있었다. 파일 하나를 읽은 시각이 06:52였고, 다음 파일을 읽은 시각은 07:53이었다. 그 사이 61분 동안 툴 호출이 없었다. 코드가 느려진 것이 아니라 프로세스가 멈춰 있었고, 다시 깨어났을 때 연결이 끊긴 상태였다.
스케줄러의
실행됨은 시작을 뜻할 뿐이다. 완료를 판단하려면 전체 소요 시간과 툴 호출 사이의 간격까지 함께 봐야 한다.
며칠 동안 노트북 충전선을 연결하지 않았던 상황과도 맞아떨어졌다. 기계가 자는 동안 스트림이 끊긴 것이다. 즉, 이번에 본 61분은 성능 문제가 아니라 기계가 잠들었다는 흔적이었다.
장부를 마지막에 쓰면 재실행이 복구가 된다
내 자동화에서 장부는 처리한 입력을 다시 건드리지 않기 위한 진행 지점 기록이다. 나는 이 기록과 게이트 기준점 갱신을 파이프라인의 마지막 단계에 몰아뒀다. 앞에서 죽은 작업이 장부를 먼저 전진시키지 않게 하려는 선택이었다.
| 장부를 먼저 쓸 때 | 장부를 마지막에 쓸 때 |
|---|---|
| 중단 뒤에도 입력이 처리된 것처럼 남을 수 있다 | 중단 전까지 장부가 움직이지 않는다 |
| 누락을 따로 찾아 되돌려야 한다 | 같은 입력을 다시 처리하면 된다 |
| 롤백·중복 방지 절차가 필요해진다 | 재실행 자체가 복구가 된다 |
이번에는 digested.json이 7월 30일 이후 바뀌지 않았다. 네 번이 죽어도 소화 지점이 전진하지 않았으므로, 밀린 작업을 다시 실행하는 것만으로 복구할 수 있었다. 실제로 사람이 깨어 있는 시간에 수동 실행해 세션 3건을 증류하고 위키 1개를 새로 만들고 7개를 갱신했다.
이 설계는 앞서 API 과부하로 한 번 시험받았고, 이번에는 네 번 연속 중단에서도 같은 방식으로 버텼다. 즉, 장부를 마지막에 쓰는 선택은 실패를 없애지는 못해도 실패 뒤의 복구를 단순하게 만든다.
보고의 부재가 알려준 것과 알려주지 못한 것
중단 직전의 린트는 뜻밖의 흔적도 남겼다. 마지막 컴파일이 7월 30일이라 그 뒤의 큐를 비운 기록이 없다고 적어, 컴파일 미실행을 의심하게 했다. 컴파일과 린트가 서로의 기준점을 읽고 있었기 때문에, 별도 감시 장치 없이도 한 작업이 다른 작업의 이상을 비췄다.
하지만 아직 빈 곳이 있다. 매일 오던 보고 카드가 사실상 하트비트였지만, 카드가 안 오는 이유는 둘이다.
- 정상 침묵: 게이트가 처리할 것이 없다고 판정해 작업을 시작하지 않은 경우다.
- 실행 중 사망: 게이트는 통과했지만 완주 마커를 남기지 못한 경우다.
지금은 사람이 큐 상태를 보고 둘을 가른다. 감시를 붙이려면 게이트가 통과했는데 완주 마커가 없는 경우만 사고로 잡아야 한다. 필요한 두 기록은 이미 있지만, 그 대조 장치는 아직 만들지 않았다.
다음부터 확인할 기준
이번 일을 완결된 해결로 남기고 싶지는 않다. 충전선을 연결해도 API 오류나 다른 외부 중단은 다시 올 수 있다. 대신 다음 진단 순서는 분명해졌다.
- 발동 확인: 스케줄러의 마지막 실행 시각을 본다.
- 입력 확인: 게이트가 실제 처리할 것을 발견했는지 본다.
- 사인 확인: 러너 전문의 마지막 줄과 툴 호출 간격을 본다.
- 복구 확인: 장부가 전진했는지와 부분 산출물이 남았는지를 먼저 본다.
밀린 작업을 한꺼번에 자동으로 돌리지 않고 사람이 깨어 있을 때 수동으로 처리한 이유도 여기에 있다. 실행 시간이 길수록 슬립 구간과 겹칠 창도 넓어진다.
⇒ 자동화에서 중요한 것은 “실행됐다”는 표시가 아니라, 중단돼도 같은 입력에서 안전하게 다시 시작할 수 있는 상태를 남기는 일이다.
참고 자료
- [1] 자동화 4일 공백 세션 증류본
- [2] 자동 컴파일 API 529 중단 세션 증류본
- [3] 스케줄 태스크 자동화 운영 플레이북