6 minute read

발단: “Claude야, 너 왜 자꾸 멈추는 거야?”

지난 글에서 Claude Code에 자바 DNA를 심는 방법을 알아봤다. Skills로 무장시키니 꽤 쓸만한 페어 프로그래머가 됐다.

그런데 어느 날 퇴근 전에 이런 생각이 들었다.

“내가 자는 동안에도 Claude가 알아서 기능을 구현하고 커밋하면 안 되나?”

아침에 출근해서 git log를 치면 밤새 20개의 커밋이 쌓여있는 상상. 실제로 이런 시스템을 구축하고 운영하는 사례들이 나오고 있다.

문제는 Claude Code의 근본적인 한계에 있었다.

[나] "이 기능 구현해줘"
[Claude] 구현 완료! 다른 도움이 필요하신가요?
[나] (자리 비움)
[Claude] ... (세션 종료)

Claude Code는 Stateless하다. 목표에 도달했다고 판단하거나, 사람의 입력이 필요하면 즉시 멈춘다. 24시간 가동? 표준 세션으로는 불가능하다.

이 문제를 어떻게 극복하는지, 직접 자료를 파헤치고 소스코드까지 뜯어보며 검증해봤다.

핵심 원리: 에이전트 루프(Agentic Loop)

Anthropic의 연구팀은 이 문제를 이렇게 비유했다.

“교대 근무하는 엔지니어들로 구성된 프로젝트를 상상해보라. 새로 온 엔지니어는 이전 교대에 무슨 일이 있었는지 전혀 기억하지 못한다.”

Effective harnesses for long-running agents, Anthropic Engineering

해결의 핵심은 Claude 바깥에서 Claude를 제어하는 것이다.

┌──────────────────────────────────────┐
│        외부 제어 계층 (Shell)          │
│                                       │
│  ┌─────────┐   ┌──────────────────┐  │
│  │ 상태 파일 │◄──│ Claude Code      │  │
│  │ (JSON)   │   │ 세션 #1          │  │
│  └─────────┘   └───────┬──────────┘  │
│       │                 │ (종료)       │
│       │        ┌────────▼─────────┐  │
│       │        │ 종료 감지          │  │
│       │        │ → 다음 작업 확인   │  │
│       │        │ → 새 세션 시작     │  │
│       │        └────────┬─────────┘  │
│       │        ┌────────▼─────────┐  │
│       ▼────────│ Claude Code      │  │
│                │ 세션 #2          │  │
│                └──────────────────┘  │
└──────────────────────────────────────┘

이 구조를 에이전트 루프(Agentic Loop)라고 부른다. 원리는 세 가지다:

  1. 종료 감지: Claude가 세션을 끝내면 외부 스크립트가 이를 감지
  2. 상태 영속화: 작업 목록과 진행 상황을 외부 파일(JSON, Markdown)에 보관
  3. 컨텍스트 격리: 매 작업을 새로운 세션에서 시작해 컨텍스트 오염 방지

여기서 3번이 특히 중요하다. 왜 그런지 설명하겠다.

컨텍스트 오염: 장기 에이전트의 숨은 적

처음에는 “하나의 세션을 계속 유지하면 되지 않나?”라고 생각했다. 틀렸다.

Claude Code의 컨텍스트 윈도우는 유한하다. 장기 실행 세션에서 무슨 일이 벌어지는지 보자:

세션 시작: 컨텍스트 사용량 5%
├── 기능 1 구현: 15%
├── 빌드 로그 출력: 25%  ← 로그가 컨텍스트를 먹기 시작
├── 테스트 실행: 40%
├── 기능 2 구현: 55%
├── 빌드 실패 로그: 70%  ← 에러 메시지가 컨텍스트를 잡아먹음
├── 디버깅 시도: 85%
└── 기능 3 구현: 95%    ← 추론 정확도 급격히 저하

빌드 로그, 테스트 출력, 에러 메시지… 이런 것들이 컨텍스트 윈도우를 급격히 소모한다. 그 결과:

  • 이전에 뭘 구현했는지 잊어버림
  • 같은 버그를 반복해서 만듦
  • 코드 품질이 세션 후반으로 갈수록 떨어짐

이게 바로 컨텍스트 오염(Context Pollution)이다. 해결책은 작업 단위마다 세션을 의도적으로 끊는 것이다. 반직관적이지만 이게 핵심이다.

Anthropic 연구 원문: 실제로 뭘 제안했나

여기서부터가 중요하다. Anthropic의 원문을 직접 읽어보고 정리한 구조는 이렇다.

2-파트 하네스 아키텍처

Anthropic이 제안한 구조는 두 종류의 에이전트 세션으로 구성된다.

[App Spec 문서]
      │
      ▼
┌─────────────────────┐
│ Initializer Agent    │  ← 최초 1회만 실행
│                      │
│ 1. init.sh 생성      │  (개발 서버 자동 실행 스크립트)
│ 2. progress.txt 생성 │  (작업 이력 문서)
│ 3. 초기 git commit   │
│ 4. features.json 생성│  (200개+ 원자적 기능 목록)
└──────────┬──────────┘
           │
           ▼
┌─────────────────────┐
│ Coding Agent         │  ← 이후 모든 세션에서 반복
│                      │
│ 1. pwd 확인          │
│ 2. git log +         │
│    progress 파일 읽기 │
│ 3. features.json에서 │
│    최상위 미완료 선택  │
│ 4. 구현 + 검증       │
│ 5. git commit        │
│ 6. progress 업데이트  │
└─────────────────────┘
     ↑         │
     └─────────┘  (세션 종료 → 새 세션 시작 → 반복)

Features List: 왜 Markdown이 아니라 JSON인가

