AI와 넉 달, 사이드 프로젝트 네 개를 굴렸다
- 공유 링크 만들기
- X
- 이메일
- 기타 앱

새벽 한 시쯤이었습니다.
아들 방 책상 위에 반쯤 조립된 아크릴 샤시가 올라가 있었습니다. 모터 두 개를 PWM으로 돌리는 코드를 Claude Code에게 받아서 올렸는데, 한쪽 바퀴만 한 방향으로 돌고 다른 쪽은 멈춰 있다가 가끔 떨었습니다. 아들은 "이거 왜 이래?" 하면서 모니터를 들여다보고 있었고, 저는 시리얼 모니터에 찍히는 값을 멍하니 보고 있었습니다.
코드는 깔끔했습니다. L298N 드라이버를 쓰는 흔한 패턴이었고 라이브러리 호출도 이상한 데가 없었고 PWM 값도 범위 안이었습니다. 그런데 안 돌았습니다.
핀맵을 다시 봤습니다. IN1은 8번, IN2는 9번, ENA는 10번. IN3는 11번, IN4는 12번, ENB는 13번. 제가 Claude에게 불러준 그대로였습니다.
두 시간쯤 봤던 것 같습니다. 코드를 뜯어보고, 배선을 뜯어보고, 멀티미터를 대보고, 다시 코드를 짜고. 그러다 ENA에 지정한 10번 핀이 Timer1을 쓰고 있다는 걸 알았습니다. 같은 보드에서 서보 라이브러리도 쓰고 있었는데 둘이 같은 타이머를 두고 다투고 있었던 겁니다. Claude가 짠 코드는 코드만 보면 멀쩡했습니다. 제 보드에 서보가 달려 있다는 걸 모르고 짰을 뿐입니다.
화가 나지는 않았는데 머리가 좀 차가워졌습니다.
ENA가 Timer1에 묶인다는 건 데이터시트에 있는 내용이고 Claude도 그걸 압니다. AFMotor 라이브러리가 Timer1과 Timer2를 잡아서 충돌이 날 수 있다는 것도 압니다. 그날 새벽 그 세션에 제 보드 사정이 안 들어 있었던 겁니다. 서보가 있다는 것도, ENA를 다른 핀으로 바꿔도 된다는 것도, 13번은 원래 PWM이 안 되는 핀이라는 것도요.
비슷한 일이 그 전에도 있었습니다. 사주 앱을 만들 때도, 타로 앱을 만들 때도, 아이랑 미연시를 만들 때도. 그때마다 다른 이유를 붙이고 넘어갔습니다. 사주는 도메인이 복잡해서, 타로는 제가 결정을 못 해서, 미연시는 감정이 원래 어려운 영역이라서. 로봇도 그 새벽에는 임베디드라서 그렇겠거니 했습니다.
사주는 22글자로 시작해서 35페이지로 끝났습니다
사주를 만들기로 했을 때는 솔직히 만만하게 봤습니다. 타로를 막 끝낸 직후였는데, 타로는 카드 78장에 리딩 레이아웃 몇 개라 한 세션 안에 다 들어왔습니다. 사주는 천간 10개, 지지 12개. 스물두 글자니까 타로보다 한참 적다고 생각했습니다.
"혹시 사주 프로그램을 만들고 싶어. 데이터는 좀 있어?"
이 한 문장을 던졌더니 10분도 안 돼서 도메인 윤곽이 돌아왔습니다. 60갑자, 오행 상생상극, 십신, 지장간, 12운성, 합충형파해. 각각이 뭔지, 서로 어떻게 엮이는지, 코드로는 어떤 구조로 잡으면 되는지까지요. 혼자 공부했으면 몇 주는 걸렸을 걸 10분에 받으니까 처음엔 좀 들떴습니다. 이러면 웬만한 분야는 다 손 닿는 데 있는 거 아닌가 싶었습니다.
코드를 쓰기 시작하니까 얘기가 달라졌습니다.
기존 사주 앱들을 몇 개 비교해봤는데, 같은 생년월일시를 넣어도 앱마다 결과가 달랐습니다. 시주가 다르고 월주가 다르고, 어떤 건 일주까지 달랐습니다. 제가 잘못 넣었나 싶어서 다시 넣어도 같았습니다. 해석이 다른 게 아니라 여덟 글자 자체가 달랐습니다.
같은 입력에 다른 출력이 나오는 건 개발하는 입장에서는 좀 받아들이기 어려운 일입니다. 들여다보니 이유가 세 가지쯤 있었습니다.
하나는 진태양시입니다. 한국 표준시는 동경 135도 기준인데 서울은 127도 근처라서 30분쯤 차이가 납니다. 시주는 두 시간 단위로 잡으니까 그 30분 때문에 시주가 통째로 바뀌기도 합니다. 이걸 보정하는 앱과 안 하는 앱의 결과가 달랐습니다.
야자시도 있었습니다. 밤 11시에서 자정 사이에 태어난 사람의 일주를 그날로 보느냐 다음 날로 보느냐인데, 학파마다 견해가 다릅니다. 일주가 바뀌면 십신을 볼 때 기준이 되는 일간이 바뀌고, 그러면 해석 전체가 다른 얘기가 됩니다.
서머타임도 있습니다. 한국도 예전에 서머타임을 한 적이 있어서, 그 시기에 태어난 사람은 출생 시각을 거꾸로 보정해야 합니다. 하는 앱도 있고 그냥 무시하는 앱도 있었습니다.
절기도 걸렸습니다. 월주는 음력 날짜가 아니라 절기로 정합니다. 입춘이 지나야 새해고 경칩이 지나야 2월입니다. 처음엔 절기를 대충 날짜로 처리했더니 절기 경계에 태어난 사람들 결과가 이상하게 어긋났습니다. 한국천문연구원이 절기 시각을 분 단위로 공개한다는 걸 알고 그걸 쓰고 나서야 입춘 당일생 월주가 제대로 나왔습니다.
이걸 Claude에게 한 번 설명하고 코드를 받으면 끝일 줄 알았는데, 다음 날 새 세션을 열면 처음부터 다시였습니다.
"이 프로젝트는 진태양시 보정을 적용해, 야자시는 당일 처리야, KASI 절기 데이터를 분 단위로 써. 서머타임은 1950~60년대만 보정해."
이걸 매번 다시 쳤습니다. 한두 번은 괜찮았는데 다섯 번째쯤 되니까 진이 빠졌습니다. 그리고 손으로 칠 때마다 조금씩 빠뜨리는 게 생겼습니다. 말로 설명하면 꼭 하나씩 빠지고, 빠뜨린 날 받은 코드는 빠뜨린 대로 짜여 나왔습니다.
그래서 문서를 만들었습니다. docs/ 폴더에 네 개를 뒀습니다. 아키텍처 문서에는 계산 엔진과 해석 레이어를 왜 나눴는지를 적었습니다. 만세력 계산은 누가 언제 돌려도 같은 값이 나와야 하고, 해석은 LLM이 매번 조금씩 다르게 써도 괜찮은 영역입니다. 둘을 섞어두면 같은 사주에 같은 여덟 글자가 안 나올 수 있습니다. 도메인 모델 문서에는 천간·지지·오행·십신의 관계와 코드에서 각각을 뭐라고 부를지를 넣었습니다. 계산 엔진 문서에는 진태양시·야자시·서머타임·절기를 어떤 순서로 처리하는지 적었습니다. 이 순서가 의외로 중요해서, 서머타임 역보정을 먼저 하고 진태양시를 적용해야 맞습니다. 반대로 하면 30분이 두 번 어긋납니다. AI 해석 문서에는 프롬프트에 어떤 필드를 넘기고 어떤 톤으로 받을지를 뒀습니다.
도메인 모델 문서는 35페이지가 됐습니다. 사이드 프로젝트에 35페이지는 좀 우습긴 한데, 그게 있으니까 새 세션에서 @docs/ 한 번이면 다 아는 상태로 시작할 수 있었습니다. 수십 번은 세션을 열었으니 시간으로 따져도 차이가 컸고, 그보다는 빠뜨리는 게 없어진 게 더 컸습니다.
이름 문제도 이걸로 정리됐습니다. 문서 없이 함수를 하나씩 따로 부탁하던 때는 같은 개념이 함수마다 다른 이름으로 나왔습니다. "일간"이 어떤 함수에서는 dayStem이고 다른 함수에서는 mainStem인 식이었습니다. 문서에 표기를 한 번 정해두니까 그런 일이 없어졌고, 십신 함수를 새로 짤 때도 일간 함수가 뭘 돌려주는지, 오행 매핑이 어떻게 생겼는지 이미 아는 상태라 서로 잘 맞물렸습니다.
생각해보면 Claude가 몰라서 생긴 일은 아니었습니다. 진태양시도 야자시도 물어보면 다 설명해줍니다. 다만 제가 이 프로젝트에서 야자시를 당일로 보기로 했다는 건 제 머릿속에만 있었습니다. 그건 지식이 아니라 제가 내린 결정이라서 어디서 배워 올 수 있는 게 아닙니다. 똑똑하냐 아니냐랑은 상관이 없는 얘기였던 것 같습니다.
타로는 78장이 아니라 156개였습니다
타로는 사주보다 먼저 했던 프로젝트입니다.
"타로카드를 보는 웹페이지를 만들고 싶어." 이 한 문장으로 시작했습니다. 기획서 같은 건 없었습니다. 평소에 관심이 있었고 웹으로 만들면 재밌겠다 싶었던 게 다였습니다.
스펙은 금방 잡혔습니다. Claude가 질문을 세 개 던졌고 거기에 답하다 보니 그렇게 됐습니다. 리딩은 원카드, 쓰리카드, 켈틱 크로스 세 가지로 하고 자유 선택 모드는 뺐습니다. 타로를 잘 모르는 사람한테 "몇 장 볼까요"를 묻는 건 친절이라기보다 부담이라고 봤습니다. 해석은 LLM API를 매번 부르지 않고 고정 텍스트로 가기로 했습니다. 스택은 React + Vite + TypeScript, 이름은 "타로 마스터". 이름 짓는 데는 5초도 안 걸렸습니다.
여기까지는 사주 때랑 비슷했습니다. 도메인이 짧으면 윤곽이 빨리 나오고, 정할 것들을 늘어놓아 주면 제가 뭘 먼저 보고 있는지가 좀 또렷해집니다.
하다 보니 일이 두 군데서 커졌습니다.
하나는 양입니다. 78장이면 되는 줄 알았는데 정방향과 역방향 해석이 따로라서 156개가 필요했습니다. 역방향이 그냥 나쁜 뜻도 아니었습니다. 정방향이 카드의 에너지가 자연스럽게 드러난 상태라면 역방향은 그게 억눌리거나 넘치거나 비틀린 상태입니다. The Magician 역방향은 "무능력"이 아니라 "속임수, 재능 낭비, 조작" 쪽입니다. 능력은 있는데 엉뚱한 데 쓰고 있다는 뉘앙스입니다. 이런 걸 156개 다 써야 했습니다.
다른 하나는 저작권입니다. 라이더-웨이트-스미스 덱이 제일 유명한데, 1909년 원본은 퍼블릭 도메인이지만 1971년에 US Games Systems가 다시 색을 입힌 버전은 따로 저작권이 있습니다. 인터넷에서 찾으면 나오는 이미지가 대부분 1971년 버전이거나 거기서 나온 것들이었습니다. 색감이 미묘하게 다르고 디테일이 조금씩 고쳐져 있어서 전문가가 아니면 구분을 못 합니다. 아무 생각 없이 가져다 썼으면 곤란해질 뻔했습니다.
둘 다 대화하다가 Claude 쪽에서 먼저 꺼낸 얘기였습니다. 카드가 몇 장이냐에서 시작해서 이미지를 어디서 구하냐, 저작권은 어떻게 되냐, 원본이랑 재색칠본이 다르다, 이런 게 한 대화 안에서 줄줄이 나왔습니다. 검색으로 여기까지 오려면 탭을 열 개는 열었을 것 같습니다.
정작 오래 붙잡고 있었던 건 156개를 누가 쓰느냐였습니다. 제가 직접 쓰기엔 너무 많았습니다. 매번 AI가 생성하게 하면 쓰는 사람이 늘수록 API 비용이 늘어나는데, 돈이 들어오기 전에 돈이 나가는 구조는 사이드 프로젝트에서 오래 못 버팁니다. 그리고 타로는 카드를 넘기자마자 결과가 보이는 그 순간이 재미인데, API 응답을 2~3초 기다리게 되면 그 맛이 없어집니다.
그래서 AI가 한 번 생성한 텍스트를 제가 다듬어서 정적 데이터로 넣었습니다.
다듬는 게 제일 손이 많이 갔습니다. 156개를 한 번에 받으면 뒤로 갈수록 문장이 서로 닮아갔습니다. 앞쪽 스무 개쯤은 "이 카드는 ~을 의미합니다"로 시작하고, 중간부터는 "~한 시기입니다"가 계속 나오는 식이었습니다. 그래서 메이저 아르카나 22장을 먼저 받아서 문장 모양을 정하고, 나머지는 슈트별로 나눠 받았습니다. 완드는 완드끼리, 컵은 컵끼리. 그러니까 슈트 안에서는 톤이 맞고 슈트끼리는 서로 달라졌습니다.
이 결정이 나오기까지가 좀 재밌었습니다. 처음에 "어떻게 해야 하지?" 하고 막연하게 물었으면 아마 "둘 다 가능합니다" 같은 답이 왔을 겁니다. 틀린 말은 아닌데 아무 도움이 안 되는 답입니다. API 비용, 응답 지연, 카드 넘기는 순간의 몰입 같은 걸 같이 넘겨주고 나서야 어느 쪽으로 갈지가 정해졌습니다. AI는 선택지를 펼치는 건 정말 잘하는데, 그중에 뭐가 제 프로젝트에 맞는지는 제가 뭘 더 중요하게 보는지를 알아야 나옵니다. 그건 제가 말 안 하면 모릅니다.
그때는 차라리 Claude가 하나 딱 골라줬으면 좋겠다는 생각도 들었습니다. 그런데 골라줬다고 해도 그게 맞는지는 결국 제가 봐야 했을 것 같습니다. 카드 넘길 때 2초를 기다리는 게 싫다는 건 제 취향이고, 그 취향을 모르는 쪽이 고른 답이면 저는 그걸 다시 의심했을 겁니다.
사주는 같은 설명을 매번 반복하느라 지쳤던 거고 타로는 그런 건 없었는데, 둘 다 제 쪽에만 있던 걸 AI가 몰랐다는 점에서는 비슷했던 것 같습니다. 이건 한참 나중에 든 생각입니다.
미연시 코드는 30분, 감정은 아직 모르겠습니다
세 번째는 좀 의외의 데서 시작했습니다.
"아빠가 요즘 취미로 게임을 만들고 있는데, 너도 같이 만들어볼래?"
아이에게 물었더니 눈이 커졌습니다. "진짜? 나도 할 수 있어?" 어떤 장르를 좋아하냐고 다시 물었습니다. 슈팅이나 퍼즐, 아니면 마인크래프트 같은 걸 말할 줄 알았습니다.
잠깐 생각하더니 아이가 말했습니다. "미연시."
좀 당황했습니다. 어디서 알게 된 건지, 친구들 사이에 유행하는 건지는 모르겠는데 표정은 꽤 진지했습니다. 그 대답이 이상하게 계속 머리에 남았습니다. 중학생 여자아이의 취향에 놀란 것도 있었지만, 그것보다는 개발자로서 뭔가 반응이 온 것 같습니다.
"미연시 게임을 만들고 싶어. 어디서부터 시작해야 할까?"
돌아온 답에서 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랑 같이 하기 좋은 일 아닌가 싶었습니다.
"감정 시스템을 어떻게 만들면 좋겠어?"라고 물었습니다. 호감도를 올리고 내리는 정도를 예상했는데 답이 의외였습니다. 감정을 층으로 나누자고 했습니다. 성향, 순간 감정, 관계 상태를 따로 두고, 사랑을 변수 하나로 두지 말고 신뢰와 친밀감과 상실 공포가 섞인 걸로 보자고요.
이건 좀 놀랐습니다. 도메인 윤곽을 잡아주는 건 이미 봤는데 감정 모델을 이렇게까지 쪼개줄 줄은 몰랐습니다. 저 혼자 했으면 호감도 하나로 갔을 겁니다. 0에서 100, 선택지 누를 때마다 +5 아니면 -5. 그게 다였을 겁니다. 세 개로 나눠서 보니까 같은 사랑이어도 어떤 사랑이냐에 따라 결말이 달라지는 구조가 보였습니다. 신뢰는 높은데 친밀감이 낮으면 어딘가 먼 관계가 되고, 친밀감은 충분한데 상실 공포가 크면 매번 확인받고 싶어 하는 관계가 됩니다.
실제로는 Ren'Py 변수 세 개로 시작했습니다. trust, intimacy, fear_of_loss. 선택지마다 세 값이 따로 오르내리게 했습니다. 약속을 지키는 선택은 신뢰만 올리고 친밀감은 그대로 둡니다. 갑자기 거리를 좁히는 선택은 친밀감을 올리면서 상실 공포도 같이 올립니다. 엔딩은 총합이 아니라 어느 값이 높은지로 나뉘게 했습니다.
돌려보니 호감도 하나였으면 안 나왔을 결말이 몇 개 생겼습니다. 셋 다 높은데 상실 공포만 유난히 높은 경우가 그랬습니다. 겉으로는 잘 지내는데 계속 불안한 관계. 그게 코드로는 조건문 세 줄이었습니다.
며칠 뒤에 아이한테 말했습니다.
"미연시 만들어보자. 네가 선택하면 이야기가 달라지는 거."
"내가 고르는 거야?"
"응. 뭘 고르느냐에 따라 이야기가 달라져."
"그러면 나쁜 걸 고르면 어떻게 돼?"
"슬퍼질 수도 있지."
"왜?"
그 질문에 바로 대답을 못 했습니다.
왜 슬퍼질까요. 가상의 캐릭터에 가상의 이야기인데 사람은 왜 거기서 진짜로 슬퍼할까요. 아이는 그냥 물어본 건데, 듣고 나니 이 프로젝트에서 제일 중요한 질문이 그거였던 것 같습니다.
그래서 그대로 AI에게 물었습니다. "왜 사람은 가상 관계에서 진짜 감정을 느끼는가?" "감정적으로 몰입할 수 있기 때문", "선택이 의미를 부여하기 때문" 같은 답이 왔습니다. 틀린 말은 아닌데 제가 알고 싶은 건 거기 없었습니다.
코드는 Ren'Py가 다 해주니까 막힐 게 없었고 감정 모델도 잘 잡혔는데, "왜 그런가"로 한 칸 내려가니까 답이 갑자기 얕아졌습니다. 구조를 짜고 코드를 쓰는 데서는 그렇게 잘 따라오던 게 거기서는 안 따라왔습니다.
처음엔 도구의 한계라고 생각했습니다. 모델이 더 좋아지면 풀리겠지 하고요. 시간이 좀 지나고 나서는 생각이 조금 바뀌었습니다. 질문 자체가 종류가 달랐던 것 같습니다. 정보로 답할 수 있는 질문이 있고 겪어봐야 답할 수 있는 질문이 있는데, 그걸 같은 방식으로 물었으니 한쪽이 안 풀리는 게 당연했던 것 같습니다.
솔직히 그 질문은 저한테 물어도 마찬가지입니다. 아이한테 바로 대답을 못 한 것도 그래서였을 겁니다. 제가 못 하는 대답을 AI는 해주길 바랐던 건, 지금 생각하면 좀 이상한 기대였습니다.
로봇에서 네 번째로 멈췄습니다
올봄에 아이한테 물었습니다. "아빠랑 로봇 만들어볼래? 카메라 달고, 센서 달고, AI가 뭘 볼지 판단하는 로봇." 2초 정도 생각하더니 "할게!"였습니다. 그날 저녁에 혼자 유튜브에서 로봇 영상을 찾아보더니, 다음 날 "내가 로봇 몸통 담당할게" 했습니다.
역할은 그렇게 정해졌습니다. 아들은 하드웨어를 맡았습니다. 조립, 결선, 센서 연결, 본체. 저는 소프트웨어였습니다. LLM 서버, 코딩 에이전트, 통신 레이어. 같이 하는 건 에이전트를 자연어로 지휘하는 일이었고, 그게 넉 달쯤 이어졌습니다.
LLM은 클라우드 API 대신 집에 있는 Mac에서 로컬로 돌리기로 했습니다. 로봇이 판단할 때마다 인터넷을 거치면 지연이 생기는데, 집 안 LAN에서 처리하면 그게 거의 없습니다. Mac mini M1 16GB, Mac mini M4 24GB, MacBook Pro M4 Pro 24GB 세 대를 비교하면서 썼는데, Apple Silicon의 통합 메모리가 로컬 추론에 생각보다 잘 맞았습니다.
세 대를 써보니 차이가 난 건 속도보다 모델 크기 쪽이었습니다. 16GB에서는 쓸 만한 크기의 모델을 올리면 다른 걸 못 띄웠습니다. 브라우저 탭 몇 개만 열어도 스왑이 시작됐습니다. 24GB에서는 같은 모델을 올려두고도 개발 환경이 같이 돌았습니다. 로봇이 판단을 요청하는 간격이 초 단위라 속도는 셋 다 충분했고, 결국 모델을 올려둔 채로 다른 일을 할 수 있느냐로 정했습니다.
여기까지는 순조로웠고, 그다음이 처음에 쓴 그 새벽입니다.
두 시간이 거의 끝나갈 때쯤 든 생각은, AI가 못한 것도 아니고 모르는 것도 아니라는 거였습니다. 다 알 수 있는 정보인데 그 세션에 없었을 뿐입니다.
그래서 정보를 들고 다니기로 했습니다. CLAUDE.md라는 파일에요. 처음엔 핀맵만 적은 10줄짜리였습니다. Timer1 일이 있고 나서 "라이브러리 선택 이유" 섹션이 생겼습니다. AFMotor는 안 쓴다, Timer1과 Timer2를 잡기 때문이다. NewPing을 쓴다, 비동기 핑이라 루프를 멈추지 않기 때문이다. 이유까지 적은 건 "AFMotor 안 씀"만 써두면 다음번에 또 AFMotor를 쓰는 코드가 나오기 때문입니다. 이유가 같이 있어야 그 제약을 지켰습니다.
이건 AI한테만 그런 게 아니라 저한테도 마찬가지일 것 같습니다. 몇 달 뒤에 제가 CLAUDE.md를 열었는데 "Timer1 사용 금지"라는 줄만 덩그러니 있으면, 그때쯤이면 그 새벽 일은 잊어버렸을 테니 이게 왜 있지 하고 지워버릴 수도 있습니다. 누가 왜 적어놨는지 모르는 규칙은 오래 안 남습니다. 그 줄 옆에 서보랑 같은 타이머를 써서 바퀴 하나가 안 돌았다는 말이 한 줄만 붙어 있어도 손을 안 댈 겁니다. 그렇게 보면 CLAUDE.md는 AI에게 주는 지시라기보다 같은 실수를 두 번 안 하려고 적어둔 메모에 더 가깝습니다. 읽는 쪽이 AI든 몇 달 뒤의 저든 별로 다르지 않습니다.
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 지점에서 딱 멈췄습니다.
"된다!"
열두 살이 자기 말 한 줄로 로봇을 멈춘 겁니다. 아들은 핀맵을 외울 필요가 없었습니다. CLAUDE.md에 있었으니까요. 라이브러리가 왜 그건지도 몰라도 됐습니다. 아이는 원하는 동작만 자기 말로 설명했습니다.
처음엔 네 개가 다 따로라고 생각했다고 앞에 썼는데, 로봇까지 하고 나서 네 개를 같이 놓고 보니 비슷한 데가 있었습니다.
새 세션을 열 때마다 처음부터 다시 설명해야 했다는 게 제일 먼저 보였습니다. 사주에서는 진태양시, 야자시, 서머타임. 타로에서는 1909년 원본만 쓴다는 것. 미연시에서는 감정을 세 층으로 본다는 것. 로봇에서는 핀맵과 라이브러리를 고른 이유. 처음 한 번은 다 AI 도움을 받아서 정했는데, 그 결정이 어디에도 안 적혀 있으면 새 세션에서는 없던 일이 됐습니다. 그래서 하게 된 일도 비슷했습니다. 사주의 35페이지짜리 도메인 모델 문서, 로봇의 120줄짜리 CLAUDE.md, 타로의 고정 텍스트 데이터. 다 AI가 안에서 기억해주길 바라지 않고 바깥에 적어둔 다음 들고 들어가는 방식이었습니다.
AI가 모르는 제약을 모르는 채로 자신 있게 코드로 짠 것도 비슷했습니다. Timer1 일이 제일 분명했고, 사주도 처음 받은 만세력 코드는 평범한 음양력 변환이었습니다. 야자시를 학파에 따라 어떻게 볼지 같은 건 빠져 있었습니다. 알려주면 들어갔고, 안 알려주면 뭐가 빠졌는지도 모르는 코드가 나왔습니다.
이게 좀 까다롭다고 느낀 건 AI가 정보가 없다는 걸 말해주지 않아서입니다. 모르는 채로 짠 코드가 아무 표시 없이 나옵니다. 게다가 AI 코드는 대체로 깔끔해 보입니다. 사람이 급하게 짠 코드면 처음부터 좀 의심하면서 보는데, 깔끔한 코드는 의심이 잘 안 듭니다. 그날 새벽에 저도 코드는 의심하지 않았습니다. 그러다 한쪽 바퀴만 도는 새벽 한 시를 만나는 겁니다.
또 하나는 표면에서 한 칸만 내려가면 답이 평평해졌다는 겁니다. 사주는 도메인 지도는 10분 만에 나왔지만 어느 학파를 따르면 뭐가 달라지는지는 사람이 정리해야 했습니다. 타로는 카드 구조는 금방 나왔지만 비용이랑 지연을 놓고 어느 쪽으로 갈지는 제가 우선순위를 말해야 정해졌습니다. 미연시는 감정 모델을 층까지 나눠줬는데 사람이 왜 가상에서 진짜 감정을 느끼는지는 일반론에서 멈췄습니다. 로봇은 코드 패턴은 정상이었는데 제 보드의 다른 라이브러리와 부딪히는 건 제가 짚어야 했습니다.
그 한 칸 아래에 있던 것도 다 같은 건 아니었던 것 같습니다. 핀맵, 라이브러리 충돌, 학파 차이, 제 우선순위 같은 건 누가 봐도 알 수 있는 정보인데 제가 안 적어준 것들입니다. 이건 적으면 풀립니다. 미연시에서 만난 질문은 그런 정보가 아니었습니다. 적어서 넘겨주려고 해도 적을 게 제 안에도 없습니다. 둘이 같은 데서 같은 모양으로 멈춰서 처음엔 하나로 보였는데, 꽤 지나고 나서야 따로 보이기 시작했습니다.
패턴이 보이고 나서 한동안 멍하니 앉아 있었습니다. 이게 도구가 잘못된 거라고는 생각하지 않습니다. 원래 사람이랑 도구가 만나는 데가 이런 것 같습니다. 도구는 일반적인 걸 들고 있고 저는 제 프로젝트의 구체적인 걸 들고 있는데, 그 둘이 한곳에 모이지 않으면 늘 같은 데서 발이 빠집니다.
모델이 좋아지면 표면은 더 빨리 잡고 코드도 더 정교하게 짜겠지만, 이건 그대로일 것 같습니다. npx 한 줄로 깔리는 멀티 에이전트 플랫폼의 거버넌스 구조를 뜯어본 적이 있는데, 거기서도 미리 준비돼 있는 건 일반적인 골격까지였습니다. 제 보드에 어떤 라이브러리가 올라가 있는지, 제가 어떤 학파를 따르기로 했는지, 타로를 보는 사람이 카드 넘기는 순간의 즉각성을 얼마나 중요하게 느낄지 같은 건 모델이 알 방법이 없습니다. 알려줘야 알고, 알려주려면 제가 어딘가에 적어둬야 합니다.
이걸 알고 나서 달라진 게 있다면 화가 덜 난다는 겁니다. 전에는 두 시간 디버깅하다 보면 이렇게 깔끔한 코드가 왜 안 도냐고 짜증이 났습니다. 지금은 같은 두 시간이어도 어디서 막혔는지 대충 짐작이 갑니다. 아 이건 내가 안 알려줘서 그렇구나 하는 생각이 먼저 들면, 그다음 일은 디버깅이라기보다 빠진 걸 적어 넣는 일이 됩니다. 시간은 비슷하게 들어도 머리는 덜 무겁습니다.
그래서 이제는 매번 처음부터 다시 시작하지 않으려고 합니다. 미연시는 아직 하는 중인데 감정 계층 모델을 따로 문서로 빼고 있습니다. 하다 보니 프로젝트마다 같은 걸 손으로 다시 만들고 있다는 것도 알게 됐습니다. 핀맵 표 짜고, 라이브러리 고른 이유 적고, 동작 제약 정리하고, 코딩 규칙 붙이고. 모양이 거의 같아서 그걸 따로 정리하는 중입니다. 다음 프로젝트에서는 이걸 처음부터 안 만들려고요. 60일에 60만 줄을 혼자 짰다는 YC 대표의 작업 방식을 뜯어봤을 때도 줄 수보다는 23개짜리 슬래시 커맨드 묶음이 더 눈에 들어왔습니다. 가져올 게 있다면 그쪽일 것 같습니다.
미연시에서 만난 건 적어서 풀리는 게 아니라는 것도 그냥 받아들이기로 했습니다. "왜 사람은 가상에서 진짜 감정을 느끼는가"는 제가 적어 넣을 답이 없는 질문입니다. 도구를 바꿔서 풀 일이 아니라, 옛날에 했던 게임들을 다시 떠올려보거나 아이가 물어본 "왜?"에 시간을 좀 두고 답을 찾아볼 일인 것 같습니다.
- 공유 링크 만들기
- X
- 이메일
- 기타 앱
댓글
댓글 쓰기