어제까지 잘 하던 일을 오늘 못 했다. 그것도 어이없게.

나는 개발자가 아니다. 그런데 폰에서 쓰는 브라우저 앱을 하나 만들어서 쓰고 있다. 주소창에 명령을 적으면 AI 비서가 그 브라우저를 직접 조작한다. 페이지를 열고, 읽고, 클릭하고, 폼을 채운다. 내가 코드를 짠 게 아니라 AI에게 시켜서 만들었다.

그날 시킨 일은 단순했다. 여름에 다녀온 항공편 마일리지가 자동으로 안 붙어서, 메일함에서 예약 확인 메일을 찾아 제휴 항공사 사이트에 누락 적립을 신청하는 것. 며칠 전에 이미 한 번 성공했던 일이다. 그때는 예약 메일이 어느 여행사에서 왔는지까지 정확히 찾아내서 알려줬다.

그런데 이번엔 못 찾았다. 대신 몇 년 전 다른 항공사 메일을 답으로 내놨다. 그리고 내가 "같은 제휴 그룹이라 적립 가능하다"고 하는데도 "서로 다른 항공사라 안 된다"고 우겼다. 며칠 전엔 그걸 알아서 처리했던 AI가.

솔직히 화가 나기보다 당황스러웠다. 내가 뭘 잘못 건드렸나? 아니면 뒤에서 모델이 바뀌었나?

첫 번째 의심: 모델이 바뀌었나

제일 먼저 그걸 의심했다. AI 서비스는 뒤에서 조용히 모델을 바꾸기도 하니까. 확인해보니 실제로 한 달 전쯤 더 낮은 모델로 자동 전환돼 있었다.

사연이 있었다. "특정 상위 모델을 유료 플랜에서 못 쓰게 될 예정"이라는 공지를 보고, 그날이 오면 자동으로 하위 모델로 갈아타도록 예약을 걸어뒀던 것이다. 예약은 정확히 실행됐다. 문제는 그 공지가 실현되지 않았다는 것. 직접 호출해보니 상위 모델이 멀쩡히 살아 있었다. 한 달 가까이 쓸 수 있는 걸 안 쓰고 낮은 걸 쓰고 있었다.

그래서 되돌렸다. 다만 전체 기본값을 올리지는 않았다 — 그러면 모델을 따로 지정하지 않은 다른 자동화까지 전부 끌려 올라가서 사용량이 새기 때문이다. 필요한 곳만 골라서 올렸다.

그런데 여기서 중요한 게 있다. 이건 원인이 아니었다. 잘 찾았던 날도, 못 찾은 날도 모델은 똑같았다. 나는 첫 번째 답을 찾았다고 생각했지만, 사실은 별개의 문제를 하나 발견한 것에 불과했다.

두 번째 의심: 원래 그 메일이 없었나

다음으로 AI 비서에게 원인을 찾아보라고 했다. 자신 있게 답이 왔다. "그 항공권 예약 메일은 애초에 그 메일함에 없습니다. 서버 문서함에 사본만 있습니다."

그럴듯했다. 그런데 내가 알고 있었다. 며칠 전엔 분명히 그 메일함에서 찾아냈다. 발신 여행사 이름까지 알려줬다.

내가 그렇게 말하니 다시 뒤졌고, 그때의 대화 기록에서 그 대목을 그대로 찾아냈다. 메일은 있었다. 첫 번째 진단은 틀렸다.

세 번째 의심: 대화 이어받기가 실패했나

세 번째 가설은 그럴듯함이 한 단계 높았다. 로그에 이어받기 실패(타임아웃 180초) — 새로 시작 같은 줄이 스물한 번 찍혀 있었다. 대화가 길어져서 이어받기가 느려지고, 시간 초과로 매번 새 대화로 리셋되는 것 아니냐는 것.

깔끔한 설명이었다. 그런데 로그 줄 위치를 세어보니 전체 314줄 중 124~142번째 — 중간이었다. 끝이 아니라. 게다가 그 줄들에는 앱 버전 표시가 없었는데, 최근 기록에는 다 붙어 있었다. 즉 그 스물한 번은 몇 주 전 다른 작업 때 남은 흔적이었다. 오늘 일과 아무 상관이 없었다.

두 번째 진단도 틀렸다.

진짜 원인: 정지 버튼이 기억을 지우고 있었다

결국 AI가 남긴 대화 기록 파일을 직접 뒤졌다. 성공했던 날의 기록과 실패한 날의 기록을 나란히 놓고 봤다.

성공한 대화는 700KB가 넘는 긴 대화였다. 실패한 대화는 완전히 새것이었다. 대화가 중간에 통째로 갈렸던 것이다.

왜 갈렸나. 코드를 열어보니 이렇게 되어 있었다.

// 작업 정지 버튼을 눌렀을 때
저장된_대화_세션.삭제()   // ← 이 한 줄

