비용을 0원에 가깝게 맞추기로 한 이상 종량제 API는 선택지가 아니었다. 이미 내고 있는 구독을 쓰기로 하니 AI를 어떻게 붙일지가 거의 저절로 정해졌다 — 어드민이 API를 호출하는 대신, 로그인된 claude 명령을 자식 프로세스로 실행한다. 이 결정 하나가 실행 위치, 작업 단위, 실패 처리 방식까지 끌고 왔다. 이 편은 그 연쇄와, 붙이면서 세 번 데인 곳을 적는다.
1. 구독을 쓰기로 하니 방식이 정해졌다
1.1 API 키를 쓰지 않기로 했다
1편에서 정한 원칙이 "고정비 0원"이었다. AI를 API로 부르면 글을 쓸 때마다 토큰만큼 돈이 나간다. 글이 늘수록 비용도 늘고, 그건 내가 통제할 수 없는 비용이다.
그런데 개발에 쓰려고 이미 구독을 내고 있었다. 같은 모델을, 이미 낸 돈으로, 한도 안에서 쓸 수 있다.
| API 키 | 이미 내는 구독 | |
|---|---|---|
| 과금 | 쓴 토큰만큼 | 추가 없음 |
| 부르는 법 | HTTP 요청 | 로그인된 명령 실행 |
| 한도 | 사실상 없음 | 주간 사용량 있음 |
| 돌 수 있는 곳 | 어디든 | 로그인된 그 기계 |
마지막 줄이 중요했다. 구독 세션은 로그인해 둔 기계에 묶여 있다. 클라우드 함수에 올려서 부를 수 있는 게 아니다.
1.2 그래서 AI가 맥북 안에서 돈다
3편에서 어드민을 집 맥북에 두기로 했으니 자리는 이미 있었다. 어드민과 AI가 같은 기계 안에 있게 된다.
덕분에 얻은 게 하나 있다. AI가 쓴 결과를 DB에 넣을 때 네트워크를 타지 않는다. 같은 프로세스가 SQLite 파일을 그냥 연다.
반대로 잃은 것도 분명하다. 맥북이 꺼져 있으면 AI도 없다. 3편 마지막에 적은 약점 그대로다.
2. 어디에 끼워 넣었나
2.1 상태가 바뀌면 AI가 깨어난다
버튼이 AI를 직접 부르지 않는다. 버튼은 글의 상태를 바꾸기만 하고, 저장 직후 도는 훅이 그 변화를 보고 AI를 띄운다.
flowchart LR
btn["어드민 버튼"] -->|상태 변경| db[("저장")]
db --> hook["저장 후 훅"]
hook -->|명령 실행| cli["claude CLI"]
cli -->|결과| db2[("다시 저장")]
버튼에서 바로 부르지 않은 이유는 저장을 붙잡지 않기 위해서다. AI 한 단계가 15초에서 60초 걸리는데, 그동안 저장 요청을 열어 두면 화면이 멈춘 것처럼 보인다. 그래서 훅은 AI를 띄워 놓고 기다리지 않는다. 결과가 나오면 그때 따로 저장한다.
상태를 거쳐 가게 한 덕에 따라온 것도 있다. 실패가 상태로 남는다. 초안이 실패하면 글은 "초안 요청됨"에 그대로 멈춰 있고, 실행 기록에 이유가 남는다. 조용히 사라지지 않는다.
2.2 AI가 하는 일은 넷이다
| 단계 | 언제 도는가 | 결과 |
|---|---|---|
| 주제 추천 | 대시보드에서 누를 때 | 후보 몇 개 |
| 초안 | 추천 주제에서 "초안 생성" | 본문·요약·태그 |
| 번역 | 영문판을 켤 때 | 영문 본문 |
| 검수 | 편집기에서 실행할 때 | 지적 목록 |
넷 다 내가 눌러야 돈다. 처음 설계에는 매일 새벽 도는 자동 실행이 있었는데 지금은 없다. 왜 뺐는지는 다음 편에서 쓴다.
3. 말이 아니라 데이터로 받는다
AI에게 "초안 써줘"라고 하면 글이 온다. 문제는 그게 글일 뿐이라는 것이다. 제목·요약·태그를 따로 꺼내려면 답변을 파싱해야 하고, 모델이 형식을 조금만 달리 쓰면 깨진다.
그래서 요청할 때 원하는 모양을 같이 보낸다. 본문은 여기, 요약은 여기, 태그는 배열로. 결과가 그 모양대로 온다.
그래도 새는 게 있었다. 실제로 겪은 두 가지다.
- 답변 전체를 코드 블록으로 감싸서 보낸다 — 그대로 넣으면 글이 통째로 코드가 된다
- 문장 뒤에 닫는 태그를 흘린다 —
…했다.</markdown>같은 게 본문에 섞인다
둘 다 결과를 글에 넣기 전에 걷어내는 층을 두어 해결했다. 다만 무턱대고 지우면 안 된다. 본문 안에 진짜 코드 블록이 있을 수도 있고, 정상적인 태그로 끝나는 글도 있다. 그래서 여는 짝이 없는 닫는 태그만 지우고, 안쪽에 또 다른 코드 블록이 없을 때만 바깥 껍질을 벗긴다.

