콘텐츠로 이동

Loop 엔지니어링 실습 — 메아리방 vs 거부 신호 루프

이 실습의 목적: concept-loop-engineering 의 핵심 한 문장 — "거부할 수 있는 무언가(테스트·타입체크·에러)가 없는 루프는 메아리방" — 을 직접 코드로 짜서 체험한다. "루프를 작성한다"는 감각을 손에 익히는 게 핵심.

시간: 8분 (셋업 2분 + Before 메아리방 2분 + 거부 신호 추가 1분 + After 검증 루프 2분 + 실제 Claude 연결 1분)

언제 보면 좋은가: concept-loop-engineering 를 읽은 직후. guide-harness-demo(하네스 5분 데모)의 다음 단계 — 하네스가 "환경"을 설계했다면, 루프는 "메커니즘 자체"를 설계한다.

전제: Node 18+ 설치. (Step 6의 실제 Claude 연결만 Claude Code 로그인 필요 — 나머지는 토큰 0으로 누구나 재현 가능)


왜 이 실습인가 — 1분 이론

현대 에이전트 루프는 Princeton·Google의 ReAct(Reason + Act) 패턴에 뿌리를 둔다 (arXiv 2210.03629):

[행동(Act)] → [관찰(Observe)] → [추론(Reason)] → [다음 행동] → …  (종료 조건까지)

여기서 관찰(Observe)이 무엇이냐가 루프의 운명을 가른다.

  • 관찰이 에이전트 자기 보고("다 됐어요")면 → 모델이 자기 출력에 동의하는 메아리방. Sonar의 표현대로 "두 낙관주의자가 서로 동의하는 것".
  • 관찰이 객관적 거부 신호(테스트·타입체크·빌드)면 → 사실에 부딪혀 교정된다. "A failing build is a fact; an opinion is a starting point" (실패한 빌드는 사실이고, 의견은 출발점일 뿐).

이 실습은 같은 루프를 거부 신호 없이 / 있게 두 번 돌려 그 차이를 눈으로 본다.


Step 1 — 데모 폴더 + 검증 대상 (2분)

풀 문제: 회문(palindrome) 검사 함수. 가짜 "에이전트"가 후보 구현을 내놓고, 테스트가 그것을 채점한다.

mkdir -p ~/loop-demo && cd ~/loop-demo

가짜 코딩 에이전트 — 실제 LLM의 비결정성을 흉내 내, 후보 3개 중 하나를 무작위로 출력한다 (1개만 정답):

cat > agent.js << 'EOF'
// 가짜 코딩 에이전트: isPalindrome 후보를 무작위로 하나 출력한다.
// 후보 3개 중 (C)만 모든 케이스를 통과한다 — 실제 에이전트의 "가끔 맞고 가끔 틀림"을 흉내.
const candidates = [
  // (A) 정규화 없음 — 대소문자·공백·구두점이 있으면 틀림
  "module.exports = (s) => s === [...s].reverse().join('');",
  // (B) 소문자화만 — 공백·구두점이 있으면 여전히 틀림
  "module.exports = (s) => { const t = s.toLowerCase(); return t === [...t].reverse().join(''); };",
  // (C) 완전 정규화 — 통과
  "module.exports = (s) => { const t = s.toLowerCase().replace(/[^a-z0-9]/g, ''); return t === [...t].reverse().join(''); };",
];
const pick = candidates[Math.floor(Math.random() * candidates.length)];
process.stdout.write(pick + "\n");
EOF

거부 신호(reject signal) — 객관적으로 pass/fail 을 돌려주는 검증자. 통과 못 하면 종료 코드 1:

cat > test.js << 'EOF'
const isPalindrome = require('./solution');
const cases = [
  ['racecar', true],
  ['A man, a plan, a canal: Panama', true],   // 공백·구두점·대소문자
  ['No lemon, no melon', true],               // 공백·구두점
  ['hello', false],
  ['', true],
];
let failed = 0;
for (const [input, expected] of cases) {
  let got;
  try { got = isPalindrome(input); } catch (e) { got = 'ERROR:' + e.message; }
  if (got !== expected) {
    console.error(`  ❌ isPalindrome(${JSON.stringify(input)}) = ${got} (기대값 ${expected})`);
    failed++;
  }
}
if (failed > 0) { console.error(`FAIL — ${failed}개 케이스 실패`); process.exit(1); }
console.log('PASS — 5개 케이스 전부 통과');
EOF