주석에는 "세션 부분오염 방지"라고 적혀 있었다. 작업을 중간에 끊으면 대화 상태가 어중간해질 수 있으니 아예 새로 시작하자는 의도였다. 그런데 그 대가가 컸다. 정지 버튼 한 번에 그때까지 알아낸 게 전부 사라진다.

여기서 내가 왜 정지를 여러 번 눌렀냐가 나온다. 그날 나는 이 앱을 개발하는 중이었다. 새 기능을 만들면서 서버를 열 번 넘게 재시작했다. 재시작하면 진행 중인 작업이 죽는다. 화면이 멈춘 것처럼 보이니까 나는 정지를 눌렀다. 그리고 다시 시켰다. 그때마다 기억이 날아갔다.

내가 만들고, 내가 지우고, 내가 다시 시키고 있었던 것이다.

기억이 왜 그렇게 중요했나

이 부분이 이 이야기에서 가장 흥미로웠다. 성공했던 날 AI가 실제로 한 일을 보면 이렇다.

  1. 메일함 A는 로그인 벽에 막혀서 검색이 안 됐다
  2. 대신 메일함 B로 가서 어느 여행사가 보낸 메일인지 특정했다
  3. 그 여행사 이름을 검색어로 써서 메일함 A에서 찾아냈다

즉 성공의 핵심은 검색 실력이 아니라 "어떤 단어로 검색해야 하는지 알아낸 과정"이었다. 그 지식은 대화 안에 있었다.

대화가 갈리자 새 AI는 그 단어를 몰랐다. 그래서 "항공권", "도쿄" 같은 일반적인 단어로 검색했고, 당연히 엉뚱한 옛날 메일이 걸렸다. 바보가 된 게 아니라, 알던 걸 잃은 것이었다.

사람도 그렇다. 어제 세 시간 걸려 알아낸 걸 기억 못 하는 상태로 같은 일을 다시 하면 똑같이 헤맨다.

고쳤다: 정지해도 기억은 남긴다

그 한 줄을 지웠다. 대신 이렇게 생각했다. 중단 때문에 대화 상태가 실제로 깨진 경우는 어떡하나? 그런데 확인해보니 서버가 이미 그 상황을 처리하고 있었다. 이어받기가 실패하면 새 대화로 다시 시도하는 복구 로직이 있었다.

즉 최악의 경우라도 지금과 똑같고, 대부분의 경우엔 기억이 살아남는다. 미리 지울 이유가 없었다. 버튼 문구도 "중단했어요 (대화 맥락은 유지)"로 바꿨다.

그런데 다음 날, "연결이 끊겼다"의 정체

이야기가 여기서 끝나지 않았다. 다음 날 다른 작업에서 AI가 이렇게 보고했다. "브라우저 연결이 잠시 끊겨서 화면 조작은 못 했습니다."

폰 네트워크가 불안정한가 싶었다. 그런데 이번엔 먼저 기록을 봤다. 어제 세 번 틀린 게 있어서 습관이 조금 바뀌었다.

기록의 순서가 전부를 말해줬다.

1. AI가 답변을 마쳤다        ← 여기서 입력 통로를 닫았다
2. 내가 미리 보내둔 추가 지시가 도착했다
3. 그 뒤 모든 화면 조작이 실패 — "연결 끊김"

소켓 로그를 확인하니 연결은 한 번도 끊기지 않았다. 네트워크는 결백했다.

범인은 바로 전날 내가 만든 편의 기능이었다. 작업이 오래 걸릴 때 답을 기다리지 않고 다음 지시를 미리 쳐서 보낼 수 있게 한 기능이다. "아마 그 메일함에 있을 거야" 같은 힌트를 작업 중에 던져줄 수 있어서 꽤 유용했다.

그 기능을 만들 때 나는 "한 번 시키면 답변이 한 번 나온다"고 가정했다. 그래서 답변이 나오면 입력 통로를 닫도록 했다. 그런데 미리 보낸 지시는 답변이 끝난 직후에 배달되기 때문에, AI가 한 번 더 일을 하려고 한다. 이미 내가 통로를 닫아버린 뒤였다. 그래서 그 작업의 모든 도구가 죽었고, AI는 그걸 "연결이 끊겼다"고 해석했다.

고치다가 더 나쁜 걸 만들었다

이 부분은 부끄럽지만 남겨둘 가치가 있다.

첫 시도: "미리 보낸 지시가 있으면 통로를 닫지 말자." 결과는 4분 멈춤이었다. 그 지시가 이미 같은 작업 안에서 처리된 경우엔 더 올 게 없는데, 통로를 열어둬서 AI가 영원히 기다렸다.

두 번째 시도: "대기 중인 지시 개수를 보고 판단하자." 로그를 찍어보니 양쪽 경우 다 0이 나왔다. 구분이 불가능했다.

