전체 글

AI 에이전트로 블로그를 매일 발행하기 — 파이프라인 8단계와 기계가 막는 관문 3곳

xXfacebook페이스북

매일 아침 8시, 사람이 아무것도 누르지 않아도 블로그 다섯 곳에 글이 한 편씩 올라갑니다. 주제를 정하고 법령 원문을 확인하고 본문을 쓰고 삽화를 그리고 검사를 통과시키고 커밋까지 하는 일을 AI 에이전트가 이어서 합니다. 이 글은 그 파이프라인이 어떻게 여덟 단계로 도는지, 그리고 사람이 지켜보지 않는 동안 규칙이 무너지지 않도록 어디에 기계 관문을 세 곳 세웠는지에 대한 기록입니다.

먼저 밝혀 둘 게 있습니다. 이 사이트(잡동사니 노트)는 세금·법률 블로그와 달리 정확도 게이트가 꺼져 있습니다. 그래서 이 글에는 시행일이나 조문을 강제로 붙이지 않습니다. 대신 note 의 기준은 다른 데 있습니다 — 직접 돌려보지 않은 것을 후기처럼 쓰지 않는다는 것입니다. 그래서 아래 내용의 근거는 전부 이 저장소의 코드와 FLOW.md 에 남은 기록입니다.

블로그 자동 발행 파이프라인의 하루 타임라인. 08시 발행, 09시 브리핑과 데드맨 점검, 14시 티스토리 미러링이 시간 축 위에 표시된 그림

하루의 자동 일정. 발행은 08시 한 번, 나머지는 그 뒤를 따라 붙는다.

하루가 어떻게 도는가

시작 신호는 사람이 아니라 스케줄러입니다. Paperclip 이라는 사내 CMP 의 루틴이 매일 08:00(Asia/Seoul)에 발화하고, 이 루틴이 실행 이슈를 만들어 에이전트에 배정하면 그게 곧 러너를 깨우는 신호가 됩니다. 러너는 발행 스크립트(pipeline/daily.sh)를 돌리고 그 안에서 사이트별로 파이프라인이 순서대로 실행됩니다.

발행 대상은 매일 다섯 사이트 전부입니다. 2026년 8월 17일부터 그렇게 바뀌었습니다. 그전에는 money 한 곳만 매일 돌고 나머지가 한 자리를 요일별로 나눠 썼는데, 그러다 보니 정치 사이트 두 곳이 몇 주가 지나도록 글 한 편에 머물렀습니다. 지금은 순서만 남았습니다 — money 가 항상 맨 앞입니다. 뒤쪽이 타임아웃으로 잘리는 날에도 살아남는 쪽이 money 여야 하기 때문입니다. 발행 자체가 끝나면 브리핑과 데드맨 점검, 티스토리 미러링 같은 후속 작업이 뒤를 따릅니다.

여덟 단계 — planner에서 distributor까지

파이프라인은 여덟 단계지만, 에이전트 정의 파일은 일곱 개입니다. .claude/agents/ 에 planner·researcher·writer·illustrator·editor·publisher·distributor 일곱이 들어 있고, 윤문(humanize)은 에이전트가 아니라 도구 단계이기 때문입니다. 그래서 "에이전트가 여덟 개"라고 쓰면 틀립니다. 순서는 이렇습니다.

  • planner — 뉴스를 훑어 주제를 확정합니다. 쓸 만한 주제가 없으면 그날은 여기서 정상 종료하고 알림만 보냅니다.
  • researcher — 1차 출처로 사실을 확인합니다. 핵심 수치를 확인하지 못하면 발행을 거르고 종료합니다.
  • writer — 리서치 메모에 없는 사실은 쓰지 못합니다. 기억에서 세율을 꺼내는 걸 막는 규칙입니다.
  • illustrator — 숫자·조건이 들어가는 삽화는 SVG 로 직접 그리고, 정보를 담지 않는 히어로 사진만 생성 이미지를 씁니다.
  • humanize — 윤문 전에 수치를 스냅샷으로 잠그고(fact-lock snapshot), 윤문한 다음 다시 대조합니다(verify).
  • editor — 판정만 합니다. 본문을 직접 고치지 않고, 같은 항목을 세 번 반려하면 중단합니다.
  • publisher — draft 를 해제하고 빌드한 뒤 커밋합니다.
  • distributor — 카드뉴스·스레드 같은 파생물을 만듭니다. 여기서 실패해도 원본 글은 살아남습니다.

