design.md는 어떤 코딩 에이전트든 불러와 Vercel 브랜드에 맞는 페이지를 만들 수 있는 단일 공개 파일입니다. 이 글에서는 design.md를 어떻게 구축했는지, 그리고 파일 안의 모든 규칙을 결정한 평가(eval) 루프를 어떻게 설계했는지 소개합니다.
Vercel 전반에서 우리는 코딩 에이전트를 활용해 Vercel다운 느낌과 모습을 갖춘 페이지를 디자인하고 구축합니다. 타이포그래피, 색상, 구성 모두 이미 직접 배포한 페이지들에 담긴 것과 동일한 판단력을 반영해야 합니다.
최근에 소개한 product-design은 에이전트가 코드베이스 안에서 작업할 때 우리가 디자인하는 방식을 가르치는 스킬입니다. 이 스킬은 각 저장소에서 해당 코드와 함께 위치하며, 에이전트가 디자인 시스템을 찾고 이해하는 방법과 빌드 중인 제품의 가이드라인을 설명합니다.
이 방식은 에이전트가 우리 코드베이스 안에서 작업할 때는 잘 동작합니다. 스킬에 필요한 모든 것이 바로 그곳에 있기 때문입니다. 하지만 보고서, 제안서, 그리고 Vercel의 모습을 유지해야 하지만 해당 파일들을 읽을 수 없는 도구에서 만들어지는 일회성 페이지는 어떻게 해야 할까요? 우리가 찾은 답은 design.md, 즉 어떤 에이전트든 불러올 수 있는 단일 공개 파일이었습니다.
design.md 구축 접근 방식product-design이 잘 작동했던 이유는 디자인 시스템과 제품 가이드라인이 저장소 안에 바로 있어 에이전트가 읽을 수 있었기 때문입니다. 그 환경 밖의 에이전트와 도구들도 같은 지식에 접근할 수 있도록 방법을 마련해야 했습니다. 그래야 최종 결과물로 나오는 페이지가 여전히 우리가 직접 디자인한 것처럼 보일 수 있으니까요. 이를 위해 두 가지 요건을 설정했습니다:
실행 환경에 관계없이 누구든 에이전트에 연결할 수 있는 단일 공개 URL.
브랜드, 레이아웃, 카피라이팅부터 디자인 시스템, 반응형 대응, 정보 아키텍처까지 product-design를 처음부터 유용하게 만들었던 모든 요소를 아우르는 가이드라인.
처음에 시도한 단순한 방법은 product-design를 공개 프롬프트로 그대로 이식하는 것이었습니다. 스킬의 참조 파일들을 하나로 합쳐 어떤 에이전트든 URL로 읽을 수 있게 하는 방식이었죠. 문제는 프롬프트가 우리의 비주얼 언어를 충분히 설명했음에도 불구하고, 이를 읽는 모델마다 해석이 달라 동일한 가이드라인에서 전혀 다른 페이지들을 생성했다는 점입니다.
이는 부분적으로 디자인 언어가 주관적이기 때문입니다. "레이아웃을 깔끔하게 유지하세요" 같은 표현은 얼마든지 다르게 해석될 수 있습니다. '깔끔하다'는 게 과연 무엇일까요? 그보다 더 큰 문제는 프롬프트가 남겨두는 나머지 맥락이었습니다. 코드베이스 안에서 에이전트는 product-design이 설명하는 것들의 실제 컴포넌트와 배포된 예시들에 둘러싸여 스킬을 읽습니다. 하지만 공개 프롬프트에는 그런 것이 전혀 없어서, 모든 모델이 오직 텍스트만으로 우리 스타일을 재구성해야 합니다.
따라서 그 환경이 제공하던 것들을 단일 파일로 증류해야 했고, 제대로 가고 있는지 알 수 있는 유일한 방법은 결과물로 나오는 페이지를 직접 보는 것이었습니다. 이식 작업은 잠시 접어두고 새 파일을 처음부터 작성하기 시작했습니다. 이번에는 변경 사항마다 반복 가능한 평가(eval) 프롬프트 세트로 테스트했습니다.
실제 사용 사례에서 가져오고 모의 입력과 짝을 이룬 프롬프트 일곱 개를 작성했습니다:
사용량 및 성능 보고서
계약 갱신 제안서
벤치마크 보고서
인터랙티브 플래닝 페이지
자체 구축 vs. 구매 비교 브리프
보안 거버넌스 브리프
프레젠테이션 덱
파일이 바뀌는 동안 프롬프트는 고정된 채로 유지되었기 때문에, 결과물의 차이는 고스란히 가이드라인으로 거슬러 올라갈 수 있었습니다.
이 평가들 덕분에 파일이 실제로 무엇을 하고 있는지, 그리고 서로 다른 에이전트들이 어떻게 해석하는지를 측정할 수 있었습니다. 첫 번째 테스트에서는 design.md이 실제로 모델이 생성하는 결과물을 바꾸는지 확인하고 싶었습니다. 동일한 환경과 동일한 모델로 갱신 제안서 평가를 두 번 실행했습니다. 한 번은 design.md 없이, 한 번은 로드한 상태로, 프롬프트, 데이터, 뷰포트를 두 실행 모두 동일하게 유지했습니다.
design.md 없이는 모델이 일반적인 SaaS 대시보드를 생성했습니다. 하지만 파일을 로드하자 페이지가 갱신 권고 사항을 전면에 내세우고, 상업적 근거를 하나의 그리드로 모으며, 실제로 비교할 수 있도록 업계 비교값을 단일 척도에 배치하고, 요약 내용을 방해하지 않으면서도 세부 내용에 접근할 수 있게 구성했습니다. 이를 통해 파일이 처음 발견했던 스타일링만이 아니라 페이지의 구조와 계층 구조까지 바꾼다는 결론을 내릴 수 있었고, 한 번에 하나의 규칙씩 이 방식으로 가이드라인을 계속 발전시키기에 충분한 신호를 얻었습니다.
design.md을 테스트하고 재구축하는 과정에서 범위가 전체를 작동하게 만드는 세 부분으로 이루어진 시스템으로 발전했습니다:
design.md는 독자의 역할을 설정하고, 근거를 구조화하며, 레이아웃을 선택하는 방법을 에이전트에게 알려주는 가이드라인을 제공합니다.
클래스와 토큰의 명확하고 문서화된 어휘를 정의하는 공개 스타일시트.
평가 루프는 반복적인 사람의 피드백을 더 나은 가이드라인과 결정론적 검사로 변환합니다.
이 레이어 각각은 고품질의 브랜드 일관성 있는 Vercel 페이지를 만드는 작업의 서로 다른 부분을 담당합니다. design.md에 담기는 판단은 에이전트에게 다음에 대한 가이드라인을 제공합니다:
빠른 임원 보고와 세부 감사 모두를 위한 페이지 구성.
구체적인 주장과 솔직한 한계를 담은 카피 작성.
근거와 산문이 서로를 뒷받침하도록 계층 구조, 타이포그래피, 색상 구성.
워드마크와 삼각형 로고의 에셋 규칙까지, Vercel로서 발행하는 방법.
design.md는 절대 원하지 않는 반복적인 생성 디자인 패턴들도 명시합니다. 패턴에 이름을 붙여줌으로써 에이전트가 훨씬 안정적으로 이를 인식하고 피할 수 있게 합니다.
에이전트들이 계속해서 자체적인 타이포그래피, 간격, 레이아웃을 만들어냈기 때문에 스타일시트를 만들었습니다. 모델에서 그런 결정권을 완전히 빼앗은 것이죠. 스타일시트는 헤더, 테이블, 통계 스트립, 차트 스타일 같은 디자인 시스템의 기본 요소들을 CSS로 패키징하여 공개 URL을 통해 어떤 페이지든 사용할 수 있게 합니다. 그런 다음 design.md은 스타일시트가 제공하는 클래스 이름과 토큰을 문서화하여, 에이전트가 이를 새로 만들지 않고 HTML에서 해당 이름으로 페이지를 구축할 수 있게 합니다.
또 다른 이점은 에이전트가 스타일시트 자체를 직접 읽지 않는다는 것입니다. 스타일시트는 브라우저에서 페이지가 렌더링될 때 로드되므로, 코드가 모델의 컨텍스트에 전혀 들어오지 않아 디자인 가이드라인을 위한 공간을 더 많이 확보할 수 있습니다.
마지막으로 평가 루프는 나머지 두 요소가 제대로 작동하도록 돕는 역할을 합니다. 결정론적 검사는 사용 가능한 너비를 무시하는 테이블과 같은 기계적 실패를 포착하는 데 활용되고, 계층 구조, 구성, 그리고 페이지가 독자에게 실제로 필요한 것을 제공하는지 여부처럼 자동화할 수 없는 주관적인 부분은 사람이 판단합니다.
design.md의 모든 가이드라인 줄은 평가 루프를 통해 자리를 얻었습니다. 고정된 시나리오에서 페이지를 생성하고, 결과물을 검토하며, 수용한 수정 사항을 인코딩하고, 각 변경이 유지되는지 확인하기 위해 시나리오를 다시 실행했습니다. 한 결과물에 도움이 된 변경이 다른 결과물에 조용히 해를 끼칠 수도 있으니까요. 다른 방식으로 들어간 것은 하나도 없습니다.
일곱 개의 프롬프트 각각은 하나의 시나리오가 됩니다. 즉, 프롬프트가 모의 입력 및 렌더 설정과 함께 고정됩니다. 예를 들어 갱신 제안서는 항상 동일한 가상 고객 데이터와 동일한 뷰포트 설정으로 실행되어, 실행 간에 바뀌는 유일한 것이 design.md가 되도록 합니다. 한 라운드는 파일의 현재 버전을 기준으로 모든 시나리오에서 새 페이지를 생성하는 것을 의미합니다. 전체 라운드는 Claude Opus 4.8과 GPT-5.5를 사용하는 Codex 모두에서 일곱 개 시나리오 전체를 실행합니다.
테이블에만 영향을 미치는 규칙 변경처럼 특정 사항을 조사하고 싶다면, 영향을 받는 시나리오나 단일 모델만 다시 실행해 반복 루프를 빠르게 유지할 수 있습니다.
일곱 페이지를 모두 함께 생성하면 나란히 비교하기도 쉬웠습니다. 눈에 띈 점은 design.md이 모든 페이지를 하나의 템플릿으로 밀어붙이지 않는다는 것이었습니다. 인터랙티브 플래닝 페이지는 컨트롤을 전면에 내세웠는데, 플래닝 페이지를 여는 사람은 숫자를 바꾸고 결과를 확인하기 위해 오기 때문입니다. 반면 갱신 제안서는 권고 사항과 그 뒤에 있는 상업적 비교를 앞세웠는데, 그 독자는 갱신 여부를 결정하는 사람이기 때문입니다. 모든 페이지가 동일한 Vercel 타이포그래피, 색상, 간격을 사용했지만, 각 페이지는 독자가 하러 온 일을 중심으로 구성되었습니다.
매 라운드에서 생성된 페이지를 검토하기 위해 전체 페이지 렌더링을 표시하고 블라인드 A/B 비교를 실행하는 로컬 앱을 구축했습니다. 이 앱은 결국 평가 하네스(harness)가 되어 각 시나리오를 실행하고 결과를 저장했습니다. 저장된 각 실행에는 프롬프트, 입력, 모델 설정, 사용된 design.md 버전, 스크린샷, 그리고 검토자가 남긴 피드백이 모두 포함됩니다. 검토자는 모든 수정 사항을 그것을 생성한 정확한 실행에 기록합니다.
검토자가 기록한 각 수정 사항은 가장 좁은 범위에서 일관되게 적용할 수 있는 위치에 반영됩니다. 판단적 변경은 design.md에 산문으로, 재사용 가능한 메커니즘은 스타일시트에, 기계적으로 검사할 수 있는 것은 코드의 결정론적 검사로 들어갑니다. 하네스 자체의 문제는 하네스에 남겨두고, 다른 모델들은 통과하는데 특정 모델만 실패하는 경우 반복될 때까지 규칙에 포함시키지 않습니다.
초기 갱신 제안서 중 하나를 예로 들겠습니다. 상업 조건 테이블이 두 배 너비로 들어갈 공간이 충분히 있었음에도 산문과 동일한 너비로 좁게 반환되었습니다.
검토 과정에서 근거 테이블은 사용 가능한 전체 너비를 써야 한다고 표시했습니다. 그런데 이전 결과물들을 살펴보니 동일한 실패가 곳곳에서 발견되었습니다. 그래서 이 수정 사항은 두 곳에 반영되었습니다:
의도된 동작을 명시한 design.md의 규칙.
동일한 레이아웃 실패가 다음에 나타날 때 포착하는 코드의 결정론적 검사.
이것이 반영된 후 이후의 갱신 제안서 프롬프트들은 올바른 전체 너비 테이블이 있는 페이지를 결과로 냈습니다. 이와 같은 변경을 검증하기 위해 인코딩한 후 영향을 받는 시나리오를 다시 실행했습니다. 마일스톤에서는 더 나아가 업데이트된 design.md을 이전 버전과 비교하는 블라인드 A/B 라운드를 실행해 각 변경을 유지할지, 수정할지, 되돌릴지 결정했습니다.
파일 구축에는 전체 라운드, 목표 검사, 드라이 런, 그리고 모든 막다른 길을 포함해 총 200회가 넘는 실행이 들었습니다. 사람 검토자들과 함께 모델 심사위원이 매 라운드마다 비평을 작성했고, 매 라운드의 피드백이 다음 실행을 개선하는 데 활용되었습니다.
그 모든 실행을 마친 후, 인코딩한 수정 사항들이 실제로 그것이 작성된 실패를 방지하는지 확인하고 싶었습니다. 그래서 데스크톱 시나리오 세 가지를 선택하고, 각각에서 GPT-5.5를 사용하는 Codex로 design.md을 로드한 채로 한 번, 로드하지 않고 한 번, 총 두 번씩 페이지를 생성하게 했습니다. 재시도 없이 매 생성의 첫 번째 시도만 유지했습니다. 그런 다음 여섯 페이지 모두에 결정론적 검사를 실행하고 사용 가능한 너비를 무시하는 테이블처럼 알려진 실패가 각 세트에서 얼마나 자주 나타나는지 집계했습니다. design.md로 생성된 페이지에는 그런 실패가 39건이었습니다. 파일 없이 생성된 페이지에는 91건이었고, 이는 이 테스트에서 57% 더 적은 수치입니다.
이 수치에는 두 가지 한계가 있습니다. 검사는 우리가 이미 발견하고 기록한 실패만 포착할 수 있기 때문에, 이 테스트는 페이지가 전반적으로 잘 디자인되었는지에 대해서는 아무것도 말해주지 않습니다. 또한 여섯 페이지는 품질이나 신뢰도에 대한 주장을 하기에 너무 작은 샘플이며, 파일 유무와 관계없이 모든 페이지에는 여전히 배포를 막을 만큼 심각한 실패가 적어도 하나씩 있었습니다. 하지만 이 테스트가 잘 말해주는 것은 일단 실패에 이름을 붙이고 인코딩하면 그 실패가 지속적으로 사라진다는 점입니다.
design.md을 최신 상태로 유지하는 방법평가 루프가 파일을 배포하게 했다면, 최신 상태를 유지하는 것은 실제 사용입니다. 내부 Slack에서 이 사용은 @design-agent를 통해 이루어집니다. eve 위에 구축된 에이전트로, 디자인 비평과 카피 대안부터 아이콘 추천, 붙여넣은 데이터로 만드는 리포트 사이트까지 모든 것에 활용합니다. 프롬프트를 설정하거나 소스 파일을 찾아다닐 필요 없이, 스레드에서 에이전트를 멘션하기만 하면 됩니다. 웹사이트 요청의 경우 현재 design.md를 로드하고, 공개된 스타일시트를 기준으로 페이지를 구축한 뒤, 전체 페이지 스크린샷과 배포 URL을 스레드에 다시 게시합니다. 고정된 시나리오와 달리 이 스레드들은 실제 요청, 실제 결과물, 그리고 이어진 피드백이나 방향 조정을 담아 가이드라인이 실제 환경에서 어떻게 작동하는지 보여줍니다.
매주 Slack 스레드와 GitHub 리뷰, Figma의 코멘트를 포함한 모든 피드백을 한곳에 모읍니다. 자동화가 반복되는 코멘트를 그룹화하고, 반복되는 불만 각각은 제안된 변경이 됩니다. 담당자가 각 제안을 검토하고 시스템이 이미 처리하는지 확인한 후, 수용된 수정이 @design-agent, product-design 스킬, design.md, 스타일시트, 결정론적 검사 중 어디에 속하는지 결정합니다. 이전에 테스트한 적 없는 종류의 페이지를 요청하기 시작하면 그 요청은 새로운 평가 시나리오가 됩니다.
이것이 효과가 있는지 알기 위해 유사한 작업에서 시간이 지남에 따라 각 종류의 불만이 얼마나 자주 나타나는지 집계합니다. 수정을 인코딩하면 그 횟수가 줄어들기 시작해야 합니다. 줄어들지 않는다면 수정에 무언가 잘못된 것이 있습니다. 규칙이 불명확하거나, 필요할 때 로드되지 않거나, 스타일시트에 이를 표현할 수 있는 기본 요소가 없거나, 산문 대신 결정론적 검사가 필요한 경우일 수 있습니다.
하나의 반복적인 결과물과 하나의 수동 비교에서 시작해 같은 루프를 직접 구축할 수 있습니다.
1. 반복적인 결과물 하나 선택하기
제안서, 성능 보고서, 벤치마크, 마이크로사이트처럼 실제 독자와 실제 입력이 있는 최근 작업을 사용하세요. "브랜드에 맞게 만들어라" 같은 광범위한 목표는 피하세요. 무엇이든 생성하기 전에 간단한 루브릭을 작성해 두세요. 좋은 루브릭은 제공된 사실이 살아남았는지, 독자의 결정이 명확한지, 그리고 손으로 계속 수정하는 그 문제가 실제로 해결되었는지 확인합니다.
2. 먼저 베이스라인 저장하기
새로운 디자인 컨텍스트 없이 페이지를 한 번 생성하고, 프롬프트, 입력, 설정, 스크린샷을 저장하세요. 하네스 자체가 실패한 것이 아니라면 결과물이 거칠어 보여도 첫 번째 결과물을 유지하세요. 새로운 컨텍스트가 도움이 되었는지는 이전 상태 없이는 알 수 없습니다.
3. 마지막 수정 사항 열 개에서 시작하기
디자인 리뷰, 풀 리퀘스트, Slack에서 계속 제공하는 피드백을 수집하고, 각 수정 사항을 관찰 가능한 것으로 다시 작성하세요. 둘 중 하나만 검사할 수 있으므로 Make the table feel less cramped 대신 Let evidence tables use the full available width처럼 작성하는 것을 의미합니다.
범위, 독자 및 과제, 관찰 가능한 결정, 사용 가능한 기본 요소에 대한 섹션을 갖춘 하나의 파일에 결정들을 담으세요. 그 파일이 첫 번째 design.md입니다.
4. 반복 가능한 메커니즘 제한하기
결과물이 자체적인 타이포그래피, 간격, 레이아웃을 계속 만들어낸다면 스타일시트를 공개하고 에이전트가 사용할 수 있는 정확한 클래스와 토큰을 문서화하세요. 판단적 내용은 산문으로 유지하고, 반복 가능한 메커니즘은 CSS나 결정론적 검사로 옮기세요.
5. 매칭 비교 한 번 실행하기
동일한 입력, 모델, 뷰포트로 페이지를 한 번 더 생성하되, 이번에는 파일을 로드한 상태로 하세요. 베이스라인과 섞은 다음 어느 쪽인지 모르는 상태로 두 결과물을 루브릭에 따라 점수를 매기세요.
시작하는 데 러너나 모델 심사위원이 필요하지 않습니다. 단 한 번의 시도로도 크고 명백한 실패를 드러낼 수 있습니다. 신뢰도를 측정하려면 여러 번의 독립적인 첫 시도 평가(에이전트를 위한 평가에 관한 Anthropic의 가이드)를 실행하고 결과가 얼마나 자주 유지되는지 보고하세요.
6. 수정 사항 인코딩하기
결과물을 보내야 했던 후속 프롬프트와 함께 검토하고 다음을 자문해 보세요:
사용자가 반복하거나 수동으로 조정해야 했던 것은 무엇인가?
규칙이 빠져 있거나 불명확한가?
스타일시트가 수정 사항을 표현할 수 있는가?
실패가 코드로 검사할 만큼 기계적인가?
수정 사항이 이 결과물을 넘어 일반화되는가?
생성된 페이지를 직접 수정하는 대신 가이드라인을 업데이트하세요. 다음 비교가 첫 시도가 실제로 개선되었는지 알려줄 것입니다.
수동 루프가 효과를 내기 시작한 후 도구를 추가하세요:
가이드라인이 적용되어야 하는 경우와 그렇지 않은 경우의 시나리오를 포함하세요.
편집하는 동안 숨겨진 소규모 홀드아웃 세트를 유지하세요.
모델과 가이드라인 버전을 기록하세요.
기계적 검사를 자동화하세요.
여러 명의 블라인드 검토자를 활용하세요.
자동화를 어디까지 진행하든 최종 변경은 반드시 사람이 검토하게 하세요.
그리고 루프를 계속 돌리세요. 주기적으로 피드백을 수집하고, 가이드라인을 변경한 후 각 종류의 불만이 실제로 줄어드는지 지켜보세요. 프로덕션에서 사람들이 같은 실수를 계속 수정한다면 평가를 통과했다는 것은 큰 의미가 없습니다.
실제 작동하는 예시를 원한다면, 우리 것은 공개되어 있습니다. 우리는 매일 design.md를 v0, Codex, Claude 같은 도구에 로드해 Vercel다운 느낌의 결과물을 만들고 있으며, eve design agent template을 활용하면 우리가 운영하는 것과 같은 Slack 디자인 에이전트를 시작할 수 있습니다.