후보 (A)·(B)는 "A man, a plan…" 같은 케이스에서 깨지고, (C)만 전부 통과한다. 즉 에이전트가 (C)를 뽑을 때까지가 "정답".


Step 2 — Before: 거부 신호 없는 루프 (메아리방) (2분)

에이전트를 1회 호출하고, 검증 없이 그 말을 믿고 끝낸다.

cd ~/loop-demo
node agent.js > solution.js
echo "🤖 에이전트: '구현 완료했습니다 ✅'"
echo "→ 거부 신호가 없는 루프는 이 보고를 그대로 믿고 종료한다."
echo "--- 생성된 코드 ---"; cat solution.js
echo "--- 그런데 실제로 돌려보면? ---"
node test.js || echo "💥 깨져 있다 — 그러나 루프는 이미 '완료'라고 보고했다 (= 메아리방)"

여러 번 실행해 보라. 약 2/3 확률로 깨진 코드인데 "완료"로 종료된다. 종료 조건이 자기 보고이기 때문 — concept-loop-engineering 의 "8번 전 실패를 기억 못 하고 같은 길을 가는" 나쁜 루프.


Step 3 — 거부 신호를 루프에 넣기 (1분)

바꿀 것은 단 하나: 종료 조건을 "에이전트의 말" → "테스트의 종료 코드"로. node test.jsexit 0 이어야만 끝낸다. 이게 ReAct 의 관찰(Observe) 을 객관화하는 것.


Step 4 — After: 검증 루프 (통과까지 재시도) (2분)

cd ~/loop-demo
for i in $(seq 1 8); do
  echo "── 사이클 $i ──"
  node agent.js > solution.js          # Act:     에이전트가 코드 생성
  if node test.js; then                # Observe: 거부 신호(테스트)가 '사실'을 반환
    echo "✅ 사이클 $i 에서 통과 — 종료 조건 충족, 루프 종료"
    break
  fi
  echo "↻ 실패 — 거부 신호가 루프를 한 번 더 돌린다 (Reason → 다음 Act)"
done

이번엔 통과하는 구현이 나올 때까지 루프가 돈다. 거부 신호(테스트의 exit code)가 사이클을 제어한다.

가짜 에이전트가 무작위라, 드물게(약 4%) 8 사이클 안에 (C)가 안 나올 수 있다 — 그땐 다시 실행. 실제 에이전트라면 실패를 피드백받아 다음 시도가 개선된다 → Step 6.

max 8 이라는 반복 상한에 주목. 업계 권고는 보통 15~25 스텝이며, 상한 없는 루프는 토큰·시간을 폭주시킨다. 종료 조건은 ① 검증 통과(goal) ② 반복 상한(resource) 둘 다 있어야 한다.


Step 5 — 차이 표 (직접 채워보기, 30초)

Before (거부 신호 없음) After (테스트 = 거부 신호)
깨진 코드로 종료될 수 있나 __ __
종료 조건의 정체 __ (에이전트 자기 보고?) __ (객관적 검증?)
같은 실수를 반복하나 __ __
사람이 매번 확인해야 하나 __ __

한 줄 소감: ________


Step 6 — 실제 Claude로 (선택, 1분)

가짜 에이전트를 진짜 Claude Code 헤드리스 호출로 바꾼다. 핵심은 실패한 테스트 출력을 stdin 으로 피드백하는 것 (공식 패턴: cat … | claude -p "…").

cd ~/loop-demo
# 일부러 틀린 구현으로 시작 (정규화 없음 → 공백·구두점 케이스 실패)
echo "module.exports = (s) => s === [...s].reverse().join('');" > solution.js