planner에서 distributor까지 여덟 단계가 화살표로 이어지고, 사실 확인 실패나 주제 없음일 때 옆으로 빠지는 거름 분기가 표시된 흐름도

여덟 단계 흐름. 왼쪽으로 흐르다 확인이 안 되면 아래 거름 분기로 빠진다.

규칙을 프롬프트에 두지 않은 이유

에이전트에게 "1200자를 넘겨라", "h2 를 세 개 이상 써라" 하고 프롬프트로 부탁할 수도 있습니다. 하지만 프롬프트는 지켜지지 않을 때 조용합니다. 그래서 본질적인 규칙은 종료 코드를 뱉는 스크립트에 박아뒀습니다. 관문은 세 종류입니다.

첫째는 fact-lock 입니다. 윤문 도구가 문장을 다듬는 과정에서 숫자 하나가 틀어질 수 있으니, 윤문 전후의 숫자·조문·날짜·링크를 집합으로 대조해 0 또는 1의 종료 코드로 답합니다. 다르면 윤문본을 버립니다. 둘째는 check-content(post:check)로, 구조와 정확도 규칙을 검사합니다. 셋째는 push 이후 CI 가 다시 check-content 와 테스트, 빌드를 돌립니다.

여기서 눈여겨볼 점은 check-content 를 세 번 돌린다는 것입니다. editor 앞에서, push 앞에서, 그리고 CI 에서. FLOW.md 에는 그 이유가 한 줄로 적혀 있습니다 — "같은 명령을 세 번 돌린다. 기준이 두 벌이면 반드시 어긋나기 때문이다." 검사 로직을 각 단계에 새로 짜 넣으면 서로 미세하게 달라지고 그 틈으로 문제가 샙니다. 그래서 판정 기준을 pipeline/check-content.ts 한 곳에 두고 모두 그것만 부릅니다.

이 사이트에서 실제로 걸리는 건 구조 게이트뿐입니다. check-content 코드는 if (!editorial?.disclaimer) return issues; 한 줄로 정확도 검사를 건너뛰는데, note 의 config 에는 disclaimer 키가 없어서 시행일·조문·고지 검사가 켜지지 않습니다. 남는 규칙은 h2 38개, 본문 1200자(공백·코드·이미지 제외), 본문 이미지 36장, h1 금지 같은 뼈대뿐입니다. 오류가 한 건이라도 있으면 명령이 exit 1 로 떨어져 push 가 막힙니다.

관문 배치도. fact-lock은 윤문 뒤, check-content는 editor 앞과 push 앞 두 번, CI는 push 이후에 놓이고 실패하면 이전 단계로 되돌아가는 화살표가 그려진 그림

관문 세 곳의 위치. 어디서 걸리든 화살표는 앞 단계로 되돌아간다.

원본과 파생물의 실패를 다르게 다룬다

실패는 다 같은 실패가 아닙니다. 이 파이프라인의 핵심 원칙은 하나입니다 — 블로그 발행이 실패하면 나머지는 전부 취소되고, 소셜이 실패해도 블로그는 남는다. 원본 글은 반드시 지키고 그 위에 얹는 파생물은 실패해도 그 채널만 조용히 건너뜁니다.

