AI 에이전트 프롬프트 지시 누락, 코드 가드로 막은 네 번의 사례
AI 에이전트로 블로그를 매일 자동 발행하면서 배운 것 중 가장 비싼 교훈은 이겁니다. 프롬프트에 "이렇게 하라"고 적어두는 것과, 그게 실제로 매번 실행되는 것은 전혀 다른 일입니다. 저는 스레드 자동 발행을 프롬프트에 맡겨뒀는데, 글이 34편 나가는 동안 실제로 스레드에 올라간 건 0편이었습니다.
이건 프롬프트를 잘못 써서가 아닙니다. 문구는 멀쩡히 거기 적혀 있었습니다. 문제는 "적어뒀다"와 "실제로 됐다" 사이에 아무런 확인 장치가 없었다는 것이고, 그 틈은 같은 저장소에서 네 번 반복됐습니다. 이 글은 그 네 자리를 하나씩 되짚고, 각각을 코드 가드로 옮긴 방법을 정리한 기록입니다. 근거는 전부 이 저장소의 git 히스토리와 현재 코드입니다.
프롬프트로 적은 지시가 왜 새는가
에이전트는 매 실행마다 컨텍스트를 처음부터 새로 읽습니다. 사람이라면 "지난번에 이거 빠뜨렸으니 이번엔 챙겨야지" 하고 기억하지만, 매 실행이 백지에서 시작하는 에이전트에게는 그런 누적이 없습니다. 프롬프트에 "발행할 때 static/media 도 함께 커밋하라"고 적어두면, 실행할 때마다 그 지시를 읽기는 합니다. 그러나 읽는 것과 빠짐없이 수행하는 것은 확률의 문제이고, 여러 단계가 얽힌 긴 작업에서는 그 확률이 100%가 아닙니다.
더 근본적인 문제는 결과를 검증하는 장치가 없으면 "지시"와 "실행 결과"가 완전히 분리된다는 점입니다. 프롬프트에 지시를 아무리 정교하게 써도, 실행 후에 "정말 그렇게 됐는지" 확인하는 코드가 없으면 누락은 조용히 지나갑니다. 빌드도 배포도 멀쩡히 끝나기 때문에 티가 나지 않습니다. 이 저장소의 스레드 발행 누락이 34편 동안 아무도 모르게 이어진 것이 정확히 이 구조 때문이었습니다. 그래서 해법은 프롬프트를 더 강하게 쓰는 게 아니라, 기계적으로 검증 가능한 동작을 아예 코드 쪽으로 옮기는 것이었습니다.
네 번 겪은 사례
같은 저장소에서 "프롬프트에 적었지만 실행에서 샜다"가 네 번 반복됐습니다. 이 저장소의 배포 에이전트 정의 파일에는 아예 이렇게 적혀 있습니다 — "이 저장소는 같은 일을 세 번 겪었다(static/media, cards, 큐). 그래서 기계로 옮겼다." 여기에 스레드 발행까지 더하면 네 번입니다. 다만 이 넷이 전부 같은 원인은 아닙니다. 카드뉴스만은 프롬프트 누락이 아니라 결이 다른데, 이 구분이 중요해서 표 아래에 따로 적었습니다.
| 사례 | 프롬프트에 적혀 있던 것 | 실제로 벌어진 일 | 코드로 옮긴 방법 |
|---|---|---|---|
| 정적 자산 (static/media) | "발행 시 static/media 도 add 하라" | 2026-08-04 실행에서 누락 — 티스토리 미러 글 이미지가 깨질 위험 | daily.sh 가 발행 커밋이 있을 때 러너가 직접 add·commit (88fdf65) |
| 카드뉴스 (cards) | (프롬프트 누락 아님) | 새 글에 cards 블록이 안 생김 | 뼈대 생성 템플릿에 cover·point·cta 슬롯을 박음 (9691228) |
| 큐 정리 | "planner 가 큐를 정리하라" | 8/12·8/13·8/15 세 번, 큐 커밋 누락으로 파이프라인 중단 | daily.sh 가 queue reconcile 후 러너가 직접 커밋 (51e3a4b) |
| 스레드 발행 | "distributor 가 publish 를 부르라" | 34편 발행 동안 실제 스레드 0편 | threads-daily.sh 를 hermes cron 09:00 별도 실행으로 분리 (570d948) |
네 사례 중 셋은 프롬프트 누락이고, 카드뉴스만 원인이 다르다.
카드뉴스는 누락이 아니라 슬롯이 없었다
나머지 셋은 "프롬프트에 지시가 있었는데 실행에서 빠진" 경우입니다. 그런데 카드뉴스는 다릅니다. 여기서는 프롬프트가 아니라 뼈대를 생성하는 코드(pipeline/new-post.ts)의 템플릿에 cards frontmatter 블록 자체가 없었습니다. 새 글을 만들 때 애초에 채울 자리가 안 만들어졌으니, 프롬프트가 "카드를 채우라"고 한들 채울 곳이 없었던 겁니다. 지시가 새어나간 게 아니라, 지시가 작동할 토대가 빠져 있던 상황입니다.
그래서 수습 방법도 반대 방향이었습니다. 다른 셋은 "실행 후 검증·수행을 코드가 대신하게" 만들었지만, 카드뉴스는 처음 만들 때부터 빈 슬롯이 강제로 남게 했습니다. 지금은 뼈대에 cover 1장, point 3장, cta 1장 자리가 박혀 있어서, 비워두면 TODO 가 남아 검사(check-content)에 걸립니다. 채우지 않으면 발행이 안 되니 누락 자체가 불가능해진 구조입니다. 같은 "코드로 옮긴다"라도 결이 달랐던 사례라 따로 떼어 적어둘 가치가 있었습니다.
큐 커밋 누락은 세 번 반복된 뒤에야 코드로 옮겨졌다.
코드로 옮겼다가 되돌린 사례 — 스레드 말투
여기까지만 보면 "웬만하면 코드로 옮겨라"가 결론 같습니다. 그런데 반대 방향으로 갔다가 되돌린 사례도 있습니다. 스레드에 올릴 문구의 말투를 코드로 만들려던 시도였습니다. 한때 social·description 에 쓴 존댓말 어미를 기계로 갈아 끼워서 스레드용 반말체를 자동 생성했습니다(e7249b9). 문법은 맞았습니다. 문제는 결과물이 늘어난다 같은 문어체 평서형이라, 스레드에서 실제로 쓰는 말이 아니었다는 점입니다.
그래서 되돌렸습니다(fc33ef1). 지금은 어미를 치환하는 코드가 아예 없고, frontmatter 의 thread 배열에 사람 또는 에이전트가 스레드 말투로 직접 쓴 조각을 넣으면 코드는 그걸 나르기만 합니다. 저장소의 pipeline/thread-chain.ts 에는 이 결론이 주석으로 박혀 있습니다 — "말투는 규칙이 아니라 글이다. 어미 치환으로 만들어지지 않는다." 비슷한 결정이 하나 더 있었습니다. 한글 AI 티를 지우는 윤문 규칙을 프롬프트에 흉내 내 적었다가 걷어내고 전용 도구로 옮긴 일입니다(c2cb3e0, 원인 커밋 7a9fb63). 근거는 같습니다. 같은 일을 두 군데서 하면 어긋나고, 표현·문체 같은 창작적 판단은 규칙으로 재현되지 않습니다.
같은 내용도 어미 치환으로 만들면 스레드 말투가 되지 않는다.
코드로 옮길 것과 프롬프트에 남길 것
이 사례들을 겹쳐 보면 가르는 기준이 보입니다. 기계적으로 검증 가능한 반복 동작은 코드로 옮깁니다. 파일이 커밋됐는지, 특정 작업이 실행됐는지, 순서가 지켜졌는지 같은 것들입니다. 이런 것은 참·거짓이 명확하고, 실행 후에 코드로 확인할 수 있으며, 매번 똑같이 반복돼야 합니다. static/media 커밋, 큐 정리, 스레드 발행이 전부 여기 해당합니다. 카드뉴스처럼 애초에 채울 자리를 강제하는 것도 같은 계열입니다.
반대로 표현·문체·판단 같은 창작적 영역은 프롬프트에 맡기되, 코드는 사후 게이트로만 씁니다. 스레드 말투를 코드가 만들려다 실패한 게 이 경계를 보여줍니다. 지금 이 저장소의 방식은 문구는 프롬프트가 쓰고, 코드(check-content)는 존댓말이 잘못 남았는지, 글자 수를 넘겼는지 같은 것만 검사합니다. 쓰는 건 프롬프트, 막는 건 코드입니다. 다만 이건 제 파이프라인에서 얻은 기준일 뿐, 모든 자동화에 그대로 들어맞는다고 단언하기는 어렵습니다. 검증 비용이 실행 비용보다 크면 게이트 자체가 부담이 되기도 하니까요.
참·거짓이 명확하고 반복되는 동작이면 코드로, 창작적 판단이면 프롬프트에 남긴다.
정리
- 프롬프트에 적은 지시는 실행 결과를 검증하는 장치가 없으면 조용히 샙니다. "적어뒀다"와 "실제로 됐다"는 다릅니다.
- 파일 커밋 여부·실행 여부·순서처럼 참·거짓이 명확한 반복 동작은 코드로 옮기고, 말투·문체 같은 창작적 판단은 프롬프트에 남기되 사후 게이트로만 검증합니다.
- 코드로 옮기는 게 늘 정답은 아닙니다. 스레드 말투처럼 규칙으로 재현되지 않는 것은 되돌려야 했습니다.
근거와 참고
- 88fdf65 "daily.sh 가 static/media 잔여물을 직접 커밋하도록" —
pipeline/daily.sh745~757행 - 9691228 "카드뉴스 뼈대를 새 글에 넣고 기존 10편에 카드 채움" —
pipeline/new-post.tscards 슬롯 - 51e3a4b "planner 큐 기록을 러너가 커밋한다 — 세 번 막힌 자리다" —
pipeline/daily.sh793~795행 주석 - 570d948 "스레드 발행을 러너가 한다 — 프롬프트에 맡겨 34편 동안 0편이 나갔다" —
pipeline/threads-daily.sh,.claude/agents/post-distributor.md65~66행 - e7249b9 → fc33ef1 스레드 어미 치환 도입과 되돌림 —
pipeline/thread-chain.ts주석 "말투는 규칙이 아니라 글이다" - c2cb3e0 "즉흥으로 쓴 '사람이 쓴 것처럼' 규칙을 걷어낸다" (원인 커밋 7a9fb63) — 윤문 규칙을 전용 도구로 이관