우리 에이전트가 design.md로 브랜드에 맞는 페이지를 만드는 방법
TMThttps://vercel.com/blog/how-our-agents-build-on-brand-pages-with-design-md
Vercel에서 우리는 Vercel다워 보이고 Vercel처럼 느껴져야 하는 페이지를 디자인하고 만드는 데 코딩 에이전트를 씁니다. 타이포그래피와 색, 구성 모두 우리가 직접 출시하는 페이지에 담는 것과 같은 판단을 담고 있어야 합니다.
최근에 우리는 에이전트가 우리 코드베이스에서 작업할 때 우리가 어떻게 디자인하는지 가르쳐 주는 스킬 product-design에 대해 글을 썼습니다. 이 스킬은 자신이 관장하는 코드와 함께 각 저장소 안에 들어 있고, 에이전트가 우리 디자인 시스템을 어떻게 찾아 이해할 수 있는지, 그리고 지금 만들고 있는 것이 무엇이든 그에 해당하는 제품 가이드라인은 무엇인지 설명해 줍니다.
에이전트가 우리 코드베이스 안에서 작업할 때는 스킬이 필요로 하는 모든 것이 바로 그 자리에 있으니 아주 잘 동작합니다. 그런데 리포트나 제안서, 그리고 여전히 Vercel처럼 보여야 하지만 그런 파일을 하나도 읽을 수 없는 도구에서 만들어지는 일회성 페이지는 어떨까요? 우리에게 그 답은 어떤 에이전트든 불러올 수 있는 공개 파일 하나, design.md였습니다.
design.md를 만들면서 접근한 방식
product-design이 잘 동작한 이유는 디자인 시스템과 제품 가이드라인이 에이전트가 읽을 수 있도록 저장소 안에 그대로 놓여 있었기 때문입니다. 우리는 그 환경 밖에 있는 에이전트와 도구도 같은 지식에 닿을 수 있는 방법이 필요했습니다. 그래야 반대편에서 나오는 페이지가 여전히 우리가 직접 디자인한 것처럼 보일 테니까요. 여기서 요구사항 두 가지가 정해졌습니다.
- 실행 환경과 상관없이 누구나 자기 에이전트에게 가리켜 줄 수 있는 공개 URL 하나.
- 브랜드와 레이아웃, 카피라이팅부터 디자인 시스템과 반응형, 정보 구조까지, 애초에
product-design을 유용하게 만들어 준 모든 것을 담은 가이드.
처음 시도한 단순한 방법은 product-design을 공개 프롬프트로 그대로 옮기는 것이었습니다. 스킬의 참조 파일들을 어떤 에이전트든 URL에서 읽을 수 있는 파일 하나로 합치는 방식이었습니다. 그런데 문제가 있었습니다. 프롬프트는 우리 시각 언어를 충분히 잘 설명했지만, 그것을 읽는 모델마다 그 설명을 다르게 해석해서 같은 가이드로 완전히 다른 페이지를 만들어 냈습니다.
여기에는 디자인 언어가 주관적이라는 사실이 한몫합니다. "레이아웃을 깔끔하게 유지하라" 같은 표현은 사실 무엇이든 뜻할 수 있습니다. "깔끔하다"는 게 무엇일까요? 그것을 넘어 더 큰 문제는 프롬프트가 남겨 두고 온 나머지 전부였습니다. 우리 코드베이스 안에서 에이전트는 product-design이 설명하는 것들의 실제 컴포넌트와 이미 출시된 예시에 둘러싸인 채로 그 스킬을 읽습니다. 하지만 공개 프롬프트에는 그런 게 하나도 들어 있지 않으니, 모든 모델이 말만 가지고 우리 스타일을 다시 세워야 합니다.
그래서 우리가 해야 할 일은 그 환경이 제공하던 것을 파일 하나로 압축해 담는 것이었고, 우리가 정답에 가까워지고 있는지 알 수 있는 유일한 방법은 나오는 페이지를 직접 보는 것이었습니다. 우리는 이식 작업을 접어 두고 새 파일을 처음부터 쓰기 시작했습니다. 이번에는 모든 변경을 반복 실행할 수 있는 평가 프롬프트 세트로 시험하면서 진행했습니다.
실제 사용 사례에서 뽑아 가짜 입력 데이터와 짝지은 프롬프트를 일곱 개 썼습니다.
- 사용량·성능 리포트
- 갱신 제안서
- 벤치마크 리포트
- 인터랙티브 플래닝 페이지
- 직접 구축 대 구매 검토서
- 보안 거버넌스 브리프
- 프레젠테이션 덱
파일은 바뀌어도 프롬프트는 고정해 뒀기 때문에, 결과물의 차이는 모두 가이드로 거슬러 올라갔습니다.
첫 비교
이 평가들은 파일이 실제로 무슨 일을 하고 있는지, 그리고 서로 다른 에이전트가 그것을 어떻게 해석하는지를 함께 측정할 수 있게 해 줬습니다. 첫 시험에서 우리가 알고 싶었던 것은 design.md가 모델의 결과물을 조금이라도 바꾸는지였습니다. 같은 환경에서 같은 모델로 갱신 제안서 평가를 두 번 돌렸습니다. 한 번은 design.md 없이, 한 번은 그것을 불러온 상태로 돌렸고, 두 번 모두 프롬프트와 데이터, 뷰포트를 똑같이 유지했습니다.
프롬프트와 데이터, 모델, 뷰포트가 모두 같습니다. 각 버전은 한 번만 생성했고 다시 돌리지 않았습니다.
design.md 없이 돌렸을 때 모델은 흔한 SaaS 대시보드를 만들어 냈습니다. 반면 파일을 불러왔을 때는 페이지가 갱신 추천 자체를 앞에 내세우고, 상업적 근거를 하나의 그리드로 모으고, 비교 대상 값들을 실제로 비교할 수 있도록 하나의 척도에 올려놓고, 뒷받침하는 세부 정보는 요약과 경쟁하지 않게 두면서도 볼 수 있게 유지했습니다. 이를 통해 우리는 이 파일이 처음에 확인했던 스타일링만 바꾸는 게 아니라 페이지의 구조와 위계까지 바꾼다고 결론지을 수 있었습니다. 이런 방식으로 규칙을 하나씩 쌓아 가며 가이드를 계속 만들어도 되겠다는 신호를 충분히 얻은 셈입니다.
이 시스템을 작동하게 만드는 세 부분
design.md를 시험하고 다시 만드는 과정에서, 그 범위는 전체를 작동하게 만드는 세 부분의 시스템으로 발전했습니다.
- design.md는 에이전트에게 읽는 사람의 과제를 어떻게 규정하고, 근거를 어떻게 구조화하고, 어떤 구성을 고를지 보여 주는 가이드를 제공합니다.
- 클래스와 토큰의 어휘를 한정해서 문서화한 공개 스타일시트가 있습니다.
- 평가 루프가 사람의 반복된 피드백을 더 나은 가이드와 결정론적 검사로 바꿉니다.
이 계층들은 품질 높고 브랜드에 맞는 Vercel 페이지를 만드는 작업에서 각각 다른 몫을 담당합니다. design.md에 담기는 판단은 에이전트에게 다음과 같은 가이드를 줍니다.
- 빠르게 훑는 경영진의 독해와 꼼꼼한 검증 모두에 맞게 페이지를 짜는 방법.
- 구체적인 주장과 정직한 유의사항을 담아 카피를 쓰는 방법.
- 근거와 산문이 서로를 뒷받침하도록 위계와 타이포그래피, 색을 구성하는 방법.
- 워드마크와 삼각형 로고의 애셋 규칙까지 포함해, Vercel의 이름으로 게시하는 방법.
design.md는 우리가 절대 보고 싶지 않은, 생성형 디자인에서 반복되는 패턴에도 이름을 붙여 둡니다. 패턴에 이름을 붙여 주면 에이전트가 그것을 훨씬 안정적으로 알아보고 피할 수 있습니다.
design.md에서 발췌한 부분입니다. 생성형 디자인에서 반복되는 패턴에 명시적으로 이름을 붙여 두면 에이전트가 그것을 알아보고 피하는 데 도움이 됩니다.
에이전트가 계속 자기만의 타이포그래피와 여백, 레이아웃을 만들어 내서 스타일시트를 만들었습니다. 그 결정을 모델에서 완전히 걷어낸 것입니다. 스타일시트는 헤더와 표, 지표 스트립, 차트 스타일처럼 우리 디자인 시스템의 프리미티브를 어떤 페이지에서든 공개 URL로 쓸 수 있는 CSS로 묶어 둡니다. 그리고 design.md가 그 스타일시트에서 제공하는 클래스 이름과 토큰을 문서화해 두어서, 에이전트가 이름을 새로 만들어 내는 대신 그 이름을 HTML에 써서 페이지를 만들 수 있게 합니다.
여기서 얻는 또 하나의 이점은 에이전트가 스타일시트 자체를 전혀 읽지 않는다는 점입니다. 스타일시트는 브라우저에서 페이지가 렌더링될 때 불러오기 때문에, 그 코드는 모델의 컨텍스트에 하나도 들어가지 않고 그만큼의 공간을 디자인 가이드에 더 쓸 수 있습니다.
마지막으로 평가 루프는 나머지 두 조각이 제대로 작동하도록 돕는 장치입니다. 결정론적 검사는 표가 자기에게 주어진 폭을 무시하는 것처럼 기계적으로 판별할 수 있는 실패를 잡는 데 쓰고, 위계와 구성, 그리고 페이지가 읽는 사람이 찾아온 것을 실제로 주는지처럼 자동화할 수 없는 주관적인 부분은 사람이 판단합니다.
가이드가 파일에 들어가는 과정
design.md에 있는 가이드는 한 줄 한 줄 평가 루프를 통과해서 자리를 얻었습니다. 고정된 시나리오로 페이지를 생성하고, 돌아온 결과를 검토하고, 받아들인 수정 사항을 파일에 담고, 시나리오를 다시 돌려서 각 변경이 제대로 붙었는지 확인했습니다. 한쪽 산출물에 도움이 된 변경이 다른 쪽을 조용히 망칠 수도 있기 때문입니다. 다른 경로로 들어온 것은 하나도 없습니다.
시나리오와 라운드
일곱 개 프롬프트는 각각 하나의 시나리오가 됩니다. 프롬프트가 가짜 입력 데이터와 렌더링 설정과 함께 고정된다는 뜻입니다. 예를 들어 갱신 제안서는 항상 같은 가상 고객 데이터와 같은 뷰포트 설정으로 돌아가고, 실행할 때마다 바뀌는 것은 design.md뿐입니다. 라운드는 현재 버전의 파일로 모든 시나리오에서 페이지를 새로 생성하는 것을 뜻합니다. 전체 라운드는 일곱 개 시나리오를 Claude Opus 4.8과 GPT-5.5를 쓰는 Codex 양쪽에서 모두 돌립니다.
표에만 영향을 주는 규칙 변경처럼 특정한 것을 살펴보고 싶으면, 영향받는 시나리오만 또는 한 모델만 다시 돌려서 반복 주기를 짧게 유지할 수 있습니다.
일곱 페이지를 한꺼번에 생성하니 나란히 비교하기도 쉬웠는데, 눈에 띈 점은 design.md가 모든 페이지를 하나의 템플릿으로 밀어붙이지 않는다는 것이었습니다. 인터랙티브 플래닝 페이지는 조작 요소를 앞과 가운데에 뒀습니다. 사람들이 플래닝 페이지를 여는 이유는 숫자를 바꿔 보고 무슨 일이 일어나는지 확인하는 것이기 때문입니다. 반면 갱신 제안서는 추천을 앞에 내세우고 그 뒤에 상업적 비교를 붙였습니다. 이 페이지를 읽는 사람은 갱신할지 말지 결정하는 중이기 때문입니다. 모든 페이지가 같은 Vercel 타이포그래피와 색, 여백을 썼지만, 각 페이지의 구조는 그 페이지를 읽는 사람이 하려는 일에 맞춰져 있었습니다.
서로 다른 시나리오의 결과물이 같은 시각 언어를 공유하면서도 서로 다른 페이지 구조를 쓰고 있습니다.
실행 하나하나를 검토하기
매 라운드에서 나온 페이지를 검토하려고, 페이지 전체 렌더링을 보여 주고 블라인드 A/B 비교를 돌리는 로컬 앱을 만들었습니다. 이 앱은 결국 우리 평가 하네스가 되어, 각 시나리오를 실행하고 결과를 저장합니다. 저장된 실행마다 프롬프트와 입력, 모델 설정, 사용한 design.md 버전, 스크린샷, 그리고 검토자가 남긴 피드백이 함께 보관됩니다. 검토자는 모든 수정 사항을 그것을 만들어 낸 정확한 실행에 붙여 기록합니다.
우리 로컬 검토 하네스입니다. 각 실행의 페이지 전체 렌더링과 블라인드 A/B 비교를 보여 줍니다.
수정 사항을 규칙과 검사로 바꾸기
검토자가 기록한 수정 사항은 그것을 일관되게 적용할 수 있는 가장 좁은 자리에 안착합니다. 판단에 관한 변경은 산문 형태로 design.md에 들어가고, 재사용할 수 있는 기계적 요소는 스타일시트로 들어가고, 기계적으로 확인할 수 있는 것은 코드 안의 결정론적 검사가 됩니다. 하네스 자체의 문제는 하네스에 남고, 한 모델만 다른 모델들과 다르게 실패할 때는 그 실패가 반복될 때까지 규칙에 넣지 않습니다.
초기 갱신 제안서 중 하나를 보겠습니다. 그 페이지에는 표를 두 배 넓게 놓을 공간이 있었는데도, 상업 조건 표가 산문과 같은 폭으로 눌린 채 돌아왔습니다.
design.md가 피드백을 반영하기 전과 후
검토 과정에서 우리는 근거 표는 주어진 폭을 전부 써야 한다고 지적했습니다. 그런데 이전 결과물들을 훑어보니 같은 실패가 곳곳에 있었습니다. 그래서 이 수정 사항은 결국 두 곳에 들어갔습니다.
- 의도한 동작을 명시한
design.md의 규칙. - 같은 레이아웃 실패가 다음에 나타났을 때 잡아내는 코드 안의 결정론적 검사.
이것이 반영된 뒤로는 갱신 제안서 프롬프트에서 표가 전체 폭을 제대로 쓰는 페이지가 나왔습니다. 이런 변경을 검증하려고, 규칙으로 담은 다음 영향받는 시나리오를 다시 돌렸습니다. 중요한 시점에는 한 걸음 더 나아가, 업데이트한 design.md를 이전 버전 파일과 맞붙이는 블라인드 A/B 라운드를 돌려서 각 변경을 유지할지, 손볼지, 되돌릴지 결정했습니다.
효과가 있었는지 측정하기
이 파일을 만드는 데는 전체 라운드와 표적 검사, 예비 실행, 그리고 온갖 막다른 길까지 합쳐 200번을 훌쩍 넘는 실행이 들어갔습니다. 사람 검토자와 나란히 모델 심사자도 매 라운드에 대한 비평을 썼고, 라운드마다 나온 피드백은 다음 실행을 개선하는 데 들어갔습니다.
지면을 아끼려고 세 번째 생성마다 보여 줍니다. 각 라운드는 다음 라운드를 개선하기 위한 피드백을 받았습니다.
그렇게 여러 번 돌린 뒤, 우리는 우리가 담아 둔 수정 사항이 그것을 쓴 이유였던 실패를 실제로 막고 있는지 알고 싶었습니다. 그래서 데스크톱 시나리오 세 개를 골라, 각각 GPT-5.5를 쓰는 Codex로 페이지를 두 번 생성했습니다. 한 번은 design.md를 불러오고, 한 번은 불러오지 않았습니다. 매 생성의 첫 시도를 그대로 채택하고 다시 돌리지 않았습니다. 그다음 여섯 페이지 전체에 결정론적 검사를 돌려서, 표가 주어진 폭을 무시하는 것처럼 이미 알려진 실패가 각 묶음에서 몇 번 나타나는지 셌습니다. design.md로 생성한 페이지에서는 그런 실패가 39번 나왔습니다. 없이 생성한 페이지에서는 91번 나왔고, 이 시험에서는 57% 적은 셈입니다.
이 숫자에는 유의할 점이 두 가지 있습니다. 검사는 우리가 이미 보고 적어 둔 실패만 잡을 수 있으니, 이 시험은 페이지가 전체적으로 잘 디자인됐는지에 대해서는 아무것도 말해 주지 않습니다. 여섯 페이지는 품질이나 신뢰성을 주장하기에는 표본이 턱없이 작기도 하고, 파일을 썼든 안 썼든 여섯 페이지 모두 출시를 막을 만큼 심각한 실패를 최소 하나씩 갖고 있었습니다. 그래도 이 시험이 잘 알려 주는 것이 하나 있습니다. 실패에 이름을 붙여 규칙으로 담아 두면 그 실패는 사라진 상태로 유지되는 경향이 있다는 점입니다.
design.md를 최신 상태로 유지하는 방법
파일을 세상에 내놓은 것은 평가 루프였지만, 그것을 최신 상태로 유지하는 것은 실제 사용입니다. 우리 슬랙에서 그 사용은 @design-agent를 통해 들어옵니다. eve로 만든 에이전트인데, 디자인 비평과 카피 대안부터 아이콘 추천, 붙여 넣은 데이터로 만드는 리포트 사이트까지 온갖 일에 씁니다. 프롬프트를 준비하거나 원본 파일을 찾아 헤맬 필요 없이, 스레드에서 에이전트를 멘션하기만 하면 됩니다. 웹사이트 요청이 오면 현재 design.md를 불러와서 게시된 스타일시트에 맞춰 페이지를 만들고, 페이지 전체 스크린샷과 배포 URL을 스레드에 다시 올려 줍니다. 고정된 시나리오와 달리 이 스레드들은 실제 요청과 실제 결과물, 그리고 그 뒤에 이어진 피드백이나 방향 수정을 그대로 담고 있어서, 가이드가 실제 환경에서 어떻게 작동하는지 보여 줍니다.
매주 우리는 그 모든 피드백을 한곳에 모읍니다. 슬랙 스레드와 함께 깃허브 리뷰와 피그마에 달린 코멘트까지 모읍니다. 자동화가 반복되는 코멘트를 묶어 주고, 반복되는 지적은 하나하나 변경 제안이 됩니다. 그다음 사람이 각 제안을 검토해서 시스템이 이미 처리하고 있는 것인지 확인하고, 받아들인 수정 사항을 어디에 넣을지 결정합니다. @design-agent인지, product-design 스킬인지, design.md인지, 스타일시트인지, 아니면 결정론적 검사인지를 정하는 것입니다. 그리고 사람들이 우리가 한 번도 시험해 보지 않은 종류의 페이지를 요청하기 시작하면, 그 요청은 새로운 평가 시나리오가 됩니다.
이 모든 것이 효과가 있는지 알기 위해, 우리는 비슷한 작업에서 각 유형의 지적이 시간이 지나며 얼마나 자주 나타나는지 셉니다. 수정 사항을 규칙으로 담고 나면 그 횟수가 줄어들기 시작해야 합니다. 줄어들지 않는다면 그 수정에 무언가 잘못된 부분이 있습니다. 규칙이 불분명할 수도 있고, 필요한 순간에 불러와지지 않고 있을 수도 있고, 스타일시트에 그것을 표현할 프리미티브가 없을 수도 있고, 산문 대신 결정론적 검사가 필요할 수도 있습니다.
직접 만들어 보기
같은 루프를 직접 만들 수 있습니다. 반복되는 산출물 하나와 수동 비교 한 번으로 시작하면 됩니다.
1. 반복되는 산출물 하나를 고르세요
제안서나 성능 리포트, 벤치마크, 마이크로사이트처럼 실제 독자와 실제 입력 데이터가 있는 최근 작업을 쓰세요. "브랜드에 맞게 만들어 줘" 같은 막연한 목표는 피하세요. 무엇이든 생성하기 전에 짧은 평가 기준을 적어 두세요. 좋은 기준은 제공한 사실이 그대로 살아남았는지, 읽는 사람의 결정이 명확한지, 그리고 늘 손으로 고치던 그 부분이 실제로 해결됐는지를 확인합니다.
2. 기준선을 먼저 저장하세요
새로운 디자인 컨텍스트 없이 페이지를 한 번 생성하고, 프롬프트와 입력, 설정, 스크린샷을 저장하세요. 하네스 자체가 실패한 경우가 아니라면, 그 첫 결과가 거칠어 보여도 그대로 보관하세요. 비교할 이전 상태가 없으면 새 컨텍스트가 도움이 됐는지 알 수 없습니다.
3. 최근에 준 수정 열 개에서 출발하세요
디자인 리뷰나 풀 리퀘스트, 슬랙에서 반복해서 주고 있는 피드백을 모으고, 각 수정 사항을 관찰할 수 있는 형태로 다시 쓰세요. 표가 덜 답답해 보이게 해 주세요 대신 근거 표는 주어진 폭을 전부 쓰게 한다처럼 쓴다는 뜻입니다. 둘 중 확인할 수 있는 것은 하나뿐이기 때문입니다.
범위, 독자와 과제, 관찰 가능한 결정, 사용할 수 있는 프리미티브를 각각의 절로 나눠서 결정 사항을 한 파일에 담으세요. 그 파일이 여러분의 첫 design.md입니다.
4. 반복되는 기계적 요소를 제약하세요
결과물이 계속 자기만의 타이포그래피와 여백, 레이아웃을 만들어 낸다면, 스타일시트를 게시하고 에이전트가 쓸 수 있는 클래스와 토큰을 정확히 문서화하세요. 판단은 산문으로 남기고, 반복되는 기계적 요소는 CSS나 결정론적 검사로 밀어 넣으세요.
5. 조건을 맞춘 비교를 한 번 돌리세요
같은 입력과 모델, 뷰포트로 페이지를 한 번 더 생성하되, 이번에는 여러분의 파일을 불러온 상태로 하세요. 그 결과를 기준선과 섞어 두고, 어느 쪽인지 모르는 상태로 둘 다 평가 기준에 따라 점수를 매기세요.
시작하는 데 실행기나 모델 심사자가 반드시 필요한 것은 아닙니다. 한 번의 시도로도 크고 분명한 실패는 드러납니다. 신뢰성을 측정하려면 독립적인 첫 시도 실험을 여러 번 돌리고(에이전트 평가에 관한 앤트로픽 가이드) 그 결과가 얼마나 자주 유지되는지 보고하세요.
6. 수정 사항을 규칙으로 담으세요
결과물을 여러분이 추가로 보내야 했던 후속 프롬프트와 함께 검토하고, 그다음 이렇게 물어보세요.
- 사용자가 직접 반복하거나 방향을 잡아 줘야 했던 것은 무엇인가?
- 규칙이 빠져 있거나 불분명한가?
- 스타일시트로 이 수정 사항을 표현할 수 있는가?
- 이 실패는 코드로 확인할 만큼 기계적인가?
- 이 수정 사항이 이번 결과물을 넘어 일반화되는가?
생성된 페이지를 손으로 다듬는 대신 가이드를 업데이트하세요. 첫 시도가 실제로 나아졌는지는 다음 비교가 알려 줍니다.
수동 루프가 성과를 내기 시작한 뒤에 도구를 붙이세요.
- 가이드가 적용되어야 하는 시나리오와 적용되면 안 되는 시나리오를 모두 넣으세요.
- 편집하는 동안 작은 홀드아웃은 감춰 두세요.
- 모델 버전과 가이드 버전을 기록하세요.
- 기계적인 검사는 자동화하세요.
- 블라인드 검토자를 여러 명 두세요.
자동화를 어디까지 밀고 가든, 최종 변경은 사람이 검토하도록 유지하세요.
그다음에는 루프를 계속 돌리세요. 정해진 주기로 피드백을 모으고, 가이드를 바꾼 뒤에 각 유형의 지적이 실제로 줄어드는지 지켜보세요. 사람들이 프로덕션에서 같은 실수를 계속 고치고 있다면, 평가를 통과했다는 사실은 그만큼 덜 중요합니다.
동작하는 예시를 보고 싶다면, 우리 것은 공개되어 있습니다. 우리는 Vercel다운 산출물을 만들기 위해 v0와 Codex, 클로드 같은 도구에 매일 design.md를 불러오고, eve 디자인 에이전트 템플릿을 쓰면 우리가 돌리는 것과 같은 슬랙 디자인 에이전트를 만들 수 있습니다.