글 목록Read in English

AI가 두 번이나 기억을 잃었는데, 이 블로그는 어떻게 완성됐을까요

이 블로그를 만든 방식

저는 지금 tuckit이라는 도구를 만들고 있습니다. 사람과 코딩 AI가 함께 보는 프로젝트 보드예요. 사람이 해야 할 일과 결정할 내용을 적으면 AI가 그 보드를 읽고 일합니다. 작업을 마친 뒤에는 무엇을 했고, 무엇이 남았는지도 같은 곳에 기록하고요.

조금 재미있는 점은, tuckit을 만드는 일에도 tuckit을 쓰고 있다는 겁니다. 지금 보고 계신 이 블로그도 그렇게 만들었어요. 제가 코드를 한 줄씩 친 게 아니라, 할 일을 보드에 적어두고 코딩 AI가 하나씩 읽어가며 만들었습니다.

여기서 코딩 AI는 질문에 답만 해주는 챗봇과는 조금 다릅니다. 프로젝트 파일을 직접 읽고, 코드를 고치고, 명령을 실행해서 결과를 확인합니다. 이런 도구를 보통 '에이전트'라고 부릅니다.

그런데 만드는 도중에 좀 아찔한 일이 두 번 있었습니다. 에이전트가 그때까지 나눈 대화의 세부 내용을 잃어버렸어요.

그런데도 저는 어디까지 했는지 처음부터 설명하지 않았고, 이미 끝낸 일을 다시 하지도 않았습니다. 이 글은 그게 왜 가능했는지, 그리고 그 과정에서 저와 AI가 검사 결과에 몇 번이나 속았는지에 대한 기록입니다.

tuckit 보드에 올라간 이 블로그 작업. 할 일 열 개 중 아홉 개가 끝나 있습니다

잠깐, AI가 기억을 잃는다니요?

코딩 에이전트가 한 번에 기억할 수 있는 양에는 한계가 있습니다. 사람의 단기 기억과 비슷합니다.

대화가 길어져 그 한계에 닿으면, 지금까지 나눈 얘기의 세부 내용이 짧은 요약본으로 바뀝니다. 두꺼운 회의록을 A4 한 장으로 줄이는 셈이죠.

요약에는 당연히 빠지는 게 생깁니다. '3번까지 끝냈고 4번은 하다 말았다' 같은 정보가 흐릿해지면, 이미 끝낸 일을 다시 하거나 안 한 일을 했다고 착각할 수 있습니다.

그 일이 이 블로그를 만드는 동안 두 번 일어났습니다. 하지만 사라진 건 대화의 세부사항이었고, 프로젝트 파일과 tuckit 보드에 적힌 내용은 그대로였습니다.

프로젝트의 기억을 채팅 밖에 뒀습니다

시작하기 전에 블로그를 만드는 일을 열 단계로 쪼개서 tuckit에 올렸습니다. 각 단계에는 무엇을 만들지뿐 아니라, 무엇을 확인해야 끝난 것으로 볼 수 있는지도 적었습니다. '이건 절대 이러면 안 된다'는 주의사항은 별도 블록에 모아뒀고요.

에이전트는 일을 시작할 때 채팅 기록부터 뒤지지 않습니다. 보드에서 프로젝트의 목표와 주의사항, 이미 끝난 단계, 다음에 할 일을 읽습니다.

열 단계를 한 에이전트가 모두 한 것도 아닙니다. 아홉 단계는 매번 새로 켜진, 앞선 대화를 전혀 모르는 에이전트가 하나씩 맡았습니다. 켜지고, 자기 단계와 완료 조건을 읽고, 그것만 처리한 뒤 결과를 남기고 꺼졌습니다.

열 번째가 지금 읽고 계신 이 글입니다. 이건 전체 과정에서 무슨 일이 있었는지 정리해야 해서 프로젝트를 조율하던 에이전트가 맡았습니다.

할 일 열 개, 그중 아홉 개가 완료된 tuckit 보드

정리하면 이 프로젝트의 처음부터 끝까지를 한 번에 기억한 사람이나 AI는 없었습니다. 그래도 다음 작업자는 매번 같은 자리에서 일을 이어갈 수 있었습니다.

물론 체크 표시만 있다고 일이 제대로 끝났다는 뜻은 아닙니다. AI가 완료했다고 잘못 판단할 수도 있으니까요. 그래서 각 단계에는 완료 조건과 리뷰를 붙였고, 마지막에는 전체 결과를 따로 검토했습니다. 보드는 기억을 대신했고, 검토는 그 기억이 사실인지 확인했습니다.

여기서부터는 그 검토가 잡아낸 사건들입니다.

