본문으로 건너뛰기

지시를 늘릴수록 덜 지켜진다 — 에이전트가 실제로 따르는 스펙 쓰는 법

홍승아11

1. 이 글은 무엇에 관한 글인가?

Addy Osmani의 글 How to write a good spec for AI agents를 읽고 정리한 글입니다.

글은 저자가 받은 질문 하나에서 출발합니다.

"에이전트용 스펙을 잘 쓰라는 얘기는 많이 들었는데, 제대로 된 프레임워크는 아직 못 봤습니다."

이 질문이 정확합니다. "스펙을 잘 써라"는 조언은 어디에나 있는데, 무엇을 얼마나 쓰라는 것인지 를 말해주는 자료는 드뭅니다. 그리고 이 글의 답은 직관과 반대 방향입니다.

스펙을 크게 쓰면 안 됩니다. 컨텍스트 윈도우 한계와 모델의 어텐션 예산(attention budget) 이 가로막습니다.

컨텍스트 엔지니어링 글"규칙을 덜어내라" 였다면, 이 글은 그 바로 옆 칸입니다 — "그러면 남길 규칙은 무엇이고, 어떤 모양이어야 하는가." 덜어내기는 뺄셈이고, 스펙 쓰기는 무엇을 남길지 고르는 일 입니다.


2. 비전을 먼저, 디테일은 에이전트에게

글의 첫 번째 원칙은 순서에 관한 것입니다. 사람이 상세 스펙을 다 쓰고 시작하지 말라 는 겁니다.

❌ 사람이 50페이지 스펙 작성 → 에이전트에 투입
✅ 사람이 짧은 제품 브리프 작성 → 에이전트가 상세 스펙 초안 → 사람이 교정 → 실행

이유가 실무적입니다. 부연(elaboration)은 모델이 잘하는 일 이고, 방향(direction)은 사람만 정할 수 있는 일 입니다. 둘을 섞어서 사람이 다 하면, 사람은 잘 못하는 일에 시간을 쓰고 모델은 잘하는 일을 못 씁니다.

글이 드는 예시 브리프는 이 정도로 짧습니다.

"사용자 계정과 영속 저장소를 가진 할 일 앱을 만든다."

여기서 에이전트가 스키마·라우팅·에러 케이스·테스트 전략을 펼쳐내면, 사람은 그걸 읽고 틀린 곳만 고칩니다. 백지에서 쓰는 것보다 빠르고, 무엇보다 빠진 항목이 눈에 보입니다.

플랜 모드가 여기에 붙는 이유

글은 이 단계에서 Plan Mode(읽기 전용) 를 쓰라고 말합니다. 파일을 못 고치는 상태로 스펙을 다듬는 것.

이게 왜 중요하냐면, 스펙 교정과 코드 작성이 섞이면 교정이 끝나기 전에 코드가 생기기 때문입니다. 코드가 생기면 스펙을 고치는 비용이 갑자기 올라갑니다. 읽기 전용은 그 비용을 0으로 묶어두는 장치입니다.

그리고 다듬은 결과는 SPEC.md 같은 파일로 남깁니다. 대화가 아니라 파일이어야 하는 이유는 3절과 6절에서 계속 나옵니다.


3. 2,500개 에이전트 설정 파일이 말해주는 여섯 칸

글에서 가장 손에 잡히는 부분입니다. GitHub이 에이전트 설정 파일 2,500개 이상 을 분석한 결과, 좋은 파일에는 공통으로 여섯 개 영역이 있었다는 것.

영역들어가야 하는 것흔한 실패
명령어(Commands)플래그까지 포함한 실행 가능한 전체 명령 (pnpm test, pytest -v)"테스트를 돌린다"로만 적혀 있음
테스트(Testing)프레임워크 · 테스트 위치 · 커버리지 기대치어떤 러너인지 안 적혀 있음
프로젝트 구조소스·테스트·문서가 각각 어디인지에이전트가 파일을 엉뚱한 곳에 만든다
코드 스타일실제 코드 예시 로 보여준 컨벤션형용사로만 적혀 있음("깔끔하게")
Git 워크플로우브랜치 명명 · 커밋 포맷 · PR 요건매번 사람이 고쳐준다
경계(Boundaries)하드 제약 · 절대 건드리면 안 되는 것아예 없다