이게 재미있었다. Anthropic이 feature 추적에 Markdown 대신 JSON을 선택한 이유:

“모델이 JSON 파일을 부적절하게 변경하거나 덮어쓸 가능성이 Markdown보다 낮다.”

실제 구조:

{
  "category": "functional",
  "description": "New chat button creates a fresh conversation",
  "steps": [
    "Navigate to main interface",
    "Click the 'New Chat' button",
    "Verify a new conversation is created",
    "Check that chat area shows welcome state",
    "Verify conversation appears in sidebar"
  ],
  "passes": false
}

규칙은 간단하다: 에이전트는 오직 passes 필드만 수정해야 한다. 테스트를 삭제하거나 수정하는 것은 금지. 원문의 표현을 빌리면:

“테스트를 삭제하거나 편집하는 것은 허용할 수 없다. 이는 기능 누락이나 버그로 이어질 수 있기 때문이다.”

Puppeteer MCP를 활용한 자율 검증

에이전트가 인간 없이 자기 작업을 검증하는 방법이다.

새 기능 구현 전
    │
    ▼
[Puppeteer MCP로 기존 기능 end-to-end 테스트]
    │
    ├── PASS → 새 기능 구현 시작
    │
    └── FAIL → 기존 기능 수정부터 처리

Puppeteer MCP를 사용하면 에이전트가 실제 브라우저에서 버튼을 클릭하고, 페이지를 탐색하고, 스크린샷을 찍어서 기능을 확인할 수 있다. 코드 리뷰만으로는 보이지 않는 버그를 실제 사용자 관점에서 발견하는 데 유효하다.

다만 한계도 있다. 원문에 따르면 Claude가 브라우저 네이티브 alert 모달을 감지하지 못하는 경우가 있었다고 한다.

4가지 실패 모드와 해결 전략

Anthropic이 실제 실험에서 관찰한 실패 패턴과 그 해결법이다. 이게 실무에서 가장 유용한 부분이었다.

실패 모드 Initializer Agent 해결 Coding Agent 해결
너무 일찍 “완료” 선언 end-to-end 검증 포함된 feature list 생성 한 번에 하나의 feature만 작업
환경이 버그 투성이로 방치 초기 git repo + progress 노트 작성 세션 시작 시 progress + git log 읽기
feature를 성급하게 완료 처리 feature list 파일 설정 self-verify 후에만 passes: true
앱 셋업 파악에 시간 낭비 init.sh 자동 실행 스크립트 작성 세션 시작 시 init.sh 읽기

“너무 일찍 완료 선언”이 가장 흔한 문제라는 게 인상적이었다. 에이전트가 “구현 완료!”라고 표시해놓고 실제로는 절반만 된 경우가 많았다고 한다. 그래서 Puppeteer로 self-verify하는 단계가 필수인 것이다.

에이전트 성숙도 3단계

이 연구를 포함해서 커뮤니티에서 사용되는 자율 에이전트 접근법을 크게 세 단계로 나눌 수 있다.

단계 접근법 컨텍스트 관리 적합한 작업
Level 1 단순 반복 루프 세션 유지 (격리 없음) 리팩토링, 테스트 보강
Level 2 하위 에이전트 격리 하위 세션에서 로그 격리 중소규모 기능 구현
Level 3 사양 기반 자율 루프 세션 단위 완전 격리 + JSON 상태 풀스택 앱 구축
  • Level 1: Ralph Wiggum 같은 도구가 bash while true 루프로 Claude를 반복 실행
  • Level 2: Craig Motlin의 markdown-tasks 플러그인처럼 하위 에이전트에게 작업을 위임
  • Level 3: Anthropic 연구의 2-파트 하네스처럼 Initializer + Coding Agent 구조

각 도구의 실제 사용법과 소스코드 분석은 다음 글 (실전편)에서 다룬다.

핵심 설계 원칙: 좋은 엔지니어가 하는 일을 에이전트에게 시키기

Anthropic 연구팀이 밝힌 설계 영감의 출처가 인상적이었다:

“이러한 실천법의 영감은 효과적인 소프트웨어 엔지니어가 매일 하는 일을 아는 것에서 왔다.”

생각해보면 당연하다:

  • 좋은 개발자: 출근하면 Jira 보고 → git log 확인 → 현재 상태 파악 → 코딩 시작
  • 좋은 에이전트: 세션 시작하면 features.json 읽고 → git log 확인 → progress 파악 → 코딩 시작

  • 좋은 개발자: 기능 만들면 테스트 돌리고 → 리뷰 받고 → 커밋
  • 좋은 에이전트: 기능 만들면 Puppeteer로 self-verify → passes 업데이트 → 커밋

자율 에이전트의 설계는 결국 “좋은 개발자의 습관을 자동화하는 것”이었다.

정리: 미래의 나에게

TL;DR

  • Claude Code는 Stateless → 외부 루프로 24시간 가동 가능
  • 핵심 3요소: 종료 감지 + 상태 파일 영속화 + 컨텍스트 격리
  • Anthropic 연구의 실제 구조: 2-파트 하네스 (Initializer Agent + Coding Agent)
  • 원문의 실제 구조: 3단계 초기화 + 반복 세션
  • Feature 추적은 Markdown보다 JSON이 안전 (모델이 덜 건드림)
  • 가장 흔한 실패: “너무 일찍 완료 선언” → self-verify 필수

주의할 점

  • 컨텍스트 오염은 눈에 보이지 않지만 코드 품질을 서서히 갉아먹는다
  • 에이전트의 “구현 완료” 선언을 그대로 믿으면 안 된다. 검증 단계가 반드시 필요

더 파볼 거리

References

Comments