[카테고리:] Uncategorized

  • 1인 콘텐츠 엔진 아키텍처, 5개 모듈로 쪼갠 기록

    1인 콘텐츠 엔진 아키텍처, 5개 모듈로 쪼갠 기록

    내가 1인 콘텐츠 엔진을 5개 모듈로 쪼개 만든 이유

    이 글은 제가 ACE(Affiliate Content Engine)를 직접 만들면서 기록한 경험담입니다. 이 글에는 어필리에이트 링크가 포함되어 있지 않습니다. 언급되는 도구는 모두 제가 실제로 사용 중인 것들이며, 링크는 투명성을 위해 공개된 슬러그 리다이렉트(/go/{slug})를 통해 이동합니다.

    4단계 블록이 지그재그로 이어지는 ACE 아키텍처 에디토리얼 일러스트

    왜 이 문제가 중요한가

    내가 처음 ACE(Affiliate Content Engine)를 설계했을 때는 generate_blog.py 하나에 모든 걸 다 넣어둘 생각이었다. 브리프를 읽고, 프롬프트를 채우고, 글을 쓰고, 링크를 끼우고, 스키마를 만들고, 워드프레스로 올리는 것까지 한 파일이면 충분할 것 같았다. 비개발자 입장에서 파일이 적을수록 이해하기 쉽다고 믿었기 때문이다. 함수 다섯 개쯤 겹쳐 쓰고, 맨 아래 if __name__ == "__main__": 하나 박아두면 끝날 거라 생각했다.

    그 믿음이 깨진 건 세 번째 글을 돌려본 날이었다. 품질 체크에서 메타 디스크립션 길이만 걸렸는데, 수정하려면 전체 파이프라인을 처음부터 다시 돌려야 했다. 프롬프트가 다시 API를 때렸고, 나는 같은 본문을 다시 생성하느라 토큰을 두 번 태웠다. 화면에는 내가 이미 합격시켰던 문단들이 다시 한 줄씩 흘러나왔고, 그걸 보는 동안 나는 점점 화가 났다. 바꿔야 할 건 메타 디스크립션 한 줄뿐이었는데.

    그때 깨달았다. 1인 운영에서 중요한 건 코드 줄 수가 아니라 rerun 비용이 얼마나 싸게 먹히는가였다. 매번 글 하나를 완성하는 데 3시간이 걸린다면, 재실행 한 번은 30초 안에 끝나야 한다. 한 덩어리 스크립트로는 절대 그 조건을 맞출 수 없었다. 모든 단계가 직렬로 엮여 있으면, 중간 한 군데만 틀어져도 앞 단계가 통째로 다시 돌아간다. 돈이 새는 게 아니라 시간이 새는 게 더 뼈아팠다.

    나한테는 투자자도, 팀원도, QA 엔지니어도 없었다. 오직 저녁 시간 2~3시간과, 이미 한도가 정해진 API 크레딧과, 새벽에 다시 돌려봐야 내일 아침까지 글이 나올지 걱정되는 마음만 있었다. 그래서 아키텍처를 “많은 글을 뽑기 위한 공장”이 아니라 “실수해도 싸게 되돌릴 수 있는 구조”로 바꿔야 했다. 하루에 여러 편을 뽑는 것보다, 한 편을 꺼내 놓고 마음에 안 드는 부분을 마음껏 고칠 수 있는 여유가 훨씬 더 귀했다.

    비슷한 고민을 하는 사람을 나는 몇 명 안다. 본업이 따로 있는 마케터, 부업으로 뉴스레터를 시작한 기획자, 제품 리뷰를 쌓아 올리려는 1인 쇼핑몰 운영자. 우리는 공통적으로 “코드는 조금 배웠지만 시스템을 만들어본 적은 없는” 사람들이다. 그래서 우리에겐 “한 번에 완벽한 공장”보다 “한 조각씩 고쳐도 무너지지 않는 골조”가 필요하다. 이 글이 그 골조를 어떻게 세우는지에 대한 힌트가 되면 좋겠다.

    이 글은 내가 한 덩어리 설계를 버리고 brief → generate → quality gate → link insert → publish 다섯 단계로 분해한 기록이다. 왜 하필 5개였는지, 각 단계가 무엇을 독립적으로 책임지는지, 그리고 비개발자인 내가 어떤 기준으로 경계선을 그었는지를 적었다. 아키텍처 이야기를 하지만 추상적인 용어는 쓰지 않으려 한다. 모든 이야기는 내 레포의 실제 파일 경로와 커밋 해시에 붙어 있다. 내가 했던 실수까지 포함해서 말이다.

    내가 실제로 한 것

    나는 5개의 커밋에 5개의 단계를 하나씩 넣었다. 한 커밋에 한 모듈이라는 규칙을 스스로에게 강제했고, 그게 지금도 ACE를 지탱하는 뼈대가 됐다. 이 규칙이 왜 중요한지 나는 뒤늦게 알았다. 한 커밋에 여러 단계를 섞어 넣으면, 나중에 어느 줄이 어떤 역할을 하는지 되짚기가 어려워진다. 반대로 커밋 하나가 모듈 하나라면, 로그를 훑는 것만으로 파이프라인의 뼈대가 한눈에 보인다.

    ACE 레포의 최상위 디렉토리 트리 (scripts, src, config, data, tests)
    ACE 레포 최상위 구조

    이걸 가능하게 만든 건 git log를 거꾸로 훑으면서 “이 커밋에서 내가 어떤 고통을 해결하려 했나”를 한 줄씩 적어본 습관이었다. 기술 블로그에 흔히 나오는 “클린 아키텍처” 이론서가 아니라, 내 터미널에 찍힌 빨간 에러 메시지들이 내 설계 선생님이었다.

    커밋 타임라인은 이렇게 흘렀다.

    • 2dac25d feat(brief): add brief schema and loader — 1단계. 구글 시트의 브리프 행을 파이썬 객체로 읽어오는 로더다. 파일은 src/brief/brief_loader.py. 이 모듈은 오직 “시트 한 행을 검증된 Brief 객체로 바꾼다”는 일만 한다. 콘텐츠 생성은 알지도 못한다.
    • 9ebcd27 feat(generators): add Phase 2 blog/threads prompt generators — 2단계. src/generators/blog_generator.py가 브리프 객체를 받아서 config/prompts/blog_comparison.md 같은 템플릿을 str.replace()로 채운다. 이 모듈도 API를 직접 때리지 않는다. 프롬프트 텍스트를 stdout으로 내보낼 뿐이다. 실제로 Claude Code(Opus/Sonnet)가 그걸 받아서 본문을 써준다.
    • 8fc9917 feat(quality): add Phase 3 quality gate and link inserter — 3단계 + 4단계. src/quality/quality_gate.py는 현재 531줄이고, src/linker/internal_linker.py는 189줄이다. 두 모듈은 이미 쓰여진 마크다운 파일을 파일 경로 하나로 받아서 검증하거나 링크를 꽂는다.
    • 924e8cf feat(publishers): add Phase 4 WordPress publisher + JSON-LD schema generator — 5단계. src/publishers/wordpress_publisher.py가 완성된 마크다운을 워드프레스 REST API로 올린다. 스키마 주입은 그 직전에 따로 돌아간다.
    • 1a55ecd feat(foundation): add foundation content type end-to-end — 파운데이션 콘텐츠 타입 추가. 이건 기존 5단계 위에 얹은 프로파일 전환이지 새로운 단계가 아니다. 한 덩어리였다면 이걸 얹는 순간 전체를 다 건드려야 했을 것이다.

    나는 scripts/ 디렉토리를 명시적으로 두껍게 유지했다. 각 단계가 독립 CLI 진입점을 하나씩 갖도록 했다. scripts/read_brief.py, scripts/generate_blog.py, scripts/check_quality.py, scripts/insert_links.py, scripts/publish_wordpress.py. 이게 얇은 파이썬 래퍼라는 사실이 중요하다. 내용물은 src/ 아래 모듈이 다 쥐고 있고, scripts/는 그냥 문 손잡이에 불과하다. 문 손잡이가 얇아야 내가 망치로 두드릴 때 안쪽 기계가 다치지 않는다.

    scripts/ 아래에는 지금 20개가 넘는 CLI 진입점이 쌓여 있다. 처음엔 과하다고 느꼈지만, 쓰다 보니 이게 가장 빠른 디버깅 도구라는 걸 알았다. 어느 단계가 터지든 해당 스크립트만 따로 돌려서 로그를 확인할 수 있다. 두 단계 사이 어딘가에서 데이터가 이상하게 변했다면, 각 스크립트를 순서대로 돌려서 어느 파일이 바뀌는 순간 이상해지는지 추적한다. 이건 pdb 같은 디버거를 쓸 줄 모르는 비개발자에게는 엄청난 무기다.

    scripts/ 디렉토리의 실제 파일 목록
    scripts/ 아래 20여 개의 CLI 진입점

    이렇게 만들고 나니, 어떤 단계에서 실패해도 나는 그 단계부터 다시 돌리면 됐다. 품질 체크가 터지면 check_quality.py만 다시 돌린다. 링크가 하나 빠졌다면 insert_links.py만 다시 돌린다. 생성 단계로 되돌아가지 않는다. 이 왜 비개발자가 이걸 직접 만들었나의 출발점이었던 “내가 오늘 밤 안에 글을 내고 자고 싶다”는 요구는 결국 이 경계선 덕분에 유지됐다.

    모듈 이름을 정할 때도 규칙이 있었다. 각 파일 이름은 동사 한 개 + 명사 한 개로만 짓는다. read_brief, generate_blog, check_quality, insert_links, publish_wordpress. do_everything.pypipeline_handler.py 같은 모호한 이름은 절대 쓰지 않는다. 이름이 모호해지는 순간 그 모듈은 책임이 너무 많아진다는 신호였고, 그럴 때는 항상 두 개로 쪼개야 했다.

    어떻게 작동하는가

    파이프라인이 돌아가는 순서를 번호로 적어본다. 중간 중간 어떤 파일이 뭘 책임지는지 같이 적는다.

    1. 브리프 로드. scripts/read_brief.py B-F-002를 치면 src/brief/brief_loader.py구글 시트briefs 탭에서 한 행을 읽고 검증한 뒤 JSON으로 뱉는다. 필수 필드가 비어 있으면 여기서 멈춘다. 검증은 pydantic 모델 하나로 묶여 있어서, 시트에 컬럼을 추가해도 모델만 수정하면 파이프라인 전체가 새 필드를 알아본다.
    2. 프롬프트 생성. scripts/generate_blog.py B-F-002config/prompts/blog_foundation.md를 열어 primary_keyword 자리표시자를 실제 브리프 값으로 바꾼다. 그 결과가 stdout으로 흘러나오고, Claude Code가 이걸 받아서 본문을 써준다. 본문 생성 자체는 파이썬 코드 밖에서 일어난다는 점이 중요하다. 모델을 바꿔도 이 모듈은 그대로다. Opus에서 Sonnet으로, 혹은 Sonnet에서 다른 모델로 갈아탈 때 내 파이썬 코드는 한 줄도 건드리지 않는다.
    3. 품질 게이트. scripts/check_quality.py data/output/blog/B-F-002.md --content-type foundation을 돌린다. src/quality/quality_gate.py는 파일 경로만 받는다. 파일 내용을 읽고, 프로파일에 맞는 체크 목록을 돌리고, 결과를 dict로 돌려준다. 프롬프트도 API도 다시 때리지 않는다. 실패하면 어느 체크에서 걸렸는지 이름과 이유가 그대로 출력되니, 어디를 고쳐야 할지 바로 보인다.
    4. 링크 삽입. scripts/insert_links.py가 본문 안의 플레이스홀더 토큰과 [LINK:...] 패턴을 실제 URL로 치환한다. 파운데이션 글은 제휴 링크가 없어서 이 마커만 /go/<slug> 리다이렉트로 바뀐다. 리다이렉트 테이블은 data/redirects.json에 있고, 새 도구를 추가하면 그 파일만 갱신한다.
    5. 발행. scripts/publish_wordpress.pysrc/publishers/wordpress_publisher.py를 호출해 워드프레스 REST API로 초안을 올린다. 스키마 JSON-LD는 발행 직전에 HTML 말미에 〈script type="application/ld+json"〉으로 주입된다. 기본 상태는 draft라서, 내가 워드프레스에 들어가서 카테고리와 대표 이미지를 확인한 뒤 직접 publish 버튼을 누를 수 있다. 완전 자동화는 일부러 피했다. 마지막 눈 한 번은 내가 꼭 넣고 싶었다.

    각 단계가 독립 모듈이라는 건, 코드에서 어떻게 보일까? quality_gate.py의 핵심은 다음과 같은 레지스트리 패턴이다.

     # src/quality/quality_gate.py (발췌)
    CHECK_REGISTRY = {
        "affiliate_default": [
            check_disclosure,
            check_no_leftover_placeholders,
            check_cta_count,
            check_word_count,
            check_seo_meta,
        ],
        "foundation": [
            check_foundation_disclosure,
            check_no_leftover_placeholders,
            check_no_cta,
            check_hangul_char_count,
            check_heading_hierarchy,
            check_alt_text,
        ],
    }
    
    def run_checks(markdown_path: Path, profile: str) -> dict:
        content = markdown_path.read_text(encoding="utf-8")
        checks = CHECK_REGISTRY[profile]
        results = {fn.__name__: fn(content) for fn in checks}
        return {"passed": all(r["ok"] for r in results.values()), "checks": results}
    

    CHECK_REGISTRY가 있어서 파운데이션 글에는 check_no_cta가 걸리고 일반 제휴 글에는 check_cta_count가 걸린다. 콘텐츠 타입이 하나 추가돼도 나는 프로파일 하나만 새로 등록하면 된다. 생성 모듈이나 발행 모듈은 수정하지 않는다. 이게 내가 레고 블록처럼 맞추고 싶었던 바로 그 모양이다.

    5단계 파이프라인의 커밋 타임라인
    brief → generate → quality gate → link insert → publish — 5개 커밋에 담긴 5단계

    그리고 이 레지스트리가 가능한 건 품질 게이트가 파일을 입력으로 받기 때문이다. 프롬프트 문자열을 받아서 검증하는 구조였다면 절대 이렇게 깔끔하게 분리되지 않았을 것이다. 내가 한참 돌아서 배운 교훈이 여기에 있다.

    링크 삽입 모듈도 같은 원칙을 따른다. src/linker/internal_linker.py는 189줄이고, 파일 경로와 브리프 ID만 받는다. 내부 링크는 FOUNDATION_SLUGS 레지스트리에서 찾아 D9 토폴로지대로 꽂고, 외부 [Cursor](https://potato-labs.xyz/go/cursor/) 같은 토큰은 data/redirects.json을 읽어 /go/cursor로 치환한다. 이 모듈도 품질 게이트를 모르고, 브리프 로더를 모르고, 워드프레스를 모른다. 자기 일만 한다.

    이 구조가 주는 또 하나의 장점은 테스트 작성이 쉬워진다는 것이다. 각 모듈이 파일 경로 입력과 파일 출력(또는 dict 반환)만 가지니까, 테스트에서 임시 디렉토리에 가짜 파일을 하나 놓고 함수를 부르면 끝이다. 나는 tests/fixtures/ 아래에 작은 샘플 마크다운을 몇 개 두고, 그걸로 quality_gate.run_checks를 호출하는 테스트를 돌린다. API를 때리지도 않고, 네트워크도 없고, 몇 초면 끝난다.

    내가 틀렸던 지점

    나는 처음에 quality gate를 generate 단계에 묶어놨다. 이유는 단순했다. “어차피 본문을 만들자마자 체크해야 할 테니 같은 스크립트 안에 두면 편하지 않나?” 라고 생각했기 때문이다. 실제 초기 구조에서 scripts/generate_blog.py의 마지막 30줄은 방금 만든 본문을 바로 검증하는 코드였다. 심지어 나는 그걸 “원스톱”이라 부르며 뿌듯해했다. 명령어 하나로 글이 나오고, 검증까지 끝난다고 말이다.

    문제는 세 번째 글을 돌린 날 드러났다. meta_description이 102자로 나왔는데 상한선이 95자였다. 앞으로 몇 글자만 자르면 되는 수정이었다. 그런데 당시 구조에서는 그 체크를 다시 돌리려면 generate_blog.py를 통째로 다시 실행해야 했고, 그 말은 Claude Code가 본문을 처음부터 다시 써낸다는 뜻이었다. rerun 한 번에 토큰을 두 번 태운 셈이다. 본문 자체는 멀쩡했는데도. 그날 밤 나는 같은 본문을 세 번 생성하면서 “왜 내가 이 짓을 하고 있지?” 라는 질문을 반복했다.

    그래서 나는 8fc9917 feat(quality): add Phase 3 quality gate and link inserter 커밋에서 품질 게이트를 독립 모듈로 뽑아냈다. src/quality/quality_gate.py 531줄은 파일 경로 하나만 받는 함수로 다시 쓰여졌다. 입력이 “방금 생성된 본문 문자열”이 아니라 “디스크에 저장된 마크다운 파일”이 된 순간, rerun 비용은 0에 수렴했다. 토큰은 전혀 쓰지 않고, 수정한 파일을 다시 읽어서 다시 검증할 뿐이었다. 나는 그날 처음으로 check_quality.py를 10번 연속으로 돌려봤는데, 터미널에 passed: True가 뜨기까지 1분도 걸리지 않았다.

    이 경험에서 내가 더 깊이 배운 건 “입력의 모양을 바꾸면 모듈의 경계가 바뀐다”는 사실이었다. 입력이 문자열이면 모듈은 호출자와 붙어 있게 된다. 입력이 파일이면 모듈은 호출자와 완전히 떨어진다. 파일이라는 공통 인터페이스가 있는 덕분에, 나는 단계별로 원하는 만큼 재시도할 수 있게 됐다. 파일 시스템이 내 메시지 큐 역할을 해주는 셈이다.

    비개발자 입장에서 이 교훈이 특히 값지다고 느끼는 이유는, “파일”이라는 개념은 누구나 이해할 수 있기 때문이다. 메시지 큐, 이벤트 버스, 워크플로우 엔진 같은 용어는 내가 두 달을 파도 제대로 설명하기 어려웠다. 반면 “마크다운 파일 하나가 단계 사이를 옮겨다닌다”는 모델은 내 머릿속에서 한 장의 그림으로 그려진다. 그 그림이 선명한 한, 나는 구조를 잊어버릴 수 없다.

    교훈은 이렇다. 모듈 경계는 “같이 실행되는가”로 긋는 게 아니라 “따로 다시 돌릴 수 있어야 하는가”로 그어야 한다. 이걸 알고 나서는 다른 단계들도 같은 기준으로 다시 점검했고, scripts/enrich_post.py(734줄)도 생성과 완전히 분리된 독립 진입점으로 만들었다. 파운데이션 글의 이미지 업로드가 네트워크 문제로 실패해도, 본문 생성은 건드리지 않고 enrich_post.py만 다시 돌리면 된다.

    이 경험은 이후 내가 새 기능을 추가할 때마다 가장 먼저 물어보는 질문이 됐다. “이 기능이 실패하면, 나는 어디서부터 다시 돌려야 하지?” 그 답이 “전체”라면 그건 설계가 잘못된 것이다. 답이 “이 한 단계만”이 되도록 경계선을 그어야 한다. 그게 내가 531줄짜리 파이썬 파일 앞에서 배운 가장 비싼 교훈이었다.

    따라해보고 싶다면

    이 구조는 복잡한 도구 없이도 복제할 수 있다. 내가 실제로 밟은 순서는 이렇다.

    1. 스프레드시트 한 장으로 데이터베이스를 대신하라. 구글 시트 한 탭에 브리프 행을 쌓고, 파이썬에서 gspread로 읽어오면 된다. 구조를 미리 고민할 필요 없이 컬럼을 그냥 추가하면 된다. 자세한 이유는 구글 시트를 콘텐츠 DB로 쓴 이유에 따로 적어뒀다.
    2. 5단계 각각을 독립 scripts/*.py로 만들어라. 각 스크립트는 CLI 인자 하나(보통 brief_id)만 받고, 자기 단계만 책임지게 둔다. 500줄이 넘어가면 그 단계가 너무 많은 일을 하고 있다는 신호다.
    3. 파일 경로를 인터페이스로 써라. 단계 간 데이터 전달은 문자열이 아니라 디스크 파일로 한다. data/output/blog/{brief_id}.md처럼 경로 컨벤션을 먼저 정해두면, 각 단계가 아무 순서로나 재실행돼도 상태가 꼬이지 않는다.
    4. 생성은 파이썬 밖에서. 본문 작성은 Claude Code 같은 AI 도구에 맡기고, 파이썬은 프롬프트 준비와 결과 검증만 한다. 모델을 바꿔도 파이썬 코드는 건드리지 않게 된다. 위임 구조의 구체적 이야기는 Claude Code 서브에이전트 위임에 적어뒀다.
    5. Cursor를 코드 편집기로, Claude Code를 오케스트레이터로. 나는 Cursor에서 파일을 열어놓고 Claude Code로 전체 파이프라인을 돌린다. 편집기와 실행기를 분리하니 실수로 엉뚱한 파일을 돌리는 일이 줄었다. Cursor는 코드 맥락을 보여주고, Claude Code는 명령을 받아 파이프라인을 꿰어주는 역할이다. 두 도구의 역할이 겹치지 않게 선을 그은 덕분에, 나는 어떤 작업을 어디서 해야 할지 헷갈리지 않는다.
    6. 매 단계마다 로그 파일을 따로 두어라. 나는 logs/ 아래에 brief.log, generate.log, quality.log, publish.log를 분리해뒀다. 문제가 생기면 해당 단계의 로그 파일만 열어보면 된다. logurulogger.add()를 단계별로 다르게 설정하는 것만으로도 충분하다. 한 파일에 모든 로그를 몰아넣으면 찾기가 힘들다.

    현실적인 기대치: 처음 5단계를 쪼개는 데 나는 주말 이틀을 썼고, 글 한 편을 처음부터 끝까지 돌리는 데 드는 API 비용은 대략 $0.20~$0.40 수준이다. rerun은 생성 단계를 건너뛰면 거의 무료다. 난이도는 파이썬 기본 문법(함수, import, 경로 조작)만 알면 충분하다. 발행까지 이어지는 마지막 조각은 WordPress REST API + Schema.org 자동 주입에서 이어서 다뤘다.

    한 가지 덧붙이자면, 처음부터 5단계를 다 만들려고 하지 말라는 말을 꼭 해주고 싶다. 나는 brief 로더 하나 만드는 데 반나절을 썼고, 그 다음 주에 generator를 붙였고, 또 그 다음 주에 quality gate를 얹었다. 한 주 한 단계씩. 그래야 각 단계가 “진짜로 뭘 해야 하는지”가 보인다. 전체 설계를 먼저 그리고 코드를 채우는 방식은, 비개발자가 시작하기엔 너무 무겁다.

    나는 지금도 이 레포를 들여다볼 때마다 “이 모듈 하나만 다시 돌리면 되는가”를 되묻는다. 그 질문에 “그렇다”고 답할 수 있는 한, 나는 내 파이프라인이 살아있다고 느낀다. 그게 비개발자 혼자 콘텐츠 공장을 굴리며 얻은 유일한 생존 기술이다. 거창한 용어로 포장하지 않아도, 매일 저녁의 한두 시간을 지켜주는 구조라면 그걸로 충분하다고 믿는다.

    FAQ

    Q1. 5단계보다 더 잘게 쪼갤 수도 있지 않나?

    쪼갤 수는 있지만 나는 일부러 멈췄다. 단계가 늘어날수록 경계에서 버그가 샌다. 5개는 “각 단계가 하나의 명확한 입력과 하나의 명확한 출력을 가질 수 있는” 최소 단위였다. 나중에 enrich_post.py를 추가하긴 했지만, 그건 기존 5단계 사이에 끼는 게 아니라 publish 직전에 병렬로 붙는 단계라 전체 구조를 깨뜨리지 않는다. 경계를 추가할 때는 항상 “이게 정말 따로 rerun될 이유가 있는가?”를 먼저 묻는다. 그 답이 흐릿하면 안 쪼갠다.

    Q2. 비개발자가 531줄짜리 quality_gate.py를 직접 다 쓴 건가?

    직접 다 썼다고 말하면 거짓말이다. 설계(어떤 체크가 필요한지, 어떤 프로파일로 분기할지)는 내가 정했고, 구현은 Claude Code한테 맡겼다. 중요한 건 내가 그 531줄 중 어느 줄이 어떤 역할을 하는지 설명할 수 있어야 한다는 점이다. CHECK_REGISTRY 패턴을 이해하고 있어서, 새 체크를 추가하라고 할 때 정확히 어디를 건드려야 하는지 지시할 수 있다. 내가 주로 쓰는 지시 문장은 “CHECK_REGISTRYfoundation 프로파일에 check_hangul_char_count 함수를 추가해줘, 본문에서 5500자 미만이면 실패로 처리해”처럼 파일명과 함수명이 박혀 있는 형태다. 구체적일수록 결과가 예측 가능해진다.

    Q3. 이 구조의 가장 큰 약점은 무엇인가?

    디스크 파일을 인터페이스로 쓰기 때문에 동시 실행에 취약하다. 두 브리프를 동시에 돌리면 같은 경로를 덮어쓸 수 있다. 지금은 1인 운영이라 순차 실행만 해서 문제가 없지만, 규모가 커지면 브리프 ID별로 디렉토리를 분리하거나 락 파일을 둬야 할 것이다. 그날이 오면 다시 쓰겠지만, 오늘의 문제는 오늘까지만 풀기로 했다. 미래의 나에게 숙제를 남기는 것도 1인 운영의 기술 중 하나다.

    [NEWSLETTER_SIGNUP]

    관련 글

  • 비개발자가 Impact에 거절당한 밤 ACE를 짠 이유

    비개발자가 Impact에 거절당한 밤 ACE를 짠 이유

    비개발자가 Impact에 거절당한 날 밤, 나는 ACE를 짜기 시작했다

    이 글은 제가 ACE(Affiliate Content Engine)를 직접 만들면서 기록한 경험담입니다. 이 글에는 어필리에이트 링크가 포함되어 있지 않습니다. 언급되는 도구는 모두 제가 실제로 사용 중인 것들이며, 링크는 투명성을 위해 공개된 슬러그 리다이렉트(/go/{slug})를 통해 이동합니다.

    비개발자가 심야에 노트북으로 어필리에이트 자동화 파이프라인을 그려나가는 에디토리얼 일러스트

    왜 이 문제가 중요한가

    2026년 3월의 어느 새벽, 나는 Impact Marketplace 대시보드 앞에서 다섯 번째 거절 메일을 받았다. 이유는 단 한 줄이었다. “도메인 권위가 충분하지 않습니다.” 내가 산 .xyz 도메인은 30일도 안 된 상태였고, 백링크는 0, Ahrefs DR은 아예 측정조차 되지 않았다. 그 거절 메일은 IMPACT_MARKETPLACE_DECLINED.md라는 파일로 내 레포에 지금도 남아 있다. 나는 그걸 삭제하지 않기로 했다. 출발선을 잊지 않기 위해서다.

    나는 개발자가 아니다. 엑셀 VBA로 업무 자동화를 15년 하다가, 작년에 처음으로 파이썬을 제대로 들여다보기 시작했다. 그런 내가 어필리에이트 마케팅을 시작하려고 하니 벽은 명확했다. 큰 플랫폼은 권위 없는 신규 도메인을 안 받아주고, 받아주는 작은 네트워크는 단가가 낮다. 그리고 기존 SaaS 콘텐츠 도구들은 월 99달러에서 299달러를 받으면서도 정작 내가 필요한 “한국어 1인칭 EEAT 포스트 + 어필리에이트 링크 + Schema.org 자동 주입”을 한 번에 해주는 물건이 없었다. 여러 SaaS를 조합해봤지만 출력물이 전부 AI 냄새가 났다. 보라 그라디언트 이미지, “포괄적인 솔루션을 제공합니다” 같은 문장, 같은 템플릿으로 찍어낸 비교표. 내가 읽기 싫은 글을 내가 쓸 수는 없었다.

    그날 밤 나는 결심했다. 내가 쓸 파이프라인을 내가 직접 짜자. 어차피 콘텐츠 전략은 내 머리에 있고, 코드는 Claude Code가 써줄 수 있다. 월 40달러 구독료 안에서 — Cursor Pro 20달러와 Claude Pro 20달러 — 나만의 콘텐츠 공장을 세울 수 있는지 실험해보자. 어필리에이트 승인이 안 나오는 지금이 오히려 기회였다. 어차피 링크가 없으니 순수한 “경험 기반 글쓰기”로 EEAT 신호를 쌓는 데 집중할 수 있었다. Google이 2024년부터 가중치를 올린 그 EEAT 말이다.

    경쟁 지형을 한 번 더 생각해봤다. 어필리에이트 마케팅 블로그는 지난 2년간 두 가지 방향으로 양극화됐다. 한쪽 끝은 초대형 퍼블리셔들이 프로그래매틱 SEO와 AI 대량 생산으로 쏟아내는 “정답 같은 생김새의 빈 글”이다. 반대쪽 끝은 1인 크리에이터가 자기 손으로 쓴, 검색엔진이 좋아하지 않지만 사람이 실제로 읽는 블로그다. 나는 두 번째가 장기적으로 이긴다고 믿는다. Google이 헬프풀 콘텐츠 업데이트를 세 번 돌리면서 분명해졌다. 사람이 실제로 읽는 글만 살아남는다. 내 전략은 사람이 실제로 읽는 글을 “기계적 품질 검사”와 “반복 가능한 파이프라인”으로 뒷받침하는 것이다. 둘은 모순이 아니다. 문체는 1인칭으로 두고, 인프라는 자동화로 두는 구조다.

    이 글은 그 실험의 “왜”에 대한 기록이다. 나머지 5편은 각각의 “어떻게”를 다룬다. 아키텍처를 왜 5단계로 쪼갰는지, DB를 왜 Google Sheets로 골랐는지, 블로그 생성을 왜 서브에이전트에 위임했는지, Quality Gate는 어떤 기계적 검사를 하는지, Schema.org JSON-LD는 어떻게 WordPress에 자동 주입되는지. 각 글은 독립적으로 읽히지만, 이 허브 글이 뒷받침하는 “왜 이걸 내가 직접 짰는가”라는 질문에 대한 내 대답은 오직 여기에 있다.

    한 가지 미리 말해두고 싶은 건, 이 시리즈는 “튜토리얼”이 아니라 “빌드 저널”이라는 점이다. 나는 내가 실제로 한 일을 적는다. 내가 실제로 틀린 것도 적는다. 그리고 지금도 내가 확신하지 못하는 부분도 적는다. 완성된 전문가의 가이드가 필요한 독자라면 더 적합한 글이 많다. 반면 “비개발자가 6개월 전에는 어떻게 시작했는가”가 궁금한 독자라면, 이 6편이 아마 지금 웹에 존재하는 것 중 가장 구체적인 기록일 것이다. 내 돈과 내 시간으로 실제로 겪었기 때문이다.

    내가 실제로 한 것

    나는 먼저 A4 노트에 ACE(Affiliate Content Engine)라는 이름을 손으로 적었다. 그 아래 5단계 파이프라인을 그렸다. Discovery → Brief → Generate → Quality Gate → Publish. 이 다이어그램은 지금도 내 책상 옆에 붙어 있다. 화살표가 삐뚤고, 박스 안의 글씨는 다 다른 크기다. 그런데 이 지저분한 노트가 이후 3주 동안 내가 길을 잃을 때마다 돌아올 기준점이 됐다.

    다음 날 아침, Cursor를 열고 빈 폴더에서 첫 커밋을 찍었다. a4dfd01 chore: initialize clean repository. 이게 ACE 레포의 출생 신고서다. 거기서부터 나는 매일 밤 2~3시간씩 코드를 붙여나갔다. 개발자가 아니라서 문법이 기억 안 날 때마다 Claude Code에게 “이거 왜 안 돼?”라고 물었고, Anthropic 공식 문서를 열어놓고 서브에이전트 문법을 학습했다. 매일 밤 기록은 handoff.md라는 파일에 남겼다. 세션을 닫기 전에 “오늘 뭘 했고, 내일 뭘 해야 하고, 지금 막힌 곳은 어디인지”를 3~5줄로 적었다. 이게 다음날 Claude Code를 다시 켰을 때의 컨텍스트 복원 장치였다.

    3주가 지났을 때 내 레포의 상태는 이랬다.

    $ git log --oneline | head -10
    1a55ecd feat(foundation): add foundation content type end-to-end
    ff4964f docs: update handoff.md with Session 3 changes
    4a17d6b docs: add handoff.md with full project history and Phase 4 plan
    8fc9917 feat(quality): add Phase 3 quality gate and link inserter
    9ebcd27 feat(generators): add Phase 2 blog/threads prompt generators
    30bde34 feat(discovery): add Phase 1B discovery pipeline
    924e8cf feat(briefs): add brief read/list/validate scripts
    2dac25d feat(sheets): add google sheets client with service account
    ...
    a4dfd01 chore: initialize clean repository
    

    나는 이 커밋 히스토리를 자주 들여다본다. 각 커밋이 정확히 하나의 단계에 매핑돼 있기 때문이다. 2dac25d가 Google Sheets 연동, 924e8cf가 brief 읽기·검증, 30bde34가 Discovery 파이프라인, 9ebcd27가 블로그·Threads 생성기, 8fc9917가 Quality Gate와 링크 인서터, 4a17d6b·ff4964f가 세션 핸드오프 문서화, 그리고 1a55ecd가 Foundation 콘텐츠 타입이다. 디버깅할 때 “어디서 무언가가 깨졌을 가능성이 있는가”를 추적하는 지도가 된다.

    하이라이트는 가장 위에 있는 커밋 1a55ecd feat(foundation): add foundation content type end-to-end였다. 이 한 커밋에 파일 34개가 변경되고 3,933줄이 추가됐다. Foundation 콘텐츠 타입 — 지금 당신이 읽고 있는 이 글의 형식 — 이 그날 태어났다. 프롬프트 템플릿, 전용 Quality Gate 프로파일, D9 내부 링크 토폴로지, 이미지 enrichment 파이프라인, WordPress 드래프트 발행까지 한 호흡에 연결된 순간이었다. 그날 새벽 3시에 나는 책상에서 혼자 “됐다”라고 소리 내서 말했다. 내 아내가 방문을 열고 “뭐가 됐어?”라고 물었고, 나는 “내 글쓰기 공장이 완성됐어”라고 답했다.

    그때까지 내가 작성한 핵심 모듈은 세 개였다. src/quality/quality_gate.py는 531줄로 5가지 기계적 검사(공개 고지, CTA 개수, 단어 수, SEO 메타, leftover placeholder)를 수행한다. src/linker/internal_linker.py는 189줄로 D9 허브 앤 스포크 토폴로지를 구현한다. 이 글이 허브이고, 나머지 5편이 스포크다. 허브는 모든 스포크를 가리키고, 각 스포크는 허브와 인접한 2개 스포크를 가리킨다. scripts/enrich_post.py는 734줄로 Nano Banana 2(Gemini 2.5 Flash Image)로 히어로 이미지를 1장당 약 0.039달러에 생성하고 WordPress 미디어 라이브러리에 업로드한다. 세 파일을 합치면 1,454줄. 비개발자 기준으로 나쁘지 않은 숫자다.

    파이썬 초심자인 내가 이 규모의 코드를 짤 수 있었던 건 한 가지 원칙 덕이었다. “한 함수는 최대 50줄, 한 파일은 한 가지 책임.” Claude Code에게 이 원칙을 CLAUDE.md에 박아두고 코드를 요청하니 LLM이 자동으로 작은 단위로 쪼개줬다. 나는 각 함수를 한 번에 하나씩 이해할 수 있었고, 이해가 안 되는 함수는 다시 더 쪼갤 수 있었다. 결과적으로 내 레포에는 50줄을 넘는 함수가 거의 없다. 대부분 20~30줄이다. 그래서 어떤 함수가 실패해도, 나는 그 함수 하나만 놓고 LLM과 대화할 수 있다.

    한 가지 더. 나는 처음부터 테스트를 같이 쓰기로 했다. tests/ 폴더에 각 모듈의 단위 테스트를 두고, 커밋하기 전에 pytest를 돌렸다. 테스트를 먼저 쓰는 건 아니다. 그건 내 수준에서 아직 어렵다. 대신 LLM이 함수를 써주면 같은 세션 안에서 “이 함수의 pytest 테스트 3개를 만들어줘”라고 이어서 요청했다. 이 한 단계만 추가해도 내 코드의 신뢰도가 체감상 두 배가 됐다. 밤에 자고 일어나서 “어제 짠 게 진짜 돌아가는 건가”라는 불안이 사라진다.

    어떻게 작동하는가

    ACE의 동작은 5단계로 쪼갤 수 있다. 나는 이걸 의도적으로 “메인 오케스트레이터 = Claude Code” + “Python = I/O만” 구조로 설계했다. 코드를 최소화하고 LLM이 직접 조율하도록 하는 게 비개발자인 나에게 유지보수가 쉬웠다. 내가 직접 읽을 수 있는 코드 양이 적을수록, 버그가 났을 때 LLM에게 상황을 설명하기 쉽다.

    이 구조가 실제로 돌아가고 있다는 증거는 내 레포의 커밋 로그에 그대로 남아 있다. 아래 스크린샷은 git log --oneline | head의 실제 출력이다. 각 커밋이 하나의 단계에 매핑되는 걸 눈으로 확인할 수 있다.

    ACE 레포의 git log --oneline 출력, 초기 10개 커밋
    ACE 레포 초기 커밋 10개 (git log –oneline | head)

    그리고 아래는 Foundation 타입을 한 번에 추가한 그 커밋의 git show --stat이다. 한 커밋에 34개 파일이 변경되고 3,933줄이 추가된 장면은 내 레포의 분기점이다.

    1a55ecd feat(foundation) 커밋의 git show --stat 출력
    1a55ecd 커밋: 34개 파일, +3933줄 — 이 파이프라인이 태어난 순간
    1. Brief 읽기: Google Sheets의 briefs 탭에서 brief_id로 한 줄을 읽는다. 제품명, 콘텐츠 타입, angle, proof_points, target_persona, founder_notes, primary_keyword, secondary_keywords 같은 필드가 구조화돼 있다. 한 brief가 한 글의 입력 명세서다. 이 부분은 구글 시트를 콘텐츠 DB로 쓴 이유에서 왜 Notion이 아니라 Sheets였는지 상세히 쓴다.
    2. 프롬프트 채우기: config/prompts/blog_*.md 템플릿을 파이썬이 str.replace()로 채워서 stdout에 뿌린다. 생성기는 단 한 줄도 모델을 호출하지 않는다. LLM 호출은 Claude Code 세션이 담당한다. 이 분리 덕에 프롬프트만 고치면 모델 비용 없이 출력을 바꿀 수 있다.
    3. 본문 생성: 메인 오케스트레이터(Opus)가 blog-writer 또는 foundation-writer 서브에이전트(Sonnet)에게 위임한다. 장문 생성은 토큰 비용이 높으니 Sonnet으로, 조율·검수는 Opus로. Foundation 포스트는 foundation-writer로, 어필리에이트 포스트는 blog-writer로 라우팅된다. 모델 라우팅의 이유는 Claude Code 서브에이전트로 블로그 위임에서 다룬다.
    4. Quality Gate: python scripts/check_quality.py <file> --content-type foundation. 531줄짜리 검사기가 Hangul character 수, CTA 0개 확인, alt 텍스트 누락, 헤딩 계층 붕괴를 기계적으로 잡아낸다. Foundation 프로파일은 affiliate 프로파일과 검사 항목이 다르다. CTA가 있으면 오히려 실패한다. 통과 기준은 Quality Gate 5단계로 쓰레기 포스트 막기에 나열돼 있다.
    5. Publish: python scripts/publish_wordpress.py워드프레스 REST API에 Basic Auth로 드래프트를 올리고, Schema.org BlogPosting + BreadcrumbList + (있다면) FAQPage JSON-LD를 〈script〉 태그로 꽂는다. 기본 상태가 draft인 건 의도적이다. 내가 WP 어드민에서 대표 이미지, 카테고리, Yoast SEO 필드를 한 번 더 확인한 뒤 발행 버튼을 누른다. 이 부분은 WordPress REST API로 Schema.org 자동 주입에서 설명한다.

    실제 brief를 읽는 코드는 이렇다. 이건 장난감 예시가 아니라 내 레포에 지금 돌아가는 코드다.

    src/briefs/brief_reader.py (발췌):

    def read_brief(brief_id: str) -> dict:
        """briefs 탭에서 brief_id 한 줄을 dict로 반환."""
        client = SheetsClient()
        rows = client.get_all_records("briefs")
        for row in rows:
            if row.get("brief_id") == brief_id:
                return row
        raise ValueError(f"brief_id not found: {brief_id}")
    

    이 20줄 안에 내 파이프라인의 설계 철학이 다 들어 있다. 단일 책임, 명시적 에러, 외부 상태 주입(SheetsClient를 함수 안에서 생성하지만 단순하게 유지). 구글 시트get_all_records() 한 줄이 탭 전체를 dict 리스트로 돌려주기 때문에 나는 컬럼 타입 캐스팅에 신경 쓸 필요가 없었다.

    이 파이프라인의 전체 구조를 5단계로 왜 쪼갰는지는 ACE 아키텍처를 5단계로 쪼갠 이유에서 별도로 다룬다. 각 단계가 왜 독립적이어야 하는지, 어느 단계를 덜 자동화하기로 했는지의 trade-off가 핵심이다. 특히 Publish 단계를 일부러 draft로 멈추는 결정은 자동화 매니아에게는 반직관적일 수 있다. 하지만 “매일 한 편”을 실제로 6개월 돌려보면 저 수동 확인 1분이 무엇보다 값지다는 걸 알게 된다.

    한 가지 더 설명할 가치가 있는 건, 각 단계 사이의 “상태 전이” 규칙이다. 한 brief의 status 필드는 ready → in_progress → generated → published → failed 순서로만 움직일 수 있다. 역방향은 금지다. 이 단방향 상태 머신이 있어서 나는 매일 아침 python scripts/list_briefs.py --status=ready를 돌려 “오늘 내가 처리할 것”을 한눈에 볼 수 있고, --status=failed로 “내가 돌아와서 고쳐야 할 것”을 따로 본다. 상태를 Google Sheets에 두는 것의 부수 효과다. 눈으로 볼 수 있는 DB는 눈으로 확인하는 작업 흐름을 가능하게 한다. 이건 숫자로 측정 안 되는 종류의 생산성 이득이지만, 매일 2시간이 2.5시간이 되면 한 달에 15시간 차이가 난다.

    내가 틀렸던 지점

    나는 처음에 Notion API로 콘텐츠 DB를 만들려고 했다. Notion은 화면이 예쁘고, 내가 이미 매일 쓰고 있었고, 무료 플랜으로 충분해 보였다. 3일간 나는 properties 스키마 캐스팅과 씨름했다. 같은 “제목” 필드가 title, rich_text, select 중 어느 타입인지에 따라 API 응답 구조가 미묘하게 달라졌고, rolluprelation 필드는 페이지네이션까지 직접 처리해야 했다. 내 코드의 절반이 Notion 응답을 파싱하는 데 쓰였다. 정작 “brief 한 줄 읽기”라는 본래 목적은 한 발짝도 진도가 안 나갔다.

    4일째 새벽, 나는 포기하고 구글 시트로 갈아탔다. gspread 라이브러리로 30줄 만에 동일 기능이 동작했다. get_all_records() 한 줄이면 탭 전체를 파이썬 dict의 리스트로 돌려줬다. 한글 컬럼도 문제없었고, 내가 브라우저에서 직접 편집해도 스크립트가 즉시 그 변화를 읽었다. 내가 틀린 건 “예쁜 UI = 좋은 DB”라는 가정이었다. 비개발자 1인 프로젝트에서 DB는 내가 브라우저로 편집할 수 있고, 스크립트로 30줄 안에 읽을 수 있으면 그걸로 충분하다. 구글 시트가 그 두 가지를 다 만족했고, Notion은 두 번째를 만족하지 못했다.

    그때 버린 Notion 어댑터 코드는 약 180줄이었다. 지금은 같은 역할을 src/utils/sheets_client.py가 90줄로 처리한다. 반으로 줄었다. 그리고 더 중요하게는, 내가 그 90줄을 전부 이해하고 있다. Notion 어댑터 180줄은 내가 이해하지 못한 채 “돌아가기만 하는” 코드였다. 이해하지 못하는 코드는 한 달 뒤 버그가 나면 손을 댈 수 없다. 이 경험이 그 뒤의 모든 기술 선택에 그림자를 드리웠다. “예쁜 것”보다 “내가 이해할 수 있는 것”을 고르는 원칙이 이때 세워졌다.

    돌이켜보면 이 3일의 시행착오는 내 시간으로 계산하면 6시간 정도의 손실이었다. 하지만 교훈의 가치는 그보다 훨씬 크다. 비슷한 함정 — “내가 이미 쓰고 있으니까 익숙하다”는 이유로 부적합한 도구를 고르는 것 — 이 그 뒤 최소 두 번 더 나를 노렸는데, 매번 Notion 사건이 떠올라서 한 발짝 물러섰다. 한 번은 데이터 시각화를 Observable로 하려다가 단순한 matplotlib PNG로 바꿨고, 한 번은 프롬프트 버전 관리를 Git submodule로 하려다가 그냥 config/prompts/ 폴더에 평문으로 두기로 했다. 둘 다 “내가 이해하는 쪽”이 이겼다.

    따라해보고 싶다면

    비슷한 상황 — 도메인 권위 0, 예산 월 50달러 미만, 코딩 경험 적음 — 이라면 이 순서를 추천한다.

    1. 도구 세팅: Cursor Pro 월 20달러 + Claude Pro 월 20달러 + 도메인 연 2달러(.xyz) + 워드프레스 자체 호스팅(VPS 월 6달러). 초기 월 고정비 48달러 선에서 시작할 수 있다. Discovery에 Keywords Everywhere를 붙이면 10만 크레딧당 10달러가 더 든다. 풀 세팅이어도 월 60달러를 잘 안 넘긴다.
    2. 노트 먼저: 종이에 파이프라인 단계를 손으로 그려라. 코드 한 줄 쓰기 전에 “무엇이 입력이고 무엇이 출력인가”를 명확히 해야 LLM에게 시킬 작업 단위가 쪼개진다. 이 노트가 없으면 Claude Code 세션이 20분 만에 옆길로 샌다.
    3. 레포 초기화: git init + 빈 CLAUDE.md + 빈 main.py. 그리고 Claude Code에게 “이 프로젝트의 CLAUDE.md를 브레인스토밍으로 함께 써보자”라고 시켜라. 이 파일이 LLM의 영구 기억이 된다. 내 CLAUDE.md는 지금 150줄이 넘고, 프로젝트 목적, 모델 라우팅 규칙, 가능한 스크립트 목록, 콘텐츠 타입 정의, Google Sheets 탭 스키마가 전부 여기에 적혀 있다.
    4. 한 커밋에 한 단계: 내 레포의 초기 10개 커밋을 보면 각 커밋이 정확히 하나의 파이프라인 단계에 매핑돼 있다. 2dac25d feat(sheets), 924e8cf feat(briefs), 30bde34 feat(discovery), 이런 식이다. 이 원칙은 나중에 디버깅할 때 구원투수가 된다.
    5. 이미지는 마지막에: Nano Banana 2 한 장이 약 0.039달러, 글당 1장이면 한 달 30편 기준 약 1.17달러다. 저렴하지만 글의 완성도가 먼저다. 이미지는 Quality Gate를 통과한 뒤에 붙여라. 반대로 하면 버려지는 이미지가 생긴다.
    6. 매일 밤 handoff.md: 세션 종료 5분 전에 오늘 한 일, 막힌 곳, 내일 할 일을 3~5줄로 적어라. 다음날 이 파일 하나로 컨텍스트가 복원된다. LLM 세션이 “처음부터 다시 설명하는” 비용을 절반으로 줄여준다.

    예상 소요 시간은 내 기준 3주 매일 2시간, 총 40~45시간이었다. Anthropic 공식 문서의 Sub-agents 섹션은 최소 3번 정독을 추천한다. 내가 가장 많이 막힌 곳이 거기였다. 서브에이전트의 모델 필드, 시스템 프롬프트, 허용 도구 범위를 공식 문서 그대로 쓰지 않고 내 마음대로 뜯어고쳤다가 반나절을 날렸다.

    난이도에 대해 정직하게 말하면, 나는 이 여정이 “쉬웠다”고 말할 생각은 없다. 어려운 순간이 있었다. 특히 서브에이전트 위임 경계를 처음 이해할 때, 그리고 WordPress REST API의 인증 헤더가 왜 실패하는지 디버깅할 때. 하지만 어려운 것과 불가능한 것은 다르다. 비개발자에게 이 프로젝트는 어렵지만 가능하다. 필요한 건 매일 2시간의 집중과, 모르는 것을 모른다고 말할 용기와, 자기가 이해하지 못한 코드는 커밋하지 않겠다는 고집이다. 이 세 가지가 있으면 LLM이 나머지를 메워준다.

    FAQ

    Q1. 개발 경험이 정말 없어도 ACE를 복제할 수 있나요?

    파이썬 문법을 전혀 모르면 어렵다. 하지만 엑셀 VBA나 구글 앱스 스크립트를 써본 적이 있다면 가능하다. 나는 Claude Code에게 “이 에러 메시지 무슨 뜻이야?”를 하루에 20번씩 물었다. 모르는 걸 모른다고 말할 수 있으면 된다. 진짜 어려웠던 건 문법이 아니라, 내가 만들고 싶은 것을 정확한 언어로 설명하는 연습이었다. 그건 코딩보다는 기획에 가까웠다.

    Q2. 월 40달러로 정말 돌아가나요?

    고정비 기준 그렇다. Cursor Pro 20달러, Claude Pro 20달러다. 변동비는 이미지 생성에 Nano Banana 2 0.039달러/장, 배포할 WordPress 호스팅이 월 6달러 내외. Discovery 단계에서 Keywords Everywhere를 붙이면 10달러/10만 크레딧이 더 든다. 풀 세팅이어도 월 60달러를 잘 안 넘긴다. 비교하자면 Surfer SEO는 월 89달러부터, Jasper는 월 49달러부터, Copy.ai는 월 49달러부터다. 내가 세 도구를 다 구독하면 월 187달러인데, ACE는 그 3분의 1 미만이면서 출력물을 100% 내가 통제한다.

    Q3. Impact Marketplace에 거절당했는데 어떻게 어필리에이트 수익을 내나요?

    이 시리즈의 목표는 “거절당한 상태에서도 승인될 수 있는 도메인 권위를 만드는 것”이다. Foundation 6편은 어필리에이트 링크가 0이다. 대신 EEAT 신호(1인칭, 실제 커밋, 구체적 실패 경험)로 도메인의 신뢰 자본을 먼저 쌓는다. 3~6개월 뒤 다시 Impact에 지원할 때의 승률이 이 6편의 존재 여부로 바뀐다는 가설이다. 동시에 Foundation 6편이 발행되는 동안 나는 상대적으로 심사가 느슨한 중소 네트워크(CJ Affiliate, 파트너스 프로그램 직접 신청)로 먼저 수익 씨앗을 심는다.

    Q4. 왜 하필 Claude Code인가요? ChatGPT나 Copilot은 안 되나요?

    되긴 된다. 내가 Claude Code를 고른 이유는 세 가지다. (1) 서브에이전트 모델 라우팅(Opus 조율 + Sonnet 장문)으로 토큰 비용을 의도적으로 쪼갤 수 있다. (2) CLAUDE.md 영구 메모리로 프로젝트 컨텍스트가 세션 간 유지된다. (3) bash 실행 권한이 있어서 python scripts/check_quality.py까지 한 세션 안에서 끝난다. 다른 도구로도 재현 가능하지만 내 조합 기준 Claude Code가 가장 매끄러웠다. 결정적인 건 (2)번이었다. 매일 밤 내가 돌아왔을 때 LLM이 어제의 나를 기억하고 있다는 감각은 다른 도구에서 쉽게 얻지 못했다.

    Q5. 이 글의 다음은 뭔가요?

    다음 글 ACE 아키텍처를 5단계로 쪼갠 이유에서 5단계 파이프라인의 trade-off를 하나씩 뜯는다. 왜 생성과 검수를 분리했는지, 왜 Publish를 기본 draft로 두는지, 왜 Discovery를 아예 다른 스크립트 번들로 뺐는지 — 의사결정의 이유가 전부 거기에 있다. 그 뒤 구글 시트를 콘텐츠 DB로 쓴 이유, Claude Code 서브에이전트로 블로그 위임, Quality Gate 5단계로 쓰레기 포스트 막기, WordPress REST API로 Schema.org 자동 주입 순서로 이어진다.

    [NEWSLETTER_SIGNUP]

    관련 글

  • Hello world!

    Welcome to WordPress. This is your first post. Edit or delete it, then start writing!