6개의 LLM cron job을 하나의 Python 스크립트로 — 모델 라우팅을 넘어 $0 파이프라인으로
6개 cron ons-running-unnoticed/ LLM LM LLM cron job이 매일 돌고 있었다. 뉴스 수집, 인사이트 추출, 시리즈 트리거, 기획, 트렌드 평가 — 각각 opencode run으로 subagent를 띄워 처리했다. 호출할 때마다 수백~수천 토큰이 소비되고, 그 비용은 하루하루 쌓였다.
6월 28일, 나는 모델 라우팅을 성능 최적화에서 비용 최적화로 전환했다. implementer를 kimi-k2.7-code($0.012/콜)에서 deepseek-v4-flash($0.00038/콜)로 내리고, wiki-compile도 pro에서 flash로 내렸다. 주간 비용이 $30 → $10로, 65% 감소. 이 이야기는 지난 BUILD LOG에서 다뤘다.
그런데 그때는 LLM 호출 비용만 최적화한 거였다. cron job 자체를 줄일 생각은 못 했다. 모델을 바꾸는 것보다 더 확실한 절감 방법이 있는데도.
LLM 호출 자체를 없애면 어떨까
7월 1일. cron 테이블을 다시 들여다봤다. 콘텐츠 파이프라인 쪽에 6개의 job이 각각 opencode run으로 LLM을 호출하고 있었다:
content-manager-periodic: 2시간마다 opencode run (deepseek-v4-pro, $0.0035/콜) content-news-trigger: 3시간마다 opencode run content-insight-trigger: 3시간마다 opencode run content-series-trigger: 3시간마다 opencode run content-planner: 4시간마다 opencode run trend-evaluate-trigger: 2분마다 opencode run (가장 비싼 놈)
이 job들이 하는 일은 본질적으로 동일했다. P2-hippocampus와 @memory/growth의 파일들을 읽고, heuristic scoring으로 후보를 거른 뒤, 통과한 후보만 kanban에 INSERT. LLM이 필요한 건 마지막 단계인 draft 작성뿐이었다.
그럼 수집·스코어링·선별까지는 Python으로 하고, LLM은 draft 작성에만 쓰면 되는 거 아닌가?
content_curator.py — 6개 job을 1개로 병합
7월 1일 아침, scripts/content_curator.py를 만들었다. 하는 일은 단순하다:
P2-hippocampus의 memories/와 @memory/growth/trend-harvester/를 스캔 Heuristic scoring으로 후보 평가 (LLM 없음 — deterministic) 통과한 후보만 kanban.db에 INSERT (category, score, source 포함) content-editor는 하루 2회(12:00, 20:00)만 opencode run으로 draft 작성
Python 스크립트 1개가 LLM cron job 6개를 대체했다. 비용: $0. 하루 14번의 불필요한 LLM 호출이 사라졌다.
같이 없앤 것들
정리한 건 여섯 개 job뿐만이 아니었다. n8n 시절부터 남아 있던 좀비 job들도 한꺼번에 정리했다:
content-taste-diff: 매일 05:00에 taste diff 분석하던 job. content-curator가 이미 커버 → 제거 taste-review-trigger: n8n에서 넘어온 워크플로우. 이미 비활성 상태였지만 잔재로 남아 있던 것 → 명시적 비활성화 trend-evaluate-legacy: 2분마다 n8n 트리거. n8n 제거 후 zombie → 비활성화 kanban-dispatcher: Hermes 제거 후 필요 없어진 cron_runner.py 연동 → 비활성화
그리고 하나 더. content-editor-periodic의 주기를 2시간 → 하루 2회(12:00, 20:00)로 줄였다. 2시간마다 draft QA를 돌릴 필요가 전혀 없었다. draft가 쌓이는 속도보다 QA가 자주 도는 게 문제였으니까.
추가한 것: content-weekly
대신 하나를 더했다. content-weekly — 주간 요약 digest를 매일 10:00에 생성하는 job이다. 6개를 없애고 1개를 더한 셈. 일주일치 콘텐츠를 한눈에 정리해주는 역할인데, 이건 LLM이 필요한 작업이라 opencode run으로 처리한다. 필요한 곳에만 LLM을 쓰는 게 원칙이다.
수치로 보면
항목6/28 이전 (성능 최적화)7/1 (비용 최적화)절감
콘텐츠 cron job 수6개 (LLM)1개 (Python)-83% 하루 LLM 호출 (콘텐츠)~14회2회-86% implementer 모델kimi-k2.7-code ($0.012)deepseek-v4-flash ($0.00038)-97% wiki-compile 모델deepseek-v4-pro ($0.0035)deepseek-v4-flash ($0.00038)-89% 주간 예상 비용$30.07/2일$10.46/2일-65% content-curator없음 (6개 분산)1개 통합 ($0)100%
이게 무슨 의미냐면
LLM은 비싼 칼이다. 모든 요리에 쓸 필요는 없다.
6월 28일의 1차 최적화는 “비싼 칼을 적당한 가격의 칼로 바꾸기”였다. 모델 라우팅을 Flash → Pro → Max 3계층으로 나누고, 실행 작업은 전부 Flash로 내린 것. 이건 칼의 가격을 최적화한 거다.
7월 1일의 2차 최적화는 “칼이 필요 없는 요리는 칼을 쓰지 않기”였다. 파일 스캔, heuristic scoring, kanban INSERT — 이 작업들은 LLM 없이도 충분히 할 수 있다. Python 200줄짜리 스크립트면 된다. 굳이 $0.00038조차 쓸 이유가 없는 거다.
이게 진짜 비용 최적화다. 모델을 바꾸는 건 1차원적 절감이고, LLM 호출 자체를 없애는 건 다른 차원의 절감이다. 그리고 두 가지를 함께 했을 때 비로소 65%가 나왔다.
남은 LLM 호출은 어디에?
다 없앤 건 아니다. 진짜 LLM이 필요한 작업은 그대로 둔다:
reviewer, reviewer-critical: 코드 품질 검증. deepseek-v4-pro / qwen3.7-max 유지 planner: 복잡한 아키텍처 계획. qwen3.7-max 유지 content-editor: draft QA와 publish. 하루 2회, deepseek-v4-flash content-weekly: 주간 digest 생성. 하루 1회, deepseek-v4-flash
규칙은 단순하다: 계획·검증은 고급 모델, 실행은 저급 모델, 결정적 작업은 Python. 이걸 jobs.json에 model_override로 명시해두면, 어떤 cron job이 어떤 모델을 쓸지 중앙에서 통제할 수 있다.
교훈
AI 에이전트 시스템을 운영하는 사람이라면 알 거다. cron job이 하나둘 늘어나고, 어느새 하루에 수십 번 LLM을 호출하게 된다는 걸. 편해서 추가한 job이 비용 폭탄이 되는 건 순식간이다.
내가 얻은 교훈은 세 가지다:
모델을 바꾸기 전에, LLM 호출 자체가 필요한지 먼저 물어라. 파일 스캔, 텍스트 비교, 점수 계산은 Python이면 충분하다. 비슷한 job은 통합하라. 6개의 LLM cron job이 본질적으로 같은 일(수집→평가→삽입)을 하고 있었다면, 하나의 Python 스크립트로 합쳐라. 주기를 재검토하라. 2시간마다 QA를 돌리던 걸 하루 2회로 줄여도 전혀 문제가 없었다. “자주 = 좋다”는 착각이다.
Drewgent의 콘텐츠 파이프라인은 이제 하루 2회의 LLM 호출 + 1개의 $0 Python 스크립트로 돌아간다. 6개의 job이 하루 14번씩 opencode run을 부르던 시절에 비하면 완전히 다른 시스템이다.
다음 목표는 뭘까. 아마 콘텐츠 파이프라인에서 LLM을 아예 제거하는 거겠지. heuristic score가 일정 수준 이상으로 올라가면, 사람이 볼 때 “이건 publish해도 되겠다” 싶은 draft를 Python이 직접 생성할 수 있을 테니까. 그건 아마 Episode 3의 이야기가 될 거다.
읽어줘서 고맙다.