내 AGENTS.md에는 실제로 무엇이 담겨 있는가
요약
코딩 에이전트의 성능을 최적화하기 위한 AGENTS.md 파일 작성 경험을 공유합니다. 단순한 스타일 가이드보다 에이전트의 잘못된 본능을 제어하고 모호할 때 질문하도록 권한을 부여하는 규칙이 더 중요함을 강조합니다.
핵심 포인트
- 에이전트에게 모호한 상황에서 멈추고 질문할 권한을 부여해야 함
- 에이전트의 기본 실패 모드는 나쁜 코드가 아닌 잘못된 가정임
- 가장 유용한 규칙은 에이전트의 나쁜 습관을 지적하고 금지하는 형태임
- 과도한 구현(Over-build)을 방지하기 위해 최소한의 코드 작성을 지시해야 함
내 코드베이스를 실행하는 지침 파일은 잡동사니 서랍처럼 시작되었습니다. 결국 중요해진 규칙들은 내가 예상했던 것들이 아니었습니다.
시작하기에 앞서 공로에 대한 메모를 남깁니다: 다음에 이어지는 내용의 대부분은 저의 독창적인 아이디어가 아닙니다. 저는 Twitter/X 스레드, 블로그 포스트, 대화 등 여기저기에 흩어져 있는 다른 엔지니어들의 성공 사례들을 통해 이러한 아이디어들을 습득했고, 이를 제 코드베이스에 직접 테스트하여 실제로 무엇이 유효한지 확인했습니다. 이것은 발명이 아니라 종합(synthesis)입니다. 제가 아닌 커뮤니티로부터 온 부분은 그렇게 명시하려고 노력했습니다. 자신이 배운 것을 조용히 공유해 주시는 모든 분께 감사의 마음을 전하는 것으로 생각해주십시오.
처음 AGENTS.md를 작성했을 때, 저는 그것을 README의 성가신 사촌 정도로 취급했습니다. 기술 스택(Tech stack), 몇 가지 스타일 노트, 그리고 에이전트에게 테스트를 실행해달라고 간청하는 한 줄 정도를 적었습니다. 그러고 나서 에이전트가 그 내용의 절반을 무시하고, 나머지 절반에 대해서는 자신 있게 잘못된 일을 수행하는 것을 지켜보았습니다.
그래서 계속 수정했습니다. 몇 달 후, 이 파일은 처음 모습과는 전혀 다르게 변했습니다. 놀라운 점은 어떤 규칙들이 자리를 잡았느냐 하는 것입니다. 그것은 포맷팅 컨벤션(formatting conventions)이나 세심한 스타일 가이드가 아니었습니다. 에이전트의 최악의 본능에 맞서는 소수의 규칙들과, 요청하는 것과 강제하는 것의 차이에 대한 한 가지 뼈아픈 교훈이었습니다.
실제로 무엇이 중요한지 제가 발견한 내용은 다음과 같습니다.
첫 번째 규칙은 코드에 관한 것이 아닙니다. 그것은 멈출 수 있는 권한에 관한 것입니다.
제 파일의 맨 처음에 있는 내용은 구문(syntax)과는 아무런 관련이 없습니다. 그것은 무언가 불분명할 때 에이전트가 멈추고 질문해야 한다는 것을 알려줍니다. 당신의 가정을 명시하세요. 두 가지 해석이 존재한다면, 하나를 조용히 선택하는 대신 둘 다 언급하세요. 더 간단한 접근 방식이 있다면 그렇게 말하세요.
제가 이것을 첫 번째로 둔 이유는 코딩 에이전트의 기본 실패 모드(failure mode)가 나쁜 코드가 아니기 때문입니다. 그것은 모호한 요청을 잘못 읽은 상태에서 구축된 자신감 넘치는 코드입니다. 그대로 내버려 두면, 모델은 모호함을 조용히 해결하고 계속 진행하며, 당신은 잘못된 문제를 해결하는 200줄의 코드를 리뷰할 때가 되어서야 그 사실을 알게 됩니다.
저를 놀라게 했던 점은 에이전트(Agent)에게 멈출 수 있는 권한이 필요하다는 사실이었습니다. 이 권한이 없으면 모델은 도움이 되는 것처럼 보이도록 최적화되며, 도움이 된다는 것은 혼란을 인정하기보다 무언가를 만들어내는 것을 의미합니다. 질문을 위해 멈추는 것이 실패가 아니라 원하는 결과라는 점을 알려주는 것이 그 어떤 단일 문장보다 모델의 행동을 더 크게 변화시켰습니다.
이 교훈은 일반화될 수 있습니다. 즉, "잘 모르겠습니다"가 허용 가능한 답변임을 알려주지 않는 한, 에이전트는 침묵을 자신감으로 채울 것입니다.
가장 유용한 규칙들은 대부분 나쁜 습관을 지적하고 이를 금지하는 형태입니다
주의를 기울이기 시작하자, 저의 가장 가치 있는 규칙들이 모두 동일한 형태를 띠고 있다는 것을 깨달았습니다. 각 규칙은 구체적이고 예측 가능한 나쁜 본능을 지목하고 에이전트에게 그것을 하지 말라고 지시했습니다. 그중 영리한 규칙은 하나도 없었습니다. 그저 경험을 통해 얻어진 것들이었습니다.
에이전트는 과하게 구축합니다 (Over-build). 함수를 요청하면 아무도 요청하지 않은 옵션들이 포함된 설정 가능한 프레임워크를 가져올 수도 있습니다. 그래서 한 가지 규칙은 이를 직설적으로 다룹니다: 문제를 해결하는 최소한의 코드만 작성할 것, 추측성 코드는 넣지 말 것, 그리고 50줄이면 충분할 곳에 200줄을 작성했다면 다시 작성할 것. 이 비용(Tax)을 명시하는 것이 비용을 지불하지 않게 만드는 방법입니다.
에이전트는 과하게 범위를 넓힙니다 (Over-reach). 실제 변경 사항과 함께 파일 전체를 친절하게 재포맷하는 에이전트는 10줄짜리 차이(Diff)를 안전하게 리뷰할 수 없는 200줄짜리로 만들어 버립니다. 그래서 또 다른 규칙은 영향 범위(Blast radius)를 제한합니다: 반드시 필요한 부분만 건드릴 것, 설령 본인이라면 다르게 했을지라도 기존 스타일을 따를 것, 그리고 모든 변경된 줄이 요청 사항과 연결되도록 할 것. 이 규칙은 코드가 아니라 리뷰를 보호하기 위한 것입니다. 읽을 수 없는 차이(Diff)는 신뢰할 수 없는 차이(Diff)입니다.
제가 가장 추천하고 싶은, 훔쳐 가도 좋은 규칙은 제가 직접 뼈아픈 경험을 하기 전까지는 그 가치를 몰랐던 것입니다. 에이전트가 코드베이스에서 서로 충돌하는 두 가지 패턴을 발견하면, 에이전트의 본능은 두 가지를 모두 존중하는 중간 경로를 찾는 것입니다. 그 결과물은 어느 패턴에도 속하지 않으며 나중에 이를 읽는 모든 사람을 혼란스럽게 만듭니다. 따라서 규칙은 다음과 같습니다: 충돌하는 패턴을 평균 내지 마십시오. 하나를 선택하고, 그 이유를 설명하며, 다른 하나는 정리를 위해 표시해 두십시오. 두 가지 모순된 규칙을 모두 만족시키려는 평균적인 코드는 저장소(Repo) 내에서 최악의 코드입니다.
이 모든 것과 병행하여 제가 계속해서 되새기는 원칙이 하나 있습니다. 만약 에이전트(Agent)가 기존 코드가 왜 지금과 같은 형태를 갖추고 있는지 설명할 수 없다면, 그 옆에 코드를 추가하기 전에 먼저 물어봐야 한다는 것입니다. "나와는 상관없어 보인다"라는 생각은 성숙한 코드베이스(Codebase)에서 가장 비용이 많이 드는 가정이 됩니다. 대부분의 미묘한 결함은 코드를 수정하는 사람에게는 완벽하게 격리된 변경처럼 보였던 작업에서 발생합니다.
파일 전체를 재구성하게 만든 교훈
제가 배운 것 중 단 하나만 남길 수 있다면 바로 이것이며, 이를 받아들이는 데 가장 오랜 시간이 걸렸습니다.
한때 저는 파일 상단에 명확하고 놓칠 수 없는 언어로 작성된 엄격한 요구사항을 두었습니다. 하지만 에이전트는 여전히 가끔 이를 건너뛰었습니다. 자주 그런 것은 아니었지만, 실제 문제를 일으킬 만큼은 충분히 빈번했습니다. 왜 그런지 파고들어 보니, 그 답은 허탈할 정도로 단순했습니다. 모델은 지침(Instruction)으로부터 벗어납니다(Drift). 마크다운(Markdown) 파일에 적힌 규칙은 강력한 권장 사항일 뿐, 계약(Contract)이 아닙니다. 실행 횟수가 쌓이다 보면, 어떤 지침이든 결국 무시되기 마련입니다.
그 깨달음 하나가 제 파일 전체를 두 가지 계층으로 나누었습니다.
하나는 가이드라인(Guidance)입니다. 스타일 선호도, 철학, "이것보다 저것을 선호하라"와 같은 것들입니다. 가이드라인은 산문(Prose) 형태로 존재하며 대부분의 경우 준수되는데, 해당 카테고리에서는 대부분의 경우 그것으로 충분합니다.
그다음은 매번 반드시 지켜져야 하는 것들입니다. 그리고 제가 배운 것은, 이러한 요소들은 산문 형태로는 결코 적합하지 않다는 사실입니다. 어떤 일이 반드시 일어나야만 한다면, 지침에 의존하지 마십시오. 강제하십시오(Enforce). 훅(Hook), 스크립트(Script), CI 체크(CI check) 등, 위반 사항이 단순히 권장되지 않는 수준을 넘어 머지(Merge) 자체가 불가능하도록 만드는 결정론적인(Deterministic) 무언가에 배치하십시오.
제 파일에는 여전히 "완료하기 전에 체크를 실행하십시오"라고 적혀 있습니다. 하지만 진짜 보증은 그 문장이 아닙니다. 에이전트의 의도가 무엇이었든 상관없이, 린트 에러(Lint error)가 발생하면 CI가 실패한다는 사실입니다. 지침은 예의일 뿐입니다. 게이트(Gate)가 보증입니다.
제가 현재 사용하는 실질적인 테스트 방법은 이렇습니다. 지침 파일에 "항상(Always)" 또는 "절대(Never)"라는 단어를 쓸 때마다, 이를 강제하는 메커니즘이 있는지 스스로에게 묻습니다. 만약 없다면, 그것은 규칙이 아니라 희망 사항일 뿐입니다.
에이전트가 약속하게 하지 말고, 증명하게 하십시오
파일에 포함된 두 가지 규칙은 실제로 "내가 해냈다"와 "작동한다" 사이의 간극에 관한 것입니다.
첫 번째 규칙은 작업(task)을 구성하는 방식을 바꿉니다. "유효성 검사(validation)를 추가하라" 대신, 성공의 모습이 어떠해야 하는지를 정의하고 그것이 충족될 때까지 루프를 돌라는 지침을 내립니다. 즉, 잘못된 입력값에 대한 테스트를 작성한 다음, 그것들이 통과하도록 만드는 것입니다. "버그를 수정하라"는 "버그를 재현하는 테스트를 작성한 다음, 그것이 통과하도록 하라"로 바뀝니다. 모호한 지시사항이, 단순히 느낌(vibes)만으로 승리를 선언하는 대신 에이전트 스스로가 대조하며 확인할 수 있는 결승선이 있는 지시사항으로 변하는 것입니다.
두 번째 규칙은 신뢰에 대해 직설적입니다. 기억에 의존해 테스트가 통과했다고 주장하지 말고, 반드시 다시 실행(re-run)하라는 것입니다. 저는 에이전트가 실제로는 그렇지 않은데도 깨끗하게 실행되었다고 보고하는 것을 본 후, 그리고 하위 에이전트(sub-agent)의 리뷰가 소스 코드에 존재하지 않는 버그를 지어내는 것을 본 후에 이 규칙을 추가했습니다. 두 경우 모두 동일한 실패 유형입니다. 즉, 새로운 확인(fresh check)과 연결되지 않은 주장입니다. 따라서 이 규칙은 모든 주장을 실제로 실행된 명령과 연결하며, 보고된 버그에 대해 조치를 취하기 전에 실제 코드와 대조하여 검증하도록 에이전트에게 지시합니다.
두 규칙의 밑바탕에 깔린 원칙은 이것입니다: 주장이 아니라 확인(check)을 믿으십시오. 에이전트의 자신감은 증거가 아닙니다.
매뉴얼에서 법률을 분리하십시오
파일 전체의 유지보수성을 유지하기 위한 한 가지 구조적 결정은, 제 AGENTS.md가 아키텍처(architecture)를 담으려 하지 않는다는 점입니다.
구조적 법칙들 — 의존성 방향(dependency direction), 고정된 계약(frozen contracts), 넘어서는 안 될 경계선 등 — 은 별도의 헌법(constitution) 문서에 존재합니다. 작업 파일은 단지 그 문서를 가리킬 뿐입니다: 패키지를 추가하거나, 레이어(layer)를 넘나들거나, 안전에 중요한(safety-critical) 요소를 건드리기 전에 이것을 읽으라고 말이죠. 그리고 만약 변경 사항이 헌법과 충돌한다면, 더 빠르게 배포하기 위해 위반 사항을 슬쩍 끼워 넣는 것이 아니라, 리팩터링(refactor)을 하거나 수정안(amendment)을 제안하는 것이 원칙임을 명시합니다. 위반의 비용은 복리로 쌓이기 때문입니다.
이들을 분리해야 하는 이유는 속도(pace) 때문입니다. 작업 매뉴얼(working manual)은 끊임없이 변하며 자유롭게 수정되어야 하는 관습(conventions)과 주의사항(gotchas)으로 가득 차 있습니다. 반면 헌법(constitution)은 변화가 느려야 하며 기계적으로 강제되어야 하는 법률들을 담고 있습니다. 이 둘을 하나의 파일에 쑤셔 넣으면, 안정적인 법률들이 운영상의 변동(operational churn) 아래 묻혀버리게 됩니다. 이들을 분리해 두면 각각의 요소가 자신만의 속도로 움직일 수 있습니다.
핵심 요약: 매주 변하는 것과 절대 변해서는 안 되는 것은 같은 문서에 있어서는 안 됩니다.
흉터(scars)를 기록하라
제 파일의 후반부는 모든 팀이 유지하기를 가장 권장하는 부분입니다. 바로 명확하지 않은 교훈들을 기록한 실행 로그(running log)입니다. 단순한 관습이 아니라, '흉터'입니다. 누군가에게 반나절의 시간을 허비하게 만들었고, 기록해 두지 않는다면 다음 사람에게도 똑같이 반나절을 허비하게 만들 구체적인 사항들 말입니다.
이 기록들은 의도적으로 구체적입니다. 기본적으로 선택되지 않는 데이터베이스 의사 컬럼(pseudo-column)과 그것을 잊었을 때 발생하는 정확한 에러 메시지. ID 대신 인덱스(index)로 이벤트를 키(key)로 사용하는 스트리밍 API(streaming API)와 예상치 못한 순서로 도착하는 현상. 허용 목록(allowlist)에 있지 않으면 조용히 컴파일에 실패하는 네이티브 의존성(native dependency). 이 중 어느 것도 원칙(principles)은 아닙니다. 이것들은 지도에 표시된 지뢰(landmines)입니다.
이 섹션은 사람과 함께할 때보다 에이전트(agent)와 함께할 때 훨씬 더 중요합니다. 에이전트에게는 흉터 조직(scar tissue)이 없기 때문입니다. 어떤 일로 하루를 허비한 인간은 그것을 기억하는 경향이 있습니다. 하지만 에이전트는 지식이 읽을 수 있는 곳에 기록되어 있지 않는 한, 매번 똑같은 지뢰를 고통스러운 방식으로 다시 발견할 것입니다. 이것이 바로 이 기록의 조용한 가치입니다. 즉, 코드베이스의 고통스럽게 축적된 지식이 다시 학습되는 대신 상속되도록 만드는 방법입니다.
예상치 못한 시간 낭비는 단 한 번만 치러야 할 가치가 있습니다. 그것을 기록하는 것이 바로 그 가치를 보장하는 방법입니다.
이 파일의 진짜 목적
모든 수정을 거친 후, 현재 제가 이 파일을 바라보는 관점은 다음과 같습니다. 이 파일은 스타일 가이드(style guide)가 아닙니다. 탭(tabs)과 세미콜론(semicolons)은 포매터(formatter)가 처리합니다. 이 파일이 실제로 하는 역할은 판단력(judgment)을 인코딩하는 것입니다. 즉, 충분한 경험을 쌓아 판단력을 갖춘 소수의 사람들 머릿속에만 존재하던 그 판단력을 기록하는 것입니다.
중요한 규칙은 에이전트 (agent)의 예측 가능한 본능, 즉 과도한 구축 (over-building), 과도한 확장 (over-reaching), 암묵적인 추측 (guessing silently), 상충하는 패턴의 혼합 (blending conflicting patterns), 검증되지 않은 성공의 주장 (claiming success it never verified)에 대응하는 규칙들입니다. 그리고 파일 전체에서 가장 중요한 구분은 당신이 명시하는 가이드라인 (guidance)과 당신이 강제하는 보장 (guarantees) 사이의 경계입니다.
따라서 만약 당신이 이런 파일을 작성하고 있다면, 바로 그 부분에 노력을 기울여야 합니다. 에이전트에게 멈춰서 질문할 수 있는 권한을 부여하세요. 계속해서 눈에 띄는 나쁜 습관들을 명시하고, 그 이름들을 직접 언급하며 금지하세요. 항상 유지되어야 하는 사항은 산문 (prose) 형태에서 벗어나 빌드 (build)를 실패하게 만드는 체크 (check) 단계로 옮기세요. 아키텍처 (architecture)를 단순히 재진술하는 대신, 그것을 지시하세요. 그리고 당신이 겪은 시행착오 (scars)의 기록을 계속해서 남겨두세요. 왜냐하면 에이전트는 당신이 지도에 표시하지 않은 모든 지뢰를 밟을 것이기 때문입니다.
포맷팅 규칙 (formatting rules)은 저절로 작성됩니다. 기록할 가치가 있는 부분은 바로 판단 (judgment)입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기