AI 에이전트 프롬프트: 코드 재앙을 방지하기 위한 가드레일 (Guardrails)
요약
AI 에이전트가 프로젝트의 기존 패턴과 의존성을 준수하도록 돕는 CLAUDE.md 활용법을 다룹니다. 에이전트에게 명확한 컨텍스트를 제공하여 코드 품질 저하와 잘못된 추론을 방지하는 가드레일 구축 방법을 설명합니다.
핵심 포인트
- CLAUDE.md는 에이전트를 위한 아키텍처 온보딩 문서 역할을 함
- 컨텍스트 부재 시 에이전트는 잘못된 패턴과 의존성을 도입할 수 있음
- Claude Code, Cursor, Aider 등 도구별 컨텍스트 설정 방식 활용 가능
- 에이전트가 프로젝트의 일관성을 유지하도록 가드레일 설정 필요
지난달에 몇 주 동안 손대지 않았던 오래된 프로젝트를 열었고, CLAUDE.md가 최신 상태인지 확인하지 않은 채 새로운 AI 세션을 시작했습니다. 10분도 채 되지 않아 에이전트는 제가 요청하지 않은 의존성 (dependency)을 도입했고, 기존 파일을 수정하는 대신 새로운 유틸리티 파일을 생성했으며, 이전 스프린트에서 교체했던 패턴을 사용하여 컴포넌트를 작성했습니다. 아무것도 고장 나지는 않았습니다. 세 가지 사항 모두 제가 이 시리즈의 이전 기사들에서 설명한 방식과 정확히 일치하게 미묘하게 잘못되어 있었습니다.
그 후 이전 스프린트의 변경 사항을 반영하여 CLAUDE.md를 업데이트하고 세션을 다시 시작했습니다. 동일한 에이전트, 동일한 작업이었습니다. 이번에는 결과물이 딱 맞았습니다. 올바른 파일 위치, 새로운 의존성 없음, 올바른 패턴의 컴포넌트까지 말이죠.
이것은 마법이 아닙니다. 바로 컨텍스트 (context)입니다. 에이전트는 자신이 볼 수 있는 범위 내에서 최적화하며, 세션 시작 시점에 무엇을 볼 수 있느냐가 에이전트가 무엇을 구축할지를 결정합니다. 모든 프롬프트에 해당 컨텍스트를 전달할 수도 있지만, 이는 매우 소모적이고 일관성이 없습니다. 대신 에이전트가 자동으로 읽는 곳에 한 번만 적어두면 됩니다. CLAUDE.md가 바로 그 장소입니다.
이 글은 이 시리즈에서 가장 실용적인 기사입니다. 철학이나 AI가 어디로 향하고 있는지에 대한 평가는 없습니다. 제가 CLAUDE.md에 무엇을 넣는지, 시스템 프롬프트 (system prompt)에 무엇을 유지하는지, 그리고 에이전트가 세션 도중 잘못된 방향으로 갈 때 이를 멈추기 위해 사용하는 짧은 문구들에 대해서만 다룹니다.
CLAUDE.md란 무엇이며 왜 중요한가
CLAUDE.md는 저장소(repository)의 루트(root)에 배치하는 파일입니다. Claude Code는 매 세션이 시작될 때 이 파일을 자동으로 읽습니다. 다른 도구들도 이에 상응하는 기능이 있습니다: Cursor의 .cursorrules, Aider 설정의 시스템 프롬프트(system prompt), OpenAI 도구를 사용할 경우의 AGENTS.md 등이 있습니다. 이름은 다르지만 기능은 유사합니다. 효과의 강도는 에이전트마다 상당히 다릅니다. 어떤 에이전트는 이 파일을 모든 응답을 규정하는 엄격한 서문(preamble)으로 취급하는 반면, 어떤 에이전트는 여러 입력 중 하나로 취급합니다. 이후의 내용은 대부분 더 강력한 효과를 내는 경우를 가정하며, 이는 저의 Claude Code 워크플로(workflow)와 일치합니다. 만약 사용 중인 에이전트가 지속적인 컨텍스트(persistent context)를 더 느슨하게 취급한다면 그에 맞춰 조정하십시오.
올바른 멘탈 모델(mental model): CLAUDE.md는 매 세션마다 프로젝트에 새로 합류하는 엔지니어를 위한 아키텍처 온보딩(architectural onboarding) 문서입니다. 그 엔지니어는 유능하고 빠르지만, 이 대화가 시작되기 전에 일어난 일에 대해서는 전혀 기억이 없습니다. 그들은 당신이 명시하지 않은 모든 것에 대해 그럴듯하게 들리는 결정을 내릴 것입니다. 그러한 결정의 대부분은 개별적으로는 방어 가능할 것입니다. 하지만 일부는 세 세션 전에 내려진 결정과 충돌할 것이며, 몇몇은 당신이 제거하는 데 시간을 들였던 패턴을 다시 도입할 것입니다.
CLAUDE.md가 없다면, 에이전트는 현재 컨텍스트에서 볼 수 있는 코드를 바탕으로 프로젝트에 대한 멘탈 모델을 재구성합니다. 에이전트는 추론(inference)을 합니다. 어떤 추론은 정확합니다. 하지만 정확하지 않은 추론은 제가 첫 번째 글에서 설명한 문제들을 발생시킵니다: 중복된 패턴, 재발명된 추상화(reinvented abstractions), 기존에 무엇이 있는지 고려하지 않고 추가된 의존성(dependencies) 등이 그것입니다.
이 시리즈의 두 번째 글에서는 AI 시대에 시니어 개발자의 핵심 강점으로서 명세 정밀도(specification precision)라는 개념을 소개했습니다. CLAUDE.md는 그 정밀도가 지속적으로 유지되는 곳입니다. 매 프롬프트마다 처음부터 재구성되는 것이 아니라, 한 번 작성되면 모든 세션에 상속됩니다.
훌륭한 CLAUDE.md는 모델이 단 한 줄의 코드를 작성하기 전에 아키텍처 결정 사항을 모델에게 가시화해 줍니다. 이것이 텍스트 파일이 할 수 있는 최선이며, 이것이 전부인 것은 아닙니다. 나머지는 엔지니어링 제어 (engineering controls)를 통해 이루어지며, 이에 대해서는 잠시 후에 다시 다루겠습니다.
효과적인 구조
지난 1년 동안 다양한 형식을 시도해 보았습니다. 다음은 제가 최종적으로 정착한 구조와 각 섹션에 대한 이유입니다.
기술 스택 및 버전 (Tech stack and versions)
실제로 사용 중인 것부터 시작하세요. 모델이 알고 있을 것이라고 가정하지 마세요. 프레임워크 버전은 매우 중요합니다. 예를 들어, Next.js 15와 Next.js 16은 비동기 파라미터 (async params) 동작이 다르며, Tailwind v3와 v4는 완전히 다른 설정을 가집니다. 버전을 명시하지 않으면 모델은 자신의 학습 데이터에서 가장 흔한 것을 기준으로 코드를 작성하며, 이는 여러분이 실행 중인 환경과 일치하지 않을 수 있습니다.
## Stack
- Next.js 16 App Router (Pages Router 아님)
...
이 목록을 작성하는 데는 30초밖에 걸리지 않지만, 각각 진단하는 데 10분이 걸릴 수 있는 일련의 오류들을 방지해 줍니다.
일반적인 방식과 다른 중요한 설정 (Critical configuration that differs from the norm)
이 섹션은 여러분의 설정에 익숙하지 않은 사람에게는 틀려 보일 수 있지만, 나름의 이유로 올바른 사항들을 문서화하는 곳입니다. 모델은 일반적인 패턴을 기본값으로 사용하므로, 이 섹션이 이를 덮어씁니다 (override).
## Critical config
- Fonts: `geist` npm 패키지 사용 (`geist/font/sans`의 `GeistSans`).
...
이것들은 관습 (conventions)이 아닙니다. 모델이 알지 못할 경우 설정 실수처럼 보이는 버그를 유발할 수 있는 여러분의 환경에 대한 사실 (facts)입니다. 이를 문서화하세요.
아키텍처 결정 사항 (Architecture decisions)
완전한 ADR (Architecture Decision Record)은 아니지만, 새로운 코드가 어떻게 작성되어야 하는지를 결정하는 선택 사항들을 짧게 기록한 것입니다. 핵심은 논의 과정이 아니라 결과물을 포함하는 것입니다.
## Architecture
- 콘텐츠는 `content/`에 위치하며, Velite에 의해 `.velite/`로 컴파일됩니다 (`@/.velite`로 임포트됨).
...
이 섹션은 모델이 기존의 결정과 충돌하는 아키텍처 결정을 내리는 것을 방지합니다. 이 섹션이 없다면 모델은 스스로를 발명(invent)합니다. 모델의 발명품들은 국소적으로는 일관성이 있어 보이지만, 바로 그 점 때문에 세션이 세 번 정도 지나고 코드베이스에 두 가지의 인증 패턴이 존재하게 될 때까지는 알아차리기 어렵습니다.
컨벤션 (Conventions)
명명 규칙 (Naming), 파일 배치 (file placement), 컴포넌트 구조 (component structure). 프로젝트가 일관된 스타일을 따르는 곳이라면 어디든 기록해 두세요. 모델은 컨텍스트(context) 내에서 볼 수 있는 모든 것을 맞추려 노력할 것입니다. 만약 현재 세션에 관련 파일이 없다면, 모델은 그럴듯해 보이는 무언가를 새로 만들어낼 것입니다.
## Conventions
- 두 군데 이상의 장소에서 사용되는 컴포넌트는 components/shared/에 위치합니다.
...
하지 말아야 할 것 — 가장 중요한 섹션
이 섹션은 다른 어떤 섹션보다 지속적으로 더 많은 역할을 수행합니다. 부정적인 규칙(negative rules)은 실제로 발생했거나 현재 적극적으로 우려하고 있는 실패를 방지하는 지점입니다.
핵심은 금지 사항과 함께 그 이유를 포함하는 것입니다. "X를 하지 마세요"는 모호합니다. "X를 하지 마세요. 왜냐하면 지난번에 X를 했을 때 Y가 발생했으므로, 이제는 대신 Z를 사용합니다"라고 하면 모델이 이전에 보지 못했을 수도 있는 유사한 상황을 인식할 수 있는 충분한 컨텍스트를 제공하게 됩니다.
## Do not
- 요청 없이 npm 의존성(dependencies)을 추가하지 마세요. `axios`가 추가되어...
...
핵심 파일 (Critical files)
새로운 기여자가 가장 먼저 알아야 할 파일들의 짧은 참조 목록입니다. 이는 모델이 불완전한 컨텍스트로부터 구조를 추론하는 대신 빠르게 방향을 잡을 수 있도록 도와줍니다.
## Key files
- config/site.ts — siteConfig, 네비게이션 링크, 가용성 상태
...
이것은 좋은 문서화(documentation)를 대체하는 것이 아닙니다. 모델이 새로운 것을 발명하는 대신 기존의 패턴을 찾을 수 있도록 안내하는 포인터(pointer)입니다.
CLAUDE.md에서 작동하지 않는 것
너무 길게 만드는 것. 이 파일은 모델의 컨텍스트 윈도우 (context window)의 일부입니다. 만약 파일이 500줄이라면, 모델이 실제 코드를 위해 사용할 수 있는 공간 500줄을 낭비하게 됩니다. 저는 150줄 미만으로 유지합니다. 규칙은 간단합니다. 만약 특정 작업에서만 언급될 내용이라면, 그것은 CLAUDE.md가 아니라 프롬프트 (prompt)에 포함되어야 합니다. 이 파일은 모든 세션에 적용되는 사항들을 위한 것입니다.
모호한 규칙. "깨끗한 코드를 작성하세요." "베스트 프랙티스 (best practices)를 따르세요." "단순하게 유지하세요." 이런 것들은 장식에 불과합니다. 모델은 이미 자신이 이해하는 바에 따라 이러한 것들을 수행하려고 노력하고 있습니다. 열망이 아닌 구체적인 규칙을 작성하세요. "순환 복잡도 (cyclomatic complexity)가 10을 초과하는 함수를 분해하세요"가 "함수를 짧게 유지하세요"보다 훨씬 낫습니다.
삭제하는 것을 잊어버린 오래된 규칙. 접근 방식을 변경했는데 CLAUDE.md를 업데이트하지 않았다면, 모델은 이전 규칙을 — 자신 있게, 일관되게, 그리고 틀리게 — 따를 것입니다. 이 파일은 유지보수가 필요합니다. 아키텍처 결정 (architectural decision)을 변경할 때는 동일한 커밋 (commit)에서 파일을 업데이트하세요. 오래된 규칙은 규칙이 없는 것보다 더 나쁩니다. 왜냐하면 적극적으로 잘못된 방향으로 유도하기 때문입니다.
README 중복 작성. CLAUDE.md는 사람이 아닌 모델을 위해 작성됩니다. 마케팅 문구, "프로젝트 비전" 단락, 또는 신입 사원을 위한 온보딩 (onboarding) 지침을 포함해서는 안 됩니다. 설정 파일 (config file)을 작성하듯 밀도 있고, 구체적이며, 명령조로 작성하세요. README는 사람을 위한 것이고, CLAUDE.md는 에이전트 (agent)를 위한 것입니다. 실제 운영 중인 SaaS 전반에서 이러한 규율이 어떻게 적용되는지에 대한 실례로, vatnode.dev 코드베이스가 정확히 이 구조를 사용하고 있습니다.
자동화가 텍스트보다 나은 지점
slug="fractional-cto"
text="저는 엔지니어링 팀이 CLAUDE.md, 린트 규칙 (lint rules), CI 게이트 (CI gates), 리뷰 규율 등 AI 보조 개발을 아키텍처적으로 일관되게 만드는 구조를 구축하도록 돕습니다."
/>
이 부분은 덜 정직한 글이라면 건너뛰었을 섹션입니다. CLAUDE.md에 포함되는 많은 규칙은 그곳에 있어서는 안 됩니다. 대신 툴체인 (toolchain)에 의해 강제되어야 합니다.
"TypeScript에서 any를 사용하지 마세요"는 린트 (lint) 규칙입니다. ESLint의 @typescript-eslint/no-explicit-any는 이를 결정론적 (deterministically)으로 잡아내지만, CLAUDE.md는 이를 확률론적 (probabilistically)으로 잡아냅니다. "묻지 않고 의존성 (dependency)을 추가하지 마세요"는 package.json 차이점 (diffs)에 대한 CI 체크입니다. ".velite에서 직접 임포트(import)하지 마세요"는 한 줄의 메시지가 포함된 ESLint의 no-restricted-imports 규칙입니다. "순환 복잡도 (Cyclomatic complexity)는 10을 초과해서는 안 됩니다"는 정적 분석 (static-analysis) 규칙입니다. "테스트는 반환 타입 (return types)이 아니라 동작을 단언해야 합니다"는 typeof X === 'number' 단언을 표시하는 커스텀 린트를 통해 부분적으로 잡아낼 수 있습니다.
패턴은 이렇습니다: 기계적으로 검증할 수 있는 모든 것은 기계적으로 검증되어야 합니다. 린터 (Linters), 포매터 (formatters), CI 게이트 (CI gates), 타입 체크 (type checks), AST 기반 규칙 (AST-based rules), 의존성 관리에서의 패키지 승인 단계 (package-approval steps) — 이 모든 것들은 모델이 무엇을 출력하기로 결정하든 상관없이 제약 조건을 강제합니다. 이들은 모델이 지침을 올바르게 읽고 따르는 것에 의존하지 않습니다. 대신 경계 (boundary)에서 잘못된 출력을 거부합니다. CLAUDE.md는 그런 방식으로 인코딩할 수 없는 것들, 즉 아키텍처 선택 (architectural choices), 역사적 이유, "이것을 시도해 보았으나 작동하지 않았다"와 같은 맥락 (context)을 잡아내는 역할을 합니다. 그 외의 모든 것에 대해서는 툴링 (tooling)에 의지하세요.
이것이 중요한 이유는 텍스트 프롬프트 (text prompts)에 한계가 있는 이유와 동일합니다. 완벽하게 작성된 지침이라 할지라도 확률론적으로 수행됩니다. 린트 규칙은 결정론적으로 수행됩니다. 선택권이 있다면, 결정론적인 메커니즘을 선택하세요. CLAUDE.md는 설정 파일 (config file)이 강제할 수 있는 것이 아니라, 시니어 엔지니어의 기억에만 담길 수 있는 것들을 위해 사용하세요.
시스템 프롬프트 (System Prompts) vs. CLAUDE.md
CLAUDE.md는 프로젝트별 관례 (per-project conventions)를 포착합니다. 시스템 프롬프트 (System prompts) — AI 도구의 설정에서 구성되어 모든 세션에 적용되는 것 — 는 모든 프로젝트에 걸쳐 나타나는 개발자별 습관 (per-developer habits)을 포착합니다.
이러한 구분은 규칙이 어디에 속해야 하는지를 결정하기 때문에 중요합니다. "Prisma 대신 Drizzle ORM을 사용하세요"는 프로젝트 컨벤션 (convention)입니다. 즉, 해당 프로젝트에만 적용됩니다. 반면 "의존성 (dependency)을 설치하기 전에 항상 물어보세요"는 개발자 습관 (developer habit)입니다. 저는 어떤 프로젝트에 있든 이 규칙이 강제되기를 원합니다.
저의 글로벌 시스템 프롬프트 (global system prompt)는 짧습니다. 보편적으로 유용하다고 제안할 만한 부분은 다음과 같습니다:
어떠한 패키지나 의존성 (dependency)을 설치하기 전에, 그것이 정말 필요한지,
그리고 동일한 요구사항을 충족하는 기존 요소가 있는지 물어보세요.
...
제 경험상 세 번째가 세 가지 중 가장 유용합니다. 모델은 특정 접근 방식을 선택하고 이를 구현하려는 경향이 있습니다. 선택지를 명시적으로 드러내는 것은 몇 초의 시간이 더 걸리지만, 나중에 다시 검토해야 했을 여러 결정으로부터 저를 구해 주었습니다.
세션의 흐름을 중간에 멈추는 프롬프트
CLAUDE.md가 세션의 시작을 처리한다면, 이 프롬프트들은 세션 중간에 출력이 원치 않는 방향으로 흐를 때 발생하는 상황을 처리합니다.
저는 이전 기사에서 이러한 중단을 유발하는 신호들을 설명했습니다 — 요청하지 않았는데 나타난 새로운 의존성 (dependency), 기존 파일을 수정하는 대신 생성된 새 파일, 기존 패턴을 따르는 대신 새로 만들어낸 패턴 등이 그것입니다. 이 프롬프트들은 그러한 신호들에 대한 대응책입니다.
새 파일을 작성하지 마세요. [path/to/existing/file]을 수정하세요.
모델이 기존 파일에 포함되어야 할 기능을 위해 새 파일을 생성하는 것을 보는 즉시 이 프롬프트를 사용하세요. 두 개의 출력을 절충하려고 하지 마세요. 생성된 것을 버리고, 대상 파일명을 명시하여 명확하게 재지시하십시오. 모델은 정밀함에 반응합니다. "기존 파일을 수정하세요"라고 하는 것보다 "lib/queries/orders.ts를 수정하세요"라고 하는 것이 더 효과적입니다.
이 패턴이 [path]에 이미 존재하는지 확인하세요. 존재한다면, 대신 그 패턴을 따르세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기