에이전트 루프는 LLM(Large Language Model)을 한 번 부르고 끝내지 않고, 모델 호출 → 도구 실행 → 결과 관찰을 반복하는 구조다. 모델은 매 턴 지금 상태를 보고 텍스트로 답하거나 도구 호출을 요청한다. 도구를 실행한 결과가 다시 모델에게 들어가고, 모델이 도구 호출 없이 답하면 루프가 끝난다.
flowchart TD
I[프롬프트 + 시스템 프롬프트 + 도구 정의 + 지난 대화] --> M[모델 호출]
M --> Q{도구 호출 요청?}
Q -- 예 --> T[도구 실행] --> A[결과를 대화에 붙임] --> M
Q -- 아니오 --> F[최종 답 반환]
"auth.ts의 실패하는 테스트를 고쳐 줘"라면 테스트를 돌리고(1턴), 파일을 읽고(2턴), 고친 뒤 다시 테스트를 돌리고(3턴), 다 통과했다고 답하며(마지막 턴) 끝난다. 계획을 미리 고정하지 않고 매 결과를 보고 다음 행동을 정한다는 점이 정해진 순서로 LLM을 부르는 워크플로와 다르다.
무엇이 쌓이나
컨텍스트 윈도우(Context Window)는 턴 사이에 비워지지 않는다. 시스템 프롬프트, 도구 정의, 지침 파일, 모든 도구 입력과 출력이 계속 쌓인다. 큰 파일 하나, 장황한 명령 출력 하나가 한 턴에 수천 토큰을 쓴다. 한도에 가까워지면 오래된 대화를 요약해 줄이는 압축(Compaction)이 일어나는데, 이때 초반 지시가 빠질 수 있다. 그래서 계속 지켜야 할 규칙은 첫 프롬프트가 아니라 매 요청에 다시 들어가는 지침 파일에 둔다 → 에이전트 컨텍스트 파일 (AGENTS.md·스킬·메모리). 토큰이 새는 지점은 LLM 사용 습관에서 다룬다.
언제 멈추나
- 정상 종료: 모델이 도구 호출 없이 최종 답을 낸다
- 회로 차단기: 최대 턴 수, 최대 비용을 넘으면 멈춘다. Claude Agent SDK는 둘 다 기본값이 "제한 없음"이라, 열린 과제("코드베이스를 개선해 줘")에서는 한없이 돌 수 있다. 운영용 에이전트에는 예산을 거는 것이 기본이다
- 실패 감지: 같은 실패를 반복하는 루프를 감지해 끊는다
- 완료 판정: 모델이 "다 했다"고 말하는 것과 실제로 끝난 것은 다르다. 테스트나 별도 평가자가 완료를 판정하게 한다 → 생성과 평가 분리
도구 실행 직전과 직후에 끼어드는 훅으로 위험한 명령을 막을 수 있다. 훅은 모델의 컨텍스트 밖에서 돌아 토큰을 쓰지 않는다. 지시로 부탁하는 대신 환경으로 막는 방식은 하네스 엔지니어링에서 다룬다.
로컬 CLI 에이전트와 서비스형 에이전트
루프 자체는 같지만 그것을 감싸는 런타임이 다르다.
| 로컬 CLI 코딩 에이전트 | 서비스형 에이전트 | |
|---|---|---|
| 생명 주기 | 사용자가 띄운 세션 동안 사는 프로세스 | 항상 떠서 요청을 기다리는 서버 |
| 도구 | 파일 시스템, 셸, git, MCP 도구 | 사내 API, 검색, DB 등 |
| 안전장치 | 권한 확인 프롬프트, 샌드박스 | 테넌트 분리, 권한 관리 |
| 추가로 필요한 것 | 작업 디렉터리 맥락 정리, diff 추적 | 요청을 받는 API, 오래 걸리는 일을 넘길 큐와 워커, 세션 저장소, 재시도와 멱등성(멱등성), 스트리밍(SSE·WebSocket) 응답 |
서비스형에서는 HTTP 요청 하나 안에서 루프를 다 돌리기 어려우니, 요청을 받으면 작업을 큐에 넣고 워커가 루프를 돌리며 진행 상황을 스트리밍하는 구조가 흔하다. 세션을 이어 가려면 대화 기록을 외부 저장소에 남겨야 한다. 에이전트 구조도 가장 단순한 형태로 시작해 필요가 증명될 때만 복잡도를 늘리는 편이 낫다. 자율적으로 도는 만큼 비용이 커지고 오류가 누적될 수 있기 때문이다.
출처: How the agent loop works Claude Agent SDK 문서 · Building effective agents Anthropic