코딩 에이전트와 복잡성 예산

TMT

https://leerob.com/agents

저는 cursor.com을 CMS에서 순수 코드와 Markdown으로 마이그레이션했습니다. 몇 주가 걸릴 것으로 예상했지만 사흘 만에 마이그레이션을 완료했고 토큰 비용은 $260이었으며 수백 개의 에이전트를 활용했습니다.

콘텐츠는 곧 코드입니다

Cursor의 RomanEric과 점심을 먹으며 cursor.com 웹사이트에 대해 이야기를 나누기 시작했습니다. 최근에 리디자인을 출시했는데, 그 변화의 일부로 모든 콘텐츠와 웹페이지가 이제 헤드리스 CMS를 통해 구축되었습니다.

테이블을 돌며 불편한 점을 공유했습니다. CMS가 원활히 작동하지 않았습니다. 아름다운 새로운 웹사이트 디자인을 갖추었음에도 새로운 콘텐츠를 배포하는 일이 그 어느 때보다 어려워진 듯했습니다.

이전에는 @cursor⁠에게 코드와 콘텐츠 수정을 요청할 수 있었지만, 이제 그 사이에 새로운 CMS 추상화가 도입되었습니다. 모든 것이 조금 더 번거로워졌습니다. 에이전트에게 작업을 요청하는 대신 다시 UI 메뉴를 클릭하게 되었습니다.

AI와 코딩 에이전트 시대에는 추상화의 비용이 그 어느 때보다 높습니다. 저는 물었습니다: 정말 CMS가 필요할까요? 사람들이 GUI 대신 챗봇으로 콘텐츠를 수정해야 한다면 과연 신경 쓸까요? 우리가 다시 순수 코드로 돌아간다면 엄청난 복잡성을 제거할 수 있습니다.

Roman이 CMS에서 코드로 마이그레이션하는 데 얼마나 걸릴지 물으셨습니다. 제 대략적인 추정은 1–2주였고, 아마 에이전시를 고용해 도움을 받을 것이라고 생각했습니다. 어쩌면 저는 AI의 현재 상태에 맞춰 일정 추정을 갱신하지 않았던 것 같습니다. 그래서 Cursor에서 마이그레이션 계획을 만들어 보고 에이전트를 실행해 1차 시도를 해보기로 했습니다. 얼마나 멀리까지 진행되는지 놀랐지만, 분명 더 많은 작업이 필요했습니다.

그 정도면 주말 동안 마이그레이션에 몰입하기에 충분했습니다. 다음은 그 주말 동안 사이트를 어떻게 마이그레이션했는지입니다.

복잡성 제거

Cursor 웹사이트는 표준 Next.js와 React 앱입니다.

새 버전은 비개발자도 마케팅 페이지를 만들고 글 작성자가 새로운 블로그 포스트를 추가할 수 있도록 CMS 위에 구축되었습니다. 다시 말해, 대부분의 사이트에서 CMS를 사용하는 것은 꽤 표준적입니다.

많은 팀에게 CMS 추상화의 비용은 그만한 가치가 있습니다. 작성자나 마케터가 로그인해 몇 번의 클릭으로 콘텐츠를 변경할 수 있는 포털이 필요하기 때문입니다. 태초(WordPress)부터 그래왔습니다.

덜 논의되는 점은, 현대 웹사이트에 헤드리스 CMS를 “잘” 통합하기 위해 필요한 기술적 복잡성의 양입니다. 저는 모든 제품과 솔루션에 익숙하고, 일부는 그 복잡성을 아주 잘 숨깁니다! 그래도 그 복잡성은 여전히 존재합니다, npm 패키지 뒤에 숨어 있더라도요.

이제 숨겨진 복잡성의 짧은 목록을 보겠습니다. 기술적 세부사항에 덜 관심이 있으시다면 건너뛰셔도 됩니다:

1. 사용자 관리

가장 즉각적인 문제는 여러 곳에서 사용자 관리를 해야 한다는 점입니다.

디자이너가 개발자인 환경에서는 마케팅 팀이 GitHub를 활용하는 것도 무리가 아닙니다. 어차피 GitHub SSO와 RBAC를 이미 설정해 두었으니까요.

새 팀 구성원이 사이트 변경을 시도하면 “CMS에 추가해 주실 수 있나요?”라고 묻게 됩니다. 물론 엔터프라이즈 요금제와 SCIM이 이를 해결할 수 있지만… 복잡성입니다.

저희는 마케팅 팀을 GitHub에 추가했습니다. 계정 관리 시스템을 하나로 통합했습니다.

2. 변경사항 미리보기

정말 빠른 마케팅 사이트를 원하실 것입니다, 그렇지요?

