루프 엔지니어링 입문부터 실전까지 — 이제 프롬프트가 아니라 루프를 설계할 때
목차
요즘 X나 블로그에서 **「Loop Engineering(루프 엔지니어링)」**이라는 말이 유난히 자주 보여요. "이제 Claude에게 지시를 내리지 않는다. 내 일은 루프를 쓰는 것이다" 같은, 살짝 재미있는 문구로 타임라인에 흘러오는 바로 그거예요.
처음에는 "또 새로운 'XX 엔지니어링'인가……" 하고 삐딱하게 봤어요.
컨텍스트 엔지니어링 책도 아직 서점에 깔려 있고, 하네스 엔지니어링이라는 말도 바로 얼마 전에 본 참이었거든요 😅
그런데 이건 결이 좀 달랐어요. 지금까지 나온 것들이 **"어떻게 잘할까"**를 알려주는 쪽이었다면, 루프 엔지니어링은 **"당신이 손을 움직이는 그 자리에서 아예 내려와라"**라는 이야기였거든요.
이 글에서는
- 애초에 루프 엔지니어링이란 무엇인가
- 루프는 무엇으로 이루어져 있는가
- 가장 진입 장벽이 낮은 Claude Code로 첫 루프 조립하기(입문·실습)
- 직접 개발한다면: Mastra로 루프 돌리기
- 계속 돌려두는 것의 대가
를 차례대로 소개할게요. 깊이 파고드는 핸즈온은 후편으로 미루고, 우선 전체 그림을 가볍게 잡는 게 목적이에요.
루프 엔지니어링이란
한마디로 하면 "지시하는 사람"을 시스템으로 대체하는 것
정의로 가장 많이 인용되는 건 Google Chrome 팀 Addy Osmani의 한 문장이에요.
Loop engineering is replacing yourself as the person who prompts the agent. You design the system that does it instead.
대략 옮기면 **"에이전트에 프롬프트를 치는 사람 자리에서 당신 자신을 빼고, 대신 그걸 쳐 주는 시스템을 설계한다"**예요.
포인트는 "프롬프트를 잘 쓴다"도 "컨텍스트를 잘 정리한다"도 아니고, 당신이라는 인간을 그 자리에서 치운다는 점이에요.
지금까지는 엔지니어가 작업자의 한 사람이었지만, 앞으로는 작업 흐름을 설계하는 사람이 된다는 위치 이동인 셈이죠.
자신을 조직의 리더로 바꿔 생각하면 금방 와닿아요. 멤버를 일일이 마이크로매니징하면 조직이 안 돌아가지만, 그렇다고 통째로 던져 놓기만 해도 안 돌아가죠. 좋은 리더가 하는 일은 목표·리뷰 체계·인수인계 구조를 갖춰서, 자기가 하나하나 참견하지 않아도 돌아가는 상태를 만드는 것이에요.
AI 에이전트를 쓸 때도 완전히 똑같고, 그게 바로 루프 엔지니어링이에요.
이 말은 2026년 6월에 거의 동시다발로 생겨나 자리를 잡았어요.
- Peter Steinberger(OpenClaw 제작자)가 "코딩 에이전트에 직접 지시하지 마라. 에이전트에 지시를 내리는 '루프'를 설계하라"고 게시
- Boris Cherny(Anthropic, Claude Code 총괄)도 "이제 Claude에게 지시를 내리지 않는다. 내 일은 루프를 쓰는 것이다"라고 발언
- Addy Osmani가 그것들을 정리해 블로그에서
Loop Engineering이라 명명
세 사람이 말한 건 결국 같은 동작이었어요. 설계 대상이 "에이전트의 1회 동작"에서 "에이전트를 계속 움직이게 하는 시스템 전체"로 올라갔다는 것이죠.
쌓아 올라가는 4층: Prompt → Context → Harness → Loop
루프 엔지니어링과, 최근 2년 동안 자주 들었던 'XX 엔지니어링'들의 관계는 사실 대체가 아니라 적층이에요.
한 단씩 위에 올라타 있고, 위층일수록 챙기는 범위가 넓어져요.
| 층 | 무엇을 관리하나 | 중심 질문 |
|---|---|---|
| Prompt engineering | 한 번의 지시문 | 모델에 무엇을 전달할 것인가 |
| Context engineering | 이 순간, 윈도에 무엇을 넣을 것인가 | 무엇을 검색하고, 무엇을 요약하고, 무엇을 버릴 것인가 |
| Harness engineering | 단발 실행의 "장비" | 어떤 도구를 허용하고, 무엇으로 완료를 판단할 것인가 |
| Loop engineering | 하네스 위에서 자동으로 돌린다 | 어떻게 스스로 몇 번이고 돌게 할 것인가 |
루프는 하네스의 바로 한 층 위라는 표현이 딱 맞아요. 아래층의 하네스가 "AI 에이전트의 1회 실행"에 장비를 채워주는 담당이라면, 위층의 루프는 "그걸 자동으로 몇 번이고 돌리는" 담당이죠.
루프는 무엇으로 이루어져 있는가
루프의 5가지 액션
루프라고 해도 "같은 코드가 빙글빙글 도는" 그런 헛돌기와는 달라요. 한 바퀴마다 구체적인 일을 제대로 하고 있고, 분해하면 5가지 액션이 나와요.
| 동작 | 하는 일 |
|---|---|
| 발견(discovery) | 이번 한 바퀴에서 무엇을 해야 하는지 스스로 찾는다 |
| 전달(handoff) | 태스크를 격리해 작업 담당 에이전트에게 넘긴다 |
| 검증(verification) | 다른 에이전트가 "이걸로 괜찮은가"를 체크한다 |
| 기억(persistence) | 상태를 대화 밖(파일 등)에 기록한다 |
| 스케줄링(scheduling) | 타이머 등으로, 내버려 둬도 계속 돌게 한다 |
개인적으로 "이게 없으면 루프가 아니다" 싶은 건 검증이에요. 여기가 허술하면 그저 생성물을 쏟아내기만 하는 장치가 돼요.
루프를 조립하는 6가지 파츠
다음으로, 5가지 액션을 실제로 움직이려면 거기에 맞는 도구가 필요해요. 개발 관점으로 구체화하면, Addy는 루프에 필요한 것을 6가지 파츠로 정리했어요.
| 파츠 | 무엇인가 | 대응하는 동작 |
|---|---|---|
| Automations | 시간표/트리거로 자동 기동 | 스케줄링 |
| Worktrees | 병렬 에이전트의 작업 디렉터리 격리 | 전달 |
| Skills | 지식을 SKILL.md에 고정해 재사용 | 발견 |
| Connectors(MCP) | 이슈 트래커·DB·Slack 등 외부 연결 | 기억 / 발견 |
| Sub-agents | 생성 담당과 평가 담당의 분리 | 검증 |
| Memory | 파일에 남는 상태(MEMORY.md 등) | 기억 |
"파일밖에 못 보는 루프는 할 수 있는 일이 적은 작은 루프"이고, 태스크 티켓, DB, 브라우저에 손이 닿아야 비로소 '대신 일해 준다'가 성립해요.
MCP / Skills는 루프를 위한 하네스라고 생각해 두면 이해하기 쉬워요.
루프 안에서 제일 중요한 것은 "아니라고 말할 수 있는 평가자"
코드를 쓴 본인은 자기 작업에 관대하기 때문이에요.
자기가 쓴 문장의 오타를 못 알아채는 것과 마찬가지로, 쓴 본인에게 채점을 시키면 "괜찮은 것 같은데" 하고 스스로를 설득해 버려요 😅 그래서 쓰는 쪽(생성 담당)과 체크하는 쪽(평가 담당)을 나누고, 평가 담당은 별도의 지시, 가능하면 별도의 모델로 둬요.
Claude Code에는 조건이 성립할 때까지 계속 돌리는 /goal이 있는데, 완료 판단을 작업한 본인이 아닌 다른 모델에게 시켜요.
구조로 보면 /goal의 내부 구현은 prompt 기반의 Stop 훅이에요. 매 턴 마지막에 판단 담당이 조건을 확인하고, 통과하지 못하면 완료시키지 않고 한 바퀴 더 돌려요. 다음 장에서 이걸 직접 조립해 볼게요.
입문: Claude Code로 첫 루프 조립하기
여기까지 읽으셨으면 루프의 개념은 완벽하게 잡히셨을 거예요. 이제 실제 코딩 작업에 쓸 수 있는 루프를 조립해 볼까요. 첫 루프는 Claude Code에 "완료의 정의"와 "빠져나갈 구멍이 없는 체크"를 넘기는 것만으로도 조립할 수 있어요.
여기서는 코딩 태스크를 "직선"이 아니라 "루프"로 돌리는 최소 구성을 파일 3개로 만들어요.
여기서부터는 본인 프로젝트에서 직접 시험해 본다는 전제로 이야기를 진행할게요. React 또는 Next.js, TypeScript로 작성된 프로젝트를 상정합니다.
① CLAUDE.md에 "루프 규약"을 쓴다 (= 완료의 재정의)
프로젝트 루트의 CLAUDE.md에 태스크를 돌리는 방식 자체를 적어요.
포인트는 "완료"를 마음대로 선언하지 못하게 하는 것이에요.
## 루프 규약
각 태스크는 "직선"이 아니라 "루프"로 실행하라:
1. 변경을 작성한다
2. 체크를 실행한다: 테스트 + linter + 타입 체크
3. 실패했는가? 에러를 읽고, 원인을 특정하고, 고친 뒤, 2로 돌아간다
4. 루프는 최대 5회까지
정지 조건:
- 전체 체크 통과 → "완료"라고 보고한다. 통과한 출력을 증거로 첨부한다
- 5회를 다 썼다 → 멈추고, 무엇이 남았는지 보고한다
- 같은 에러가 2회 연속 → 루프를 멈추고, @fixer 를 부르도록 안내한다
금지: 체크 출력 없이 "완료"라고 보고하는 것
금지: 어서션 삭제나 테스트 약화로 통과시키는 것. 고치는 것은 코드이지, 점수판이 아니다
마지막 두 줄의 "금지"가 사실 이 설정에서 제일 잘 들어요. 이걸 안 넣고 돌렸더니, Claude가 3바퀴째에 어서션을 한 줄 슬쩍 지워서 테스트를 통과시킨 적이 있었거든요 😅 테스트는 통과하는데 버그는 그대로. 그것도 아주 천연덕스럽게요. 평가 기준(정지 조건)을 애매하게 두면 루프는 태연하게 꼼수를 써요.
② .claude/settings.json에 훅을 둔다
CLAUDE.md에 규약을 써 놔도 Claude는 도중에 잊어버릴 수 있어요.
그래서 settings.json의 훅을 "단단한 제약"으로 둡니다.
Claude가 멈추려 할 때마다 시스템이 강제로 체크를 실행하고, 그 결과를 대화에 밀어 넣는 구조예요.
{
"hooks": {
"Stop": [{ "hooks": [{ "type": "command", "command": "npm test --silent 2>&1 | tail -20" }] }],
"PostToolUse": [{ "matcher": "Write|Edit", "hooks": [{ "type": "command", "command": "npx tsc --noEmit --pretty false 2>&1 | head -10" }] }]
}
}
- PostToolUse: 파일을 편집할 때마다 타입 체크. 쓰면서 즉시 고치게 한다
- Stop: Claude가 "완료"라고 말하려는 순간, 풀 테스트를 실행한다. 실패하면 출력이 그대로 대화에 들어가, 강제로 한 바퀴 더
이게 아까 나온 **"/goal의 정체 = Stop 훅"**의 자체 제작 버전이에요.
Stop 훅이 "아니라고 말할 수 있는 평가자"를 맡고 있어서, 여기를 통과하지 않는 한 루프는 끝나지 못해요.
Python이라면 pytest -q + pyright, Rust라면 cargo test --quiet + cargo check로 갈아 끼우기만 하면 돼요.
③ .claude/agents/fixer.md (막힌 상황을 별도 컨텍스트로 돌파)
같은 대화 안에서는 못 고치는 문제도 있어요. 이미 4번 시도했고, 컨텍스트가 실패 로그투성이가 돼서 사고가 잠겨 버린 상태죠. 이럴 때는 깨끗한 컨텍스트를 가진 별도 에이전트에게 넘겨요.
---
name: fixer
description: 같은 테스트가 2회의 수정 시도 후에도 실패할 때 사용하는, 교착 상태 돌파용 에이전트
tools: Read, Edit, Grep, Glob, Bash
model: opus
---
너는 실패한 체크를 고친다. 추측은 금지.
1. 실패한 체크를 직접 실행하고, 에러 전문을 읽어라
2. 실패 경로상의 파일을 처음부터 끝까지 전부 읽어라
3. 한 문장으로 써라: 진짜 원인은 무엇인가
4. 그 원인만 고쳐라. 김에 하는 리팩터링은 하지 마라
5. 체크를 재실행하고, 수정 전후의 출력을 보고하라
금지: 테스트 삭제, 어서션 완화, try/catch로 에러 뭉개기, 테스트 skip 처리
메인 대화에서 호출되지만 직전 4번의 실패 기억이 없는 채로 처음부터 진단하기 때문에, 한 번에 통과하는 경우가 꽤 있어요. 이게 바로 6가지 파츠의 Sub-agents(생성 담당과 평가 담당의 분리) 그 자체예요.
조립하고 나면
CLAUDE.md에 규약, .claude/settings.json에 훅, .claude/agents/fixer.md에 fixer. 이 셋을 두고 Claude에게 실제 태스크를 넘기기만 하면 돼요.
조립이 끝나면 터미널의 에러를 복사해서 다시 붙여 넣는 작업이 사라져요. Claude가 스스로 실행하고, 스스로 보고, 스스로 고치고, 막히면 fixer를 불러요.
당신은 마지막에 diff를 한 번 보기만 하면 돼요 ☺️
직접 개발한다면 — Mastra
여기까지의 Claude Code는 새 프레임워크 없이 바로 손에서 시험해 볼 수 있는 입구였어요. 이제부터는 자기 서비스나 도구에 루프를 심어서 개발하는 쪽 이야기로 넘어갈게요.
Mastra의 Goals 기능
루프의 뼈대를 처음부터 직접 쓰지 않아도, 아까의 /goal(조건이 성립할 때까지 돌리고, 완료는 별도 모델이 판단)에 해당하는 Goals 기능이 프레임워크에 내장돼 있어요.
Goals는 "상시 유지되는 목표"를 스레드에 갖게 하고, 매 바퀴마다 별도의 판단 담당(judge) 모델이 채점하며, 달성하거나 상한에 도달할 때까지 계속 돌려요.
import { Agent } from '@mastra/core/agent';
const worker = new Agent({
name: 'worker',
instructions: '소프트웨어 태스크를 끝까지 해낸다',
model: 'openai/gpt-5.5',
memory, // storage를 연결한 memory가 전제
goal: {
judge: 'openai/gpt-5-mini', // 작업 담당과는 별도의 "판단 담당" 모델
maxRuns: 30, // 헛돌기의 상한 (= 비용 상한선)
prompt: '테스트가 통과했을 때만 완료로 간주한다',
},
});
// 스레드에 대한 "상시 유지되는 목표"를 설정 (리로드해도 남는다)
await worker.setObjective('/health 엔드포인트를 추가하고 테스트한다', {
threadId,
resourceId,
});
judge(작업 담당과 별도 모델로 채점)와 maxRuns(헛돌기의 상한 = 비용 상한선), 이 둘이 핵심이에요. Claude Code의 /goal에서 하던 일이 설정 2개로 그대로 옮겨져 있죠.
[보완 — 추가된 내용] Mastra Goals 공식 문서: https://mastra.ai/docs (Goals는 agent.setObjective()로 목표를 스레드 상태에 저장하고, 매 반복마다 별도 judge 모델이 판단하는 구조입니다.)
Goals는 비교적 새로운 experimental 기능(
@mastra/core@1.42.0추가)으로, 공식 문서에서도 "향후 바뀔 수 있다"고 밝히고 있습니다. 최신 인자는 공식 문서를 확인해 주세요. 정지 조건이 코드로 True/False를 낼 수 있는 결정적인 것이라면, 워크플로의.dountil()을 쓰는 방법도 있습니다.
계속 돌려두는 것의 대가
편리한 반면, 루프에는 내버려 두면 조용히 빚을 쌓는 측면이 있어요.
A loop running unattended is also a loop making mistakes unattended. (아무도 보지 않는 루프는, 아무도 보지 않는 채로 계속 실수하는 루프이기도 하다)
크게 4가지예요.
| 함정 | 증상 | 한 줄 예방법 |
|---|---|---|
| 검증 밀림 | 성과물은 쌓이는데 아무도 체크하지 않는다 | 작업 담당과 별도의 평가 담당을 넣는다 (= Stop 훅) |
| 이해 저하 | 코드는 늘어나는데 머릿속 지도는 낡은 그대로 | 정기적으로 산출물을 읽고, 스스로 설명할 수 있는지 확인한다 |
| 판단 포기 | 루프가 돌면, 사람이 "뭐 됐지" 하고 의견을 갖지 않게 된다 | 실행은 맡겨도 판단은 놓지 않는다 |
| 토큰 폭주 | 사용량이 요동치고 청구액을 예측할 수 없다 | 프로덕션 투입 전에 예산·최대 재시도 횟수 상한을 정해 둔다 |
특히 토큰 폭주는 말 그대로 돈 이야기예요. 버그 하나로 밤새 헛돌면, 아침에 일어났을 때 고쳐진 코드가 아니라 낯선 청구서가 기다리고 있을 수도 있어요.
비용 절감이라기보다 사고 방지를 위해, 프로덕션 투입 전에 반드시 하드 상한(1회 예산·1일 예산·최대 재시도 횟수)을 정해 두세요. 아까 CLAUDE.md의 "최대 5회", "같은 에러 2회면 즉시 정지"도 바로 이 상한의 일종이에요.
만약, API 비용이 걱정된다면, Anthropic·OpenAI 모두 조직 단위 지출 한도(spend limit)와 사용량 알림을 콘솔에서 설정할 수 있습니다. 국내 결제는 달러 청구이므로 환율 변동(1달러 ≈ 1,400원대)까지 감안해 예산을 잡는 것이 안전합니다.
마지막으로 — 사실 이 글도 루프로 썼습니다
여기까지 "루프는 이렇게 만든다"는 이야기를 했는데, 마지막으로 한 가지 털어놓을게요 ☺️
이 글 자체를, 여기서 설명한 루프에게 쓰게 했어요. 구성은 그야말로 6가지 파츠의 미니멀 버전이었죠.
- 지시서
PROMPT.md(무엇을 쓸지·문체·참조처) = 발견 - **본문을 쓰는 나(생성 담당)**가 절마다 초고 작성 = 전달
- 평가 담당 서브에이전트 셋 = 검증
- 문체 체크 담당(Sonnet): 내 과거 글에 가까운 문체·구성인가
- 코드 검증 담당(Haiku): 글에 실은 코드가 실제로 동작하는가
- 출처 체크 담당(Haiku): 용어의 할루시네이션 확인
- **
MEMORY.md**에 조사 메모와 판단을 기록 = 기억 - 골(정지 조건)은 "세 평가 담당이 전부 OK", 주 게이트는 문체 체크 담당의 "과거 글에 충분히 가까운가"
솔직히 말하면 이 평가 담당들에게 꽤 시달렸어요. 코드 검증 담당에게는 "Mastra 샘플은 여기 재확인 필요", 문체 체크 담당에게는 "과거 글보다 용어가 딱딱하다", 결정타로 출처 체크 담당에게 "그 용어, 참조 원문에 없다. 마음대로 만든 이름 아닌가?"라고 할루시네이션을 지적당해서 몇 바퀴나 고쳤어요 😅 그야말로 CLAUDE.md의 "금지: 점수판을 만지지 마라"를 제가 당한 꼴이죠.
그리고 제일 실감한 게 이거예요. Addy의 마지막 한 문장이 꽂혔거든요.
Build the loop. But build it like someone who intends to stay the engineer, not just the person who presses go.
루프는 만들어라. 다만 "실행 버튼만 누르는 사람"이 아니라, "엔지니어로 남을 작정인 사람"으로서 만들어라.
루프는 생성을 무한히 싸게 만들지만, "어느 방향으로 쓸 것인가, 어느 산출물을 멈출 것인가"를 정하는 판단만은 대신해 주지 않아요. 이 글도 평가 담당의 배선(엔지니어링)보다, "어떤 글이 좋은 글인가"라는 잣대를 CLAUDE.md에 써서 평가 담당에게 넘긴 부분이 제일 효과가 컸어요.
그릇은 엔지니어링으로 만들 수 있어도, 거기 담을 좋고 나쁨의 기준은 결국 그 일을 해 온 사람 안에만 있는 거더라고요.
궁금해진 분은 어렵게 생각하지 말고, 이 글의 입문 파트대로 CLAUDE.md에 "루프 규약"과 Stop 훅을 두고 실제 태스크를 하나 넘겨 보는 것부터 추천해요.
"아, 이런 거구나" 하고 아마 금방 감이 올 거예요.
이 글이 도움이 됐다면 추천해 주세요