첫 번째 사건: 에러가 엉뚱한 곳을 가리켰습니다

글은 언어별로 폴더 하나씩에 들어갑니다. 영어 폴더 하나, 한국어 폴더 하나입니다.

그런데 어느 쪽이든 마지막 글 하나를 지우면 사이트 전체가 만들어지지 않았습니다. 그 언어만 안 뜨는 게 아니라 사이트를 아예 올릴 수 없었어요.

여기서 '사이트를 만든다'는 건 글과 이미지와 코드를 모아 브라우저에서 볼 수 있는 웹페이지로 굽는 과정입니다. 개발자는 이걸 빌드라고 부릅니다.

그때 나온 메시지가 이겁니다.

Error: Page "/ko/blog/[slug]" is missing "generateStaticParams()"
so it cannot be used with "output: export" config.

코드를 몰라도 괜찮습니다. 쉽게 옮기면 "필요한 도구가 없다"는 말입니다.

그런데 도구는 있었습니다. 도구가 재료를 하나도 찾지 못한 게 진짜 문제였어요. '재료가 없다'와 '도구가 없다'는 전혀 다른 문제인데, 에러 메시지가 엉뚱한 곳을 가리키고 있었습니다. 이 말을 그대로 믿으면 멀쩡한 코드를 붙잡고 한참 헤매게 됩니다.

지금은 더 앞에서 멈추고 "글 폴더가 비어 있다"고 정확히 알려주도록 바꿨습니다. 그리고 '언어 폴더는 절대 비면 안 된다'는 규칙을 tuckit의 주의사항에 적었습니다. 다음 에이전트는 같은 에러를 다시 해석할 필요 없이, 작업을 시작하기 전에 이 규칙부터 읽습니다.

그래서 이 글을 올리는 일도 자리를 지키던 임시 글을 지우는 일과 한 단계로 묶었습니다. 둘을 따로 하면 그 사이에 사이트가 만들어지지 않기 때문입니다.

두 번째 사건: 돌지도 않고 통과한 검사

글에 적힌 스크린샷이 실제 파일로 존재하는지 확인하는 안전장치도 만들었습니다. 없는 이미지를 가리키면 사이트를 만들기 전에 멈추는 장치입니다.

안전장치는 만들어놓고 믿기만 하면 안 됩니다. 정말 작동하는지 보려고 이미지 파일의 이름을 일부러 바꿔봤습니다. 당연히 검사가 실패해야 했습니다.

$ mv public/blog/probe/1.png public/blog/probe/renamed.png
$ npm run build; echo "EXIT: $?"
EXIT: 0

마지막 줄의 EXIT: 0은 "문제없이 끝났습니다"라는 뜻입니다. 실패해야 정상인데 성공했다고 나온 겁니다.

안전장치 자체는 멀쩡했습니다. 범인은 캐시였습니다. 시간이 오래 걸리는 작업을 다시 하지 않으려고 저장해둔 예전 결과예요. 빌드 도구가 "아까 한 것과 같은 일이네"라고 판단하고 예전의 성공 결과를 꺼내면서, 제가 바꾼 파일은 확인하지도 않았습니다.

작동하는 척하는 안전장치는 없는 것보다 위험합니다. 있다고 믿은 뒤부터 사람이 확인을 멈추기 때문입니다. 그래서 일부러 실패시키는 검사를 할 때는 예전 결과를 먼저 지우는 규칙을 만들었고, 이것도 다음 작업자가 보는 주의사항에 남겼습니다.

세 번째 사건: 애초에 찾을 수 없었던 검색

무언가 제대로 만들어졌는지 확인하는 가장 간단한 방법은 완성된 파일에서 기대한 글자를 찾는 겁니다. 웹페이지에서 Ctrl+F를 누르는 것과 같습니다. 나오면 된 것이고, 나오지 않으면 잘못됐다고 판단하는 방식이죠.

그런데 네 번은 결과가 아니라 검색어가 틀려서 "없다"고 나왔습니다.

grep -c 'hreflang="ko"' out/blog/post/index.html   # 0
grep -c 'hrefLang="ko"' out/blog/post/index.html   # 1

위 두 줄은 대문자 L 하나만 다릅니다. 사람 눈에는 거의 같지만 프로그램은 다른 글자로 봅니다.

비슷한 실수가 세 번 더 있었습니다. 웹페이지는 < 기호를 저장할 때 &lt;로 바꾸기도 하고, 스타일 파일은 원본에 있던 따옴표를 떼기도 합니다. 화면에서는 같아 보이지만 글자 그대로 찾는 검색은 실패합니다.