그렇다면 이상적으로 가능한 한 많은 것을 사전에 프리렌더링해야 하며, 방문자마다 페이지의 새 버전을 계산하지 않도록 해야 합니다. 네, 캐싱 알고 있습니다. 프리렌더링은 캐싱의 한 형태입니다.

복잡성 제거라는 관점에서, 정적으로 프리렌더링된 페이지는 엄청난 운영 복잡성을 제거합니다. CMS가 가용성에 잠깐 문제가 생겼다고 마케팅 사이트가 다운되면 곤란합니다. 그러니 저희는 정적 페이지를 원했습니다.

케이크(정적 페이지)를 갖고 먹는 것(CMS 사용)도 가능하지만… 다소 성가십니다. Next.js에는 draft mode가 있습니다. Vercel 툴바에서 드래프트 모드로 토글하는 훌륭한 통합도 있습니다. 이는 사이트를 변경하는 동안 CMS에서 최신 콘텐츠를 동적으로 가져오도록(예: 서버 사이드 렌더) 하면서 프로덕션은 여전히 빠르고 정적으로 렌더링되게 해줍니다. 훌륭합니다!

하지만 이것이 복잡성 예산을 많이 소모합니다. 이제 드래프트 콘텐츠의 URL을 사람들이 보게 하려면 Vercel에 로그인했거나 Vercel 계정이 있어야 합니다. 물론 가능하지만, 또 다른 엔터프라이즈와 SCIM 계정 관리 이슈가 됩니다.

콘텐츠가 코드일 때는 PR을 만들고, 변경사항이 반영된 링크를 받아 누구와도 공유할 수 있습니다. 로그인은 필요하지 않습니다. 단순함입니다.

3. 국제화

현지화된 콘텐츠와 라우트를 갖춘 마케팅 사이트를 원하신다면 앱에 i18n 지원이 필요합니다. 여기에는 은근히 많은 복잡성이 있지만, 보통 더 나은 사용자 경험을 위해 그럴 가치가 있습니다.

소스 코드와 콘텐츠를 받아 빌드 단계에서 AI를 사용해 자동으로 현지화 번역을 생성하는 새로운 도구들이 있습니다. 각 언어에 맞게 정확하도록 규칙과 조정을 제공할 수 있고, 매번 그 작업을 다시 하지 않도록 잠금 메커니즘도 있습니다.

이 접근은 사실 꽤 매끄럽습니다! 저희는 이를 활용해 문서를 여러 언어로 번역했습니다. 다만 CMS가 추가되면 다시 복잡해집니다. 오픈 소스 변형들이 충분히 견고하지 않아, 로컬라이제이션 도구가 CMS와 함께 작동하도록 플러그인 시스템을 구축하기 위해 외부 인력을 고용해야 했습니다.

블로그 포스트 /blog/2-0⁠이 있다고 가정하겠습니다. 이는 CMS의 콘텐츠 항목입니다. 그러면 각 언어마다 다른 변형이 필요해 CMS에 매우 많은 항목이 생깁니다. 또한 번역된 복사본들을 생성하는 발행 프로세스를 자동화해야 합니다. 작동하게 만들었지만, 고통스러웠습니다.

더 쉬운 방법이 있습니다. 소스를 코드로 정의하고, 그다음 컴파일러와 AI를 사용하십시오.

4. CDN과 에셋 전달

이제 저희는 CMS를 제1원리로 분해하고 있습니다.

사용자 관리, 콘텐츠 관리, 그리고… 에셋 관리입니다. 블로그와 웹페이지에는 거의 반드시 이미지와 비디오가 필요하므로, CMS의 큰 부분은 클라우드에 에셋을 업로드하고 저장하는 것입니다.

CMS 제공자는 또한 정적 에셋을 전달하기 위한 CDN 역할을 합니다. 물론 에셋 관리만을 위한 또 다른 SaaS 제품(예: Cloudinary)을 사용할 수도 있습니다. 하지만 대부분은 그냥 CMS를 사용하며, 여기서 많은 사용료 수익이 발생합니다.

몇 달 전 CMS 기반 웹사이트를 출시한 뒤, 저희는 블로그, 변경 로그, 웹페이지 에셋을 CMS와 그 CDN에서 제공해 왔습니다. 다음과 같은 사용량 과금이 발생하기 시작했습니다:

  • 대역폭
  • API 요청
  • CDN 요청

저희는 9월 출시 이후 CDN 사용료로 $56,848을 지출했습니다. Cursor 사이트가 인기는 있지만… 분명 이 비용은 타당하지 않습니다. 훨씬 더 저렴한 비용으로 에셋을 제공할 방법이 충분히 있습니다. GUI의 편의성에 대해 큰 마진을 지불하는 셈입니다.

대신 저희는 에셋을 객체 스토리지에 직접 호스팅하고, 업로드/관리/삭제할 수 있는 작은 GUI를 구축하기로 했습니다. 이는 몇 개의 프롬프트로 해결되었습니다.