여섯 칸 중 실무에서 제일 자주 비는 건 경계 입니다. 나머지 다섯은 "설명"이라 쓰다 보면 채워지는데, 경계는 사고를 겪어봐야 떠오르는 항목 이기 때문입니다.

글이 인용하는 한 문장이 이걸 요약합니다.

"절대 시크릿을 커밋하지 마라(Never commit secrets)" 가 가장 흔하게 등장한, 그리고 가장 도움이 된 제약이었다.

권장 골격

글이 제시하는 마크다운 구조는 이렇습니다.

# Project Spec: [이름]
 
## Objective
[명확한 목표 한 문장]
 
## Tech Stack
[구체적인 버전과 의존성]
 
## Commands
- Build: `npm run build`
- Test: `npm test`
 
## Project Structure
- `src/` – 애플리케이션 코드
- `tests/` – 유닛/통합 테스트
 
## Boundaries
- ✅ 항상: [안전한 행동]
- ⚠️ 먼저 물어볼 것: [영향이 큰 변경]
- 🚫 절대 금지: [하드 스톱]

눈여겨볼 건 분량이 아니라 형태 입니다. 각 항목이 한 줄에 하나의 사실 이고, 형용사가 아니라 명령어·경로·이름 입니다. 모델이 지킬 수 있는 건 이 모양뿐입니다.


4. 지시의 저주 — 스펙이 커지면 오히려 안 지켜진다

이 글의 이론적 뼈대입니다. 학계에서 "지시의 저주(the curse of instructions)" 라 부르는 현상.

프롬프트 안의 지시·데이터가 늘어날수록, 각각의 요구사항을 지키는 비율이 눈에 띄게 떨어진다. GPT-4나 Claude 같은 모델도 동시에 많은 요구를 만족시키는 데 어려움을 겪는다.

읽고 나서 제일 오래 남은 문장입니다. 우리가 스펙을 늘리는 동기는 보통 "이것도 빠뜨리면 안 되니까" 인데, 그 동기가 만들어낸 문서가 앞서 적어둔 항목까지 같이 무너뜨립니다.

직관:  규칙을 20개 적으면 20개가 지켜진다
실제:  규칙을 20개 적으면 5개짜리 문서보다 개별 준수율이 떨어진다

그래서 스펙 작성은 덧셈이 아니라 예산 배분 입니다. 새 규칙 하나를 넣는 건 공짜가 아니라, 기존 규칙들의 준수 확률을 조금씩 깎아서 사는 것 입니다. 이 관점이 덜어내는 컨텍스트 엔지니어링과 정확히 같은 지점에서 만납니다.

쪼개는 네 가지 방법

글이 제시하는 해법은 전부 "나누기"입니다.

방법내용
컴포넌트별 분할백엔드 스펙 · 프론트엔드 스펙을 나눠 맥락에 맞는 것만 넣는다
요약이 붙은 확장 목차상세 절을 참조하는 압축 목차를 만들어 필요할 때만 펼친다
서브에이전트 / 스킬도메인별 특화 에이전트가 스펙의 자기 몫만 들고 간다
순차 집중프롬프트 하나에 작업 하나. 중간에 컨텍스트를 새로 간다

세 번째 항목이 스킬 거버넌스 글과 이어집니다. 거기서 서브에이전트는 "대부분 컨텍스트 윈도우를 지키려고 존재한다" 고 정리했는데, 이 글은 그 방어의 이론적 근거 를 대주는 셈입니다. 컨텍스트를 지키는 건 용량 문제가 아니라 준수율 문제 입니다.

병렬 에이전트에 붙은 단서

글은 독립적인 기능이라면 "하나는 코딩, 하나는 테스트, 하나는 리뷰" 로 동시에 돌리는 방식도 소개합니다. 다만 조건을 답니다 — 처음엔 2~3개로 제한하고, 작업 분리가 명확해야 하며, 공유 컨텍스트를 조율할 오케스트레이션이 필요하다는 것.

