“AGENTS.md의 역할은 개발자를 복제하는 것이 아니라, 사고를 막아야 하는 경계만 고정하는 데 있다.”
정도현 — 로보코 수석 컨설턴트
AI 에이전트가 코드를 작성하는 시대가 되면서, 팀마다 “에이전트에게 어떤 규칙을 주어야 하는가?”라는 질문과 마주하게 되었다. AGENTS.md, CLAUDE.md, .cursorrules, .github/copilot-instructions.md — 프로젝트 루트에 규칙 파일을 하나둘씩 추가하다 보면 어느새 수백 줄짜리 문서가 만들어져 있다. 문제는 이 규칙들이 진짜로 지켜야 하는 규칙인지, 아니면 그냥 특정 개발자의 취향인지 구분하지 않은 채 뒤섞여 있다는 점이다.
이 글에서는 OpenClaw1 프로젝트가 운영하는 AGENTS.md를 사례로 삼아, “저장소 규칙”과 “개인 취향”을 어떻게 나눌 수 있는지, 그리고 그 구분이 왜 중요한지 정리해본다.
왜 이 구분이 중요한가
AI 코딩 에이전트에게 지시를 내릴 때, 모든 것을 규칙으로 만들면 세 가지 문제가 발생한다.
첫째, 인지 과부하다. 에이전트의 컨텍스트 윈도우는 유한하다. AGENTS.md에 300줄의 규칙이 적혀 있으면, 에이전트는 매 턴마다 그 300줄을 읽고 해석해야 한다. 그중 절반이 “함수명은 동사로 시작하라” 같은 취향 사항이라면, 에이전트는 진짜 중요한 규칙 — 예를 들어 “이 모듈은 절대 직접 import하지 마라” — 을 놓치기 쉽다.
둘째, 위양성(false positive)이다. 취향을 규칙으로 적어두면, 에이전트는 취향을 위반한 코드를 “오류”로 간주하고 수정하려 든다. 심지어 정상 작동하는 코드를 “스타일 가이드 위반”이라며 지워버릴 수도 있다. 리뷰어의 시간을 빼앗고, 에이전트의 토큰을 낭비하는 결과를 낳는다.
셋째, 팀의 적응성이 떨어진다. 새로운 팀원이 합류했을 때, “이건 꼭 지켜야 하는 거야?” vs “이건 그냥 누군가의 선호야?”를 구분할 수 없으면, 모든 것을 똑같이 무거운 규칙으로 받아들이게 된다. 코드 리뷰에서 “취향”과 “규칙”이 뒤섞인 피드백은 리뷰이에게 혼란을 준다.
그래서 “규칙”과 “취향”을 명확히 가르는 것은 단순한 정리의 문제가 아니다. 에이전트의 효율, 코드의 안정성, 팀의 속도 모두에 영향을 미치는 아키텍처 결정이다.
OpenClaw가 실제로 보여준 것
OpenClaw는 GitHub에서 31만 개 이상의 스타를 받은 오픈소스 프로젝트로, Cursor, Windsurf, Claude Code, GitHub Copilot, Aider, Codex, Cline, Continue 등 최소 8종 이상의 AI 에이전트가 동시에 코드를 제출하는 환경이다. 하루에 수십 건의 PR이 들어오고, 그중 상당수가 AI가 작성한 코드다.
이런 환경에서 OpenClaw가 채택한 전략은 단순하다. AGENTS.md에 “사고를 막아야 하는 경계”만 적는다는 것이다. 코드를 어떻게 짜야 하는지가 아니라, 어떻게 짜면 안 되는지를 적어둔다. 이 접근의 핵심은 “무엇을 해야 하는가”보다 “무엇을 하면 안 되는가”가 에이전트에게 훨씬 더 명확한 신호라는 점에 있다.
OpenClaw의 AGENTS.md를 분석해보면, 적힌 항목들은 대부분 다음 질문에 대한 답이다: “이것을 위반하면 버그, 충돌, 보안 사고, 또는 리뷰 혼선이 발생하는가?” 답이 “그렇다”인 것만 규칙으로 남아 있다.
리포지토리와 함께 관리해야 하는 규칙
버전 관리가 필요한 규칙은, 위반했을 때 시스템이 망가지거나 팀이 혼란에 빠지는 것들이다. OpenClaw의 사례와 실무 경험을 종합하면, 다음 아홉 가지 범주가 이에 해당한다.
1. 빌드 및 테스트 명령어
pnpm build
pnpm test
pnpm check
에이전트가 “빌드해봐”라는 지시를 받았을 때 어떤 명령어를 실행해야 하는지는 프로젝트마다 다르다. 이것이 명시되어 있지 않으면 에이전트는 npm run build를 실행할 수도 있고, cargo build를 실행할 수도 있다. 빌드 명령어가 틀리면 이후 모든 작업이 의미 없어진다. 마찬가지로 테스트 명령어도 반드시 명시해야 한다. 테스트를 돌리지 않은 코드가 머지되는 것을 막는 첫 번째 방어선이기 때문이다.
2. Git 안전 규칙
- 절대 main 브랜치에 직접 커밋하지 않는다
- force push는 금지한다
- .env 파일을 커밋하지 않는다
- git push --force는 어떤 상황에서도 사용하지 않는다
AI 에이전트가 git 작업을 수행할 때, 가장 위험한 것은 되돌릴 수 없는 파괴적 명령이다. git reset --hard, git push --force, git clean -f 같은 명령은 한 번의 실수로 작업물을 날려버릴 수 있다. 이런 규칙은 반드시 AGENTS.md에 명시하고, 가능하면 pre-commit 훅이나 CI에서도 강제해야 한다.
3. 아키텍처 제약
- src/core/의 모듈은 src/plugins/를 직접 import하지 않는다
- 데이터베이스 쿼리는 repository 레이어에서만 수행한다
- UI 컴포넌트에서 비즈니스 로직을 직접 호출하지 않는다
아키텍처 제약은 코드가 일정 수준 이상 복잡해지면 반드시 필요하다. 순환 의존성, 레이어 간 침범, 잘못된 의존성 방향 — 이런 것들이 누적되면 코드베이스는 점진적으로 유지보수 불가능한 상태로 빠져든다. 특히 AI 에이전트는 “일단 작동하게 만들면 된다”는 경향이 있으므로, 아키텍처 경계를 명시적으로 선언하는 것이 중요하다.
4. 보안 경계
- 사용자 입력은 항상 sanitize한다
- API 키는 절대 하드코딩하지 않는다
- SQL 쿼리는 문자열 조립이 아닌 파라미터화된 쿼리를 사용한다
- 외부 URL로의 요청은 허용 목록(allowlist)을 사용한다
보안 규칙은 위반했을 때 즉각적인 사고로 이어진다. AI 에이전트가 “빠르게 구현”하기 위해 보안을 건너뛰는 것을 방지하려면, 보안 경계를 AGENTS.md에 명시하고 CI에서도 검증해야 한다.
5. 고위험 영역
- src/auth/ 변경 시 반드시 security 리뷰어를 지정한다
- 데이터베이스 마이그레이션은 항상 되돌릴 수 있어야 한다
- 결제 관련 로직은 단위 테스트 커버리지 100%를 유지한다
프로젝트에는 특별히 주의가 필요한 영역이 있다. 인증, 결제, 권한, 데이터 처리 등이 그렇다. 이런 영역에서의 변경은 작은 실수가 큰 장애로 이어지므로, 에이전트에게 “이 영역은 특별히 조심하라”는 신호를 주어야 한다.
6. PR/커밋 단위 원칙
- 하나의 PR은 하나의 논리적 변경만 포함한다
- 커밋 메시지는 conventional commits 형식을 따른다
- 500줄 이상의 PR은 사전 논의가 필요하다
AI 에이전트는 한 번에 수십 개의 파일을 변경하는 경향이 있다. “이 버그도 고치고, 저 리팩토링도 하고, 테스트도 추가하고…” — 모두 한 PR에 몰아넣는다. 이것은 리뷰를 불가능하게 만든다. PR/커밋의 단위 원칙은 리뷰 품질을 유지하기 위한 필수 규칙이다.
7. 워크플로 순서
- 변경 전 반드시 이슈를 생성한다
- PR 생성 전 로컬에서 pnpm check를 통과해야 한다
- 머지 전 최소 1명의 승인이 필요하다
팀이 정한 워크플로를 AI 에이전트가 건너뛰면, 팀의 프로세스가 무너진다. 이슈 없이 PR을 만들고, 리뷰 없이 머지하고, 테스트 없이 배포하는 — 이런 일이 에이전트를 통해 발생하지 않도록 워크플로 순서를 명시해야 한다.
8. AI 투명성 요구사항
- AI가 작성한 코드는 커밋 메시지에 명시한다
- AI 생성 코드의 테스트는 사람이 직접 검증해야 한다
- AI가 제안한 아키텍처 변경은 설계 리뷰를 거쳐야 한다
AI가 코드를 작성했다는 사실을 감추는 것은 팀의 신뢰를 깎는다. OpenClaw를 비롯한 선도적인 프로젝트들은 AI의 개입을 투명하게 공개한다. 이것은 규정 준수의 문제이기도 하지만, 더 중요한 것은 디버깅 가능성이다. AI가 작성한 코드에서 버그가 발견되었을 때, 그 코드의 출처를 알면 디버깅 전략이 달라진다.
9. 품질 게이트
- 코드 커버리지는 80% 이상을 유지한다
- 모든 공개 API에는 JSDoc 주석이 있어야 한다
- TypeScript strict 모드를 사용한다
- lint 에러 0개, warning은 10개 이하
품질 게이트는 “이 프로젝트가 받아들일 수 있는 최소한의 기준”이다. 이 기준을 명시하지 않으면, AI 에이전트는 “일단 작동하게”만 코드를 제출한다. 테스트 없는 코드, 문서 없는 API, 타입 없는 함수 — 이런 것들이 누적되면 프로젝트의 유지보수성이 급격히 떨어진다.
개인의 취향으로 남겨둬도 되는 영역
반면, 위반했을 때 “나라면 이렇게 안 짰을 텐데” 정도의 반응만 나오는 것들은 규칙이 아니다. 이런 것들은 AGENTS.md에서 빼고, 개인의 로컬 설정이나 에이전트의 기본 동작에 맡기는 것이 좋다.
포매팅 세부사항
들여쓰기를 탭으로 할지 스페이스로 할지, 한 줄의 최대 길이를 80자로 할지 120자로 할지 — 이런 것은 Prettier나 ESLint가 알아서 처리한다. AGENTS.md에 적을 필요가 없다. 포매터 설정만 .prettierrc에 잘 정의해두면, 에이전트든 사람이든 저장 시 자동으로 통일된다.
함수명 붙이는 방식
getUserById vs findUserById vs fetchUserById — 어느 것을 선택하든 기능에는 영향이 없다. 중요한 것은 일관성이며, 일관성은 linter 규칙이나 네이밍 컨벤션 파일로 관리하는 것이 효율적이다. AGENTS.md에는 “함수명은 동사로 시작하라” 정도의 가이드만 있으면 충분하고, 구체적인 동사 선택은 취향에 맡긴다.
import 순서
// 이런 순서 규칙은 eslint-plugin-import로 관리한다
import React from 'react';
import { useState } from 'react';
import { Button } from '@/components/ui';
import { useAuth } from '@/hooks';
import { formatDate } from './utils';
import 문의 순서는 IDE와 linter가 자동으로 정렬해준다. eslint-plugin-import의 import/order 규칙을 설정해두면, 에이전트가 어떤 순서로 import를 작성하든 저장 시 자동으로 정렬된다. AGENTS.md에 “import는 알파벳순으로 정렬하라”고 적을 필요가 없다.
주석 스타일
// 방식 A: 인라인 주석
const result = processData(input); // 데이터 처리
// 방식 B: 블록 주석
/* 데이터 처리 */
const result = processData(input);
// 방식 C: JSDoc
/** 입력 데이터를 처리하여 결과를 반환합니다 */
const result = processData(input);
주석은 어떤 스타일을 쓰든 기능에 영향을 주지 않는다. JSDoc이 필요한 곳(공개 API)과 아닌 곳(내부 유틸리티)의 구분이 중요하지, 주석의 문체나 스타일은 개인의 영역이다.
테스트 파일 배치
src/
components/
Button.tsx
Button.test.tsx ← 같은 디렉토리
tests/
components/
Button.test.tsx ← 별도 디렉토리
테스트 파일을 소스 파일 옆에 둘지, 별도의 __tests__ 디렉토리에 둘지는 팀마다 다르다. 어느 쪽이든 테스트가 잘 돌아가기만 하면 기능적 차이는 없다. 이것은 팀 컨벤션의 영역이지, AGENTS.md에 강제할 규칙이 아니다.
추상화 시기
“DRY 원칙을 위해 세 번 이상 반복되면 함수로 추출하라” vs “반복되더라도 명시적인 것이 낫다” — 이것은 철학의 문제이지 규칙의 문제가 아니다. 프로젝트 초기에는 명시적인 것이 좋고, 성숙기에는 추상화가 좋을 수 있다. 에이전트에게 “항상 추상화하라”거나 “절대 추상화하지 마라”고 지시하는 것은 오히려 해롭다.
기능별 vs 계층별 구조
// 기능별 구조
src/
auth/
components/
hooks/
utils/
dashboard/
components/
hooks/
// 계층별 구조
src/
components/
auth/
dashboard/
hooks/
auth/
dashboard/
프로젝트 디렉토리를 기능별로 구성할지 계층별로 구성할지는 팀의 선택이다. 어느 쪽이든 장단점이 있으며, 한 번 정하면 일관성만 유지하면 된다. 이것은 AGENTS.md보다는 프로젝트의 디렉토리 구조 자체가 보여주는 것이 낫다.
에러 처리 패턴
// try-catch
try {
const data = await fetchData();
} catch (error) {
handleError(error);
}
// Result 타입
const result = await fetchData();
if (result.isErr()) {
handleError(result.error);
}
// Either 모나드
const result = await fetchData();
result.match({
ok: (data) => process(data),
err: (error) => handleError(error),
});
에러 처리를 try-catch로 할지 Result 타입으로 할지는 프로젝트의 철학에 따라 다르다. 중요한 것은 에러를 무시하지 않는다는 규칙이지, 에러를 어떻게 표현할지는 취향이다. “모든 에러를 처리하라”는 규칙으로 두고, 구체적인 패턴은 에이전트에게 맡긴다.
프롬프트 스타일
AI 에이전트에게 지시를 내릴 때, “다음 코드를 리팩토링해줘”라고 할지 “이 코드의 가독성을 개선하고, 함수를 단일 책임 원칙에 맞게 분리해줘”라고 할지는 개인의 선호다. 프롬프트의 상세함 정도, 어조, 구조 — 이런 것들은 AGENTS.md의 영역이 아니다.
한 줄 판별 기준
지금까지의 논의를 한 줄로 압축하면 이렇다:
위반했을 때 버그, 충돌, 보안 사고, 리뷰 혼선이 나면 저장소 규칙이다. 위반했을 때 “나라면 이렇게 안 짠다” 정도면 취향이다.
이 기준을 실제로 적용해보자. “데이터베이스 쿼리는 repository 레이어에서만 수행한다” — 이것을 위반하면 여러 컴포넌트에서 같은 쿼리가 중복되고, 스키마 변경 시 수정 포인트가 흩어지며, 테스트가 어려워진다. 규칙이다.
“import는 외부 라이브러리, 내부 모듈, 상대 경로 순으로 정렬한다” — 이것을 위반하면? 아무 일도 일어나지 않는다. IDE가 자동 정렬해주고, lint가 잡아준다. 취향이다.
“API 키는 절대 하드코딩하지 않는다” — 위반하면 즉각적인 보안 사고. 규칙이다.
“주석은 항상 영어로 작성한다” — 위반하면? 한국어 주석이 있어도 코드는 잘 돌아간다. 취향이다.2
이 기준을 팀에서 공유하면, 코드 리뷰에서도 “이건 규칙 위반이야”와 “이건 내 취향과 달라”를 명확히 구분할 수 있다. 리뷰의 질이 올라가고, 리뷰이의 수용 여부도 명확해진다.
실전 설계: 네 층으로 나누면 깔끔해진다
규칙과 취향을 구분했다면, 이제 그것을 어디에 어떻게 배치할지 정해야 한다. 실무에서 가장 효과적인 네 층 구조를 소개한다.
제1층: AGENTS.md — 하드 가드레일
AGENTS.md(또는 CLAUDE.md, .cursorrules)는 하드 가드레일만 적는다. 앞서 나열한 아홉 가지 범주 — 빌드 명령어, git 안전 규칙, 아키텍처 제약, 보안 경계, 고위험 영역, PR/커밋 원칙, 워크플로 순서, AI 투명성, 품질 게이트 — 만 여기에 들어간다. 목표는 에이전트가 절대로 넘지 말아야 할 선을 명확히 하는 것이다.
# AGENTS.md 예시
## 빌드 및 테스트
- 빌드: pnpm build
- 테스트: pnpm test
- 전체 검사: pnpm check
## 절대 금지
- main 브랜치에 직접 커밋 금지
- force push 금지
- .env 파일 커밋 금지
- src/core/에서 src/plugins/ import 금지
## 고위험 영역
- src/auth/ 변경 시 @security-team 리뷰 필수
- 데이터베이스 마이그레이션은 항상 rollback 가능해야 함
## PR 규칙
- 하나의 PR = 하나의 논리적 변경
- 커밋 메시지: conventional commits
- AI 작성 코드는 커밋 메시지에 명시
이 정도면 충분하다. 30~50줄이면 에이전트가 넘지 말아야 할 선을 모두 정의할 수 있다.
제2층: 포매터/린터 — 팀 컨벤션
포매팅, 네이밍, import 순서 같은 자동으로 검증 가능한 스타일 규칙은 포매터와 린터에 맡긴다. Prettier, ESLint, oxlint, biome — 무엇을 선택하든, 코드를 저장할 때 자동으로 적용되도록 설정한다.
{
"semi": true,
"singleQuote": true,
"trailingComma": "all",
"printWidth": 100,
"tabWidth": 2
}
이 설정은 .prettierrc에 들어간다. AGENTS.md에 “세미콜론을 사용하라”고 적을 필요가 없다. 포매터가 알아서 처리한다.
린터 규칙도 마찬가지다. no-unused-vars, no-explicit-any, prefer-const — 이런 것들은 .eslintrc에 정의하고, CI에서 강제한다. 에이전트가 이 규칙을 위반하면 CI가 빨간불을 띄우고, 에이전트는 스스로 수정한다.
제3층: 스킬/플레이북 — 반복 절차
특정 작업에 대한 반복적인 절차는 스킬(skills)이나 플레이북(playbook)으로 관리한다. “버그를 수정할 때의 절차”, “새로운 API 엔드포인트를 추가할 때의 체크리스트”, “릴리스 절차” — 이런 것들은 AGENTS.md에 적기에는 너무 길고, 매번 에이전트의 컨텍스트를 차지하기에는 비효율적이다.
skills/
bug-fix/
SKILL.md # 버그 수정 절차
new-api/
SKILL.md # API 추가 체크리스트
release/
SKILL.md # 릴리스 절차
에이전트가 해당 작업을 수행할 때만 관련 스킬을 로드하고, 작업이 끝나면 해제한다. 이렇게 하면 AGENTS.md는 가볍게 유지하면서도, 복잡한 절차를 체계적으로 관리할 수 있다.
제4층: 개인 로컬 프롬프트 — 개인 취향
마지막으로, “나는 함수를 이런 식으로 작성하는 걸 좋아한다” 같은 개인의 취향은 로컬 설정에 둔다. Claude Code의 ~/.claude/settings.json, Cursor의 개인 설정, VS Code의 settings.json — 이런 곳에 개인의 선호를 적어둔다.
# ~/.claude/settings.json (예시)
{
"preferences": {
"commentStyle": "korean",
"functionNaming": "verb-first",
"testLocation": "co-located"
}
}
이 설정은 저장소에 커밋되지 않으므로, 다른 팀원이나 에이전트에게 영향을 주지 않는다. 개발자 각자의 취향이 저장소 규칙과 충돌하지 않으면서, 개인의 생산성을 극대화할 수 있다.
중요한 것은 개발자를 복제하는 것이 아니다
AI 에이전트에게 규칙을 줄 때 흔히 하는 실수는 특정 개발자의 코딩 스타일을 에이전트에게 복제하려는 것이다. “나는 이런 식으로 코드를 짜니까, 에이전트도 이렇게 짜야 해” — 이것은 자연스러운 욕구지만, 올바른 방향은 아니다.
에이전트가 따라야 하는 것은 개인의 스타일이 아니라 프로젝트의 경계다. 이 함수를 어떻게 구현할지는 에이전트가 결정하게 두되, 이 모듈에서 저 모듈을 import하면 안 된다는 것만 명확히 하면 된다. 어떤 에러 처리 패턴을 쓸지는 에이전트가 상황에 맞게 선택하게 두되, 에러를 무시하면 안 된다는 것만 강제하면 된다.
OpenClaw의 AGENTS.md가 훌륭한 이유는 바로 이 점에 있다. 그 문서는 “어떻게 코드를 작성할지”가 아니라 “어떻게 코드를 작성하지 말아야 할지”를 가르쳐준다. 31만 스타 프로젝트에 수천 건의 PR이 쏟아지는 환경에서, 이런 최소한의 경계만으로 품질을 유지할 수 있다는 것은 시사하는 바가 크다.
규칙은 가볍게, 취향은 자유롭게. 이 원칙만 기억하면, AI 에이전트와 함께 일하는 환경을 훨씬 더 효율적으로 설계할 수 있다.