How our agents build on-brand pages with design.md
어떤 코딩 에이전트든 불러올 수 있는 단일 공개 파일 design.md를 만든 과정과, 그 안의 모든 규칙을 결정한 평가 루프에 대한 이야기입니다.

Vercel에서는 팀 전반에 걸쳐 코딩 에이전트를 활용해 Vercel다운 모습과 느낌을 가져야 하는 페이지를 디자인하고 만듭니다. 타이포그래피, 색상, 구성 모두가 우리가 직접 배포하는 페이지에 담아온 것과 같은 판단을 담고 있어야 합니다.
최근에 소개한 product-design은 에이전트가 우리 코드베이스에서 작업할 때 우리가 어떻게 디자인하는지를 가르쳐 주는 스킬입니다. 이 스킬은 자신이 관장하는 코드와 함께 각 저장소 안에 존재하며, 에이전트가 우리의 디자인 시스템은 물론 지금 만들고 있는 것이 무엇이든 그에 해당하는 제품 가이드라인을 찾아 이해하는 방법을 설명합니다.
이 방식은 에이전트가 우리 코드베이스 안에서 일할 때, 즉 스킬이 필요로 하는 모든 것이 바로 그 자리에 있을 때 아주 잘 동작합니다. 그렇다면 리포트나 제안서, 그리고 여전히 Vercel처럼 보여야 하지만 그런 파일들을 전혀 읽을 수 없는 도구에서 만들어지는 일회성 페이지들은 어떨까요? 우리의 답은 어떤 에이전트든 불러올 수 있는 하나의 공개 파일, design.md였습니다.
design.md를 만들면서 접근한 방식
product-design이 잘 동작했던 이유는 디자인 시스템과 제품 가이드라인이 에이전트가 읽을 수 있도록 저장소 안에 그대로 놓여 있었기 때문입니다. 우리는 그 환경 밖에 있는 에이전트와 도구도 동일한 지식에 닿을 수 있는 방법이 필요했습니다. 그래야 그 결과로 나오는 페이지가 여전히 우리가 직접 디자인한 것처럼 보일 테니까요. 여기서 두 가지 요구사항이 도출되었습니다.
- 실행 환경과 무관하게 누구나 자신의 에이전트에게 가리킬 수 있는 단일 공개 URL.
- 브랜드, 레이아웃, 카피라이팅부터 디자인 시스템, 반응형, 정보 구조에 이르기까지 애초에
product-design을 유용하게 만들었던 모든 것을 다루는 가이드.
우리가 처음 시도한 순진한 접근은 product-design을 그대로 공개 프롬프트로 옮기는 것이었습니다. 스킬의 레퍼런스 파일들을 어떤 에이전트든 URL로 읽을 수 있는 하나의 파일로 압축한 것이죠. 그런데 문제가 있었습니다. 프롬프트가 우리의 시각 언어를 설명하는 것 자체는 문제가 없었지만, 그 설명을 읽는 모델마다 해석이 달랐고, 같은 가이드에서 완전히 다른 페이지들이 만들어졌습니다.
여기에는 디자인 언어가 주관적이라는 사실도 한몫합니다. “레이아웃을 깔끔하게 유지하라” 같은 표현은 사실상 무엇이든 뜻할 수 있습니다. “깔끔하다”는 게 뭘까요? 그보다 더 큰 문제는 프롬프트가 남겨두고 온 나머지 전부였습니다. 우리 코드베이스 안에서 에이전트는 product-design이 설명하는 것들의 실제 컴포넌트와 배포된 예시에 둘러싸인 채로 그 스킬을 읽습니다. 하지만 공개 프롬프트에는 그런 것이 하나도 담기지 않기에, 모든 모델이 오직 글만으로 우리 스타일을 재구성해야 했습니다.
그래서 우리가 해야 했던 일은 그 환경이 제공하던 것을 하나의 파일로 증류해 내는 것이었고, 우리가 정답에 가까워지고 있는지 알 수 있는 유일한 방법은 결과로 나오는 페이지를 직접 보는 것이었습니다. 우리는 포팅 작업을 접어두고 새 파일을 처음부터 다시 쓰기 시작했습니다. 이번에는 모든 변경을 반복 가능한 평가(eval) 프롬프트 세트로 테스트하면서 말이죠.
실제 사용 사례에서 뽑아내고 목(mock) 입력을 짝지은 7개의 프롬프트를 작성했습니다.
- 사용량 및 성능 리포트
- 갱신 제안서
- 벤치마크 리포트
- 인터랙티브 기획 페이지
- 직접 만들기 vs 구매하기 브리프
- 보안 거버넌스 브리프
- 프레젠테이션 덱
프롬프트는 고정한 채 파일만 바꿨기 때문에, 결과물의 모든 차이는 가이드로 되짚어 추적할 수 있었습니다.
첫 번째 비교
이 평가들은 파일이 실제로 무엇을 하고 있는지, 그리고 그것이 서로 다른 에이전트에게 어떻게 해석되는지를 모두 측정할 수 있는 방법이 되어 주었습니다. 첫 번째 테스트에서 우리가 알고 싶었던 것은 design.md가 모델의 결과물을 조금이라도 실제로 바꾸는가였습니다. 우리는 같은 환경에서 같은 모델로 갱신 제안서 평가를 두 번 실행했습니다. 한 번은 design.md 없이, 한 번은 그것을 불러온 채로 말이죠. 두 번의 실행에서 프롬프트, 데이터, 뷰포트는 동일하게 유지했습니다.