저는 이 단서가 본문보다 중요하다고 봤습니다. 병렬 에이전트는 컨텍스트를 나누는 장치인 동시에 충돌을 만드는 장치 입니다. 작업 경계가 흐린 상태로 늘리면, 지시의 저주를 피하려다 머지 충돌과 서로 덮어쓰기 를 삽니다.


5. 세 단계 경계 — ✅ / ⚠️ / 🚫

3절에서 가장 자주 빈다고 한 그 칸의 구체적인 모양입니다.

단계의미예시
✅ 항상확인 없이 해야 하는 것"커밋 전에 항상 테스트를 돌린다"
⚠️ 먼저 물어볼 것되돌리기 비싼 것"DB 스키마를 고치기 전에 물어본다"
🚫 절대 금지예외 없는 하드 스톱"시크릿을 커밋하지 않는다"

세 칸으로 나뉜 게 핵심입니다. 경계를 금지 목록 하나로만 쓰면 두 가지가 동시에 망가집니다.

금지만 있을 때: 애매한 것이 전부 "금지 아님"으로 흘러가 그냥 실행된다
금지만 있을 때: 반대로 조심시키려고 전부 금지에 넣으면 에이전트가 아무것도 못 한다

⚠️ 칸은 "사람에게 되돌려주는 밸브" 입니다. 이 밸브가 없으면 모든 판단이 흑백이 되고, 실제 개발에서 중요한 결정은 대부분 회색이라 밸브 없는 스펙은 늘 둘 중 한쪽으로 고장 납니다.

자가 점검을 스펙 안에 심기

글은 경계 외에도 세 가지를 더 권합니다.

  • 스펙 대조 검증 — 코드를 쓴 뒤 에이전트가 자기 결과물을 스펙 요구사항과 대조 하게 시킨다
  • LLM-as-a-Judge — 주관적 판단이 필요한 항목에 심사용 모델을 둔다
  • 적합성 테스트(conformance test)스펙에서 직접 도출한 테스트를 붙인다

세 번째가 가장 값이 큽니다. 스펙 문장이 테스트가 되면, 스펙은 읽히기를 바라는 문서 에서 안 지키면 빨간불이 켜지는 장치 로 바뀝니다. 코드 품질은 제약에 있다에서 정리한 "에이전트를 둘러싸는 것"이 바로 이 모양입니다.


6. 스펙은 살아 있는 산출물이다

글의 마지막 원칙입니다. 한 번 쓰고 끝나는 문서가 아니라는 것.

스펙은 공유된 진실의 원천(shared source of truth) 이 된다 — 프로젝트와 함께 진화하는 살아 있고 실행 가능한 산출물.

실천 항목은 이렇습니다.

항목내용
마일스톤마다 테스트스펙과 구현이 어긋난 걸 일찍 잡는다
스펙 갱신요구사항이 빠졌던 걸 발견하면 문서를 고친다 (대화에서 때우지 않는다)
스펙의 버전 관리스펙 자체를 형상관리한다
에이전트 행동 로그지시를 어떻게 오해했는지 역추적한다
컨텍스트 관리 도구큰 스펙은 RAG·MCP·벡터 DB로 필요한 절만 꺼내 쓴다

마지막 항목이 4절의 해법과 같은 얘기입니다. 스펙이 커지는 걸 못 막는다면, 적어도 한꺼번에 들어가는 걸 막으라는 것.

모델 선택과 비용

글은 실무 항목도 하나 답니다 — 계획과 중요한 단계에는 좋은 모델을, 기계적인 부연에는 싼 모델을. 그리고 컨텍스트 크기를 적절히 조절하라고 덧붙입니다.

토큰이 많다고 결과가 좋아지지 않는다. 길이보다 컨텍스트의 품질이 중요하다.

Claude 모델 고르기에서 정리했던 것과 같은 결론입니다. 단계마다 필요한 게 다르고, 전 단계에 최고 모델을 쓰는 건 비용을 태우는 방식으로 게으른 것 입니다.


7. 안티패턴 — GitHub 연구가 꼽은 실패들

