4편에서 벽을 세웠다면, 다음 질문은 이것입니다. 그 안에서 에이전트는 몇 바퀴를 돌고, 언제 멈추나? 도구·샌드박스·CI가 있어도, 루프에 정지 조건이 없으면 토큰만 태우고 같은 실수를 반복합니다.
핵심 한 줄: 루프 엔지니어링은 “계속 생각하게”가 아니라 plan → act → observe → (retry | stop | escalate) 를 명시적으로 설계하는 일입니다.
이 글은 「코딩은 대화다」 5편입니다. 하네스 다음에 사이클과 한도로 올라갑니다. 역할 분리는 3편, 도구 선택은 1편을 참고하세요.
하네스와 루프는 다른 축
| 축 | 질문 | 4편 / 5편 |
|---|---|---|
| Harness | 무엇을 할 수 있나? | 도구·샌드박스·권한·eval 벽 |
| Loop | 무엇을 몇 번 시도하고 멈추나? | 턴 한도, WIP, 재시도 정책 |
하네스는 사고 반경을 줄입니다. 루프는 시간·비용·복잡도를 줄입니다. 둘 다 없으면 “Allow 연타”나 “같은 프롬프트 10번”이 기본값이 됩니다.
Rendering diagram…
Plan → Act → Observe (ReAct의 실무 버전)
2022년 Yao 등의 ReAct 논문은 추론(Thought)과 행동(Action)을 한 사이클에 섞고, 도구 결과를 Observation으로 다음 턴에 넣는 패턴을 정리했습니다. 제품마다 이름은 다르지만, 지금 쓰는 에이전트 런타임 대부분은 이 뼈대를 갖고 있습니다.
실무용으로 짧게 옮기면:
- Plan — 이번 턴의 목표 1개 (파일 하나, 테스트 하나, 초안 섹션 하나)
- Act — 도구 호출 / 패치 / 명령 (하네스가 허용한 범위 안에서)
- Observe — exit code, diff, 린트, 사람 피드백, API 응답
- Decide — 완료 / 입력 바꿔 재시도 / 사람에게 에스컬레이션
“생각만 길게”는 Plan이 아닙니다. Plan은 검증 가능한 다음 한 걸음이어야 합니다.
정지 조건이 없으면 루프가 아니다
OpenAI Agents SDK의 러너는 대략 이런 루프입니다: 모델 호출 → 도구 실행 또는 핸드오프 → 최종 출력이면 종료. 그리고 max_turns 로 상한을 겁니다. 한도를 넘으면 MaxTurnsExceeded가 나고, 핸들러로 “범위 줄여 다시” 같은 통제된 응답을 줄 수 있습니다 (Running agents, as of 2026-08-03). 여기서 turn은 한 번의 AI 호출(+ 그에 딸린 도구 호출) 에 가깝습니다.
LangGraph 쪽에서는 그래프 super-step 상한인 recursion_limit 이 있고, 초과 시 GraphRecursionError가 납니다. 런타임 config에 {"recursion_limit": N} 을 넘깁니다 (Graph API, GRAPH_RECURSION_LIMIT, as of 2026-08-03). 기본값·버전은 문서 기준으로 확인하고, “무한 허용”을 기본으로 두지 마세요.
| 정지 신호 | 의미 | 예시 |
|---|---|---|
| 성공 게이트 | Definition of Done 충족 | 테스트 green, 품질 ≥85, PR 체크 |
| 예산 게이트 | 턴·토큰·시간 소진 | max_turns, 타임박스 25분 |
| 패턴 게이트 | 같은 실패 N회 | 동일 파일·동일 에러 3회 → escalate |
| 사람 게이트 | 고위험 / 판단 필요 | 배포, 스키마, 시크릿 |
정지 조건을 프롬프트에만 쓰면 모델이 “거의 다 됐다”고 거짓말할 수 있습니다. 하네스(4편)가 exit code·훅·CI로 판정하고, 루프는 그 판정을 Observe로 받아야 합니다.
WIP=1 — 한 번에 하나만
에이전트가 느려 보이는 흔한 이유는 모델이 약해서가 아니라 WIP가 폭발해서입니다. 한 턴에 리팩터 + 테스트 수정 + 문서 + 배포 스크립트를 동시에 물면, Observe가 해석 불가능한 잡음이 됩니다.
PapaCoder Labs 개발 보드는 의도적으로 Now 카드 1장(WIP=1) 입니다. 두 장이 올라오면 오케스트레이터 버그로 취급합니다. 에디토리얼 시리즈도 한 런에 slug 하나입니다. 사람 팀과 에이전트 팀에 같은 규칙을 쓰면, “왜 루프가 안 끝나지?”의 절반이 사라집니다.
실무 규칙:
- 한 루프의 목표 = 검증 가능한 산출물 1개
- 병렬은 읽기 전용 조사까지만; 쓰기는 직렬
- 막히면 새 목표를 추가하지 말고, 현재 목표를 쪼개거나 에스컬레이션
재시도 정책 — 같은 버튼을 누르지 마라
무한 재시도는 루프가 아니라 스핀입니다. 정책 예시는 이렇게 잡습니다.
retry_budget: 3
on_fail:
1) change_input # 에러 로그·실패한 테스트·좁힌 스코프를 다음 Plan에 넣기
2) change_tool # 다른 검색/다른 파일/읽기만
3) escalate # 사람·리뷰어 역할·이슈 티켓
never:
- identical_prompt_replay
- silent_ignore_of_observe
Observe를 무시하고 “한 번 더”만 누르면, 컨텍스트만 커지고 원인은 그대로입니다. 좋은 재시도는 입력이 바뀐 재시도입니다.
에이전트가 같은 import 에러로 네 번 패치를 돌리길래 로그를 보니, 실제 원인은 환경 변수 누락이었습니다. 다섯 번째 패치가 아니라 Observe를 읽고 루프를 멈춘 것이 해결이었습니다.
실습: 루프 계약 (복사해서 쓰기)
AGENTS.md 또는 오케스트레이터 프롬프트에 붙여 넣으세요.
# Loop contract
## Goal (WIP=1)
- One verifiable outcome per run (e.g. "tests for auth middleware green").
## Cycle
1. Plan: state the next smallest step.
2. Act: only harness-allowed tools.
3. Observe: paste exit codes / failing asserts / diff summary.
4. Decide: stop | retry-with-new-input | escalate.
## Budgets
- max_turns: 8 (or team default)
- max_identical_failures: 2
- wall_clock: 25m then escalate
## Done when
- [ ] Automated checks required by harness pass
- [ ] No new scope added mid-loop without human OK
OpenAI SDK를 쓸 때는 러너에 상한을 명시합니다 (버전·기본값은 문서 확인, as of 2026-08-03):
result = Runner.run_sync(
agent,
"Fix the failing unit tests in packages/core only.",
max_turns=8,
)
Cursor에서 루프를 다루는 법
- 긴 Agent 모드일수록 목표를 한 문장으로 고정하고, 중간 산출물(테스트 통과 스크린샷·파일 목록)을 Observe로 요구하세요.
- 같은 파일이 왔다 갔다 하면 Stop 하고, 실패 로그를 붙여 “입력 바꿔 재시도” 프롬프트를 새로 엽니다. 이어 붙이기보다 새 계약이 싸게 먹힙니다.
- Hooks/CI(4편)가 없으면 Cursor 루프는 “말이 그럴듯한 스핀”으로 전락합니다. 정지 판정을 IDE 밖으로 빼세요.
- Composer/Agent가 병렬로 여러 큰 수정을 제안하면 WIP=1로 거절하고 순서를 다시 줍니다.
PapaCoder에서의 루프
- 개발: 보드
Now= 1, 사이클마다 산출물 제한, Done 전에 acceptance 확인 (agents/development/workflow.md). - 에디토리얼: Research → Draft → Review → Fact-check → (실패 시 입력 바꿔 수정) → Internal API draft only. Publish는 사람. accuracyFail면 점수와 무관하게 막습니다 — 이것이 성공 게이트입니다.
- 시리즈 오케스트레이터: 한 런에 queued slug 하나. 다음 편은 플랜의 Next action.
루프는 “자동화의 멋”이 아니라 실패를 싸게 만드는 리듬입니다.
FAQ
Q. 루프와 멀티에이전트는 뭐가 다르죠?
A. 멀티에이전트(3편)는 역할과 핸드오프, 루프는 한 역할(또는 오케스트레이터) 안의 반복과 정지입니다. 핸드오프도 결국 바깥 루프의 Act입니다.
Q. max_turns을 크게 잡으면 더 똑똑해지나요?
A. 대개 비용만 커집니다. 예산은 좁은 목표 + 좋은 Observe와 같이 써야 합니다.
Q. 언제 그래프(6편)로 넘어가나요?
A. 분기가 많아지고 “누가 다음에 말할지”가 선형 if/else로 안 보일 때입니다. 지금은 한 사이클을 믿을 수 있게 만드는 게 먼저입니다.
Q. 사람이 매 턴 승인하는 건 루프인가요?
A. 감독은 가능하지만 스케일이 안 됩니다(4편 approval fatigue). 루프의 기본 정지는 자동 게이트, 사람은 에스컬레이션에 두는 편이 낫습니다.
소스
- Yao et al., ReAct: Synergizing Reasoning and Acting in Language Models (2022) — https://arxiv.org/abs/2210.03629
- OpenAI Agents SDK — Running agents — https://openai.github.io/openai-agents-python/running_agents/ (as of 2026-08-03)
- OpenAI — Running agents (API guide) — https://developers.openai.com/api/docs/guides/agents/running-agents (as of 2026-08-03)
- LangGraph — Graph API / recursion limit — https://docs.langchain.com/oss/python/langgraph/graph-api (as of 2026-08-03)
- PapaCoder — development WIP=1 —
agents/development/workflow.md
시리즈에서 다음
| # | 주제 |
|---|---|
| 01–04 | 도구 → 바이브 → 멀티에이전트 → 하네스 |
| 05 | 루프 (지금) |
| 06 | 그래프/DAG — 분기·병렬·에스컬레이션 언제 |
| 07 | PapaCoder 현장 노트 |
다음에 읽을 글: 그래프 엔지니어링 — 선형 루프가 부러질 때 노드와 엣지로 올리는 법.
지금까지의 한 줄: 벽(하네스) 안에서, 한 목표씩, 입력을 바꿔 재시도하고, 예산이 끝나면 사람에게.