-
Ralph Loop — bash while true + LLM CLI가 만든 어이없게 강력한 에이전트 패턴IT 2026. 5. 7. 23:00
"deterministically bad in an undeterministic world"
2025년 중반, 호주 개발자 Geoffrey Huntley가 이상한 블로그 글을 하나 올렸다. 제목은 "Ralph Wiggum as a software engineer". 핵심은 이 한 줄이었다.
while :; do cat PROMPT.md | claude-code ; done무한 루프로 LLM CLI를 계속 호출한다. 그게 전부다. 이름은 Simpsons의 Ralph Wiggum에서 왔다. Huntley 본인이 "비결정적 세계에서 결정적으로 멍청하게 반복한다"고 표현한 그 바보같은 캐릭터다. 글의 맨 앞에 붙은 밈은 "이 기법이 얼마나 멍청해 보이는지를 인정하는 농담"이었다.
놀랍게도 이게 작동했다. Huntley 본인은 $50,000 규모 컨설팅 계약을 $297 API 비용으로 완료했다고 보고했다. Y Combinator 해커톤에서 한 팀은 하룻밤에 6개 저장소를 배포했다. Boris Cherny(Claude Code 공동 창시자), Daisy Hollman(Anthropic) 같은 내부 인사까지 쓰기 시작했고, 2026년에는 Anthropic 공식 플러그인(
ralph-loop,ralph-wiggum)으로 정식 편입됐다.Ralph의 핵심 아이디어 — 컨텍스트를 버리고 디스크로
Ralph가 다른 에이전트 루프와 구분되는 가장 큰 특징은 매 iteration마다 새로운 컨텍스트 창으로 LLM을 기동한다는 점이다. 세션이 유지되지 않는다. 기억은 디스크 파일(코드, git 히스토리, 상태 md)로만 흐른다.
일반 chat 세션은 컨텍스트가 쌓이면서 포화(compaction)나 drift가 생긴다. Ralph는 이걸 원천 회피한다. 매번 빈 창으로 시작해서 디스크 상태를 읽고 딱 하나의 작업만 한다.
기본 Bash 구현
원본 one-liner는 충격적으로 단순하다. 조금만 가드레일을 붙이면:
#!/bin/bash MAX=50 i=0 while [ $i -lt $MAX ]; do echo "=== iter $i @ $(date) ===" | tee -a ralph.log cat PROMPT.md | claude -p --dangerously-skip-permissions \ >> ralph.log 2>&1 # 종료 신호 감지 if grep -q "<promise>COMPLETE</promise>" ralph.log; then echo "done"; break fi git add -A && git commit -m "ralph iter $i" --allow-empty i=$((i+1)) sleep 2 done이게 전부다. 복잡한 프레임워크도, 전용 orchestrator도 필요 없다. Bash + LLM CLI + git만 있으면 Ralph다.
상태와 결과 관리 — 디스크 파일 규약
Ralph는 컨텍스트가 아니라 파일시스템에 상태를 둔다. 핵심은 입력 파일(불변)과 상태 파일(가변)을 명확히 분리하는 것. Huntley가 쓰는 표준 구성:
파일 가변성 역할 PROMPT.md🔒 불변 매 iteration에 그대로 주입되는 작업 명령서 (목표 + 진행 규칙 + 완료 조건). 사람이 미리 정해 두고 손대지 않는다. AGENT.md/CLAUDE.md🔒 불변 컴파일/실행/테스트 방법, 코드 컨벤션. 프로젝트 자체의 규칙이라 Ralph 도중에는 안 바뀐다. specs/디렉토리🔒 거의 불변 기술 명세서. 에이전트가 매 턴 참조하는 진실의 원천. fix_plan.md🔄 매 iteration 갱신 유일한 동적 상태 파일. 우선순위 TODO 리스트. 에이전트가 항목 하나를 처리하면 그 항목을 지우고, 진행 중 발견한 새 작업·후속 이슈를 추가한다. 이게 다음 iteration의 의사결정 기준이 되므로 Ralph가 점진적으로 나아지는 핵심 메커니즘. git commits 🔄 매 iteration 누적 iteration 말미에 커밋. 다음 턴이 git log/diff로 "지금까지 뭐 했는지"를 파악. 다음 iteration 주입 방식이 중요하다. 같은 PROMPT.md를 그대로 다시 stdin으로 준다. 입력은 변하지 않지만 디스크 상태(
fix_plan.md+ git history)가 변했으므로 에이전트가 매번 다른 결정을 내린다. 이게 "결정적으로 멍청한" 무한 루프가 실제로 진척을 만드는 트릭이다 — 입력이 같아도 상태가 달라지니까. Huntley의 조언: "pick the best, most important item and only do one" — 한 번에 하나만.종료 조건 3가지
- Promise 태그 — 프롬프트에 "작업 끝나면
<promise>COMPLETE</promise>출력"을 지시. grep으로 감지 - Max iterations —
--max-iterations 20. 관행적으로 35~50 - 테스트 통과 —
npm test && echo DONE같이 외부 검증 통과 시 종료
Anthropic 공식 플러그인은 Stop Hook 메커니즘을 쓴다. Claude가 종료하려 할 때
hooks/stop-hook.sh가 exit code 2로 가로채 세션을 살린 채 같은 프롬프트를 재주입한다. 편리하지만 "신선한 컨텍스트" 장점을 잃는다는 비판이 있다(뒤에서 자세히).성공 사례와 실패 사례
성공 사례
- Huntley $297 사례 — $50,000 컨설팅 계약을 API 비용 $297로 완료. 가장 자주 인용되는 수치
- Y Combinator 해커톤 — 한 팀이 하룻밤에 6개 저장소 배포
- CURSED 컴파일러 — Huntley가 3개월간 Ralph로 자작 프로그래밍 언어 컴파일러 개발
- Boris Cherny — Claude Code 공동 창시자도 Ralph 사용자로 알려짐
실패 사례
- 플레이스홀더 구현 — 에이전트가
// TODO: implement만 채우고 complete 선언 - 중복 구현 — ripgrep 검색 실패로 이미 있는 함수를 다시 작성
- 컴파일 오류 폭증 — 수정이 다른 오류를 낳고, 다음 iteration이 엉뚱한 곳을 건드려 에러가 누적
- 공식 플러그인의 역설 — Stop hook 방식은 세션을 살려두기 때문에 원본 bash 루프의 장점(신선한 context)을 잃고, 30 iteration 지나면 "cognitive baggage"가 쌓여 이미 기각된 제안을 반복
- 토큰 폭증 — 복잡한 50-iteration 실행 시 $50~$100+ API 비용
변형 패턴
Ralph + Verifier
단일 Ralph 대신 plan → build → review → QA 각 단계를 별개 에이전트로 순차 실행. 각각 신선한 context에서 자기 역할만 한다. 작업 혼란은 격리된다.
Ralph + 파일 메모리
매 iteration에
iter_XX_report.md로 결과를 디스크에 남기고, 다음 턴에 최근 N개 리포트를 프롬프트에 포함. git 커밋 히스토리 자체가 메모리 역할.Ralph + 테스트 루프 (TDD Ralph)
#!/bin/bash while true; do cat PROMPT.md | claude -p --bare git add -A && git commit -m "ralph iter" --allow-empty if pytest --maxfail=1 && ruff check .; then echo "all tests pass, done" break fi done종료 조건을 pytest 통과로 설정. machine-verifiable 완료 기준이 생기면 무한 루프 회피가 쉬워진다.
서드파티 구현
- ralphify.co — Claude Code/Aider/Codex 범용 Ralph 래퍼
- snarktank/ralph — PRD 체크리스트 기반 자동 종료
- frankbria/ralph-claude-code — "intelligent exit detection"
- vercel-labs/ralph-loop-agent — Vercel AI SDK 연동
한계 — 왜 Ralph가 항상 작동하지는 않는가
Simon Wang(ITNEXT)의 "Ralph Loop Is Innovative. I Wouldn't Use It for Anything That Matters"가 가장 정밀한 비판이다. 핵심 논지는 이렇다.
"인간 없이 생성된 코드에 왜 프로덕션을 걸겠는가. 경영 KPI(출하 속도, 테스트 통과, API 비용)로는 성공이지만, 아키텍처 일관성·유지보수성은 iteration마다 즉시 오류는 고치되 누적적 drift가 쌓여 장기적으로 비싼 검증 비용으로 귀결된다."
Ralph 성공의 전제 조건이 엄격하다는 지적도 있다. 네 가지가 모두 갖춰져야 한다.
- 완성도 높은 requirement 문서
- 모든 주요 디자인 결정이 선행 완료
- 에이전트가 엉뚱한 길로 샐 경로 예측
- 무한 토끼굴 대비 가드레일
역설적이다. Ralph는 "사전 specification 비용을 줄이는 기법이 아니라 오히려 더 요구"한다. spec이 허술하면 Ralph는 멍청하게 돈을 태우는 무한 루프로 전락한다.
"7 Ralph Loop Mistakes" — 실전 회피 포인트
- max-iterations 미설정 → 무한 루프, 토큰 폭증
- 완료 기준 모호 → "완성된 척" 코드 제출
- "iteration = progress" 착각 → 돌았다고 진행한 게 아님
- 디스크 상태 관리 없음 → 매번 같은 실수 반복
- 테스트 없이 Ralph → 실패를 감지할 수 없음
- 한 번에 여러 작업 → "한 번에 하나만" 원칙 위반
- 중복 구현 체크 안 함 → 이미 있는 함수 재작성
개인 프로젝트에서 Ralph 시작하기
처음 시도한다면 이 최소 세팅을 추천한다.
# 1) PROMPT.md 작성 (불변) cat > PROMPT.md <<'EOF' You are implementing feature X per specs/feature-x.md. Rules: - Pick ONE item from fix_plan.md. Do only that. - After the change, run `pytest && ruff check .` - If both pass, remove the item from fix_plan.md and commit. - If all items in fix_plan.md are gone, output <promise>COMPLETE</promise>. EOF # 2) fix_plan.md에 TODO 나열 # 3) specs/ 에 요구사항 문서 # 4) Ralph 루프 실행 MAX=30; i=0 while [ $i -lt $MAX ]; do cat PROMPT.md | claude -p --bare --allowedTools "Read,Edit,Bash(pytest *,ruff *,git *)" > log.txt 2>&1 grep -q "COMPLETE" log.txt && break i=$((i+1)) done정리 — 언제 Ralph가 빛나는가
Ralph의 본질은 "컨텍스트 포화를 원천 회피하면서 단일 작업을 완료까지 밀어붙이는 패턴"이다. 이게 빛나려면 세 가지가 갖춰져야 한다.
- 명확한 spec — 에이전트가 읽을 기준이 있어야 함
- 검증 가능한 완료 조건 — 테스트, lint, 또는 구체적 promise 태그
- 작은 작업 단위 — 한 iteration에 하나만
세 가지가 있으면 Ralph는 강력하다. 없으면 Ralph는 "deterministically bad" 그 자체가 된다. 이름이 괜히 Ralph Wiggum이 아니다.
이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.
'IT' 카테고리의 다른 글
로컬 챗봇 시리즈 #5 — 다른 프로젝트의 venv를 subprocess로 빌려쓰기: 의존성 추가를 거부하는 영리함 (0) 2026.05.08 로컬 챗봇 시리즈 #4 — Vision 32B에서 7B로, 그리고 포기까지 — 두 vLLM 동거 시행착오 (0) 2026.05.08 로컬 챗봇 시리즈 #3 — 도구에 '지금 누구의 첨부인지' 어떻게 알려주나: ContextVar 패턴 (0) 2026.05.08 로컬 챗봇 시리즈 #2 — Project 시스템 프롬프트는 왜 글로벌 Custom Instructions '다음에' 와야 하나 (0) 2026.05.08 로컬 챗봇 시리즈 #1 — 메시지 편집은 왜 그렇게 단순해야 하나: 컨텍스트 엔지니어링 관점에서 (0) 2026.05.08 GPU 스케줄러를 Ollama warmup에서 vLLM 컨테이너로 옮긴 과정 — 시작·종료 시퀀스를 다시 짜다 (0) 2026.05.06 RAG 청크 맥락에서 thinking을 꺼야 하는 이유 — enable_thinking=False가 필요한 순간 (0) 2026.05.06 OpenAI-compat 표준화로 어댑터 100줄 들어내기 — passthrough가 가져온 코드 청결도 (0) 2026.05.06 vLLM reasoning_parser — <think> 블록을 정규식 말고 구조로 받는 법 (0) 2026.05.06 KV cache FP8로 동시 요청 76배 수용하기 — LLM 메모리의 숨은 주범 정리 (0) 2026.05.06 - Promise 태그 — 프롬프트에 "작업 끝나면