5. 의존성과 추상화 비대화

CMS를 사용하면 그들이 콘텐츠를 저장하고 렌더링하는 데 사용하는 맞춤 포맷을 사용해야 합니다. 결국에는 모두 동일한 DOM 요소와 이미지로 변합니다.

저희 코드베이스는 비대해졌습니다. 전적으로 CMS의 잘못은 아니지만, 과도한 추상화로 이어질 수 있습니다. 코드 유지보수가 더 어려워졌고, 더 나쁘게는 머릿속에 유지하기도 어려워졌습니다.

예를 들어보겠습니다. navbar.tsx⁠와 footer.tsx⁠가 있고, 그 콘텐츠는 CMS에서 파생됩니다. 여담이지만, 이 콘텐츠를 그렇게 자주 바꾸시나요? 그냥 코드를 수정하면 되지 않겠습니까?

좋습니다, 본론으로 돌아가겠습니다… 코드는 대략 다음과 같을 것입니다:

export default async function Navbar() {
  const data = await fetchFromCMS("navigation");

  return (
    <nav>
      <ul>
        {data.map((item) => (
          <li key={item.id}>
            <Link href={item.url}>{item.label}</Link>
          </li>
        ))}
      </ul>
    </nav>
  );
}

아마도 어떤 특별한 쿼리 문법으로 네트워크를 넘어 JSON을 받아옵니다.

여러분(그리고 AI 에이전트)은 코드만 보고 내비게이션 항목이 무엇인지 알 수 없습니다. 에이전트는 도구로 grep하고 코드를 편집할 수 없습니다. 그 네트워크 경계는 비용이 큽니다.

복잡성을 줄이고 항목을 인라인하겠습니다.

const navItems = [
  { id: "1", label: "Home", url: "/" },
  { id: "2", label: "Docs", url: "/docs" },
  { id: "3", label: "Features", url: "/features" },
  { id: "4", label: "Enterprise", url: "/enterprise" },
];

export default function Navbar() {
  return (
    <nav>
      <ul>
        {navItems.map((item) => (
          <li key={item.id}>
            <Link href={item.url}>{item.label}</Link>
          </li>
        ))}
      </ul>
    </nav>
  );
}

이것이 확실히 더 낫지만, 이것은 또한 제가 개인적으로 React에서 아쉬운 점입니다. 모든 것을 배열로 바꾸어 map을 돌리는 방식입니다. 이미 자기완결적인 컴포넌트가 있으니, 그냥 JSX를 렌더하는 편이 낫습니다. Tailwind를 사용할 때는 스타일을 복제하는 것이(잘못된 추상화보다) 종종 더 좋기 때문에 이 점은 더욱 중요합니다.

export default function Navbar() {
  return (
    <nav className="flex">
      <ul className="flex space-x-4">
        <li>
          <Link className="px-3 py-2 hover:text-primary" href="/">
            Home
          </Link>
        </li>

        <li>
          <Link className="px-3 py-2 hover:text-primary" href="/docs">
            Docs
          </Link>
        </li>

        <li>
          <Link className="px-3 py-2 hover:text-primary" href="/features">
            Features
          </Link>
        </li>

        <li>
          <Link className="px-3 py-2 hover:text-primary" href="/enterprise">
            Enterprise
          </Link>
        </li>
      </ul>
    </nav>
  );
}

내비게이션을 변경하고 싶으시다면 에이전트에게 "/features를 /contact-sales로 바꿔 주세요"라고 한 줄 프롬프트를 던지시면 됩니다. 다시 말하지만, 이 코드 스멜이 전적으로 CMS의 잘못은 아니지만, 이번 리팩터링 동안 저는 프롬프트로 이 지저분한 부분을 많이 정리할 수 있었습니다.

마이그레이션 수행

저는 Opus 4.5로 계획을 만들었습니다. Cursor는 몇 가지 명확화 질문을 되돌려 주었습니다.

Cursor가 제안한 것은 분명했지만 제가 즉시 떠올리지 못했던 것입니다. 저희는 이미 CMS에서 콘텐츠를 가져올 수 있는 API 키가 있습니다. 메뉴를 뒤지며 다운로드하는 대신, 콘텐츠를 내보내고, 구조를 검증하고, 이를 리포지토리의 마크다운과 파일로 변환하고, 이미지와 비디오를 객체 스토리지에 업로드하는 일련의 스크립트를 만들 수 있습니다.

저는 아마 10번 정도 에이전트를 실행해 80%까지 도달했습니다. Cursor는 의존성을 설치하고 제거했으며, 스크립트를 실행하고, 수많은 페이지의 콘텐츠를 구축했습니다. 그러나 대부분의 엔지니어링 작업과 마찬가지로 마지막 20%가 대부분의 시간을 잡아먹습니다. 하지만 이미 늦었습니다. 저는 완전히 몰입해 있었습니다. 반드시 이 작업을 끝내고 싶었습니다.

