AI랑 4개월 일하면서 네 번 같은 자리에서 막혔다

AI랑 4개월 일하면서 네 번 같은 자리에서 막혔다
새벽 한 시쯤이었다.
아들 방의 책상 위에는 반쯤 조립된 아크릴 로봇 샤시가 올라가 있었다. 모터 두 개를 PWM으로 제어하는 코드를 Claude Code에게 받아서 올렸는데, 한쪽 바퀴가 한 방향으로만 돌고 있었다. 다른 쪽은 멈춰 있다가 가끔 떨기만 했다. 아들은 옆에서 "이거 왜 이래?" 하고 모니터를 들여다보고 있었고, 나는 시리얼 모니터에 찍히는 값을 멍하니 보고 있었다.
코드는 깔끔했다. 그건 의심하지 않았다. L298N 모터 드라이버를 쓰는 표준적인 패턴이고, 라이브러리 호출도 정상이고, PWM 값도 범위 안에 들어가 있었다. 그런데 작동을 안 했다.
핀맵을 다시 봤다. IN1은 8번, IN2는 9번, ENA는 10번. IN3는 11번, IN4는 12번, ENB는 13번. 내가 Claude에게 부르는 대로 적어준 핀이었다.
두 시간을 봤다. 두 시간 동안 코드를 뜯고, 배선을 뜯고, 멀티미터를 대보고, 다시 코드를 짜고. 그러다 어느 순간 깨달았다. ENA에 지정한 10번 핀이 Timer1을 건드리고 있었고, 내 프로젝트는 같은 보드에서 서보 라이브러리도 쓰고 있었다. 둘은 같은 타이머를 두고 다투고 있었던 거다. Claude가 짠 코드는 코드만 보면 멀쩡했다. 다만 내 프로젝트에 서보가 있다는 걸 모르고 짠 것뿐이었다.
그 순간이 묘했다. 화는 안 났는데, 머리가 조금 차가워졌다.
이게 AI가 못한 건가? 아니었다. ENA가 Timer1에 묶인다는 건 데이터시트에 있는 얘기고, Claude는 그걸 안다. AFMotor 라이브러리가 Timer1과 Timer2를 점유해서 충돌 위험이 있다는 것도 안다. 그런데 그날 새벽의 그 코드는 내 보드의 사정을 모른 채 쓰였다. 서보가 있다는 걸, ENA를 옮길 수 있다는 걸, 13번이 PWM이 안 되는 핀이라는 걸. 다 알 수 있었지만, 그 세션에는 그 정보가 없었다.
그 뒤로 같은 자리에 세 번 더 멈췄다는 걸, 한참 지나서야 알게 됐다. 도메인은 다 달랐다. 사주, 타로, 미연시, 로봇. 도메인이 이렇게 멀면 막히는 모양도 달라야 정상이다. 그런데 네 번 다 같은 자리에서 발이 멈췄다. 처음엔 각각 다른 이유라고 정리했다. 사주는 도메인이 너무 복잡해서, 타로는 의사결정 마비라서, 미연시는 감정이라는 영역이 원래 어려운 거라서, 로봇은 임베디드라서. 그렇게 네 개의 다른 이야기로 두고 있었는데, 네 개를 옆에 놓고 보고 나서야 비슷한 모양이라는 걸 알았다.
이 글은 그 네 번에 대한 이야기다. 그리고 그게 같은 자리였다는 걸 알게 된 그 뒤의 이야기다.
사주 앱이 만만하지 않았던 이유
사주를 처음 만들기로 했을 때, 솔직히 만만하게 봤다.
타로를 한 번 끝낸 직후였다. 78장 카드, 정방향과 역방향 합해서 156개 해석, 리딩 레이아웃 몇 개. 그 정도의 도메인은 한 세션 안에 들어왔다. 그래서 사주도 비슷할 거라고 생각했다. 천간 10개, 지지 12개. 22글자. 타로의 78장보다 한참 적다.
그게 첫 번째 착각이었다.
"혹시 사주 프로그램을 만들고 싶어. 데이터는 좀 있어?"
이 한 문장을 Claude에게 던졌을 때, 10분도 안 되는 시간에 도메인의 전체 윤곽이 정리돼서 돌아왔다. 60갑자, 오행 상생상극, 십신 분석, 지장간, 12운성, 합충형파해. 각 요소가 뭔지, 서로 어떻게 엮이는지, 코드에서 어떤 구조로 잡아야 하는지까지.
처음엔 감탄했다. 사주를 독학하려면 몇 주가 걸리는 분야인데, 이걸 10분으로 압축해준다고? 사이드 프로젝트에서 이게 가능하면 거의 모든 도메인이 손이 닿는 거리에 들어온다.
그런데 코드를 쓰기 시작하니까 다른 그림이 보였다.
기존 사주 앱들을 벤치마킹하다가 황당한 사실을 발견했다. 같은 생년월일시를 넣었는데 앱마다 결과가 다르다는 것. 시주가 다르고, 월주가 다르고, 어떤 경우는 일주까지 달랐다. 처음엔 내가 입력을 잘못한 줄 알았다. 다시 넣었다. 같은 결과였다. 사주 팔자 여덟 글자 자체가 앱마다 다르게 나왔다.
같은 입력에 다른 출력이 나오는 건 프로그래밍의 기본 전제를 깨는 일이다. 그런데 사주에서는 그 전제가 성립하지 않았다. 원인은 세 가지였다.
진태양시. 한국 표준시는 동경 135도 기준인데 서울은 실제로 약 127도다. 약 30분 차이가 난다. 사주에서 시주는 두 시간 단위로 잡히기 때문에, 30분이 시주를 바꿔버린다. 이걸 보정하는 앱과 안 하는 앱의 결과가 갈렸다.
야자시. 밤 11시부터 자정 사이에 태어난 사람의 일주를 당일로 볼 것인가 다음 날로 볼 것인가. 학파마다 견해가 다르다. 일주가 바뀌면 십신 분석의 기준점인 일간이 바뀌고, 사주 해석 전체가 흔들린다.
서머타임. 한국은 1950년대와 60년대에 일광절약시간을 시행한 적이 있다. 그 시기에 태어난 사람의 출생 시각을 진짜 태양 시간으로 되돌리려면 역보정이 필요하다. 어떤 앱은 한다, 어떤 앱은 무시한다.
문제는 이 세 가지를 Claude에게 한 번 설명해서 코드를 받았다고 끝이 아니었다는 거다. 다음날 새 세션을 열면 다시 처음부터였다. "이 프로젝트는 진태양시 보정을 적용해, 야자시는 당일 처리야, KASI 절기 데이터를 분 단위로 써. 그리고 서머타임은 1950~60년대만 보정해…" 매번 이걸 다시 썼다. 한두 번은 괜찮았다. 다섯 번째쯤 되니 진이 빠졌다.
한 가지 더 있었다. 절기 데이터. 사주에서 월주는 음력 날짜가 아니라 절기로 정해진다. 입춘이 지나야 새해이고, 경칩이 지나야 2월이다. 그런데 절기의 정확한 시각은 천문학적 계산이 필요하다. 한국천문연구원이 분 단위로 절기 데이터를 공개한다는 사실을 알고 나서야 입춘 당일에 태어난 사람의 월주가 정확히 잡혔다. 그 전에는 대략적인 날짜로 처리해서, 절기 경계의 사례에서 결과가 이상하게 어긋났다.
해법은 결국 문서로 갔다. docs/ 폴더에 아키텍처, 도메인 모델, 계산 엔진, AI 해석. 네 개를 만들었다. 그중 도메인 모델 문서는 35페이지가 됐다. 사이드 프로젝트치고는 분명 과한 분량이다. 그런데 그게 있어야 새 세션에서 @docs/ 한 번으로 AI가 전체 맥락을 안 상태로 시작할 수 있었다.
문서가 없으면 새 세션마다 컨텍스트 세팅이 15분쯤 걸렸다. 문서를 로드하면 1~2분이면 끝이었다. 사주 프로젝트는 수십 번의 세션을 거쳤다. 절약된 시간보다 더 큰 건 컨텍스트의 품질이었다. 구두로 설명하면 매번 빠뜨리는 게 있다. 문서를 로드하면 빠뜨릴 게 없다.
문서를 한 번 만들고 나니 코드 품질에도 차이가 났다. 십신 분석 함수를 새로 짤 때 AI가 이미 일간 계산 함수의 반환 형태와 오행 매핑 테이블을 알고 있었다. 그러니까 인터페이스가 자연스럽게 맞물렸다. 문서 없이 함수 하나씩 따로 요청했을 때는 같은 개념을 서로 다른 이름으로 부르는 경우가 종종 있었다. "일간"이 어떤 함수에선 dayStem이고 다른 함수에선 mainStem이 되는 식. 문서 안에 표기를 한 번 박아두니까 그런 게 사라졌다.
이게 사주에서 처음 막힌 자리였다. 정확히는 막혔다기보다, 같은 설명을 반복하다가 비효율이 견딜 수 없는 수준으로 쌓인 거였다. 그리고 그 비효율의 원인은 AI가 똑똑하지 않아서가 아니었다. 내 프로젝트의 결정들이 AI 바깥에 있어서였다.
타로는 78장이 아니라 156개였다
타로는 사주 전에 한 프로젝트였다.
"타로카드를 보는 웹페이지를 만들고 싶어." 이 한 문장으로 시작했다. 기획서도 PRD도 없었다. 그냥 평소에 타로에 관심이 있었고, 웹으로 만들면 재밌겠다는 생각이 전부였다.
10분 만에 스펙이 잡혔다. Claude가 던진 질문 세 개에 내가 답하다 보니 그렇게 됐다. 리딩 모드는 원카드, 쓰리카드, 켈틱 크로스 세 가지. 자유 선택 모드는 뺐다. 타로에 익숙하지 않은 사람한테 "몇 장 볼까요"를 묻는 건 친절이 아니라 부담이다. 해석은 LLM API를 매번 호출하는 대신 고정 텍스트로. 기술 스택은 React + Vite + TypeScript. 이름은 그냥 "타로 마스터"로 했고, 5초 안 걸렸다.
이 단계는 사주랑 비슷했다. 도메인이 짧으면 AI가 빠르게 윤곽을 잡아준다. 의사결정 축을 나열해주면 내 우선순위가 또렷해진다. 거기까지는 흐름이 매끄러웠다.
그런데 두 가지가 발을 잡았다.
하나는 작업량 자체였다. "타로는 78장"이라고 시작했는데, 정방향과 역방향이 별도 해석이라는 걸 도중에 알게 됐다. 그러면 78장이 아니라 156개의 해석 텍스트가 필요했다. 단순한 좋고 나쁨이 아니라, 정방향은 카드의 에너지가 자연스럽게 발현된 상태이고 역방향은 그 에너지가 억눌리거나 과잉되거나 왜곡된 상태였다. The Magician의 역방향은 "무능력"이 아니라 "속임수, 재능 낭비, 조작"이다. 능력이 있지만 잘못된 방향으로 쓰이고 있다는 뉘앙스. 이걸 156개 다 써야 했다.
다른 하나는 저작권이었다. 라이더-웨이트-스미스 덱이 가장 유명한데, 이게 1909년 원본은 퍼블릭 도메인이지만 1971년에 US Games Systems가 재색칠한 버전은 별도 저작권이다. 인터넷에서 검색하면 나오는 타로 이미지 대부분이 1971년 버전이거나 그로부터 파생된 것이었다. 색상 톤이 미묘하게 다르고 디테일이 수정돼 있어서, 전문가가 아니면 구분하기가 어렵다. 무심코 가져다 썼다가는 저작권 문제가 생기는 구조였다.
여기서 흥미로운 게, AI가 이 두 가지를 도중에 알려줬다는 점이다. "카드 수 → 이미지 소싱 → 저작권 → 원본 대 재색칠"이 한 흐름의 대화 안에서 풀렸다. 이걸 구글링으로 같은 깊이까지 가려면 탭을 열 개는 열어야 했을 거다.
그런데 막힌 자리는 다른 데였다.
해석 텍스트 156개를 누가 쓸 것인가. 사람이 직접 쓰면 시간이 너무 오래 걸린다. AI가 매번 생성하면 LLM API 비용이 사용량에 비례해서 늘어난다. 사이드 프로젝트가 수익이 나기 전에 비용이 나가는 구조는 지속 가능하지 않다. 게다가 카드를 뒤집고 결과를 보는 순간의 즉각성이 타로의 핵심인데, API 호출로 2~3초 대기가 생기면 그 몰입이 깨진다.
그래서 결국 미리 작성된 고정 텍스트로 갔다. AI가 한 번에 생성한 텍스트를 사람이 다듬어서 정적 데이터로 박는 방식. 이 결정은 옳았다고 본다. 그런데 결정 자체가 흥미로웠다.
AI에게 "어떻게 해야 하지?"라고 막연히 물었으면 "둘 다 가능합니다"라는 답이 돌아왔을 거다. 그게 틀린 답은 아니다. 다만 도움이 안 된다. "API 비용", "응답 지연", "몰입감 유지" 같은 내 프로젝트의 제약을 같이 넘겨줘야 비로소 결정이 됐다. 즉, AI는 선택지를 잘 펼친다. 하지만 그 선택지 사이에서 어느 쪽이 내 프로젝트에 맞는지는, 그 프로젝트의 결정들이 AI 손에 쥐어져 있어야 답이 나온다.
여기가 두 번째 막힌 자리였다. 도메인이 단순해서 컨텍스트 붕괴는 안 일어났다. 그런데 다른 형태의 정체가 생겼다. 의사결정 축이 펼쳐졌는데, 그 축 위에서 내가 어디로 가야 하는지를 AI가 먼저 말하지 않았다. 내 우선순위를 명시적으로 던져주기 전까지는.
이 막힘이 사주에서의 막힘과 표면적으로는 달라 보였다. 사주는 "같은 설명을 매번 반복하는 비효율"이었고 타로는 "내 우선순위가 AI에 없는 비효율"이었다. 그런데 한 발 떨어져서 보면 둘 다 비슷했다. 내 프로젝트 바깥에서는 AI가 정확한 답을 줄 수 없다는, 같은 자리의 다른 얼굴이었다.
미연시 코드는 30분, 감정은 아직 모르겠다
세 번째 프로젝트는 좀 의외였다.
"아빠가 요즘 취미로 게임을 만들고 있는데, 너도 같이 만들어볼래?"
아이에게 물었더니 눈이 커졌다. "진짜? 나도 할 수 있어?" 어떤 장르를 좋아하냐고 다시 물었다. 슈팅이나 퍼즐, 아니면 마인크래프트 같은 걸 말할 줄 알았다.
잠깐 생각하더니 아이가 말했다. "미연시."
당황했다. 어디서 접한 건지, 친구들 사이에 유행하는 건지 모르겠지만, 아이는 꽤 진지한 표정이었다. 그런데 이상하게 그 대답이 머리에서 떠나지 않았다. 중학생 여자아이의 취향에 놀란 것도 있지만, 그보다는 개발자로서 뭔가가 반응했던 것 같다.
"미연시 게임을 만들고 싶어. 어디서부터 시작해야 할까?"
Claude에게 던졌고, 돌아온 답에서 Ren'Py라는 이름을 처음 봤다. Python 기반 비주얼 노벨 엔진. 게임 엔진이라기보다 미연시 제작 도구에 가까운 물건이었다. 대화, 선택지, 분기, UI가 이미 다 들어있고, script.rpy 한 파일만 수정하면 미연시가 동작한다.
최소 예시가 이거였다.
define e = Character("Eileen")
label start:
scene black
e "안녕하세요."
e "첫 미연시 테스트입니다."
menu:
"선택지 A":
jump route_a
"선택지 B":
jump route_b
label route_a:
e "A를 선택했네요."
return
label route_b:
e "B를 선택했네요."
return
이게 전부였다. label로 장면 만들고, menu로 선택지 만들고, jump로 분기한다. 10줄이면 미연시 하나가 작동했다. 30분이면 엔진 이해는 끝났다.
여기까지는 너무 빨랐다. 사이드 프로젝트 한정으로 보면 이상적인 출발이었다. 코드의 진입장벽이 거의 없으니 시나리오에 집중하면 된다. 그리고 시나리오야말로 AI랑 가장 잘 협업할 수 있는 영역 아닌가?
그렇게 생각하고 들어갔는데, 그게 아니었다.
AI에게 "감정 시스템을 어떻게 만들면 좋겠어?"라고 물었다. 호감도를 올리고 내리는 단순한 시스템을 예상했다. 그런데 돌아온 답은 의외였다. 감정을 계층으로 나누자고 했다. 성향과 순간 감정과 관계 상태를 분리하자고. 사랑이라는 감정을 단일 변수로 두지 말고, 신뢰와 친밀감과 상실 공포의 조합으로 보자고.
여기서 좀 놀랐다. AI가 도메인 표면을 잡는 건 알았는데, 감정 모델을 이렇게 결대로 쪼개주는 건 예상 밖이었다. 솔직히 내가 혼자 설계했으면 호감도 하나로 시작했을 거다. 0에서 100까지의 단일 변수. 선택지를 누를 때마다 +5 혹은 -5. 그게 끝이었을 거다. 그런데 신뢰와 친밀감과 상실 공포를 따로 두자는 제안을 듣고 나니, 같은 사랑이라도 어떤 사랑인지에 따라 다른 결말이 나올 수 있는 구조라는 게 보였다. 신뢰는 높은데 친밀감이 낮으면 멀게 느껴지는 관계가 되고, 친밀감은 충분한데 상실 공포가 강하면 매번 확인을 요구하는 관계가 된다.
그래서 한 발 더 들어갔다. 며칠 뒤에 아이한테 말했다.
"미연시 만들어보자. 네가 선택하면 이야기가 달라지는 거."
"내가 고르는 거야?"
"응. 뭘 고르느냐에 따라 이야기가 달라져."
"그러면 나쁜 걸 고르면 어떻게 돼?"
"슬퍼질 수도 있지."
"왜?"
그 질문에 바로 대답하지 못했다.
왜 슬퍼질까. 가상의 캐릭터, 가상의 이야기인데. 왜 사람은 거기서 진짜 감정을 느낄까. 아이가 던진 단순한 질문이 사실은 이 프로젝트의 핵심이었다.
그래서 그 질문을 그대로 AI에게도 옮겼다. "왜 사람은 가상 관계에서 진짜 감정을 느끼는가?" 돌아온 답은 "감정적 몰입이 가능하기 때문", "선택이 의미를 부여하기 때문" 같은 일반론이었다. 틀린 말은 아니었다. 그런데 그 안에 진짜 답은 없었다.
여기서 멈췄다. 코드는 막힌 게 아니었다. Ren'Py가 다 해주니까. 도메인 표면도 막힌 게 아니었다. 감정 모델까지는 잘 잡았다. 그런데 "왜 그런가"에 들어가니까 답의 깊이가 갑자기 얕아졌다. 표면에서 한 단계 아래로 내려가는 그 순간에, AI가 따라오지 않았다.
이게 세 번째 막힌 자리였다. 정보 정리, 구조 설계, 코드 작성. 이런 영역에서 AI는 정말 강하다. 그런데 "이 구조가 왜 사람의 감정을 건드리는가"처럼 경험과 기억에서 길어 올려야 하는 질문 앞에서는, 답이 갑자기 평평해졌다.
처음엔 이게 AI 도구의 한계라고 생각했다. 모델이 더 좋아지면 풀릴 문제. 그런데 한참 지나서 생각이 조금 바뀌었다. AI의 한계가 아니라 질문의 성격이 달랐던 거다. 정보로 답할 수 있는 질문과, 경험으로 답해야 하는 질문은 다른 종류였다. 그 둘을 같은 도구로 풀려고 하니까 어느 한 쪽이 안 풀리는 게 당연했다.
로봇에서도 발은 같은 자리에 빠졌다
네 번째가 로봇이었다.
올봄에 아이한테 물어봤다. "아빠랑 로봇 만들어볼래? 카메라 달고, 센서 달고, AI가 뭘 볼지 판단하는 로봇." 2초 정도 생각하더니 "할게!" 였다. 그날 저녁에 혼자 유튜브에서 로봇 영상을 찾아보더라. 다음 날 "내가 로봇 몸통 담당할게." 했다.
그렇게 역할이 정해졌다. 아들은 하드웨어를 맡았다. 조립, 결선, 센서 연결, 로봇 본체. 나는 소프트웨어 쪽이었다. LLM 서버, 코딩 에이전트, 통신 레이어. 같이 하는 부분은 에이전트를 자연어로 지휘하는 자리였다. 약 4개월에 걸쳐 진행한 프로젝트가 됐다.
LLM은 클라우드 API를 안 쓰고 집에 있는 Mac에서 로컬로 돌리기로 했다. 로봇이 실시간으로 판단할 때마다 인터넷을 거치면 지연이 생긴다. 집 안 LAN에서 처리하면 그 지연이 거의 없다. Mac mini M1 16GB, Mac mini M4 24GB, MacBook Pro M4 Pro 24GB. 세 대를 비교하면서 쓰는 구조. Apple Silicon의 통합 메모리가 로컬 LLM 추론에 의외로 잘 맞았다.
여기까지는 부드러웠다. 글의 도입부에서 말한 그 새벽 사건이 그다음에 왔다.
ENA 핀 10번 Timer1 충돌. 두 시간 디버깅. 한쪽 바퀴만 돌던 그 장면.
같은 에피소드를 두 번 쓰는 셈인데, 한 번 더 짚을 가치가 있다. 그날 두 시간이 끝나갈 무렵 떠오른 생각이 이거였다. 이건 AI가 못해서가 아니다. AI가 모르는 것도 아니다. 다 알 수 있는 정보다. 그런데 지금 이 세션에 그 정보가 없을 뿐이다.
그래서 그 정보를 들고 다니기로 했다. CLAUDE.md라는 파일에. 처음엔 10줄짜리였다. 핀맵만 적어뒀다. Timer1 충돌 이후에 "라이브러리 선택 이유" 섹션이 붙었다. AFMotor를 안 쓴다, Timer1과 Timer2를 점유하기 때문이다. NewPing을 쓴다, 비동기 핑이라서 루프를 안 멈춘다. 이런 이유까지 적었다. "AFMotor 안 씀"만 적으면 다음에 다시 쓰는 코드가 나올 수 있어서다. 이유가 붙어야 그 제약을 지킨다.
ENA는 10번에서 5번(Timer0)으로 옮겼다. ENB는 13번에서 6번(Timer0)으로 옮겼다. 13번은 애초에 PWM이 안 되는 핀이었는데 처음에 그것도 모르고 박혀 있었다. 이 두 줄짜리 변경을 끝내고 나니 모터가 멀쩡하게 돌았다.
CLAUDE.md는 그 뒤로도 계속 자랐다. ROS2 토픽 명명 규칙을 처음 요청할 때 그 섹션이 생겼다. 동작 제약 조건이 들어갔다. HC-SR04 측정값 20cm 이하면 전진 명령 무시, 15cm 이하면 정지 후 후진. 코딩 규칙도 붙었다. Timer1 사용 금지, loop() 안에 delay() 금지, 시리얼 디버그는 9600 baud로 통일. 한 번 당할 때마다 한 줄씩 늘었다. 지금은 약 120줄이다.
흥미로운 건 어떤 부분은 내가 안 짜고 Claude가 직접 넣었다는 거다. 처음 하네스 구성을 부탁하면서 NewPing 레포 URL과 Adafruit Motor Shield 레포 URL을 같이 넘겼다. Claude가 두 레포의 코드를 읽었고, NewPing이 비동기 핑이라는 이유를 스스로 적어줬다. Adafruit 코드에서 타이머 점유 방식을 보고 "AFMotor Timer 충돌 주의"를 제약으로 넣었다. 내가 데이터시트를 일일이 읽고 정리한 게 아니라, Claude가 코드를 읽고 판단했다. 그게 약간 신선했다.
진짜 보상은 며칠 뒤에 왔다. 아들이 Claude Code 창에 처음으로 직접 타이핑한 게 이거였다.
"초음파 센서 값 20cm 이하면 멈추게 해줘"
문법이 틀려도 상관없다, 설명이 부족해도 상관없다, 아들 말로 그냥 써라. 그게 규칙이었다. 그렇게 보낸 한 줄로 Claude Code가 코드를 짰다. CLAUDE.md에 HC-SR04 핀맵이 있으니 Trig와 Echo를 알아서 맞게 썼다. 동작 제약 조건에 20cm 기준이 있으니 로직도 그걸 따랐다.
코드를 저장하고, 내가 옆에서 Arduino IDE 업로드를 도와줬다. 로봇 앞에 손을 뻗었다. 20cm 지점에서 정확히 멈췄다.
"된다!"
12살이 자연어 한 줄로 로봇을 멈춘 순간이었다. 아들이 핀맵을 외울 필요가 없었다. CLAUDE.md에 있었다. 라이브러리가 왜 그건지 알 필요도 없었다. 맥락은 하네스에 있었다. 아이는 원하는 동작만 자기 말로 설명했다.
이게 네 번째 자리였다. 사주에서는 매번 설명을 반복하다 지친 자리. 타로에서는 내 우선순위를 명시하기 전엔 결정이 안 되던 자리. 미연시에서는 표면 한 단계 아래로 못 내려가던 자리. 그리고 로봇에서는, 내가 안다고 가정한 제약을 AI가 모른 채로 자신 있게 코드를 짰던 자리.
네 번 다 모양은 달랐다. 그런데 발이 멈춘 위치는 비슷했다.
네 번 같은 자리에 멈췄다
처음엔 이 네 가지가 다 별개의 경험이라고 생각했다.
사주는 도메인이 너무 복잡해서. 타로는 의사결정 마비라서. 미연시는 감정이라는 영역이 원래 어려운 거라서. 로봇은 임베디드라는 도메인이 특수해서. 각각 다른 이유로 막힌 거라고 정리했다.
그런데 네 개를 옆에 놓고 보니, 패턴이 보이기 시작했다.
가장 먼저 눈에 들어온 건 새 세션마다 도메인 지식을 처음부터 다시 설명해야 했다는 점이었다. 사주에서는 진태양시, 야자시, 서머타임. 타로에서는 1909년 원본만 써야 한다는 사실. 미연시에서는 감정 계층 모델. 로봇에서는 핀맵과 라이브러리 선택 이유. 모두 처음 한 번은 AI가 도움을 줘서 잡혔다. 그런데 그 결정이 어디에도 안 적혀 있으면, 새 세션에서 다시 0부터였다.
해결책도 네 번 다 같은 모양이었다. 외재화. 사주의 35페이지짜리 도메인 모델 문서. 로봇의 120줄짜리 CLAUDE.md. 타로의 고정 텍스트 데이터. 다 같은 방향이었다. AI가 매번 안에서 기억하게 두지 말고, 바깥에 적어두고 매번 들고 들어가게 하기.
비슷한 결로 또 한 가지가 있었다. AI가 모르는 제약을 자신 있게 코드로 옮겼다는 점. ENA 10번 Timer1 충돌이 가장 명확한 사례다. 코드만 보면 멀쩡했다. 다만 내 보드에 서보가 있다는 걸 몰랐다. 사주에서도 비슷했다. 처음 받은 만세력 계산 코드는 표준적인 음양력 변환이었다. 거기에 학파별 야자시 처리 같은 제약은 빠져 있었다. 알려주면 들어갔다. 안 알려주면 빠진 게 있는지조차 모르는 코드가 나왔다.
이게 AI가 못 짠 건 아니다. 정보가 없는 상태에서 짰을 뿐이다. 그런데 AI는 정보가 없다는 사실을 말해주지 않는다. 모르는 채로 짠 코드가 모르는 채로 나온다. 이게 사람이 모르는 채로 짠 코드보다 더 까다로운 게, AI 코드는 평균적으로 깔끔해 보이기 때문이다. 깔끔한데 틀린 코드는 처음에 의심이 안 든다. 그러다 한쪽 바퀴만 도는 새벽 한 시를 만난다.
그리고 마지막 하나. 표면 구조는 빠르게 잡지만, 그 아래 한 단계 깊이로 들어가면 답이 평평해진다는 점. 사주의 도메인 지형도는 10분에 잡혔지만, "이 학파를 따를 때 그 제약이 어떻게 바뀌는가"는 안에서 다시 사람이 정리해야 했다. 타로의 카드 구조는 한 답변에 정리됐지만, "내 프로젝트의 비용과 지연을 고려할 때 어느 쪽이 맞는가"는 내가 우선순위를 던져줘야 했다. 미연시의 감정 모델은 계층까지 들어갔지만, "왜 사람이 가상에서 진짜 감정을 느끼는가"는 일반론에서 멈췄다. 로봇의 코드 패턴은 정상적이었지만, "내 보드의 다른 라이브러리와 어떻게 충돌하는가"는 내가 짚어줘야 했다.
표면에서 한 단계 더 들어가는 그 자리. 거기서 네 번 다 발이 멈췄다.
좀 더 풀어보면 이렇다. 표면이라는 건 도메인의 전체 윤곽, 익숙한 구조, 자주 나오는 패턴이다. AI는 학습 데이터에서 이런 표면을 수없이 봤다. 그래서 빠르게 잡아준다. 그런데 그 아래는 항상 두 종류의 정보로 갈린다. 하나는 누가 봐도 알 수 있는데 내가 안 적어준 정보. 핀맵, 라이브러리 충돌, 학파별 차이, 우선순위 같은 것. 이건 외재화로 풀린다. 다른 하나는 정보 자체가 아니라 경험에서만 길어 올릴 수 있는 답. "왜 사람이 가상에서 진짜 감정을 느끼는가" 같은 질문. 이건 외재화로 풀리지 않는다. 적을 정보 자체가 내 안에도 없으니까. 이 두 종류가 같은 자리에서 같은 모양으로 막혔다는 게, 처음엔 한 가지로 보였다가 한참 지나서야 갈라 보이기 시작했다.
이 패턴이 보이고 나서 한동안 멍하니 앉아 있었다. 이게 AI 도구의 결함은 아니다. 이 자리가 원래 그런 자리다. 사람과 도구가 만나는 자리. 도구는 일반적인 것을 안고, 사람은 자기 프로젝트의 구체적인 것을 안고. 그 둘이 한 자리에 모이지 않으면, 항상 같은 곳에서 발이 빠진다.
도구가 아무리 좋아져도 이 자리는 사라지지 않을 것 같다. 모델이 커지면 표면을 더 빨리 잡을 거다. 더 정교한 코드를 짤 거다. 그런데 내 보드에 어떤 다른 라이브러리가 올라가 있는지, 내가 어떤 학파를 따르기로 했는지, 내 사용자가 카드 뒤집기에서 느끼는 즉각성을 얼마나 중요하게 보는지. 이런 건 모델이 알 길이 없다. 알려줘야 알 수 있고, 알려주려면 내가 그걸 어디엔가 적어둬야 한다.
발이 멈춘 그 자리는 막힌 자리가 아니라 만나는 자리였다. 도구가 끝나고 내 프로젝트가 시작되는 자리.
그 자리를 발견하기 전과 후가 좀 달라졌다. 발견 전에는 두 시간을 디버깅하다가 화가 났다. 왜 이런 깔끔해 보이는 코드가 안 도느냐고. 발견 후에는 같은 두 시간이 와도 화가 덜 났다. 어디가 막힌 건지 모양이 보이니까. "아, 이건 내가 컨텍스트를 안 줘서 그렇구나" 하는 진단이 먼저 오면, 그 뒤의 작업이 디버깅이 아니라 컨텍스트 정리가 된다. 같은 시간이 들어도 머릿속의 무게가 다르다.
그래서 어떻게 했냐고
네 번 막힌 다음부터는 매번 다시 시작하지 않기로 했다.
사주에서 한 35페이지짜리 도메인 모델 문서. 타로에서 한 정적 데이터 정리. 로봇에서 한 120줄짜리 CLAUDE.md. 미연시에서는 아직 진행 중인데, 감정 계층 모델을 별도 문서로 빼는 중이다. 네 번 다 모양은 달랐는데, 결국 같은 일을 한 거다. 내 프로젝트의 결정을 AI 바깥에 적어두는 일.
이걸 한 번 하고 나면 다음에 비슷한 자리에 발이 닿을 때 좀 다르다. 그 자리에서 멈추는 게 아니라, "아, 이건 외재화가 안 된 자리구나" 하고 자연스럽게 넘어간다. 막힌 게 어디 있는지 보이면 막힌 게 아니라 그냥 다음에 해야 할 일이 된다.
그런데 같은 일을 매 프로젝트마다 손으로 만들었다. 처음에 핀맵 표를 짜고, 라이브러리 선택 이유를 적고, 동작 제약을 정리하고, 코딩 규칙을 추가하고. 그러다 보니 어느 순간 이게 매번 비슷한 모양으로 반복되고 있다는 걸 알게 됐다. 그래서 그 패턴을 따로 정리하고 있다. 다음 프로젝트 시작할 때 같은 구조를 처음부터 다시 만들지 않게.
그리고 한 가지 더, 미연시에서 만난 자리는 외재화로 풀리지 않는다는 걸 받아들였다. "왜 사람은 가상에서 진짜 감정을 느끼는가" 같은 질문은 내가 적어 넣을 답이 없는 자리다. 그건 도구를 바꿔서 풀 게 아니라, 내가 옛날에 했던 게임들을 다시 떠올리거나, 아이가 던진 "왜?"에 시간을 두고 답을 찾는 식으로 풀 일이다. 이런 자리는 자리가 다른 자리라는 걸 인정하는 것만으로도 시간이 덜 든다.
이 글이 정답을 주는 글은 아니다. 그날 새벽의 두 시간을 두 시간보다 짧게 줄일 수 있다는 약속도 못 한다. 다만 이런 게 궁금하다.
당신은 AI랑 일하면서 어떤 자리에서 반복적으로 멈추는가. 그게 매번 다른 자리 같은가, 아니면 모양만 다른 같은 자리 같은가. 그 자리가 보이기 시작했을 때, 그다음 한 발은 어디로 갔는가.
다음 글에서는 그중에서도 컨텍스트가 사라지는 문제를 따로 풀어볼 생각이다.
댓글
댓글 쓰기