design.md가 없을 때 모델은 흔한 SaaS 대시보드를 만들어 냈습니다. 하지만 파일이 있을 때는 페이지가 갱신 추천안 그 자체로 시작했고, 상업적 근거를 하나의 그리드로 모았으며, 동종 업계 수치를 하나의 척도 위에 올려 실제로 비교할 수 있게 했고, 뒷받침하는 세부 정보는 요약과 경쟁하지 않으면서도 볼 수 있도록 유지했습니다. 이를 통해 우리는 이 파일이 처음에 발견했던 스타일링뿐 아니라 페이지의 구조와 위계까지 바꾼다는 결론을 내릴 수 있었고, 이런 식으로 한 번에 하나의 규칙씩 가이드를 계속 만들어 나갈 만한 충분한 신호를 얻었습니다.
시스템을 작동하게 만드는 세 가지 요소
design.md를 테스트하고 다시 만들어 가는 동안, 그 범위는 전체를 작동하게 만드는 3단 시스템으로 발전했습니다.
- design.md는 에이전트에게 독자가 해야 할 일을 어떻게 규정하고, 근거를 어떻게 구조화하며, 어떤 구성을 선택할지 알려주는 가이드를 제공합니다.
- 공개 스타일시트는 제한적이고 문서화된 클래스와 토큰의 어휘를 정의합니다.
- 평가 루프는 반복되는 사람의 피드백을 더 나은 가이드와 결정론적 검사로 바꿔 놓습니다.
이 레이어들은 각각 고품질의 브랜드에 맞는 Vercel 페이지를 만드는 작업의 서로 다른 부분을 담당합니다. design.md에 인코딩된 판단은 에이전트에게 다음과 같은 가이드를 제공합니다.
- 빠른 경영진용 훑어보기와 상세한 감사 모두에 맞게 페이지를 구성하기.
- 구체적인 주장과 정직한 단서를 담아 카피 쓰기.
- 근거와 산문이 서로를 뒷받침하도록 위계, 타이포그래피, 색상을 구성하기.
- 우리의 워드마크와 삼각형 로고에 대한 에셋 규칙에 이르기까지, Vercel로서 게시하는 방법.
design.md는 또한 우리가 결코 보고 싶지 않은, 반복적으로 나타나는 생성형 디자인 패턴들에 이름을 붙여 둡니다. 패턴에 이름을 부여함으로써 에이전트가 그것들을 훨씬 더 안정적으로 알아채고 피할 수 있게 합니다.