일부 페이지는 완벽한 일치가 아니었기에, 저는 모든 페이지에서 다음 에이전트를 실행했습니다:

/features 페이지가 프로덕션과 완벽히 일치하지 않습니다. 홈 페이지의 CMS 데이터 내보내기 방식을 검토하고, 하위 구성요소, 이미지, 배경을 모두 포함해 features 페이지의 전체 콘텐츠를 다시 내보내세요.

@browser를 사용해 스크린샷을 찍고, 로컬 버전이 완벽히 일치할 때까지 반복하세요. 첫 번째 첨부 이미지는 로컬, 두 번째는 프로덕션입니다.

에이전트를 실행하기 시작하자, 성가시던 모든 코드베이스 패턴에 대해 훨씬 더 광범위한 리팩터링을 시작했습니다. 또한 곧 출시될 서브에이전트(subagents)를 사용할 좋은 기회였습니다. 저는 Opus와 함께 제가 원하는 API 형태에 대한 계획을 만들고, Cursor에게 서브에이전트를 실행해 여러 호출 지점에서 병렬로 변경을 수행하도록 요청했습니다. 훨씬 빨랐습니다.

Cursor 계획 모드에서 서브에이전트 사용

작은 행운의 사고들

복잡성을 제거하는 데 너무 들떠서 Storybook도 완전히 삭제했습니다.

Storybook에는 멋진 기능이 있지만 저희는 거의 사용하지 않았습니다. 게다가 Cursor 브라우저로 시각적으로 편집할 수 있게 되면서, 이제 그 가치를 그다지 느끼지 못합니다.

게다가, 모든 머신과 CI 실행에서 많은 의존성을 다운로드하고 설치해야 하지만, 저는 이 단순 버전을 매우 빠르게 구축할 수 있었습니다.

플레이그라운드에서 모든 컴포넌트 보기

또한 객체 스토리지 위에서 에셋을 관리할 GUI는 여전히 필요했습니다. 에이전트에게 3~4개의 프롬프트만 던져도 괜찮고 쓸 만한 결과를 얻을 수 있었습니다. 최소 기능 세트입니다. 물론 더 많은 기능을 추가할 수 있지만, 지금은 그럴 필요가 없습니다.

깔끔한 GUI에서 객체 스토리지의 모든 에셋 보기

콘텐츠가 코드에 있을 또 다른 이점은 모든 변경이 git을 통해 흐른다는 것입니다. 과거에도(되돌리거나 누가 변경했는지 파악하는 데) 유용했지만, 코딩 에이전트가 자율적으로 탐색하기에는 엄청나게 도움이 됩니다.

결과

이건 정말 놀라웠습니다. Cursor는 저희 API를 사용해 사용량을 계산하는 스크립트를 작성했습니다:

  • $260.32 및 297.4M 토큰(대부분 캐시됨)
  • 344 에이전트 요청
  • 66 수동 탭 변경
  • 67 커밋(+43K / -322K 라인)

몇 주가 걸리고 아마 에이전시의 도움이 필요할 것이라고 생각했던 일이 $260의 토큰(혹은 월 $200의 Cursor 요금제)으로 마무리되었습니다.

더 중요하게는, 마이그레이션은 이미 충분한 가치를 입증했습니다. 다음 날 저는 휴대폰의 클라우드 에이전트로 웹사이트의 수정을 머지했습니다. 그다음 날에는 한 엔지니어가 하나의 PR에서 제품과 마케팅 사이트 전반에 걸친 기능을 출시했습니다.

저희는 에셋을 더 낮은 비용의 객체 스토리지로 옮겨 CDN 사용료에서 수천 달러를 절감하고 있습니다. 부수 효과로, 프리렌더링 시 CMS로 향하는 네트워크 I/O를 제거하면서 빌드 시간이 2배 빨라졌습니다.

AI 시대의 추상화 비용은 매우 높습니다. 과도한 추상화는 늘 성가시고 코드 스멜이었지만, 이제 쉬운 해결책이 있습니다. 토큰을 과감히 사용하면 됩니다. 코드베이스에서 복잡성을 제거하는 데 든 비용은 충분히 가치가 있었고 이미 상쇄되었습니다.

이 이야기가 전체 경제에 걸쳐 어떻게 펼쳐질지 상상해 보시기 바랍니다. 코딩 에이전트는 팀들이 가장 대담한 아이디어를 시도하고, 백로그 깊숙이 묻혀 있던 기술 부채를 해결하도록 돕고 있습니다. 저는 풍부하고 고품질의 소프트웨어 세계가 기대되며, Cursor가 그 실현에 계속해서 기여하길 바랍니다.

Edit this page