안티패턴왜 실패하나
모호한 프롬프트"멋진 거 만들어줘"에는 붙잡을 앵커가 없다
요약 없는 초장문 컨텍스트50페이지 문서 투하는 실패한다 (4절)
사람 리뷰 건너뛰기테스트 통과 = 정확함 이 아니다
프로토타이핑과 프로덕션을 뭉뚱그림어디서 엄격해야 하는지 를 스펙에 적어야 한다
여섯 칸 중 일부 누락빈 칸이 그대로 구멍이 된다 (3절)
속도·비확정성·비용 리스크 무시검증이 에이전트 속도를 따라가야 한다

네 번째가 특히 자주 보입니다. 같은 저장소 안에서도 버려질 프로토타입1년 갈 코드 가 섞여 있는데, 스펙이 그 구분을 안 하면 에이전트는 둘 다 같은 강도로 처리합니다. 결과는 둘 다 나쁩니다 — 프로토타입은 과하게 만들어지고, 프로덕션 코드는 프로토타입 취급을 받습니다.

마지막 항목의 "검증이 속도를 따라가야 한다" 도 한 번 더 씹을 만합니다. 에이전트는 사람보다 빠르게 코드를 만들어내는데, 리뷰 속도는 그대로입니다. 그 격차가 벌어지면 검토되지 않은 코드의 재고 가 쌓이고, 그건 속도가 아니라 부채를 빨리 쌓는 것 입니다.


8. 이 저장소에 대입해보면 — 여섯 칸 중 네 칸이 비어 있다

읽기만 하면 남는 게 없으니 이 블로그 저장소의 CLAUDE.md를 3절의 여섯 칸에 대봤습니다.