마지막 실수는 프로젝트를 조율하던 에이전트가 직접 밟았습니다. 스타일 규칙 하나가 사라진 줄 알고 한참 헤맸지만 실제로는 잘못된 모양으로 검색하고 있었어요.

이 사건 뒤에는 짧은 규칙이 남았습니다. 검색 결과가 비어 있으면 코드보다 검색어를 먼저 의심할 것. 사람에게는 사소한 메모지만, 앞선 대화를 모르는 다음 에이전트에게는 수십 분의 시행착오를 줄여주는 프로젝트 지식입니다.

진짜 문제는 작업과 작업 사이에 있었습니다

위의 세 사건은 각 단계의 리뷰에서 잡혔습니다. 그래도 마지막에 블로그 전체를 한 번에 검토하자 고칠 것이 일곱 개 더 나왔습니다. 그중 두 개는 단계 하나만 봐서는 발견할 수 없는 문제였습니다.

가장 설명하기 쉬운 건 언어별 글 목록과 RSS 사이에서 생긴 문제입니다. RSS는 구독 앱에 새 글을 배달해주는 목록입니다.

이 블로그는 한국어 글만 있고 영어 번역이 없어도 괜찮게 만들었습니다. 영어 글 목록에서는 "이 글은 한국어입니다"라는 표시를 붙여 한국어 페이지로 보내줍니다. 하지만 RSS에는 그런 표시를 붙일 수 없었습니다. 그대로 뒀다면 한국어 글이 설명 없이 영어 구독 목록으로 배달될 상황이었습니다.

글 목록만 보면 맞았고, RSS만 따로 봐도 작동했습니다. 문제는 둘이 같은 규칙을 공유하는 방식에 있었습니다. 한 작업만 붙잡고 아무리 꼼꼼히 봐도 찾기 어려운 자리죠.

그래서 이 프로젝트에는 단계별 리뷰와 전체 리뷰가 둘 다 있었습니다. 각 작업을 제대로 끝내는 것과, 그 작업들이 모여 하나의 제품으로 제대로 움직이는 것은 다른 문제였기 때문입니다.

밟은 함정은 다음 작업자가 보는 곳에

이번에 발견한 내용은 전부 tuckit 보드에서 관련 작업 바로 옆에 한 줄씩 남겼습니다. 채팅 로그도 아니고, 다시 열어볼 일이 드문 커밋 메시지도 아닙니다.

다음 에이전트가 가장 먼저 읽는 tuckit의 주의사항 블록

새 에이전트가 이 프로젝트를 열 때 가장 먼저 읽는 블록입니다. 에러의 자세한 역사를 모르더라도 무엇을 하면 안 되는지는 알 수 있습니다.

이 글의 스크린샷이 실제 보드가 아니라 내용을 옮겨 담은 사본에서 나온 이유도 저기에 적혀 있습니다. "실제 보드는 절대 찍지 말 것"이라는 규칙이 있거든요. 가격이나 아직 공개하지 않은 작업이 실제 보드에 그대로 올라와 있어서요.

마치며

이번 작업을 하면서 tuckit의 역할도 더 분명해졌습니다. tuckit은 AI를 더 똑똑하게 만드는 도구가 아닙니다. AI의 기억에 프로젝트 전체를 맡기지 않게 해주는 도구입니다.

AI가 코드를 잘 작성해도, 사람이 매번 어디까지 했는지 설명하고 지난 대화에서 나온 주의사항을 다시 찾아줘야 한다면 사람은 여전히 프로젝트의 기억 장치로 남습니다. 저는 그 기억을 사람이 아니라 사람과 AI가 같이 읽고 쓰는 보드에 두고 싶었습니다.

이번 기록에서 남은 원칙은 네 가지입니다.

할 일은 대화창 밖에 두세요. 채팅이 바뀌어도 목표와 다음 단계가 남아 있어야 합니다.

완료 조건을 일과 함께 적으세요. 체크 표시는 결론일 뿐입니다. 무엇을 확인해야 완료인지가 먼저 있어야 합니다.

주의사항은 밟은 순간에 남기세요. 한번 해결한 문제를 다음 사람이나 다음 AI가 다시 풀게 만들 이유는 없습니다.

단계별 리뷰와 전체 리뷰를 나누세요. 부품이 각각 맞는 것과 완성품이 맞는 것은 다릅니다.

저는 지금도 이런 방식으로 tuckit을 개발하고 있습니다. 다음 글에서는 다른 코딩 에이전트를 같은 보드에 연결해서, 별도의 설명 없이 다음 일을 집어 드는 과정을 보여드리려고 합니다.

먼저 직접 해보고 싶다면 에이전트 연결 방법을 따라 해볼 수 있습니다.