에이전트와 Git 커밋, 스레드를 엮어 코드의 ‘이야기’를 보존하고 필요할 때 문서와 통찰을 끌어내는 방법에 대한 글.
“아직 데이터 룸에 기술 문서가 제대로 없어서요. 필요한 게 있으면 그냥 말씀해 주시면 됩니다.” CTO가 말했다.
“사업 운영도 하셔야 하고, 투자 유치 중에 불필요한 오버헤드를 더하고 싶진 않아요. 코드 읽기 권한만 받을 수 있을까요?” 내가 물었다.
약간 뻔뻔한 요청이긴 했다. 내가 기술 실사(TDD, Technology Due Diligence)를 해야 했던 그 회사는 소스 코드를 보여줄 의무가 딱히 없었다. 이건 고수준의 ‘설명서에 적힌 대로 정말 하는지’ 확인에 가까웠지, 기술 감사를 하는 자리가 아니었으니까.
하지만 그들은 추가적인 펀드레이징 행정 업무를 피할 수 있다는 점에서 어딘가 안도하는 듯했다. 나 역시 급히 조립된, 그리고 아마도 구식일 블록 다이어그램 더미를 뒤질 필요가 없어서 안도했다. 빠르게, 진짜 인사이트를 얻을 수 있었다.
그들은 70개가 넘는 저장소를 갖고 있었다(“마이크로서비스가 엄청 유행할 때 사업을 만들었거든요”). 내가 실제로 리뷰할 수 있는 시간은 며칠뿐이었다. 프런트엔드는 모노레포로 구현되어 있었기에, 나는 먼저 그쪽부터 파고들기로 했다. 클론한 다음, 내 코딩 에이전트인 Amp를 불렀다.
현재 레포를 리뷰해야 합니다. 코드를 분석하고 다음 정보를 담은 보고서(REPORT.md)를 작성하세요.
- 저장소의 구조
- 전반적인 목적과 기능
- 코딩 컨벤션
- 코드 품질 / “코드 스멜”
- 잠재적 이슈
Amp는 늘 그렇듯 열정적으로 뛰어들었고, 특정 이슈에 대해 몇 차례 주고받았다. 그러고 나서 나는 이렇게 프롬프트했다.
새 스레드로 핸드오프해서 HISTORY.md 파일을 만들어 주세요. git 커밋 히스토리와 브랜치에서 드러나는 이 앱의 개발 과정, 진척, 위기를 ‘이야기’로 들려주는 문서여야 합니다.
결과는 예술 작품이었다.
에이전트는 자발적으로 문서를 여러 막(act)으로 구성된 장대한 희곡 형태로 짜기로 결정했다. 나는 즐거운 마음으로 읽었다.
서막(Prologue): 첫 커밋
제1막: 기초(Foundation)
[xxx] 시대의 시작
패키지 매니저 전쟁
[xxx] 빌더: 시그니처 기능
제2막: 급격한 확장(Rapid Expansion)
최고 속도(Peak Velocity)
v1.0.0: 첫 메이저 릴리스
제3막: [xxx]와 [yyy] 위기(The [xxx] and [yyy] Crisis)
임베더블 위젯의 탄생
웹소켓 사가
제4막: 마이그레이션 시기(The Migration Period)
레포 마이그레이션
코어 재구축
제5막: 현대(The Modern Era)
AI 에이전트, 무대에 등장하다
대 UI 마이그레이션
[zzz]: 최신 프런티어
나는 아직 코드 한 줄도 읽지 않았다(다시 말하지만, TDD는 종종 코드 접근 권한 자체가 포함되지도 않는다). 하지만 회사가 제품을 어떻게 설계하고, 구축하고, 출시하고, 사고를 해결하기 위해 영웅적으로 허둥지둥 뛰었는지, 그리고 지금은 어떻게 앞으로 나아가고 있는지—그 영광스러운 서사는 어떤 팀 인터뷰나 구식 아키텍처 다이어그램보다도 내게 더 많은 것을 말해 주었다.
코드는 존재했고, 코드에는 역사가 있었으며, 내 에이전트는 그 역사를 바탕으로 이야기를 들려줄 수 있었다. 그리고 그 이야기는, 바로 그 순간 내 업무에 정말 중요했다.
‘소스 코드의 이야기를 들려주기’에는 긴 역사가 있다. 1984년, 현대 프로그래밍의 아버지 중 한 명인 도널드 크누스(Donald Knuth)는 _Literate Programming_이라는 제목의 논문을 썼다.
나는 프로그램을 훨씬 더 잘 문서화할 시기가 무르익었다고 믿으며, 프로그램을 _문학 작품_으로 간주함으로써 이를 가장 잘 달성할 수 있다고 생각한다 … 우리의 주요 과제가 _컴퓨터_에게 무엇을 하라고 지시하는 것이라고 상상하기보다는, 인간에게 우리가 컴퓨터가 무엇을 하기를 원하는지 설명하는 데 집중하자.
…
나는 프로그램을 구성하는 한 방법을 우연히 발견했는데, 이것이 나를 매우 들뜨게 한다. 사실, 내 열정이 너무 커서 독자에게 내가 하는 말의 상당 부분을, 방금 위대한 빛을 본 듯한 광신도의 헛소리로 치부해 달라고 경고해야 할 정도다.
…
내 프로그램은 그 어느 때보다도 더 잘 설명될 뿐 아니라, 더 나은 프로그램이기도 하다. 왜냐하면 새로운 방법론이 내가 더 나은 일을 하도록 장려하기 때문이다.
크누스는 이어서 자신이 개발한 시스템 WEB을 설명한다. “WEB이라는 이름을 선택한 이유 중 하나는, 컴퓨터에 이미 적용되지 않은 몇 안 되는 영어 3글자 단어였기 때문이다.” — 1984년의 이야기다.
그의 아이디어의 핵심은, 먼저 코드의 이야기를 들려주고, 이야기의 요소들에 코드를 덧붙여서, 결국 전체 이야기가 코드로 말해지도록 하자는 것이었다. WEB은 이를 가능하게 하는 메타언어에 가깝다. 크누스의 리터러시 프로그래밍 개념을 구체적으로 구현한 방식에서, 문서 처리기는 이야기를 사람이 이해할 수 있는 문서로 “직조(weave)”한다. 별도로 컴파일러는 이야기를, 오직 컴퓨터만 이해할 수 있는 기계 코드의 뒤엉킨 그물로 “엉킴(tangle)” 처리한다. 결과적으로 소프트웨어와 문서가 같은 소스로부터 생성된다.
Literate Programming 논문은 문서 직조(weaving)를 위해 TeX(크누스가 만들었고, LaTeX의 기반이며 오늘날에도 학술 출판에서 널리 쓰인다)를 사용하자고 제안했다. 별도로 Pascal 언어는 WEB 이야기를 컴파일된 프로그램으로 엉킴(tangle) 처리했다.
오늘날 당신은 리터러시 프로그래밍에 폭넓게 영감을 받은 Jupyter나 Wolfram 노트북 같은 프레임워크를 사용해 본 적이 있을지도 모른다. 내가 접한 개념의 구현 중 가장 흥미로운 사례로는 Physical Based Rendering이 있다. 책의 표현을 빌리면:
이 책(지금 읽고 있는 장을 포함하여)은 하나의 긴 리터러시 프로그램이다. 이는 이 책을 읽는 과정에서 당신이 고수준의 설명만이 아니라, pbrt 렌더링 시스템의 전체 구현을 읽게 된다는 뜻이다.
메타 그 자체다. 여기서는 지도가 곧 영토다.
나는 Amp를 아주 좋아한다. 최근 Sourcegraph에서 스핀아웃한 에이전틱 코딩 에이전트다. 다른 AI 코딩 에이전트처럼, Amp에게 코드를 작성/분석/업데이트/리팩터링하도록 프롬프트할 수 있다. Amp는 팀 협업도 강조하는데, 에이전트와의 각 대화는 “스레드(thread)”라고 불린다. 이 스레드는 이후 스레드에서 참조할 수 있고, 기본적으로 팀원들에게 보이며, 심지어 공개로 만들 수도 있다.
Amp의 CEO이자 공동창업자가 코드 편집을 한다. 이것이 스레드의 전부다.
스레드의 훌륭한 점:
특정 기능을 구현할 때 당신(또는 팀원)이 어떤 이유로 그렇게 구현했는지, 스레드로 되돌아가 확인할 수 있다.
무엇을 구현 중인지, 혹은 왜 특정 방식으로 코딩되었는지에 대한 더 많은 맥락을 얻기 위해 AI 에이전트에게 해당 스레드를 참조하라고 할 수 있다.
오늘날의 대규모 언어 모델(LLM)의 동작 방식 때문에, 스레드는 특정 작업에 집중된 상태로 유지하는 것이 가장 좋다. 내게는 “스레드 하나, git 커밋 하나” 규칙이 잘 맞는다.
Amp 개발자들이 이를 “스레드”라고 부를 때 크누스를 염두에 두었을 것 같진 않지만, 리터러시 프로그래밍의 직조(weaving)와 엉킴(tangling) 은유와 꽤 운율이 맞는다.
에이전트를 사용해 코드베이스에서 작업하면, 사용자와 에이전트 사이의 대화가 변경의 의도와 추론을 모두 포착한다. 생산적인 스레드는 보통 사용자로부터 상세하고 모호하지 않은 지시나 질문을 받고, 에이전트로부터는 더 상세한 추론과 결과 기록을 얻는다. 중요한 점은 이 대화가 지속될 수 있다는 것이다. 예를 들어 Amp는 스레드를 보존하고, 팀원이나 전 세계와 공유할 수 있게 해 준다.
Mitchell Hashimoto가 Amp를 사용해 Ghostty 개선 작업을 한다.
스레드는 코드베이스의 특정 부분 뒤에 있는 “왜(why)”와 “어떻게(how)” 모두의 기록이 된다.
나는 최근 스레드를 코드베이스의 이야기와 깊게 통합하는 간단한 방법을 쓰기 시작했다. 이건 새롭지도 않고, 리터러시 프로그래밍 분야에 무슨 기여를 하겠다는 의도도 아니다. 다만 코드 뒤의 이야기를 포착하는 방식으로 생각하기에 유용하다고 느낀다.
Git은 오늘날 개발자들 사이에서 압도적으로 주요한 소스 관리 도구다. 여러 기능 중에서도, 개발팀이 변경사항(“커밋”)을 자세히 설명할 수 있게 하고, 소스 코드의 서로 다른 브랜치를 유지하며, 특정 커밋에 “태그”를 달아 예컨대 릴리스를 표시할 수 있고, 코드의 모든 줄에 대해 변경 주체를 추적할 수 있게 한다. 2000년 7월의 C++ Boost 라이브러리가 어떤 모습이었는지 보고 싶나? 아직도 거기에 있다.
실무에서(그리고 이는 팀에 따라 크게 다르지만) Git 커밋 메시지는 대체로 간결해지는 경향이 있다—특히 설계 결정이 더 긴급한 시기에는 더 그렇다.
하지만 에이전트는 커밋 메시지를 쓰는 데 지치지 않는 챔피언이다. 리터러시 에이전트 클럽의 첫 번째 규칙은 _에이전트에게 커밋을 맡기라_는 것이다.
나는 코딩 에이전트가 어떻게 행동해야 하는지 알려주는 마스터 지시 파일인 전역 AGENTS.md에 다음 지시를 넣어 둔다.
When committing code:
1. **Detailed commit messages**: 2-20 lines depending on complexity
- First line: concise summary (50 chars max)
- Blank line
- Body: explain what and why (not how)
2. **Always include the agent thread tag**:
Agent-thread: https://ampcode.com/threads/T-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
3. **Always add co-author line**:
Co-authored-by: Amp amp@ampcode.com
솔직히 말해, 이 일을 시작하기 전까지 Git 커밋 메시지에 태그와 공동 작성자(co-author)를 추가할 수 있다는 것조차 몰랐다.
Quinn이 에이전트에게, 프롬프트로 쓰는 게 코드로 직접 타이핑하는 것보다 더 길었을 법한 변경을 구현해 달라고 부탁하는 스크린샷? 농담으로만 보여준 게 아니다.
server/src/routes/(marketing)/models/model-box.svelte의 259번째 줄에서:
git blame를 하면 그 줄을 바꾼 정확한 커밋이 드러난다.
커밋 메시지는 변경의 의도(즉 왜 그랬는지)를 드러낸다.
연결된 스레드는 변경 뒤의 추론(즉 어떻게 그 결론에 도달했는지)을 드러낸다.
그 결과, ModelBox.backgroundImage가 설정되어 있다면 _스파크가 있는 회로기판 애니메이션을 보여주고 싶지 않다_는 것을 이해하게 된다. 그런 의미에서, 원본 스레드로의 링크가 포함된 커밋 메시지는 변경의 완벽한 이야기다.
물론 Quinn이 코드에 그 효과를 설명하는 주석을 달 수도 있었을 것이다. 하지만 복잡한 변경에서는 이런 일이 더 번거로워지고, 사람은 게으르다(물론 여기 있는 우리는 예외지만), 특히 해결책을 빨리 내야 하는 압박이 있으면 잡무를 더 피하게 된다.
에이전트가 주도하게 하면, 각 줄이 그 의도, 사용자의 프롬프트, 에이전트의 추론으로 주석 처리된 저장소를 얻게 된다. 이제 레포는 리터러시 프로그램이다.
그 관점에서 보면, 사람이 손으로 작성하고 커밋한 코드는 거의 빈곤해 보이기도 한다. 좋은 주석과 철저한 커밋 메시지로도 우리가 스스로 작성한 코드 뒤에 있는 완전한 추론을 담아내기란 쉽지 않다.
“말의 그물, 우리의 고운 고운 그물을,
여러 물에서 번들번들하고 살찐 물고기를 끌어올리는 그 그물을,
힘센 짐승 라카는 알고 있을까?”
— N.P. van Wyk Louw, “Raka”
크누스의 원래 WEB과 달리, 우리의 코드 이야기는 리터러시 프로그램을 정적인 문서로 직조하지 않는다. 대신 우리의 스레드는 필요할 때 코드에서 통찰을 끌어올리는 데 쓸 수 있는 그물이 된다.
우리는 에이전트에게 다음 지시를 주는 것부터 시작한다.
현재 프로젝트는 “리터러시 코딩” 접근법을 따르며, 여기에는 다음이 포함됩니다:
- 코드 변경은 Amp만 할 수 있습니다.
- 커밋 메시지는 상세해야 하며 마지막 Amp 스레드 링크를 포함해야 합니다.
- Amp 스레드는 이전 스레드에서 핸드오프되었을 수 있으며, 이를 탐색할 수 있습니다.
이제 필요한 어떤 문서든 끌어낼 수 있다.
Amp 스레드가 애플리케이션과 어떻게 동기화되는지 보여 주는 UML 시퀀스 다이어그램을 Mermaid로 그려 주세요
(아… 이걸 손으로 그릴 때의 그 지루함이란!)
Painter에게 애플리케이션의 재미있고 컬러풀한 유스 케이스 다이어그램을 시각화해 달라고 해 주세요.
현재 프로젝트의 커밋 히스토리를 바탕으로, “get_billing_period” 함수가 왜 이런 특정 기본 인자를 사용하는지 설명해 주세요.
응답:
19-18 청구 주기는 Octoco의 내부 청구 기간 구조입니다. 원본 스레드에서 사용자가 명시적으로 이렇게 말했습니다:
“우리는 매달 19일부터 다음 달 18일까지를 청구 주기로 쓰고 있어서, 그 기간 동안 발생한 비용이 청구서 발행 때 항상 큰 화제예요.”
기본 인자:
reference_date: date | None = None—date.today()로 기본 설정되어 현재 청구 기간을 자동으로 보여줍니다.
count: int = 6(get_previous_billing_periods에서) — 드롭다운 선택기를 위해 6개월치 이력을 제공합니다.
현재 프로젝트의 커밋 히스토리를 바탕으로, 가장 복잡했던 설계 반복이 무엇이었는지, 그리고 에이전트가 올바른 해법으로 수렴하는 데 어려움을 겪었던 지점이 어디였는지 평가해 주세요.
응답:
평가: 설계 반복과 수렴의 어려움
커밋 히스토리와 스레드 분석을 바탕으로, 핵심 발견 사항은 다음과 같습니다:
가장 복잡했던 설계 반복: 데이터 집계 전략 (스레드 T-019be72e)
에이전트는 처음에 레포지토리별 사용량 집계를 위해 N+1 API 호출을 제안했습니다. 사용자는 이를 로컬 데이터베이스 동기화 방식으로 유도했고, 그 결과 SQLite vs Postgres, SQLAlchemy ORM 선택, 그리고 sync.py의 점진적(incremental) 동기화 아키텍처에 대한 결정으로 연쇄적으로 이어졌습니다.
눈에 띄는 수렴 어려움:
Google OAuth 세션 상태 (스레드 T-019bf022) — 에이전트는
streamlit-google-auth가 사용자 데이터를user_email이 아니라user_info["email"]에 저장한다는 점을 몰라 어려움을 겪었습니다. 올바른 키를 확인하기 위해 사용자 제공 스크린샷을 통한 디버깅이 필요했습니다.Python import 실행 순서 — 두 가지 별개 문제:
* Streamlit이 `app.py`를 스크립트로 실행할 때 상대 임포트가 실패(절대 임포트로 수정)* `load_dotenv()`가 실행되기 전에 모듈 레벨에서 `SUPERADMIN` 환경변수를 읽음(함수 스코프로 `os.getenv`를 지연)3. 어드민 부트스트랩 메커니즘 — CLI 전용이나 설정 파일 옵션 대신
SUPERADMIN환경변수를 부트스트랩 방식으로 채택하는 데 사용자 측의 명시적 가이드가 필요했습니다.에이전트는 단순한 스캐폴딩에서는 잘 수행했지만, 라이브러리 동작이 가정과 달랐던 경우(OAuth 세션 키)나 Python의 모듈 레벨 실행 타이밍이 중요했던 경우에는 사용자 교정이 필요했습니다.
오늘 동료가 말하길, 대략 같은 시기에 함께 바뀌는 경향이 있는 파일 구간을 분석해 달라고 요청하는 것만으로도, 과도하게 결합된 코드 영역을 찾는 데 이 방법을 쓸 수도 있다고 한다.
에이전트와의 대화 스레드를, 버전 관리에 신중히 커밋하면, 저장소가 리터러시 프로그램이 된다:
코드는 예전과 마찬가지로 파일 구조 안에 있다.
변경사항과 의도는 커밋 메시지에 요약된다.
커밋 메시지는 변경 뒤의 서사를 기록한 스레드로 링크된다.
이 구조를 통해 코드베이스의 역사는 상세히 기록된다. 에이전트는 여기서 문서를 만들어 낼 수 있다.
우리는 소프트웨어 엔지니어가 코드를 만드는 동안의 내면 독백을 완전히 포착할 수 있었던 적이 사실상 없다. 에이전트는 우리의 생각을 대화로 바꾸어, 의도와 추론을 모두 담아낼 수 있게 한다. 우리가 늘 쓰던 도구들이, 모든 지시와 편집을 공간적으로(파일과 코드 줄 전반에 걸쳐) 그리고 시간적으로(레포의 변경 일지 전반에 걸쳐) 기록한다.
무엇보다도, 이제 우리는 필요할 때 문서를 만들어 주는 에이전트를 갖게 됐다—심지어 매우 구체적인 요구에도. 에이전트는 다이어그램을 스케치하고, 그림을 그리고, 변경 뒤의 이유를 찾아내고, 버그나 취약점에 대한 포렌식을 수행할 수 있다. 그리고 이 모든 것을, 완전한 설계 대화를 손에 쥔 상태로 할 수 있다.
이제 다른 회사의 코드베이스에 대한 그 오디세이를 읽을 때, 나는 팀이 제품을 내기 위해 최선을 다한 이야기로 읽는다. 어쩌면 우리는 코드의 “품질”을 코드 조각을 처음 보며 드는 ‘wtf’ 렌즈로 слишком 자주 평가하는지도 모른다. 어쩌면 우리는 각 편집이 발생한 배경의 이야기들을 더 중요하게 여겨야 한다.