우리가 스타일시트를 만든 이유는 에이전트들이 계속해서 자기만의 타이포그래피, 여백, 레이아웃을 발명했기 때문입니다. 그래서 그 결정을 모델에게서 아예 빼앗아 왔습니다. 이 스타일시트는 헤더, 테이블, 통계 스트립, 차트 스타일 같은 우리 디자인 시스템의 프리미티브를 어떤 페이지든 공개 URL로 사용할 수 있는 CSS로 패키징합니다. 그리고 design.md가 스타일시트에서 제공하는 클래스 이름과 토큰을 문서화해 두어, 에이전트가 그것들을 새로 발명하는 대신 HTML에 그 이름들을 써서 페이지를 만들 수 있게 합니다.
여기에는 또 다른 이점이 있습니다. 에이전트는 스타일시트 자체를 전혀 읽지 않습니다. 스타일시트는 페이지가 브라우저에서 렌더링될 때 로드되므로 그 코드가 모델의 컨텍스트에 들어가지 않고, 그만큼의 공간을 디자인 가이드에 더 쓸 수 있습니다.
마지막으로, 평가 루프는 나머지 두 조각이 제대로 작동하도록 돕는 요소입니다. 결정론적 검사는 테이블이 주어진 너비를 무시하는 것처럼 기계적인 실패를 잡아내는 데 쓰이고, 사람은 위계나 구성, 그리고 그 페이지가 독자가 찾아온 목적을 실제로 충족시키는지처럼 자동화할 수 없는 주관적인 부분을 판단합니다.
가이드가 파일에 들어가게 된 과정
design.md의 모든 가이드 한 줄 한 줄은 평가 루프를 통해 그 자리를 얻어냈습니다. 우리는 고정된 시나리오로 페이지를 생성하고, 돌아온 결과를 검토하고, 받아들인 수정 사항을 인코딩한 다음, 시나리오를 다시 돌려 각 변경이 유지되는지 확인했습니다. 어떤 산출물에 도움이 된 변경이 다른 산출물에는 조용히 해를 끼칠 수도 있기 때문입니다. 그 외의 방법으로 들어온 것은 아무것도 없습니다.
시나리오와 라운드
7개의 프롬프트 각각이 하나의 시나리오가 됩니다. 즉 프롬프트가 목 입력과 렌더링 설정과 함께 고정된다는 뜻입니다. 예를 들어 갱신 제안서는 항상 같은 가짜 고객 데이터와 같은 뷰포트 설정으로 실행되며, 실행 간에 바뀌는 것은 오직 design.md뿐입니다. 라운드란 현재 버전의 파일을 대상으로 모든 시나리오에서 새 페이지를 생성하는 것을 뜻합니다. 전체 라운드는 Claude Opus 4.8과 GPT-5.5를 쓰는 Codex 양쪽에서 7개 시나리오를 모두 다룹니다.
테이블에만 영향을 주는 규칙 변경처럼 특정한 것을 확인하고 싶을 때는, 영향을 받는 시나리오만 또는 단일 모델만 다시 실행해서 반복 루프를 짧게 유지할 수 있습니다.
7개 페이지를 함께 생성하면 나란히 놓고 비교하기도 쉬웠는데, 여기서 눈에 띈 점은 design.md가 모든 페이지를 하나의 템플릿으로 밀어붙이고 있지 않다는 것이었습니다. 인터랙티브 기획 페이지는 컨트롤을 앞뒤 없이 전면에 배치했습니다. 사람들이 기획 페이지를 여는 이유는 숫자를 바꿔 보고 무슨 일이 일어나는지 보기 위해서니까요. 반면 갱신 제안서는 추천안과 그 뒤를 받치는 상업적 비교로 시작했습니다. 그 독자는 갱신할지 말지를 결정하는 중이기 때문입니다. 모든 페이지가 같은 Vercel 타이포그래피, 색상, 여백을 사용했지만, 각각은 그 독자가 하러 온 일에 맞춰 구조화되어 있었습니다.

