ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 한 285줄 스크립트가 self-recovering 자율 시스템이 되기까지 — ralph-loop.sh 의 8 commit 진화사 (Ralph Loop 시리즈 8편 통합)
    IT 2026. 5. 18. 22:00
    한 285줄 스크립트가 self-recovering 자율 시스템이 되기까지 — ralph-loop.sh 의 8 commit 진화사 (Ralph Loop 시리즈 8편 통합)

    한 주말 취미 프로젝트에서 자동차 회피 게임을 AI 에이전트한테 자율적으로 시켜보려고 285줄짜리 bash 스크립트를 짰다. 그 스크립트는 처음부터 self-recovering 시스템이 아니었다 — 단순한 while true; do opencode run; done 한 줄에서 시작해서, 사고 한 번에 한 줄씩 늘어나며 어떻게 끝나든 자기 자원을 정리하고 / vLLM 죽음을 자동 복구하고 / 막힌 항목을 자동 우회하고 / 거짓 보고를 외부 검증으로 잡는 시스템이 됐다.

    이 글은 시리즈 8편 통합편이다. 1~7편에서 각 디자인 결정의 왜와 어떻게 를 한 편씩 풀었다면, 이 글은 그 모든 결정이 한 스크립트 안에서 어떻게 합쳐져 자율성을 만드는가 를 본다. 8개의 commit 으로 진화한 ralph-loop.sh 의 timeline, 그 각 commit 이 어떤 사고에 대한 대응 이었는지, 그리고 38 iter 운영의 정량 결과까지.

    전체 시리즈 인덱스

    먼저 1~7편이 무엇을 다뤘는지 한눈에 정리한다.

    1. 1편 (컨텍스트 3축): PLAN.md (불변) + CHECKLIST.md (가변) + git (실측 가변) 의 분리. 매 iter 같은 PLAN 이 던져지지만 CHECKLIST 와 git 이 진척을 누적해 모델은 매번 새 세션이지만 작업은 누적 된다.
    2. 2편 (부팅과 셧다운): GPU acquire + vLLM 기동 + cleanup trap. released=false idempotent flag 가 어떻게 끝나든 정확히 한 번 release 보장.
    3. 3편 (vLLM 헬스 보장): ensure_vllm_alive() 의 9가지 진단 정보 캡처 + 자동 재기동. vllm-deaths.log 가 학습 자산이 됐다.
    4. 4편 (프롬프트 다층 메시지): 수행 순서 7단계는 효과 있음, 도구 가이드는 효과 0. prompt 가 닿는 layer 와 못 닿는 layer 의 구분.
    5. 5편 (opencode 호출 함정): yargs -f array 함정의 -- 구분자, 5분 timeout, 직후 헬스체크. 한 줄 호출이 17줄로 늘어난 이유.
    6. 6편 (외부 검증 후크): CHECKLIST 토글 + 직전 3분 commit 의 AND. 모델이 보고하는 채널검증하는 채널 분리.
    7. 7편 (3-strike + 진척률 거짓말): dead-letter 패턴 + 진척률 32% 가 80% 로 보였던 사건. 사용자 통찰로 sed 한 줄 복원.

    이 7개 패턴이 처음부터 다 있었던 게 아니다. 초기 scaffold + 7번의 사고 대응을 거치며 한 줄씩 추가됐다 — 총 8 commit. ralph-loop.sh 의 git log 가 그 8단계를 정확히 기록한다.

    ralph-loop.sh 의 8 commit 진화 timeline

    $ git log --oneline --reverse -- ralph-loop.sh
    
    404f9d1 infra: Ralph Loop scaffold with PLAN, CHECKLIST, loop script
    2348801 fix(ralph): pin model id to actual opencode provider config
    20f2a12 fix(ralph): use -- separator to prevent -f array swallowing the prompt
    64dadd5 feat(ralph): remove iteration cap (default unlimited)
    eb70f0e feat(ralph): tool-use guidance + 3-strike skip for stuck items
    8486e7d feat(ralph): integrate gpu-scheduler to keep vLLM loaded during loop
    f190ae6 feat(ralph): per-iter vLLM healthcheck + crash diagnostics
    3bd0362 feat(ralph): wrap opencode call with 300s timeout

    각 commit 이 어떤 사고에 대한 대응이었는지 — 정확한 시간과 함께 짚는다.

    ralph-loop.sh 의 8 commit 진화 timeline

    그림 설명 — 8 commit 의 시간 분포가 집중적 이다. 처음 4 commit (09:33~09:35) 은 초기 scaffold + 즉시 발견된 함정들 (모델 ID, -- 구분자, 무제한 iter). 그 다음 4 commit (09:44~13:02) 이 운영 중 실제 사고에 대응한 진화. 색깔이 commit 의 카테고리 를 표현한다 — 보라(infra), 노랑(fix/feat), 빨강(긴급 대응), 녹색(중요 feature). 사고가 발생할 때마다 한 줄 추가된 git log 가 어떻게 시스템이 자율성을 키워갔는지 의 정확한 기록이다.

    각 commit 의 사고 스토리

    몇 commit 의 사고 → 대응 스토리를 짧게 본다.

    commit 3 (20f2a12 fix: -- 구분자 추가) — 처음 ralph-loop.sh 를 실행하자마자 Error: File not found: 당신은 PLAN.md 의 ... 발생. 30초 만에 수정. yargs -f array 가 $prompt 를 file 경로로 흡수한 거였고 -- 한 단어로 해결. 사소해 보이지만 없으면 ralph-loop 자체가 작동 안 함. 가장 작은 commit 이지만 가장 결정적이었다.

    commit 5 (eb70f0e feat: 도구 가이드 + 3-strike 스킵) — 처음 운영 중 한 항목 (src/main.js 부트스트랩) 에서 30분 정체. 모델이 도구 호출 schema 에러 반복. 검증 후크가 즉시 exit 시키면 자율성 0이라 — 시도 카운트 + 자동 마킹의 dead-letter 패턴 도입. 동시에 prompt 에 도구 사용 가이드 5단 추가 (효과는 거의 없었지만 안전장치로 남김). 이 commit 이 ralph-loop 를 반자동 에서 자율 로 바꾼 변곡점이었다.

    commit 7 (f190ae6 feat: 매 iter 헬스 + 진단 캡처) — vLLM 컨테이너가 KV cache assertion 으로 자살하고 30분간 ralph-loop 가 정체. 시리즈 3편의 사건. 이 commit 이 self-recovering 의 핵심 — 매 iter 시작 + opencode 직후 헬스체크 + 진단 9가지 캡처 + 자동 재기동. 한 함수가 65줄. ralph-loop 의 가장 큰 진화였다.

    commit 8 (3bd0362 feat: 5분 timeout) — vLLM 이 죽었지만 자동 복구 후 opencode 가 죽은 connection 에 hang. 헬스체크는 통과했는데 opencode 자체는 응답 끊긴 상태로 영영 대기. timeout 한 줄로 막음. 이건 직전 commit 7의 보완 — 자동 복구가 모든 hang 을 막진 못한다는 발견이었다.

    각 commit 이 그 직전 사고의 대응 이라는 패턴이 명확하다. 처음부터 self-recovering 으로 짜진 게 아니라 — 사고 → 대응 → 사고 → 대응 의 8 cycle 끝에 self-recovering 이 됐다.

    최종 형태 — 285줄이 만드는 self-recovering 시스템

    8 commit 후 ralph-loop.sh 가 어떤 일을 하는지 한 번에 본다. 의미 단위로 묶은 285줄의 구조:

    단계 줄 범위 역할 시리즈 매핑
    전역 변수 1~30 경로·env·자원 ID·flag
    ensure_vllm_alive() 32~97 매 iter 헬스 + 진단 + 재기동 3편
    cleanup() + trap 99~108 idempotent 종료 처리 2편
    부팅 시퀀스 110~130 GPU acquire + vLLM 기동 + 헬스체크 2편
    메인 while 루프 132~280 iter 처리
    ↪ 다음 항목 추출 137~145 CHECKLIST grep 1편
    ↪ iter 시작 헬스 147~151 1번째 헬스체크 호출 3편
    ↪ git 컨텍스트 캡처 153~155 git log + status 1편
    ↪ prompt 조립 164~211 3축 컨텍스트 + 7단계 + 도구 가이드 1편, 4편
    ↪ 시도 카운트 214~224 .prev_item / .attempts 누적 7편
    ↪ opencode 호출 226~240 timeout + -- + tee + rc 분리 5편
    ↪ 직후 헬스체크 242~249 2번째 헬스체크 호출 3편, 5편
    ↪ 외부 검증 251~260 CHECKLIST 토글 + commit AND 6편
    ↪ 검증 분기 262~277 완료 vs 시도 카운트 vs 자동 스킵 6편, 7편

    매 줄이 어느 시리즈의 어느 패턴 에 매핑된다. 285줄 중 단 한 줄도 — 우연히 거기 있는 줄은 없다. 모두 어떤 사고의 대응 또는 어떤 운영 원칙의 구현 이다.

    정량 결과 — 38 iter 운영의 숫자들

    ralph-loop 의 마지막 사이클을 정량으로 본다.

    지표 해석
    총 iter 수 38 재시작 포함
    총 commit 수 72 인프라 + 게임 코드 합산
    vitest 테스트 수 200 passed (14 files) 모든 단위 테스트 통과
    vLLM 자살 횟수 2 모두 KV cache off-by-one assertion
    vLLM 자동 복구 횟수 2 / 2 100% 자동 복구
    schema 에러 (qwen3_xml 시점) 108회 (30 iter) iter 평균 3.6회
    schema 에러 (qwen3_coder 시점) 0회 (30 iter) 한 줄 변경의 효과
    자동 스킵 → 잔여 복원 횟수 1 (sed 한 줄, 20 항목) 환경 변경 후 일괄
    최종 CHECKLIST 완료 44 / 잔여 0 / 스킵 0 100% 진척
    사용자 개입 횟수 (긴급) 0 cron 보고 + 결정만, 직접 코드 수정 0
    총 운영 시간 ~5시간 09:33 시작 ~ 14:55 완료

    몇 가지 주목할 점이 있다.

    vLLM 자살 100% 자동 복구 — 인프라 자체의 안정성은 못 고쳤지만 (vLLM 0.19 KV cache 버그는 우리가 패치 못함), 그 위에 복구 layer 를 만들어 운영이 안 멈추는 결과를 달성. 알려진 버그가 있는 인프라 위에서도 자율성을 살린 실용적 답이었다.

    schema 에러 한 줄 변경의 효과 — qwen3_xml → qwen3_coder 한 줄이 평균 3.6회/iter 였던 schema 에러를 0으로. 인프라의 정공 이 prompt 강화 며칠보다 효과적이었다는 데이터.

    사용자 개입 0회 (긴급) — 38 iter 운영 중 사용자가 직접 코드 수정 한 적 0번. 모두 cron 보고와 결정만으로 처리. 진정한 자율성의 정량 증거.

    Ralph Loop 패턴의 일반화 — 다른 자율 시스템에 가져갈 것

    이 시리즈에서 다룬 7가지 패턴은 ralph-loop 만의 것이 아니다. 외부 LLM 또는 도구를 호출하는 모든 장기 실행 자율 시스템 에 적용된다.

    패턴 일반화 적용 도메인 예시
    컨텍스트 3축 불변 정의 + 가변 진척 + 실측 상태 CRM 자동화, 운영 자동화, IaC 적용
    idempotent cleanup 외부 자원 정확히 한 번 release DB connection, lock, lease, port bind
    매 iter 헬스 + 진단 의존성 자동 복구 + 9-인자 진단 캡처 마이크로서비스 호출, ETL 파이프라인
    prompt 다층 의사결정 layer 지시 + 도구 layer 가이드 (효과 다름) 모든 LLM 응용
    CLI 호출 안전망 -- 구분자 + timeout + rc 분리 + 직후 헬스 외부 서비스 호출, batch job
    외부 검증 후크 응답 채널 ≠ 검증 채널, AND of 외부 사실 모든 자율 에이전트
    3-strike dead-letter 막힘 자동 우회 + 명시적 마킹 + 사후 검토 큐 시스템, 워커 풀, retry 로직

    핵심은 — AI 자율 에이전트 라는 새로운 영역에서도 운영의 원칙은 분산 시스템 운영의 고전 과 다르지 않다는 것. dead-letter, idempotent retry, 외부 검증, layer 분리 — 모두 메시지 큐와 마이크로서비스에서 수십 년간 쌓여온 패턴들이다. AI 에이전트 운영도 같은 원칙 위에 서 있다.

    한 가지 안타까웠던 점 — 사용자 통찰 없이 못 알아챈 함정

    시리즈 7편의 진척률 거짓말 함정 — 32% 가 80% 처럼 보였던 것 — 은 사용자가 직접 짚어줘야 알아챘다. "스킵도 잔여로 가야 하지 않나" 라는 한 마디. 운영 metric 을 명시적으로 쪼개서 보여주고 있었음에도 불구하고 — 인지적으로는 진척처럼 느껴지는 함정이 있었다.

    이게 자율 시스템의 한 한계일 수도 — 시스템이 보여주는 metric사용자가 인지하는 의미 사이의 거리. 시스템이 정직하게 데이터를 보여줘도 사용자가 잘못 해석하면 잘못된 결정으로 이어진다. 이 한계를 줄이려면 — metric 의 의미를 명시적 한 줄로 같이 보여주는 디자인이 필요하다. "스킵 = 진정 미완성, 사람 검토 또는 환경 변경 후 재시도 필요" 같은 한 줄.

    이 시리즈를 읽는 분 중 자율 시스템을 운영하는 분이 있다면 — 내 시스템의 metric 이 정직하게 보이는가? 사용자가 그 metric 을 정확히 해석할 수 있는가? 한 번 점검해볼 가치가 있다.

    결론 — 자율 시스템은 진화한다

    이 시리즈의 큰 메시지 — self-recovering 자율 시스템은 처음부터 그렇게 짜이지 않는다. 진화한다.

    ralph-loop.sh 의 첫 commit (404f9d1 infra: scaffold) 은 286줄이 아닌 ~150줄짜리 단순한 스크립트였다. 그 위에 사고 → 대응 → 사고 → 대응 의 8 cycle 이 누적되어 — 마지막 commit (3bd0362) 시점엔 어떻게 끝나든 자기 자원 정리 / vLLM 죽음 자동 복구 / 막힘 자동 우회 / 거짓 보고 검증 의 4 layer 안전망을 갖춘 285줄 시스템이 됐다.

    각 진화는 그 사고가 일어나기 전엔 예측 못 한 것이었다. yargs -f array 함정, vLLM KV cache 자살, opencode hang, schema 에러의 진짜 원인 — 모두 첫 scaffold 짤 때는 모르는 일이었다. 예측 가능했다면 첫 commit 에 다 넣었을 것이다. 못 넣은 게 지식의 한계 였고 그 한계를 운영 중 사고로 발견 했고 commit 으로 영구 박았다.

    그래서 이 시리즈가 들려주고 싶은 건 — 완벽한 첫 설계 라는 환상이 아니라 — 진화할 수 있는 구조 를 만드는 게 더 중요하다는 것. ralph-loop.sh 가 진화 가능했던 이유는 — 작고 (285줄), 단일 파일이고, git history 가 깔끔하고, 각 패턴이 서로 직교 했기 때문이다. 만약 처음에 1000줄짜리 거대한 자율 시스템 프레임워크를 짰다면 — 8 commit 진화 동안 곳곳에서 부서졌을 것이다.

    주말 취미로 짠 285줄짜리 bash 스크립트가 — 진정한 자율 시스템 의 모양을 가르쳐줬다. AI 시대에도 운영의 원칙은 변하지 않는다 — 작게 시작하고, 사고에서 배우고, git log 가 그 학습을 영구 기록한다. 이게 그 시리즈의 한 줄 결론이다.

    (시리즈 끝. 1~8편 모두 비공개 draft 로 작성됐습니다. 저자가 사후 리뷰 후 비공개 발행 예정.)


    이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.

Designed by Tistory.