4. 세 번 데였다
4.1 제한 시간을 하나로 뒀다
처음엔 모든 단계에 2분을 줬다. 주제 추천은 넉넉했다. 그런데 초안 단계가 통째로 죽었다. 모델이 수천 자를 쓰는 동안 2분은 그냥 지나간다.
시간을 늘려 봤다. 그래도 죽었다. 그날 실행 기록에 세 번이 그대로 남아 있다.
| 시각 | 바꾼 것 | 걸린 시간 | 결과 |
|---|---|---|---|
| 14:10 | — (제한 120초) | 120초 | 시간 초과로 중단 |
| 14:16 | 제한을 600초로 | 600초 | 또 시간 초과 |
| 14:33 | 프롬프트에 분량 한 줄 | 88초 | 성공 |
모델은 준 만큼 쓴다. 10분을 줬더니 10분을 썼다. 고친 건 시간이 아니라 요구였다 — 분량: 공백 포함 2500~3500자 한 줄을 프롬프트에 넣자 그제서야 끝났다.
그렇게 해놓고도 단계마다 쓰는 분량이 다르니 시간은 따로 잡아야 했다.
| 단계 | 제한 시간 |
|---|---|
| 초안 · 번역 | 10분 |
| 검수 | 6분 |
| 주제 추천 | 4분 |
4.2 한꺼번에 눌렀다
추천 주제 네 개에 "초안 생성"을 연달아 눌렀더니 네 개가 다 실패했다. 같은 구독을 향해 명령 네 개가 동시에 뜬 탓이다.
지금은 한 번에 하나씩 줄을 세운다. 뒤에 눌린 것은 앞이 끝날 때까지 기다린다.
4.3 작업 폴더를 잘못 잡았다
이게 제일 알아채기 어려웠다. 추천 주제를 받아 봤는데 이 CMS의 최근 커밋 이야기를 들고 왔다. 블로그 주제를 달라고 했는데 내 프로젝트 근황을 요약해 온 것이다.
명령을 프로젝트 폴더 안에서 실행한 게 원인이었다. 그 폴더에 쌓인 프로젝트별 기억이 프롬프트에 딸려 들어갔다. 아무 프로젝트도 아닌 폴더에서 돌리자 사라졌다.
같은 프롬프트를 글자 하나 바꾸지 않고 두 번 돌려 봤다. 달라진 것은 명령을 실행한 폴더뿐이다.
| 프로젝트 폴더 안에서 | 아무 프로젝트도 아닌 폴더에서 | |
|---|---|---|
| 1 | SQLite로 CMS를 운영할 때 동시 쓰기가 무너지는 지점 | PgBouncer 도입기: 동시 쓰기 300 커넥션부터 터지는 PostgreSQL 병목 잡기 |
| 2 | Cloudflare Tunnel + Access로 맥북을 외부에 노출했을 때의 지연과 보안 트레이드오프 | GitHub Actions로 무중단 배포 파이프라인 만들기: 롤백까지 자동화한 기록 |
| 3 | Claude CLI를 서브프로세스로 돌릴 때 타임아웃과 토큰 비용을 설계하는 법 | LLM API 타임아웃률을 낮춘 방법: 큐잉·재시도·백오프 설계와 실측 개선률 |
| 4 | AI 초안과 사람 수정본의 diff로 프롬프트 품질 측정하기 | Nginx rate limiting으로 로그인 무차별 대입 공격 막기 |
| 5 | 정적 사이트 + 로컬 어드민 조합에서 최소한으로 필요한 보안 | 1인 개발로 블로그 인프라 처음부터 구축하기: 선택과 시행착오 기록 |
| 딸려 들어간 토큰 | 38,294 | 6,323 |
왼쪽은 블로그 주제가 아니라 내 프로젝트 근황이다. 굵게 칠한 부분이 전부 이 저장소 안에만 있는 이야기다.
5. AI에게 손을 주지 않았다
마지막은 고장이 아니라 의도한 제약이다.
AI를 부를 때 도구를 아무것도 주지 않았다. 파일을 읽지도, 명령을 실행하지도, 화면을 캡처하지도 못한다. 프롬프트에 담긴 것만 보고 답한다.
무인으로 도는 과정이 내 디스크를 뒤지게 두고 싶지 않았고, 초안을 쓰는 데 파일 접근이 필요하지도 않았다.
대신 대가가 있다. AI는 자기가 못 하는 일을 만난다. 화면 캡처, 실제 측정, 벤치마크 결과. 도구가 없으니 할 수 없다.
그래서 못 하는 일은 지어내는 대신 비워 두게 했다.
[[작업: 무엇이 필요한지 한 줄]]
이 표시가 남아 있으면 발행이 막힌다. 기계를 쓸 수 있는 쪽이 채워야 한다.
여기까지가 AI를 붙인 이야기다. 그런데 이 파이프라인은 세운 다음에 오히려 줄었다. 매일 도는 자동 실행이 사라졌고, 자동으로 돌던 검수도 손으로 돌리게 바뀌었다.
무엇을 왜 뺐는지는 다음 편에서 쓴다.