영역이 저장소의 현재판정
명령어"나머지는 package.json scripts 참고"로 위임 + pnpm fetch:popular만 명시⚠️
테스트없음 — vitest인지 playwright인지, 어디 있는지 안 적혀 있다
프로젝트 구조contents/blog/YYYY/MM/*.md 규약은 있음. src/ 배치는 없음⚠️
코드 스타일없음
Git 워크플로우배포 경로(developmastergh-pages)는 상세함
경계없음 — ✅/⚠️/🚫 구분이 하나도 없다

재보고 나서 흥미로웠던 건, 비어 있는 칸이 실제로 사고가 났던 칸과 겹친다 는 점입니다.

이 저장소에는 글 하나를 추가할 때마다 같이 고쳐야 하는 하드코딩된 테스트 기대값이 네 군데 있습니다(글 수, 카테고리 카운트, 최신 글 날짜, About 페이지 편수). CLAUDE.md에는 이게 한 줄도 없습니다. 테스트 칸이 비어 있으니 매번 빨간불로 배웁니다.

경계 칸도 마찬가지입니다. 이 저장소에는 명백한 🚫와 ⚠️가 이미 존재합니다.

🚫 `pnpm format`을 기존 글에 돌리지 말 것 — 표 정렬이 전부 바뀐다
🚫 `public/blog-assets/`를 커밋하지 말 것 — gitignore된 파생물이다
⚠️ `develop`에서 `pnpm fetch:popular`를 돌려 커밋하면 머지 때 충돌할 수 있다

세 번째는 CLAUDE.md에 산문으로 적혀 있지만, ⚠️로 표시돼 있지 않습니다. 문단 속에 묻힌 경고와 ⚠️가 붙은 한 줄은 같은 정보가 아닙니다. 전자는 읽히길 기대하는 것이고, 후자는 분류된 것 입니다.

그래서 이 저장소에서 실제로 할 일은 이렇게 좁혀집니다.

  1. 경계 절을 만들고 이미 아는 🚫/⚠️를 옮긴다 — 새로 발견할 게 아니라, 이미 산문에 흩어져 있는 걸 분류 하는 일이다
  2. 테스트 칸을 채운다 — 러너·위치·그리고 "새 글 추가 시 같이 고칠 네 곳"
  3. 늘리지 않는다 — 4절이 있으니, 코드 스타일 칸은 실제로 사고가 난 뒤에 채운다

3번을 적어두는 게 중요합니다. 여섯 칸을 보면 여섯 칸을 다 채우고 싶어지는데, 그게 바로 이 글이 경고한 방향입니다. 빈 칸은 결함이 아니라 아직 비용을 치르지 않은 칸 일 수도 있습니다.


9. 오해하기 쉬운 지점

오해 1 — "스펙을 쓰라는 건 워터폴로 돌아가라는 뜻이다"

글의 주장은 반대입니다. 스펙은 한 번 확정하고 얼어붙는 문서 가 아니라 6절의 살아 있는 산출물 입니다. 그리고 초안은 사람이 아니라 에이전트가 씁니다(2절). 워터폴의 핵심은 "앞단계를 확정하고 넘어간다"인데, 여기서는 매 마일스톤마다 스펙으로 돌아옵니다.

오히려 이건 스펙을 코드처럼 다루자 는 얘기에 가깝습니다. 버전이 있고, 테스트가 붙고, 커밋으로 바뀌는 것.

오해 2 — "긴 컨텍스트 모델이 나오면 해결된다"

4절이 그 기대에 대한 답입니다. 지시의 저주는 용량 문제가 아니라 주의(attention) 문제 입니다. 창이 넓어져도 지시 20개를 동시에 지키는 문제 는 그대로 남습니다.

컨텍스트 윈도우:  얼마나 넣을 수 있는가  → 하드웨어/모델이 푼다
어텐션 예산:      얼마나 지켜지는가      → 넣는 사람이 푼다

오해 3 — "여섯 칸을 다 채우면 좋은 스펙이다"

8절에서 걸린 지점입니다. 여섯 칸은 체크리스트가 아니라 진단 도구 입니다. 아무 일도 안 일어난 영역을 추측으로 채우면, 그건 4절이 말한 준수율을 깎는 지시 가 됩니다. 칸이 비었으면 왜 비었는지 를 먼저 보는 게 맞습니다 — 사고가 안 난 건지, 사고가 났는데 안 적은 건지.

오해 4 — "자가 검증을 붙였으니 사람 리뷰는 줄여도 된다"

안티패턴 목록이 직접 못박습니다. 테스트 통과 = 정확함이 아닙니다. 그리고 5절의 자가 점검은 전부 에이전트가 자기 작업을 자기 스펙에 대보는 것 이라, 스펙 자체가 틀렸을 때는 전부 통과합니다. 스펙이 틀렸는지는 스펙 안에서 알 수 없고, 이건 사람이 볼 수밖에 없는 자리입니다.


10. 정리

내용
1. 비전은 사람, 디테일은 에이전트짧은 브리프 → 에이전트 초안 → 사람 교정방향은 사람만, 부연은 모델이 잘한다
2. 여섯 칸으로 진단하라명령어·테스트·구조·스타일·Git·경계2,500개 파일에서 공통으로 나온 축
3. 경계는 세 단계로✅ 항상 / ⚠️ 먼저 묻기 / 🚫 절대 금지⚠️가 없으면 판단이 전부 흑백이 된다
4. 지시를 늘리면 준수율이 떨어진다지시의 저주새 규칙은 기존 규칙의 확률을 깎아서 산다
5. 그래서 쪼갠다컴포넌트별 · 목차 · 서브에이전트 · 순차컨텍스트 분리는 용량이 아니라 준수율 장치
6. 스펙 문장을 테스트로적합성 테스트 · 스펙 대조 검증읽히길 바라는 문서 → 빨간불이 켜지는 장치
7. 검증이 속도를 따라가야 한다테스트 통과 ≠ 정확함안 그러면 미검토 코드의 재고가 쌓인다

이 블로그에서 이어온 이야기와 겹쳐보면 자리가 분명해집니다.

질문으로 돌아가면, "제대로 된 프레임워크"는 결국 여섯 칸과 세 단계 경계, 그리고 늘리지 않겠다는 규율 입니다. 앞의 둘은 목록이라 오늘 바로 쓸 수 있고, 어려운 건 세 번째입니다. 스펙은 쓸수록 길어지고 싶어지는 문서인데, 길어지는 순간 자기가 적어둔 것부터 지켜지지 않기 시작하기 때문입니다.


참고 문서