모든 실행을 검토하기
각 라운드가 만들어 낸 페이지를 검토하기 위해, 우리는 전체 페이지 렌더를 보여주고 블라인드 A/B 비교를 수행하는 로컬 앱을 만들었습니다. 이 앱은 결국 각 시나리오를 실행하고 결과를 저장하는 우리의 평가 하네스가 되었습니다. 저장된 각 실행은 프롬프트, 입력, 모델 설정, 사용한 design.md의 버전, 스크린샷, 그리고 검토자가 남긴 피드백을 모두 보관합니다. 검토자는 모든 수정 사항을 그것을 만들어 낸 바로 그 실행에 대해 기록합니다.
수정 사항을 규칙과 검사로 바꾸기
검토자가 기록한 각 수정 사항은 그것을 일관되게 강제할 수 있는 가장 좁은 자리에 안착합니다. 판단에 관한 변경은 산문으로 design.md에 들어가고, 재사용 가능한 메커니즘은 스타일시트로 들어가며, 기계적으로 검사할 수 있는 것은 코드상의 결정론적 검사가 됩니다. 하네스 자체의 문제는 하네스에 남고, 단일 모델이 다른 모델들에는 없는 방식으로 실패할 때는 그것이 반복될 때까지 규칙에 넣지 않습니다.
초기 갱신 제안서 중 하나를 예로 들어 보겠습니다. 상업 조건 테이블이 산문과 같은 너비로 짜부라진 채 돌아왔습니다. 페이지에는 테이블이 두 배로 넓어질 공간이 있었는데도 말이죠.

검토 과정에서 우리는 근거 테이블이 주어진 너비를 최대한 활용해야 한다고 지적했습니다. 그런데 이전 결과물들을 살펴보니 같은 실패가 도처에 있었습니다. 그래서 이 수정 사항은 결국 두 곳에 들어가게 되었습니다.
- 의도한 동작을 명시하는
design.md의 규칙. - 같은 레이아웃 실패가 다음에 나타날 때 잡아내는 코드상의 결정론적 검사.
이것이 반영된 후 이어진 갱신 제안서 프롬프트들은 올바른 전체 너비 테이블을 가진 페이지를 만들어 냈습니다. 이런 변경을 검증하기 위해 우리는 인코딩한 뒤 영향을 받는 시나리오들을 다시 실행했습니다. 주요 시점에는 한 걸음 더 나아가, 업데이트된 design.md를 이전 버전과 맞붙이는 블라인드 A/B 라운드를 돌려 각 변경을 유지할지, 손볼지, 되돌릴지 결정했습니다.
효과가 있었는지 측정하기
파일을 만드는 데는 전체 라운드, 표적 검사, 드라이런, 그리고 온갖 막다른 길을 포함해 200회가 훌쩍 넘는 실행이 들었습니다. 사람 검토자와 함께 모델 심사자가 각 라운드에 대한 비평을 작성했고, 매 라운드의 피드백은 다음 실행을 개선하는 데 반영되었습니다.

