내가 LLM으로 소프트웨어를 만드는 방법
(stavros.io)- LLM을 활용한 소프트웨어 개발에서 아키텍트-개발자-리뷰어 다중 에이전트 워크플로우를 통해 수만 줄 규모의 프로젝트를 낮은 결함률로 유지하는 구체적인 방법론 공유
- 프로그래밍 자체보다 무언가를 만드는 것에 관심이 있었으며, LLM이 코딩을 잘하게 되면서 만들기에 집중할 수 있게 됨
- 코드 작성 능력보다 시스템 아키텍처 설계와 올바른 선택을 내리는 엔지니어링 스킬이 훨씬 더 중요해짐
- 서로 다른 모델을 혼합 사용하여 코드 리뷰 품질을 높이고, 각 모델의 강점과 약점을 역할별로 분리 활용
- 실제 이메일 기능 추가 세션 전체를 공개하며, 아키텍처 결정부터 QA까지 인간이 주도하는 LLM 협업 과정을 상세히 기록
LLM으로 만들기의 이점
- 프로그래밍을 좋아한다고 생각했지만, 실제로는 만드는 것 자체를 좋아했으며, LLM이 프로그래밍을 잘하게 되면서 쉬없이 무언가를 만들고 있음
- Codex 5.2 출시 무렵, 그리고 최근 Opus 4.6에 이르러 매우 낮은 결함률로 소프트웨어 작성이 가능해짐. 직접 손으로 코딩할 때보다 결함률이 유의미하게 낮을 수 있음
- 이전에는 2~3일 작업 후 코드가 유지보수 불가 상태로 빠졌지만, 현재는 수 주간 연속 작업하며 수만 줄의 유용한 코드를 안정적으로 성장시키는 중
- 엔지니어링 스킬이 쓸모없어진 것이 아니라 이동한 것: 코드를 올바르게 작성하는 능력 대신, 시스템을 올바르게 아키텍처하고 사용 가능하게 만드는 판단력이 핵심
- 기반 기술을 잘 아는 프로젝트(예: 백엔드)에서는 수만 SLoC에서도 문제가 없지만, 잘 모르는 기술(예: 모바일 앱)에서는 여전히 잘못된 선택의 누적으로 코드가 엉망이 됨
- LLM 초기(davinci 이후)에는 모든 코드 라인을 검토해야 했고, 이후 세대에서는 함수 단위, 현재는 전체 아키텍처 수준에서만 확인하면 되는 추세. 내년에는 그조차 불필요해질 수 있음
이 방식으로 만든 프로젝트들
- Stavrobot: 보안에 초점을 맞춘 LLM 개인 비서. 캘린더 관리, 가용성 판단, 리서치, 코드 작성으로 자기 확장, 리마인더, 집안일 자율 관리 등 수행. 하나의 킬러 기능이 아니라 수천 개의 작은 불편을 해소하는 것이 핵심 가치
- Middle: 음성 메모를 녹음하고 텍스트로 변환해 웹훅으로 전송하는 작은 펜던트 장치. 항상 휴대 가능하고 마찰 없이 사용 가능한 것이 핵심
- Sleight of Hand: 초 단위로 불규칙하게 째깍거리지만 분 단위에서는 항상 정확한 벽시계 아트 프로젝트. 500ms~1500ms 가변 틱, 인지하기 어려운 속도 변화 후 무작위 정지, 이중 속도로 달려가 30초 대기 등 다양한 모드 제공
- Pine Town: 무한 멀티플레이어 캔버스로, 각자 작은 땅을 받아 그림을 그리는 인터랙티브 메도우 프로젝트
- 이 모든 프로젝트를 LLM으로 만들었고, 대부분의 코드를 직접 읽은 적이 없지만 각 프로젝트의 아키텍처와 내부 작동 방식을 잘 알고 있음
하네스(Harness) 도구
- 하네스로 OpenCode를 사용 중이며, Pi도 좋은 경험이 있었음
- 하네스의 필수 요건 두 가지:
- 여러 회사의 다양한 모델 사용 가능: 대부분의 퍼스트파티 하네스(Claude Code, Codex CLI, Gemini CLI)는 자사 모델만 지원하므로 이 요건 불충족
- 커스텀 에이전트가 서로 자율적으로 호출 가능
복수 모델 사용의 필요성
- 특정 모델을 한 사람으로 간주할 수 있으며, 컨텍스트를 초기화해도 같은 의견/강점/약점을 유지하는 경향
- 모델에게 자기가 쓴 코드를 리뷰하게 하면 자기 동의 경향이 있어 거의 무의미하지만, 다른 모델에게 리뷰를 맡기면 품질이 크게 향상됨
- 현재 기준 Codex 5.4는 꼼꼼하고 까다로워서 리뷰에 적합, Opus 4.6는 본인이 내렸을 결정과 잘 일치, Gemini 3 Flash도 다른 모델이 놓친 해법을 제시하는 경우 있음
- 최적의 결과를 위해 모든 모델의 혼합 사용이 필요
워크플로우: 아키텍트 → 개발자 → 리뷰어
- 워크플로우는 아키텍트, 개발자, 1~3명의 리뷰어로 구성. 이들은 OpenCode 에이전트(스킬 파일)로 설정됨
- 복수 에이전트 사용의 세 가지 이유:
- 비싼 모델(Opus)은 계획 수립에, 저렴한 모델(Sonnet)은 코드 작성에 사용하여 토큰 절약
- 서로 다른 모델로 리뷰하면 각기 다른 문제를 포착
- 역할별 권한 분리 가능(예: 읽기 전용 vs 쓰기 가능)
- 같은 모델, 같은 권한으로 두 에이전트를 사용하는 것은 한 사람이 다른 모자를 쓰는 것과 같아 큰 의미 없음
- 스킬 파일은 직접 수작업으로 작성. LLM에게 스킬을 쓰게 하면, 누군가에게 "훌륭한 엔지니어가 되는 법"을 쓰게 한 뒤 그 지침을 돌려주며 "이제 훌륭해져라"라고 하는 것과 같음
아키텍트 역할
- 아키텍트(현재 Claude Opus 4.6)는 유일하게 직접 대화하는 에이전트이며, 가장 강력한 모델이어야 함
- 매우 구체적인 기능이나 버그 수정 목표를 제시하고, 목표·제약·트레이드오프를 확정할 때까지 최대 30분간 대화 진행
- 결과물은 개별 파일과 함수 수준의 상당히 저수준의 계획
- 단순 프롬프팅이 아니라 LLM의 도움으로 계획을 형성하는 과정. LLM이 틀리거나 본인 방식과 다를 때 많이 교정하며, 이것이 프로젝트를 "자기 것"으로 만드는 핵심 기여
- "approved"라는 단어를 명시적으로 말할 때까지 구현을 시작하지 않도록 설정. 일부 모델은 스스로 이해했다고 판단하면 성급하게 구현에 착수하는 경향
- 승인 후 아키텍트가 작업을 태스크로 분할하여 계획 파일에 상세히 기록하고 개발자를 호출
개발자 역할
- 개발자는 더 약하고 토큰 효율적인 모델(Sonnet 4.6) 사용 가능
- 계획이 재량의 여지를 최소화하므로, 역할은 엄격하게 계획의 변경사항을 구현하는 것
- 구현 완료 후 리뷰어를 호출
리뷰어 역할
- 각 리뷰어가 독립적으로 계획과 diff를 검토하고 비평
- 최소 Codex를 항상 사용하고, 때로 Gemini 추가, 중요 프로젝트에는 Opus도 추가
- 피드백은 개발자에게 돌아가며, 리뷰어 간 의견 불일치 시 아키텍트에게 에스컬레이션
- Opus는 올바른 피드백을 선택하는 데 뛰어나며, 너무 까다로운(구현 대비 실질적 문제 가능성이 낮은) 피드백은 무시하기도 함
전반적 접근 방식과 실패 모드
- 이 방식으로 함수 수준 이상의 모든 선택을 파악하고, 후속 작업에 해당 지식을 활용
- LLM이 코드베이스에서 특정 요소를 놓치는 맹점이 있을 때, "Y를 사용해야 한다"고 지시하면 LLM이 Y의 존재를 인식하고 더 나은 방법으로 전환
- 기술에 익숙하지 않을 경우 LLM의 잘못된 결정을 포착하지 못하고, 그 위에 잘못된 결정이 누적되어 결국 풀 수 없는 상태에 도달
- "코드가 작동하지 않는다"고 반복 지적하면 LLM이 "알겠습니다! 고치겠습니다"라며 오히려 더 망가뜨리는 것이 전형적 실패 패턴
- 따라서 특정 기술에 익숙하지 않더라도 계획 단계에서 가능한 한 많이 이해하려고 노력
실제 세션: Stavrobot에 이메일 지원 추가
- 실제 주석 달린 세션 전문을 공개하며, 도구 호출과 장황한 부분은 생략했지만 대화와 의사결정 과정은 그대로 유지
-
초기 대화: 높은 수준의 목표 제시("이 봇에 이메일 지원을 추가하고 싶다") → LLM이 코드를 읽고 현재 패턴(인바운드 웹훅 → enqueueMessage → LLM 처리 → 응답) 파악 후 설계 질문 제시
- 인바운드 방식(IMAP 폴링, 웹훅, SMTP 서버), 아웃바운드 방식, 양방향 여부, 아키텍처(별도 컨테이너 vs 인프로세스), HTML 이메일 처리, 스레드 추적, 첨부파일 등
- 설계 결정: Cloudflare Email Worker 웹훅으로 인바운드, SMTP 클라이언트로 아웃바운드, 완전 양방향 대화, 인프로세스 처리, 마크다운 변환, 독립 이메일 취급, 첨부파일 지원
-
LLM의 상세 설계 제안: MIME 파싱(mailparser 사용), 웹훅 인증(공유 시크릿), 아웃바운드 제목줄 필요성, HTML 전용 이메일 처리, From 주소 정체성, 전달된 이메일 처리, 아웃바운드 첨부파일 등 7개 우려사항과 구체적 설계안 제시
- Worker 페이로드를
{ from, to, raw }로 단순화하고 서버 측에서 파싱하는 방식 제안 - Config 구조, 인바운드/아웃바운드 흐름, 수정 파일 목록, 명시적 비목표(YAGNI) 항목 정리
- Worker 페이로드를
- 계획 정제: README.md와 config.example.toml 업데이트, 이메일 허용목록 페이지에서 E.164 검증 제거 등 누락 사항을 인간이 지적 → LLM이 통합
- 6개 태스크로 분할: Config/의존성, 허용목록, 허용목록 UI/백엔드 검증, 인바운드 이메일, 아웃바운드 이메일, README/테스트
- 추가 개선: SMTP 설정 없이도 인바운드 이메일만 작동하도록 SMTP 필드를 선택사항으로 변경하는 아이디어 제안 → 구현
-
QA에서 발견된 버그: 소유자 이메일이 등록되지 않아 메시지가 드롭되는 문제 →
seedOwnerInterlocutor가 이메일 채널을 누락한 것이 원인 → 수정 -
코드 개선 제안: 채널별 하드코딩된 if 블록 대신 공유 채널 목록을 순회하도록 리팩토링. Telegram의 숫자 변환 특수 케이스 논의 후,
seedOwnerInterlocutor에만 루프를 적용하고getOwnerIdentities는 유형 차이가 본질적이므로 유지하기로 결정 -
와일드카드 이메일 허용목록:
*@example.com형태의 도메인 수준 와일드카드 지원 추가. 일회용 이메일 주소를 사용하는 실제 유스케이스에서 필요- 보안 고려:
"me@mydomain.com"@evildomain.com같은 공격 방지를 위해*를[^@]*로 변환하여 @ 경계를 넘지 못하도록 처리 -
myusername+*@gmail.com같은 부분 와일드카드도 지원 - 정규식 사용 시 이메일 주소의 다른 모든 문자를 이스케이프해야 함을 인간이 지적
- 보안 고려:
- 소유자 필드와 허용목록 모두에서 와일드카드 작동 확인, 동일한
matchesEmailEntry헬퍼 함수 사용 - 전체 기능 구현에 약 1시간 소요
에필로그
- 극도로 화려한 셋업은 아니지만 매우 잘 작동하며, 프로세스의 신뢰성에 만족
- Stavrobot을 거의 한 달간 24/7 운영 중이며 매우 안정적
GeekNews Weekly에 포함된 글입니다.
에디터 코멘트 보기
댓글과 토론
20여년전 웹에디터나 양산형 블로그의 유행으로 아무도 보지 않을 홈페이지나 포스트들이 대거 양상되었던 것처럼 인공지능 시대를 맞이하여 비슷한 양상이 있으나 커스텀앱을 만들고 그 프로세스나 루틴을 공유하는 것은 분명 훌륭하고 큰 자산이라고 생각함. 개인적으로 지금 시대는 인공지능으로 돈이 되는 앱이나 서비스를 만드는 것이 아니라 내가 필요한 커스텀 도구를 손쉽게 만들어서 생산성을 높이는 것이라고 봄
- 모델에게 자기가 쓴 코드를 리뷰하게 하면 자기 동의 경향이 있어 거의 무의미하지만, 다른 모델에게 리뷰를 맡기면 품질이 크게 향상됨
사실 인간도 그럴거 같긴 합니다. 인간이 다양성을 추구해야 하는 이유도 이럴 게 아닐까요...
비싼 모델(Opus)은 계획 수립에, 저렴한 모델(Sonnet)은 코드 작성에 사용하여 토큰 절약
계획은 Sonnet. 코드 구현은 Opus 으로 하시는 경우도 많던데 여긴 반대네
전 계획도 opus 와 codex 티키타카 시키고 있네요.
코딩은 opus 시키고 코드 리뷰는 또 다른 opus 와 codex 시킴.
하면서 느끼는건 ai나 사람이나 남 지적질은 참 잘하는거 같다는..
https://code.claude.com/docs/ko/model-config#opusplan-모델-설정
저도 클로드의 opusplan 으로 모델 설정해놓고 쓰고있어요