완료 보고가 다른 문으로 들어갔다
작업은 끝났다. 노드는 완료 보고를 보냈다. 그런데 본부는 그 사실을 못 봤다. 보고가 엉뚱한 문으로 들어갔기 때문이다. 같은 방 안 어딘가에는 도착했다. 하지만 판단하고 머지해야 하는 자리에는 도착하지 않았다. 발신 로그만 보면 성공이었다. 운영 관점에서는 실패였다.
이런 사고는 겉으로 보기 애매하다. 메시지가 유실된 것도 아니다. 완전히 안 간 것도 아니다. 누군가는 받았다. 그래서 처음엔 “보냈는데 왜 못 봤지?”가 된다.
답은 단순했다. 주소가 틀렸다.
전송은 성공했지만, 수신자가 틀렸다. 일은 끝났지만, 끝났다는 사실이 올바른 사람에게 닿지 않았다.
완료는 결과가 아니라 도착까지 포함한다
AI 작업 노드를 여러 개 굴리면 일이 끝나는 지점이 많아진다. 맥미니가 끝내고, 노트북이 끝내고, 데스크탑이 끝낸다. 각 노드는 자기 자리에서 보고서를 만든다. “작업 완료”, “검증 통과”, “PR 열림”, “파일 생성”.
하지만 그 보고가 어디로 가야 하는지는 별도의 문제다.
완료 보고를 받아야 하는 쪽은 대개 일을 시킨 사람이나 최종 판단권자다. 특히 머지, 배포, 제출처럼 다음 행동을 결정해야 하는 경우에는 더 그렇다. 보고가 옆자리 노드로 가면, 그 노드는 보고를 봐도 결정할 수 없다. 본부는 못 봤고, 옆자리는 할 수 없다. 결과적으로 시스템은 멈춘다.
발신자 입장에서는 억울할 수 있다. 나는 보냈다. 명령도 성공했다. 메시지도 어딘가 도착했다. 하지만 운영에서는 충분하지 않다.
완료는 “보냈다”가 아니다. “필요한 사람이 받았다”까지다.
전송 성공과 수신 성공은 다르다
이전에도 비슷한 일이 있었다. 메시지 API가 ok를 반환했지만, 실제 사용자는 못 본 경우였다. 그때 배운 건 “ok가 떴다, 도착은 안 했다”였다.
이번에는 결이 조금 다르다. 메시지는 도착했다. 다만 잘못된 곳에 도착했다. 우편물이 사라진 게 아니라 옆집 우편함에 들어간 것이다.
택배 앱에는 배송 완료가 떴다. 그런데 내 문 앞이 아니라 옆집 문 앞이다. 택배사는 “완료”라고 말할 수 있다. 받는 사람 입장에서는 완료가 아니다.
자동화 메시지도 똑같다. 전송 함수가 성공했다고 해서 수신이 성공한 게 아니다. 수신이 성공했다고 해서 올바른 수신자가 받은 것도 아니다. 성공을 어디까지 정의하느냐가 중요하다.
이번 문제는 “전송 성공”을 너무 빨리 완료로 본 데 있었다. 실제로 필요한 확인은 하나 더 있었다.
누가 받았는가?
이 질문을 빼먹으면, 시스템은 엉뚱한 곳에 잘 배달하고도 성공이라고 말한다.
주소는 데이터다
사람끼리는 문맥으로 주소를 보정한다. “본진에 보고해”라고 말하면, 사람이 알아서 본진이 누구인지, 지금 어느 채널이 맞는지, 어떤 창이 활성인지 판단한다.
자동화는 그렇게 하지 않는다. 자동화는 주소를 데이터로 들고 있어야 한다. 어느 노드가 발신자인지, 어느 엔진이 일을 했는지, 누가 이 task의 issuer인지, 최종 보고는 어디로 가야 하는지. 이 값들이 정확해야 한다.
문제는 이 주소 데이터가 여러 층에 흩어져 있다는 점이다. 오래된 legacy 파일이 있고, 새로 만든 per-engine 기록이 있고, task별 issuer가 있고, 노드별 기본 경로가 있다. 어느 하나가 옛값을 들고 있으면 보고가 옆길로 샌다.
그날도 그랬다. 작업 노드는 자기 기준으로 보고를 보냈다. 하지만 완료 보고가 가야 할 본부 주소가 아니라, 이전에 잡힌 다른 경로를 탔다. 결과는 어색했다. 일을 지시한 쪽은 조용하고, 옆자리만 보고를 받는다.
주소는 부가 정보가 아니다. 주소가 틀리면 작업 결과가 없는 것처럼 보인다. 아무리 좋은 보고서를 써도, 문을 잘못 두드리면 없는 보고서와 같다.
라우팅은 마지막 장식이 아니다
자동화 설계에서 라우팅은 자꾸 뒤로 밀린다. 먼저 일하게 만들고, 그다음 보고하게 만들고, 마지막에 보기 좋게 묶는다. 실제 구현도 그렇게 흘러가기 쉽다.
하지만 여러 노드가 움직이는 구조에서는 라우팅이 핵심이다. 일을 누가 하는지도 중요하지만, 그 일이 끝났다는 사실이 어디로 가는지가 그만큼 중요하다. 보고가 잘못 가면 다음 단계가 시작되지 않는다. 머지할 사람이 못 보고, 승인할 사람이 못 보고, 사용자에게 알려야 할 사람이 못 본다.
이번 사고에서 코드는 일을 못 한 게 아니었다. 보고서를 못 만든 것도 아니었다. 마지막 길이 틀렸다. 그런데 마지막 길이 틀리면 전체 작업은 완료되지 않는다.
이건 택배의 마지막 100미터와 비슷하다. 물류센터에서 도시까지 잘 왔고, 동네까지 잘 왔고, 기사에게도 잘 넘어갔다. 그런데 마지막 문 앞에서 주소를 틀리면 배송은 실패다. 앞의 모든 성공이 마지막 주소 하나 때문에 무효가 된다.
자동화의 완료 보고도 그렇다. 마지막 100미터가 틀리면, 사람은 결과를 못 본다.
해결은 라벨이 아니라 영수증이었다
이 문제를 고치는 방법은 “다음부터 잘 보내자”가 아니었다. 주소를 더 예쁘게 표시하는 것도 아니었다.
필요한 것은 영수증이었다. 발신자가 누구인지, 수신자가 누구인지, 어떤 task의 보고인지, 어느 채널로 보냈는지, 그리고 그게 실제로 어느 쪽에 도착했는지 남기는 것. 그래야 다음에 같은 일이 났을 때 “보냈다”에서 멈추지 않고 “어디로 보냈는가”를 볼 수 있다.
그리고 가능하면 완료 보고는 task의 원 발신자에게 돌아가야 한다. 일을 시킨 쪽이 결과를 받아야 한다. 중간 노드가 대신 받는 구조는 언젠가 헷갈린다. 특히 한 물리 기계 안에 여러 엔진이 같이 도는 경우에는 더 그렇다. 같은 맥미니라도 Claude 세션과 Codex 세션은 다른 실행자다. 보고 경로도 그 차이를 알아야 한다.
자동화가 커질수록 “노드”만으로는 주소가 부족하다. 노드 + 엔진 + task issuer까지 봐야 한다. 이게 귀찮아 보이지만, 귀찮은 이유가 곧 필요한 이유다. 주소가 정밀하지 않으면 보고가 섞인다.
배운 것
일을 끝내는 것과 끝났다는 사실이 올바른 곳에 도착하는 것은 다른 일이다. 작업 결과가 있어도, 판단권자가 못 보면 시스템은 완료된 게 아니다.
전송 성공은 수신 성공이 아니다. 수신 성공도 올바른 수신자 성공이 아니다. 자동화 보고에서 성공의 정의는 “필요한 사람이 봤다”까지 가야 한다.
그리고 주소는 문맥이 아니라 데이터여야 한다. 누가 시켰고, 누가 했고, 어디로 돌아가야 하는지. 이 값이 틀리면 좋은 보고서도 옆집 우편함에 들어간다.
완료 보고는 보낸 순간 끝나는 게 아니다. 맞는 문 앞에 도착해야 끝난다.
— 강대종 / @ssamssae