for i in $(seq 1 5); do
  echo "── 사이클 $i ──"
  if node test.js > test.log 2>&1; then
    echo "✅ 통과 — 종료"; cat test.log; break
  fi
  echo "↻ 실패 — 에러를 Claude 에 피드백해 수정 요청"
  cat test.log | claude -p "solution.js 의 isPalindrome 구현이 아래 테스트에서 실패한다. 근본 원인을 찾아 solution.js 만 수정하라. 에러를 숨기지 말 것. 대소문자·공백·구두점은 무시해야 한다." \
    --allowedTools "Read,Edit,Bash(node *)"
done
  • claude -p(=--print)는 비대화형으로 1회 실행 후 종료하므로 for 루프로 감싸기에 딱 맞다 (공식 docs).
  • --allowedTools 로 도구를 좁혀 자동 승인 — 프롬프트 없이 무인 실행.
  • 이게 Reflexion·Self-Refine 의 핵심: 실패 신호를 언어로 받아 다음 시도를 개선 (Reflexion · Self-Refine).

⚠️ 토큰 비용: 사이클마다 모델을 호출한다. Addy Osmani 의 신중론 — "토큰 비용에 절대적으로 주의". 반드시 반복 상한(max 5)과 검증 게이트를 두고, 무인 루프는 비용을 모니터링하라. src-copilot-token-pricing 의 종량제 전환과 같은 맥락.


정리 (30초)

cd ~ && rm -rf ~/loop-demo

좋은 루프가 답해야 할 체크리스트

concept-loop-engineering 의 4가지 설계 질문 + 외부 조사로 보강:

  • 이 루프의 종료 조건은 무엇인가? verify(테스트·타입체크)로 표현되는가, 아니면 자기 보고인가?
  • 루프 안에 거부할 수 있는 무언가가 있는가? (실패를 반환할 수 있는 객관적 검사)
  • 반복 상한(max iterations)과 토큰 예산이 있는가? (업계 권고 15~25 스텝)
  • 한 사이클이 8번 전 실패를 기억하는가, 아니면 같은 길을 다시 가는가? (피드백 전달)
  • 재시도해도 안 될 때의 다음 행동(사람에게 에스컬레이션)은?
  • 사이클 결과가 사람에게 어디서·어떻게 보고되는가?

같은 인사이트 패턴 — "거부 신호 없는 자동화는 폭주한다"

이 실습이 보여준 원리는 위키 전반에 반복된다 (concept-loop-engineering 에 누적):

영역 폭주 시나리오 거부 메커니즘 참조
AI 루프 검증 없이 자기 출력에 동의 → 메아리방 테스트·타입체크를 루프 안에 (이 실습) concept-loop-engineering
Hooks 위험 명령 자유 실행 → 사고 guard.sh exit 1 → 도구 차단 concept-claude-hooks
멀티 에이전트 단일 에이전트 자기검증 통과 편향 Critic 의 CONDITIONAL REJECT concept-multi-agent-pattern
선언 층 부정 명령이 잊힘 STOP 트리거 → 명시적 중단 조건 concept-claude-md

공통 원리: 자동 사이클에는 반드시 밀어내는 신호(reject·timeout·exit code) 가 짝지어 있어야 한다.


원본·외부 출처

개념·발화 (2026-06): concept-loop-engineering / src-loop-engineering - Boris Cherny (2026-06-02 Acquired 인터뷰) "My job is to write loops" · Peter Steinberger (2026-06-07) · Addy Osmani Loop Engineering (2026-06-07)

이론 (1차 출처): - ReAct — Yao et al. 2022, arXiv 2210.03629 - Reflexion — Shinn et al. 2023, arXiv 2303.11366 - Self-Refine — Madaan et al. 2023, arXiv 2303.17651 - "검증 없는 루프 = 단순 자동화" — Sonar 블로그

구현 (공식): - Claude Code 헤드리스 모드 — code.claude.com/docs/headless - 모범 사례 (검증 게이트·Stop 훅) — code.claude.com/docs/best-practices


관련 페이지