그 모든 실행을 거친 뒤, 우리는 인코딩한 수정 사항들이 그것들이 쓰여진 목적대로 실제로 실패를 막고 있는지 알고 싶었습니다. 그래서 데스크톱 시나리오 세 개를 골라, 각각에 대해 GPT-5.5를 쓰는 Codex로 페이지를 두 번씩 생성했습니다. 한 번은 design.md를 불러온 채로, 한 번은 없이 말이죠. 모든 생성에서 첫 번째 시도를 그대로 채택했고 리롤은 없었습니다. 그런 다음 6개 페이지 전부에 결정론적 검사를 돌려, 주어진 너비를 무시하는 테이블처럼 이미 알려진 실패가 각 세트에서 몇 번이나 나타나는지 셌습니다. design.md로 생성한 페이지에서는 그런 실패가 39건이었습니다. 없이 생성한 페이지에서는 91건이었고, 이 테스트에서는 57% 더 적은 셈입니다.
이 수치에는 두 가지 단서가 붙습니다. 검사는 우리가 이미 목격하고 적어 둔 실패만 잡아낼 수 있으므로, 이 테스트는 페이지가 전반적으로 잘 디자인되었는지에 대해서는 아무것도 말해 주지 않습니다. 또한 6개 페이지는 품질이나 신뢰성에 대해 주장하기에는 표본이 턱없이 작고, 파일이 있든 없든 그중 어느 하나도 배포를 막을 만큼 심각한 실패를 최소 하나씩은 갖고 있었습니다. 그럼에도 이 테스트가 잘 알려주는 것이 있습니다. 일단 실패에 이름을 붙이고 인코딩하고 나면, 그 실패는 계속 사라진 상태로 남는 경향이 있다는 사실입니다.
design.md를 최신 상태로 유지하는 방법
평가 루프가 이 파일을 세상에 내보냈다면, 이것을 최신으로 유지하는 것은 실제 사용입니다. 우리 Slack 안에서 그 사용은 @design-agent를 통해 이루어집니다. eve 위에 만들어진 이 에이전트는 디자인 비평이나 카피 대안부터 아이콘 추천, 붙여넣은 데이터로 만드는 리포트 사이트까지 무엇에든 활용됩니다. 프롬프트를 세팅하거나 소스 파일을 뒤질 필요 없이, 스레드에서 에이전트를 멘션하기만 하면 됩니다. 웹사이트 요청의 경우 현재의 design.md를 불러오고, 게시된 스타일시트를 기준으로 페이지를 만든 다음, 전체 페이지 스크린샷과 배포 URL을 스레드에 다시 올려 줍니다. 고정된 시나리오와 달리 이 스레드들 각각은 실제 요청, 실제 결과물, 그리고 뒤따르는 피드백이나 방향 조정을 담고 있어서 가이드가 야생에서 어떻게 작동하는지를 보여 줍니다.
매주 우리는 그 모든 피드백을 한곳에 모읍니다. Slack 스레드는 물론 GitHub 리뷰와 Figma의 코멘트까지 함께요. 자동화가 반복해서 나오는 코멘트를 묶어 주고, 반복되는 불만 하나하나가 변경 제안이 됩니다. 그러면 사람이 각 제안을 검토해서 시스템이 이미 그것을 다루고 있는지 확인하고, 채택된 수정 사항이 어디에 속하는지를 결정합니다. @design-agent인지, product-design 스킬인지, design.md인지, 스타일시트인지, 아니면 결정론적 검사인지 말이죠. 그리고 우리가 한 번도 테스트해 본 적 없는 종류의 페이지를 사람들이 요청하기 시작하면, 그 요청은 새로운 평가 시나리오가 됩니다.
이 모든 것이 효과가 있는지 알기 위해, 우리는 각 종류의 불만이 비슷한 작업에서 시간이 지남에 따라 얼마나 자주 등장하는지를 셉니다. 수정 사항을 인코딩하고 나면 그 횟수는 줄어들기 시작해야 합니다. 그렇지 않다면 수정에 뭔가 잘못된 부분이 있는 것입니다. 규칙이 불분명하거나, 필요한 순간에 로드되지 않고 있거나, 스타일시트에 그것을 표현할 프리미티브가 없거나, 산문 대신 결정론적 검사가 필요한 것일 수 있습니다.
직접 만들어 보기
하나의 반복되는 산출물과 한 번의 수동 비교부터 시작해서, 같은 루프를 직접 만들 수 있습니다.
1. 반복되는 산출물 하나를 고르세요
제안서, 성능 리포트, 벤치마크, 마이크로사이트처럼 실제 독자와 실제 입력이 있는 최근 작업을 활용하세요. “브랜드에 맞게 만들어 줘” 같은 막연한 목표는 피하세요. 무언가를 생성하기 전에, 짧은 루브릭을 적어 두세요. 좋은 루브릭은 주어진 사실이 살아남았는지, 독자의 결정이 명확한지, 그리고 당신이 매번 손으로 하던 수정이 실제로 해결되었는지를 확인합니다.
2. 기준선을 먼저 저장하세요
새로운 디자인 컨텍스트 없이 페이지를 한 번 생성하고, 프롬프트와 입력, 설정, 스크린샷을 저장하세요. 하네스 자체가 실패한 경우가 아니라면, 그 첫 결과물이 아무리 거칠어 보여도 남겨 두세요. ‘이전’이 없으면 새 컨텍스트가 도움이 되었는지 알 수 없습니다.
3. 최근의 수정 사항 10개에서 시작하세요
디자인 리뷰나 풀 리퀘스트, Slack에서 반복적으로 주고 있는 피드백을 모아, 각 수정 사항을 관찰 가능한 형태로 다시 쓰세요. 즉 테이블이 답답해 보이지 않게 하라 대신 근거 테이블은 사용 가능한 전체 너비를 쓰게 하라라고 쓰라는 뜻입니다. 둘 중 하나만 검사할 수 있으니까요.
그 결정들을 범위, 독자와 과업, 관찰 가능한 결정, 사용 가능한 프리미티브 섹션으로 나눈 하나의 파일에 담으세요. 그 파일이 당신의 첫 design.md입니다.
4. 반복 가능한 메커니즘을 제약하세요
결과물이 계속 자기만의 타이포그래피, 여백, 레이아웃을 발명한다면, 스타일시트를 게시하고 에이전트가 사용해도 되는 정확한 클래스와 토큰을 문서화하세요. 판단은 산문에 두고, 반복 가능한 메커니즘은 CSS나 결정론적 검사로 밀어 넣으세요.
5. 짝을 맞춘 비교를 한 번 실행하세요
같은 입력, 모델, 뷰포트로 페이지를 한 번 더 생성하되, 이번에는 당신의 파일을 불러온 채로 하세요. 그것을 기준선과 섞은 다음, 어느 쪽이 어느 것인지 모른 채 둘 다 루브릭으로 채점하세요.
시작하는 데 러너나 모델 심사자가 꼭 필요하지는 않습니다. 단 한 번의 시도만으로도 크고 명백한 실패는 드러납니다. 신뢰성을 측정하려면 독립적인 첫 시도 실험을 여러 번 실행하고(에이전트 평가에 대한 Anthropic의 가이드) 그 결과가 얼마나 자주 유지되는지를 보고하세요.
6. 수정 사항을 인코딩하세요
결과물을, 당신이 보내야 했던 후속 프롬프트들과 함께 검토한 다음 이렇게 물어보세요.
- 사용자가 반복하거나 수동으로 방향을 잡아 줘야 했던 것은 무엇인가?
- 빠졌거나 불분명한 규칙이 있는가?
- 스타일시트가 이 수정 사항을 표현할 수 있는가?
- 이 실패는 코드로 검사할 만큼 기계적인가?
- 이 수정 사항이 이 결과물을 넘어 일반화되는가?
생성된 페이지를 손으로 다듬는 대신 가이드를 업데이트하세요. 다음 비교가 첫 시도가 실제로 나아졌는지를 알려 줄 것입니다.
수동 루프가 성과를 내기 시작한 뒤에 도구를 추가하세요.
- 가이드가 적용되어야 하는 시나리오와 적용되지 말아야 하는 시나리오를 모두 포함하세요.
- 편집하는 동안에는 작은 홀드아웃을 감춰 두세요.
- 모델과 가이드의 버전을 기록하세요.
- 기계적인 검사를 자동화하세요.
- 여러 명의 블라인드 검토자를 활용하세요.
자동화를 어디까지 밀어붙이든, 최종 변경은 사람이 검토하도록 유지하세요.
그리고 루프를 계속 돌리세요. 일정한 주기로 피드백을 모으고, 가이드를 바꾼 뒤에 각 종류의 불만이 실제로 덜 나타나는지 지켜보세요. 사람들이 프로덕션에서 같은 실수를 계속 고치고 있다면, 평가를 통과했다는 사실은 그만큼 의미가 줄어듭니다.
동작하는 예시를 원한다면, 우리 것은 공개되어 있습니다. 우리는 v0, Codex, Claude 같은 도구에 design.md를 매일 불러와 Vercel다운 산출물을 만들고 있으며, eve design agent 템플릿을 쓰면 우리가 운영하는 것과 같은 Slack 디자인 에이전트를 만들 수 있습니다.