FLOW.md 의 실패 지점별 표를 옮기면 이렇습니다. 주제가 없거나 사실 확인이 안 되면 정상 종료하고 그날은 글이 없습니다. editor 가 같은 항목을 세 번 반려하면 중단하고 커밋을 남기지 않습니다. post:check 가 실패하면 push 하지 않되 커밋은 남겨 사람이 볼 수 있게 합니다. push 자체가 실패하면 중단하고 알림을 보냅니다. 반면 카드·스레드·티스토리 같은 파생 채널이 실패하면 원본은 그대로 두고 그 채널만 빠집니다.

실패 지점별 결과 매트릭스. 주제 없음, editor 3회 반려, post:check 실패, push 실패, 소셜 실패 각각에 대해 무엇이 멈추고 무엇이 남는지 표로 정리한 그림

실패 지점마다 멈추는 범위가 다르다. 아래로 갈수록 원본에서 멀어진다.

무인 실행이 거짓말하지 않게 만든 것들

사람이 안 볼 때 가장 무서운 건 실패가 아니라 성공한 척하는 실패입니다. 그래서 몇 가지 장치를 덧댔습니다.

첫째, 완료 계약입니다. 파이프라인은 출력 마지막 줄에 PIPELINE_RESULT 발행|거름|중단 을 찍어야 하고, pipeline/verdict.ts 가 이 줄을 읽습니다. 이 줄이 없으면 결과를 "미완료"로 떨어뜨립니다. 모르면 정상으로 보는 쪽이 아니라 모르면 이상으로 보는 쪽에 뒀습니다. 계기가 있었습니다 — 2026년 8월 12일, politics-right 를 돌리던 중 researcher 가 사실을 확인하는 도중에 오케스트레이터가 "완료 알림을 기다립니다"로 턴을 끝내 버렸는데, claude 프로세스는 exit 0 으로 끝나 러너가 "주제 없음"으로 잘못 알렸습니다. 종료 코드만 믿으면 이렇게 속습니다.

둘째, 러너 안전장치입니다. 발행 스크립트는 .runner.lock 을 mkdir 로 원자적으로 잡아, 이미 돌고 있으면 물러납니다. 그리고 하루 기동 횟수를 BLOG_MAX_RUNS_PER_DAY(기본 3)로 못 박습니다. 이건 실제 사고에서 나온 값입니다. 2026년 8월 7일, track open 이 러너 자신을 이슈 담당자로 박는 바람에 러너가 자기를 다시 부르는 신호가 됐고 그렇게 자기를 14,221번 다시 불렀습니다. 잠금만으로는 겹치지 않고 하나씩 빠르게 이어지는 재기동을 못 막아서, 원인과 무관하게 횟수 자체를 막았습니다.

여기에 하나 더 있습니다. 스레드 발행을 에이전트에게 프롬프트로 시켰더니 34편 동안 0편이 나갔습니다. 그래서 지금은 스레드를 에이전트가 아니라 러너가 push 뒤에 기계적으로 올립니다. "알아서 잘 해줘"가 통하지 않는 일은 코드로 내려버린 셈입니다.

남은 것과 사람이 하는 일

자동화하지 않은 자리도 분명히 있습니다. 카카오 로그인은 2단계 인증이 걸려 사람이 한 번 해줘야 하고, 스레드 토큰 발급도 마찬가지입니다. 무엇보다 정정 판단은 사람이 합니다. post:stale 이라는 명령이 인용한 법령이 그 뒤에 개정된 글을 찾아 주지만, 그걸 보고 고칠지 말지는 자동으로 결정하지 않습니다. push 와 배포도 기본은 커밋까지이고, 그 너머는 사람이 지시할 때만 진행합니다.

정리하면 이렇습니다. 매일 여덟 단계가 무인으로 돌지만, 규칙은 프롬프트가 아니라 종료 코드에 박혀 있습니다. 관문 세 곳이 같은 기준으로 세 번 판정하고 실패의 무게에 따라 멈추는 범위가 다릅니다. 그리고 무인 실행이 거짓말하지 못하도록 완료 계약과 기동 상한을 걸어 뒀습니다 — 사고가 한 번씩 남긴 값들입니다.

이 글 공유하기xXfacebook페이스북