아들과 함께 만들어보는 인공지능(LLM) 로봇 만들기 프로젝트 — EP 1. AI 코딩 에이전트 하네스 설계, 12살도 쓸 수 있게
아들과 함께 만들어보는 인공지능(LLM) 로봇 만들기 프로젝트 — EP 1. AI 코딩 에이전트 하네스 설계, 12살도 쓸 수 있게
처음으로 Claude Code에게 로봇 코드를 짜달라고 했던 날이 있었다.
"L298N 모터 드라이버로 DC 모터 2개를 PWM으로 속도 제어하는 아두이노 코드 짜줘. IN1은 핀 8, IN2는 핀 9, ENA는 핀 10. IN3은 핀 11, IN4는 핀 12, ENB는 핀 13."
코드가 나왔다. 깔끔했다. 그런데 테스트해보니 모터가 이상하게 작동했다. ENA 핀을 10번으로 지정했는데 코드 내부에서 Timer1을 건드리고 있었고, 그게 서보 라이브러리와 충돌하고 있었다. 에러 메시지가 애매하게 나와서 원인을 찾는 데만 두 시간이 걸렸다.
Claude는 내가 서보 라이브러리를 같이 쓰고 있다는 걸 몰랐다. 핀 10번과 Timer1의 관계도 내 세팅 맥락에서 따로 고려하지 않았다. 범용 코드로서는 나쁘지 않았다. 그냥 내 프로젝트를 몰랐던 것뿐이다.
그 에러 하나가 "범용 AI 코딩 도구를 그냥 쓰면 안 된다"는 결론으로 이어졌다.
Claude Code를 기본 세팅으로 쓰면 생기는 문제
Claude Code든, Cursor든, Aider든 — 범용 AI 코딩 도구는 기본적으로 코드를 잘 짠다. 언어 문법 이해하고, 라이브러리 사용법 알고, 버그도 잘 잡는다. 일반적인 웹 서비스나 스크립트를 만들 때는 큰 문제가 없다.
임베디드 로봇 프로젝트는 다르다.
내 프로젝트에는 고유한 제약이 있다. 내가 사용하는 부품 목록이 있고, 그 부품들의 핀맵이 있다. L298N 모터 드라이버에서 IN1은 8번 핀, ENA는 10번 핀. HC-SR04 초음파 센서는 Trig가 4번, Echo가 5번. 이걸 Claude가 모른다. 매번 알려줘야 한다.
라이브러리 선택 이유도 있다. 나는 모터 드라이버에 AFMotor 라이브러리를 쓰지 않기로 했다. Timer1, Timer2를 너무 많이 점유해서, 나중에 서보 라이브러리나 인터럽트 기반 센서를 추가할 때 충돌이 생긴다. 초음파 센서는 NewPing을 쓰기로 했다. 타이밍 정밀도 때문이다. 이런 결정의 이유까지 Claude가 알 리 없다.
결국 매 대화마다 이런 식으로 반복했다.
"우리 프로젝트에서 모터 드라이버는 L298N을 씁니다. IN1=8, IN2=9, ENA=10, AFMotor는 Timer 충돌로 사용 금지. 초음파는 NewPing 씁니다. HC-SR04 Trig=4, Echo=5. ROS2 토픽은 /robot/cmd_vel 형식으로…"
대화가 새로 시작될 때마다 이걸 다시 썼다. 빠뜨리면 코드가 엉뚱하게 나왔다. 이걸 계속 반복하는 게 피로했고, 아들한테 이 도구를 쥐여줄 수가 없었다. 아들이 이 컨텍스트를 매번 타이핑할 수는 없으니까.
에이전트 하네스, 다시 정리
EP 0에서 짧게 언급했던 개념인데 여기서 좀 더 구체적으로 풀어본다.
에이전트 하네스는 모델 바깥에 있는 모든 것이다. Agent = Model + Harness라는 공식으로 쓴다.
모델 자체(Claude, GPT 등)는 내가 바꿀 수 없다. 학습 데이터, 가중치, 추론 방식은 Anthropic이나 OpenAI 영역이다. 하지만 모델이 작동하는 환경은 내가 설계할 수 있다. 이게 하네스다.
Claude Code에서 하네스는 .claude/ 디렉토리 전체로 구성된다. 에이전트, 스킬, 훅, 그리고 프로젝트 컨텍스트 문서.
.claude/
├── CLAUDE.md # 프로젝트 전체 맥락 (핀맵, 제약, 규칙)
├── agents/
│ ├── hardware-agent.md # 아두이노/Pi 코드 전담 에이전트
│ └── ros2-agent.md # ROS2 노드·토픽 전담 에이전트
├── skills/
│ ├── upload-code.md # 코드 업로드 스킬
│ └── test-sensor.md # 센서 검증 스킬
└── settings.json # 훅, 권한 설정
에이전트는 특정 역할을 맡은 서브 에이전트다. hardware-agent는 핀맵과 라이브러리 규칙만 알면 되고, ros2-agent는 토픽 명명 규칙과 노드 구조만 집중한다. 역할을 분리하면 각 에이전트가 자기 영역에서 더 정확하게 작동한다.
스킬은 반복 작업을 정의해두는 곳이다. "센서 테스트"라고 부르면 센서 값을 읽고 정상 범위를 확인하는 일련의 작업이 실행된다. 매번 같은 절차를 설명할 필요가 없다.
훅은 이벤트 기반 자동 실행이다. 코드 파일이 저장되면 자동으로 린트를 돌린다거나, 특정 조건에서 경고를 낸다거나 하는 걸 settings.json에 정의한다.
CLAUDE.md는 이 중 하나다. 프로젝트 전체가 공유하는 컨텍스트 문서. 핀맵, 라이브러리 선택 이유, 동작 제약 조건 같은 것들이 여기 들어간다. 하네스의 일부지, 하네스 전체가 아니다.
하네스 구성, Claude가 만들었다
트렌드가 바뀌었다. 예전이라면 어떤 에이전트가 필요한지, 스킬을 얼마나 쪼개야 하는지, 훅을 어디에 걸어야 하는지 내가 다 설계했을 거다. 지금은 다르다. 방향을 제시하면 Claude가 구성한다. .claude/ 폴더 전체를 처음부터 내가 설계하는 게 아니라, 프로젝트가 뭔지 설명하면 Claude가 그에 맞는 하네스를 만들어준다.
실제로 그렇게 했다. Claude에게 프로젝트를 설명하고, 관련 GitHub 레포들을 학습시킨 다음 하네스 전체를 구성하게 했다.
실제로 이렇게 입력했다.
이 프로젝트는 Arduino Uno + L298N 모터 드라이버 + HC-SR04 초음파 센서 + USB 웹캠으로 구성된
AI 로봇 프로젝트야. 나중에 Raspberry Pi로 전환 예정이고, ROS2를 붙일 거야.
LLM 서버는 로컬 Mac에서 돌리고, 로봇이 LAN으로 명령을 받는 구조.
아래 레포들을 참고해서 이 프로젝트에 맞는 에이전트, 스킬, 훅 구성을 포함한 하네스를 만들어줘:
- https://github.com/teckel12/arduino-new-ping (초음파 센서 라이브러리)
- https://github.com/adafruit/Adafruit-Motor-Shield-library (모터 드라이버 참고용, 우리는 직접 제어할 거라 이걸 안 씀)
Claude가 두 레포의 코드를 읽고 .claude/ 구조 전체를 만들었다. hardware-agent와 ros2-agent를 분리해서 각자 자기 도메인만 집중하게 했다. NewPing의 비동기 핑 방식을 읽고 "블로킹 delay 방지"라는 이유를 스스로 CLAUDE.md에 붙였다. Adafruit 라이브러리 코드를 보고 타이머 점유 방식을 파악해서 hardware-agent에 "AFMotor Timer 충돌 주의" 제약을 넣었다. 내가 직접 찾아서 쓴 게 아니라 Claude가 코드를 읽고 판단했다.
거기에 내 구체적인 배선 정보(핀 번호)를 추가로 알려줬고, 처음 몇 번 실제로 돌려보면서 틀린 부분만 수정했다.
에이전트, 스킬, 훅 구조를 처음부터 내가 다 설계할 필요 없이, Claude가 이 종류의 프로젝트에서 필요한 걸 먼저 잡아줬다. 나는 거기서 틀린 것만 고쳤다.
CLAUDE.md는 그 하네스 안의 컨텍스트 문서 부분이다. 지금 구조는 이렇다.
# Robot Project — AI Coding Harness
## 하드웨어 구성
### 엣지 (로봇 본체)
- MCU: Arduino Uno (→ Raspberry Pi 4B 전환 예정)
- 카메라: USB 웹캠 (720p, 30fps)
- 거리 센서: HC-SR04 초음파 센서
### 핀맵 (Arduino Uno 기준)
| 컴포넌트 | 핀 | 비고 |
|---|---|---|
| L298N IN1 | 8 | 좌측 모터 방향 A |
| L298N IN2 | 9 | 좌측 모터 방향 B |
| L298N ENA | 10 | 좌측 모터 속도 (PWM) |
| L298N IN3 | 11 | 우측 모터 방향 A |
| L298N IN4 | 12 | 우측 모터 방향 B |
| L298N ENB | 13 | 우측 모터 속도 (PWM) |
| HC-SR04 Trig | 4 | 초음파 송신 |
| HC-SR04 Echo | 5 | 초음파 수신 |
## 라이브러리 선택 (이유 포함)
- **초음파 센서**: NewPing
- 이유: 블로킹 방식(pulseIn)은 루프를 멈추게 함. NewPing은 비동기 핑 지원
- **모터 드라이버**: 직접 digitalWrite/analogWrite (AFMotor 불사용)
- 이유: AFMotor가 Timer1, Timer2 점유 → 서보/인터럽트 기반 센서 추가 시 충돌
- **서보 (추후)**: Servo.h (표준 라이브러리)
## ROS2 구조 (Pi 전환 이후)
### 토픽 명명 규칙
- 모터 명령: /robot/cmd_vel (geometry_msgs/Twist)
- 초음파 데이터: /robot/sensor/ultrasonic (sensor_msgs/Range)
- 카메라 프레임: /robot/camera/frame (sensor_msgs/Image)
- LLM 명령: /robot/llm/command (std_msgs/String)
## 동작 제약 조건 (안전)
- HC-SR04 측정값 20cm 이하: 전진 명령 무시
- 측정값 15cm 이하: 즉시 정지 + 후진 30cm
- 최대 PWM: 200/255 (배터리 수명 보호)
## 코딩 규칙
- Timer1 사용 금지 (서보 라이브러리 충돌)
- Serial 디버그 출력: 9600 baud 통일
- loop() 내 delay() 사용 금지 → millis() 기반 비동기 처리
- 전역 변수 최소화, 모터 상태는 구조체로 관리
핀맵 테이블만 있는 게 아니다. 왜 그 선택을 했는지의 이유가 같이 들어있다. "AFMotor 불사용"만 써두면 Claude가 다음에 AFMotor 쓰는 코드를 다시 짤 수 있다. "Timer 충돌 때문"이라는 이유가 붙어야 그 제약을 이해하고 지킨다.
코딩 규칙 섹션의 delay() 사용 금지도 그렇다. 아두이노 경험자라면 당연히 아는 내용이지만, 명시하지 않으면 Claude가 간단한 예시 코드에 delay(100) 넣는 경우가 있었다. 한 번 당하고 나서 바로 넣었다.
이게 처음부터 완성된 게 아니다. 10줄짜리 핀맵으로 시작했다가, 코드가 이상하게 나올 때마다 조금씩 추가됐다. Timer1 충돌 사건 이후에 라이브러리 선택 이유 섹션이 생겼다. ROS2 코드를 처음 요청하면서 토픽 명명 섹션이 추가됐다. 이런 식으로 프로젝트와 같이 자란다.
Cursor나 Aider는 안 되나
할 수 있다. CLAUDE.md 대신 .cursor/rules 폴더나 .aider.conf.yml 같은 도구별 설정 파일에 같은 내용을 넣으면 된다. 원리는 똑같다.
내가 Claude Code를 선택한 건 세 가지 이유였다. 하나는 내가 이미 Anthropic API를 쓰고 있어서 Claude에 더 익숙하다. 두 번째는 CLAUDE.md 방식이 직관적이다. 마크다운 파일 하나에 다 쓰면 되고, 별도 문법 없이 자연어로 쓴다. 세 번째는 나중에 MCP 서버를 추가할 때 Claude Code 에코시스템이 더 편하다.
도구 선택은 취향이라고 생각한다. Cursor를 쓰는 사람이라면 .cursor/rules에 같은 내용을 넣으면 된다. Aider를 쓴다면 시스템 프롬프트 파일로 관리할 수 있다. 어떤 도구를 쓰든 상관없다. "AI에게 프로젝트 도메인 지식을 어떻게 체계적으로 제공하느냐"가 진짜 문제다.
MCP와 툴 유즈는 나중에
CLAUDE.md로 지식 주입은 됐다. 그다음에 고민한 게 도구다.
MCP(Model Context Protocol)를 쓰면 Claude에게 외부 시스템을 도구로 줄 수 있다. 예를 들어 아두이노에 코드를 자동으로 업로드하는 MCP 서버를 만들면, Claude가 코드 짜고 나서 직접 로봇에 올리는 것도 가능하다.
이걸 구현하려다가 멈췄다. 지금 당장 필요한가?
아직 기본 모터 제어도 완성 단계가 아니다. 코드 수정이 하루에 몇 번 되지 않는다. 그 몇 번을 위해 MCP 서버를 만드는 건 아직 과하다. 코드 짜고 Arduino IDE에서 직접 업로드하는 게 지금은 충분하다.
MCP는 "업로드가 실제 병목이 된다"고 느끼는 시점에 추가할 것이다. 지금은 CLAUDE.md 기반으로만 운영한다. 이게 현재 하네스의 의도적인 미완성이기도 하다. 안 쓰는 기능을 미리 만들어두는 건 하네스도 코드도 복잡하게만 만든다.
12살용 인터페이스 설계가 진짜 고민이었다
이 프로젝트에서 제일 많이 생각한 부분이 여기다.
나는 터미널이 자연스럽다. Claude Code 창에 명령 치고, 코드 확인하고, 이상하면 수정 요청하는 흐름이 익숙하다. 근데 아들은 다르다. 터미널 창 자체가 낯설다. 코드를 보면 뭔지 모른다. 에러 메시지가 빨간색으로 나오면 일단 당황한다.
처음에는 내가 중간 다리를 했다. 아들이 "로봇이 이렇게 하면 좋겠는데"라고 말하면 내가 Claude Code에게 전달하고, 코드를 올려줬다. 아들은 로봇 반응을 보고 피드백을 줬다.
그렇게 몇 번 하다 보니 아들이 점점 뒤로 물러났다. "아빠가 다 하잖아"가 됐다. 맞는 말이었다. 아들은 구경꾼이 됐다.
그래서 방식을 바꿨다. 아들이 직접 Claude Code 창에 타이핑하게 했다.
문법이 틀려도 상관없다. 설명이 부족해도 상관없다. 아들이 원하는 걸 아들 말로 써서 제출한다. 그게 규칙이었다.
처음 타이핑한 게 이거다.
"IR 센서 값 20cm 이하면 멈추게 해줘"
Claude Code가 코드를 짰다. CLAUDE.md에 HC-SR04 핀맵이 있으니 Trig/Echo 핀을 알아서 맞게 썼다. 동작 제약 조건에 20cm 기준이 있으니 로직도 그걸 따랐다. 아들이 그걸 저장하고, 내가 옆에서 Arduino IDE 업로드를 도와줬다. 로봇 앞에 손을 뻗었다. 20cm 지점에서 멈췄다.
"된다!"
이게 바이브 코딩이다. 문법을 외우지 않아도, 코드를 읽지 않아도, 원하는 동작을 말로 설명할 수 있으면 만들 수 있다. 아들한테는 이 진입점이 맞다고 생각했고, 실제로 됐다.
물론 이게 완전한 건 아니다. 에러가 나면 아들이 여전히 당황한다. Claude Code가 에러 메시지를 분석해서 수정 제안을 해줘도, 터미널에서 그 흐름을 따라가는 게 아직 어렵다. 지금은 에러가 나면 내가 개입한다. 아들이 에러 메시지를 읽는 것 자체에 조금씩 익숙해지면, 단계적으로 혼자 해결하게 할 계획이다. 지금은 아니다.
CLAUDE.md에 아들 전용 섹션을 넣었다
CLAUDE.md를 아들도 참고할 수 있게 만드는 것도 생각했다.
기술 문서처럼 써두면 나는 괜찮지만 아들은 안 읽는다. 그래서 아들용 섹션을 하나 따로 만들었다. 기술 문서가 아니라, 로봇한테 시키고 싶은 거 있을 때 참고하는 치트시트처럼.
## 아들에게 — 로봇한테 시킬 거 있으면 이렇게 써
로봇이 할 수 있는 것들:
- 앞으로 가기, 뒤로 가기, 멈추기
- 왼쪽/오른쪽 돌기
- 속도 바꾸기 (더 빠르게, 천천히)
- 앞에 뭔가 있으면 멈추기
쓰는 법:
"[원하는 동작]하게 코드 짜줘"
예시:
"왼쪽으로 돌게 해줘"
"센서 값을 화면에 보여줘"
"장애물 50cm 이하면 멈추게 해줘"
규칙:
- 핀 번호는 바꾸지 마 (위 표에 나와 있어)
- delay() 쓰면 아빠한테 물어봐
- 모르겠으면 "이게 뭐 하는 코드야?" 물어봐도 돼
아들이 이걸 보고 썼다. "왼쪽으로 돌게 해줘"라고 입력했고, Claude Code가 IN1/IN2/IN3/IN4 조합을 핀맵에 맞게 정확하게 짜줬다. CLAUDE.md에 핀맵이 있으니까.
아들이 핀맵 테이블을 외울 필요가 없다. CLAUDE.md에 있다. 라이브러리가 왜 그걸 쓰는지 알 필요도 없다. 맥락을 AI가 갖고 있다. 아들은 원하는 동작만 말로 설명하면 된다.
이게 하네스가 12살한테 의미 있는 이유다. 아이 혼자서는 접근하기 어려운 도메인 지식을 하네스가 채워주고, 아이는 의도만 전달한다. 문법을 배우지 않고도 AI를 지휘하는 방식을 배운다. 나는 이게 파이썬 문법 교육보다 지금 이 시점에 더 중요한 능력이라고 생각한다. 코드를 짜는 능력보다, 원하는 걸 설명해서 결과를 얻어내는 능력이 앞으로 더 필요해질 테니까.
지금 하네스 상태
현재 CLAUDE.md는 약 120줄이다.
10줄짜리에서 시작해서 조금씩 늘었다. Timer1 충돌이 생겼을 때 라이브러리 섹션이 추가됐다. ROS2 코드를 처음 요청할 때 토픽 명명 규칙이 생겼다. 아들이 "delay() 쓰지 말라는 게 뭐야"라고 물었을 때 아들용 섹션에 한 줄이 더 붙었다. 지금도 계속 고치고 있다.
이 과정이 하네스 엔지니어링의 실제 모습이다. 처음부터 완성된 문서를 만드는 게 아니다. 프로젝트와 같이 자라고, Claude가 반복적으로 실수하는 지점에서 규칙이 추가된다. 새 부품이 들어오면 핀맵이 생기고, 새 결정이 생기면 이유가 붙는다.
아직 없는 것도 많다. Raspberry Pi로 전환하면 Pi 관련 섹션이 생긴다. LLM 서버 통신 프로토콜이 정해지면 그것도 들어간다. 로컬 LLM을 연동하면 비전 인터페이스 명세가 추가된다. 지금 이 120줄은 아두이노 단계 하네스다.
아들한테 이 파일을 보여줬더니 "이게 뭐야"라고 했다. "로봇 설명서야. 근데 사람한테 쓰는 게 아니라 AI한테 쓰는 거야." 한참 생각하더니 "AI 설명서?"라고 했다.
맞다.
다음 편은 이 하네스를 실제로 써서 코드를 짜는 첫 시도다. HC-SR04 거리 측정부터 시작해서 모터 제어까지. 하네스가 실제로 어떻게 작동하는지, 그리고 어떤 대화가 필요했는지 그대로 올릴 예정이다.
댓글
댓글 쓰기