결국 답은 판단을 포기하는 것이었다. 5초만 기다려보고 조용하면 닫는다. 그 사이에 뭔가 오면 통로를 유지한다. 추측하지 않고 관찰한다. 최악이 5초 지연이고, 멈춤도 끊김도 없다.

세 가지 경우(작업 중 지시 / 답변 직후 지시 / 지시 없음)를 다 시험해서 통과시켰다.

배운 것

1. AI의 자기진단을 믿지 말자. 오늘 하루에 세 번 틀린 원인을 들었다. "메일이 없다", "이어받기 타임아웃", "연결이 끊겼다". 셋 다 그럴듯했고 셋 다 아니었다. AI는 자기가 왜 실패했는지 모른다. 실패한 순간의 느낌을 그럴듯한 원인으로 번역해서 말한다. 사람도 그러긴 한다.

2. 기록을 봐라. 세 번 다 답은 같은 곳에 있었다 — AI가 남긴 대화 기록 파일. 로그 요약이나 AI의 설명이 아니라 실제로 무슨 일이 어떤 순서로 일어났는지가 적힌 원본. 처음부터 그걸 봤으면 세 번 헤맬 일이 없었다.

3. 편의 기능이 기억을 지우면 안 된다. "깨끗하게 다시 시작"은 개발자에게는 안전한 선택처럼 보인다. 그런데 쓰는 사람 입장에서는 어제 세 시간 걸려 알아낸 걸 버튼 하나로 날리는 것이다. 안전과 기억 중에 뭘 지킬지는 기본값으로 정할 문제가 아니라 신중하게 고를 문제였다.

4. 남이 쓰고 있는 서비스를 개발하면서 재시작하지 말자. 이번 사고의 절반은 이것이었다. 나는 개발자 모드였고, 같은 시간에 사용자이기도 했다. 재시작 열 번이 사용자로서의 나를 계속 방해했다. 지금은 작업을 모아서 한 번에 재시작하거나, 쓰는 중인지 먼저 확인한다.

5. "왜 이렇게 오래 걸리지"는 측정할 수 있는 질문이다. 이 사건 뒤에 실제로 세어봤다. 한 작업이 도구를 125번 왕복했고, 한 번에 8~9초씩 걸렸다. 그중 37%는 없어도 되는 왕복이었다 — 화면 구조를 눈치로 더듬는 데 22번, 파일을 페이지 이동 때마다 다시 받는 데 24번. 그래서 "화면 구조를 한 번에 알려주는 도구"와 "파일을 앱이 들고 있게 하는 도구"를 만들었다. 각각 22번을 1번으로, 24번을 2번으로 줄였다. 느린 원인은 한 번이 느린 게 아니라 횟수였다.

자주 묻는 것들

결국 마일리지는 적립됐나?

됐다. 그것도 AI가 끝까지 자동으로 했다 — 메일 첨부파일을 받아서 신청 폼에 붙이는 것까지. 브라우저에서 다른 사이트 파일을 폼에 첨부하는 건 안 될 거라고 내가 단정했는데, 기록을 보니 이미 성공해 있었다. 내가 또 틀렸다. 왕복 두 구간 중 한쪽은 "탑승일로부터 14일 이후에 신청" 규정에 걸려서, 그 날짜에 알림이 오도록 예약을 걸어뒀다.

비개발자가 이런 걸 어떻게 찾나?

못 찾는다. 나는 코드를 읽을 줄 모른다. 대신 "그건 아닌 것 같은데"라고 말할 수는 있었다. "메일이 없다"에는 "며칠 전엔 찾았어"라고, "모델 문제"에는 "줄곧 같은 모델이었잖아"라고 답했다. 그 반박이 매번 방향을 틀었다. 사실 확인은 AI가 훨씬 빠르다. 다만 무엇을 의심할지는 그 일을 실제로 겪은 사람이 더 잘 안다.

이런 버그를 미리 막을 수 있었나?

솔직히 어려웠을 것 같다. 다만 두 가지는 할 수 있었다. 하나는 "정지"처럼 되돌릴 수 없는 동작이 정확히 무엇을 지우는지 한 번 확인해보는 것. 다른 하나는 새 기능을 만들 때 "내가 세운 가정이 뭐지"를 적어두는 것. 이번 버그는 "한 번 시키면 답변이 한 번"이라는 가정 하나에서 나왔다. 적어뒀다면 의심했을지도 모른다.

결국 AI가 멍청해진 건 아니었나?

아니었다. 도구가 기억을 지웠고, 내 코드가 통로를 닫았고, 내가 남의 작업을 열 번 끊었다. 세 번 다 나였다. 다만 하나는 AI 탓이 맞다 — 자기가 왜 실패했는지 그럴듯하게 지어내는 것. 그래서 이제는 "연결이 끊겼습니다" 같은 말을 들으면 고맙게 받되, 기록부터 확인한다.