콘텐츠로 이동

멜트다운: 롱호라이즌 루프의 붕괴

10-3 챕터에서 다룬 오류 복리는 수학적 현상이다. 하지만 실제 에이전트 루프에서는 단순한 확률 저하를 넘어 더 극적인 현상이 발생한다. 루프가 길어질수록 에이전트의 행동 패턴이 시스템적으로 붕괴하는 것이다. 이것을 **멜트다운(meltdown)**이라고 부른다.

멜트다운은 “가끔 실수를 한다”는 수준이 아니다. 에이전트가 초기에는 합리적으로 보이는 행동을 하다가, 이터레이션이 쌓일수록 점점 비일관적이고, 자기 모순적이고, 목표와 무관한 행동을 하게 되는 시스템적 붕괴다.

┌─────────────────────────────────────────────────────────────────┐
│ 멜트다운 진행 단계 │
├──────────────────┬──────────────────────────────────────────────┤
│ 1단계 │ 일관성 있는 오류 (Coherent-Wrong) │
│ │ 에이전트가 틀린 방향을 확신하며 추진 │
│ │ 표면적으로는 논리적이지만 전제가 잘못됨 │
├──────────────────┼──────────────────────────────────────────────┤
│ 2단계 │ 컨텍스트 포화 (Context Saturation) │
│ │ 도구 결과, 에러 메시지, 재시도 로그가 축적 │
│ │ 유효한 신호와 노이즈가 구분 불가 │
│ │ 모델이 중간 정보를 처리하지 못하기 시작 │
├──────────────────┼──────────────────────────────────────────────┤
│ 3단계 │ 비일관성 루프 (Incoherent Loop) │
│ │ 이미 완료한 작업을 반복 시도 │
│ │ 이전 스텝과 모순된 결정 │
│ │ 원래 목표와 무관한 도구 호출 │
└──────────────────┴──────────────────────────────────────────────┘

1단계 — 일관성 있는 오류는 가장 위험한 단계다. 이 시점에서 에이전트의 행동은 겉으로 보기에 합리적이기 때문이다. 에이전트는 확신을 갖고 특정 방향을 추진한다. 하지만 그 방향의 전제가 잘못됐다. 예를 들어, 코드 버그를 고치는 루프에서 에이전트가 “이 버그는 라이브러리 버전 문제”라는 잘못된 진단을 내리고 그 방향으로 모든 노력을 집중하는 경우다. 이후 모든 스텝이 이 잘못된 전제 위에 쌓인다.

2단계 — 컨텍스트 포화는 컨텍스트 창이 오염되는 단계다. 이터레이션이 쌓이면서 도구 결과, 에러 메시지, 실패한 시도들의 로그가 축적된다. 5-2 챕터에서 다룬 “중간에 묻히는 정보” 문제가 여기서 심화된다. 처음 이터레이션에서 확인한 중요한 사실이 수백 토큰 분량의 이후 내용에 묻혀 모델이 더 이상 적절히 참조하지 못한다.

3단계 — 비일관성 루프는 임계점을 넘은 상태다. 이미 완료한 파일 수정을 다시 시도하거나, 이미 실패했던 방법을 같은 파라미터로 재시도하거나, 갑자기 원래 목표와 전혀 무관한 도구를 호출하기 시작한다. 외부에서 보면 에이전트가 “제정신이 아닌” 것처럼 보이지만, 오염된 컨텍스트 안에서는 나름의 내부 논리가 있다.

코드 작업에서 멜트다운은 특히 관찰하기 쉽다. 초기 이터레이션에서 에이전트는 읽기 좋은 코드를 생성한다. 함수는 적절히 분리돼 있고, 변수명은 의미 있으며, 로직은 일관적이다. 그런데 루프가 길어지면서 수정을 거듭할수록 코드 품질이 저하되는 패턴이 나타난다.

에러를 빠르게 수정하려는 압박 때문에 임시방편(hotfix)이 쌓인다. 원래 구조와 맞지 않는 특수 케이스 처리가 추가된다. 이미 다른 곳에서 처리되고 있는 로직이 중복 추가된다. 이 모든 것이 에이전트가 전체 코드베이스 구조를 파악하기 어려운 오염된 컨텍스트 속에서 국소적 문제만 해결하려다 생기는 현상이다.

멜트다운에 대한 방어는 세 레이어로 구성된다.

레이어 1 — 컨팩션(Context Compaction)

컨텍스트가 특정 임계치(예: 전체 컨텍스트 창의 80~90%)에 도달하면 전체를 요약해 새 컨텍스트로 교체한다. Claude Code는 이 임계치를 약 92%로 설정해 사용한다. 요약 시에는 완료된 작업의 결과, 발견된 핵심 정보, 아직 해결되지 않은 문제만 보존하고 상세 과정 로그는 제거한다.

컨팩션의 핵심은 요약이 단순 압축이 아니라 구조화된 상태 추출이어야 한다는 점이다.

개념 이해용 의사 코드이며 실제 API와 다를 수 있습니다.

def compact_context(messages: list[dict], model: str) -> list[dict]:
"""
컨텍스트가 임계치를 넘으면 구조화된 상태 요약으로 교체한다.
"""
summary_prompt = """다음 에이전트 루프 히스토리를 분석해 핵심 상태를 추출하라.
반드시 포함할 내용:
1. 원래 목표
2. 완료된 작업 목록 (결과 포함)
3. 발견된 중요 사실
4. 현재 미해결 문제
5. 다음 시도할 접근법
루프 히스토리:
{history}
JSON 형식으로 응답하라."""
history_text = messages_to_text(messages)
summary = call_model(summary_prompt.format(history=history_text), model=model)
# 요약만 남기고 전체 히스토리 교체
return [
{"role": "user", "content": f"[컨텍스트 요약]\n{summary}"},
{"role": "assistant", "content": "이해했습니다. 요약된 상태를 기반으로 계속 진행합니다."},
]

레이어 2 — 체크포인트

중요한 마일스톤마다 에이전트 상태를 저장한다. 파일 수정이 완료될 때마다 git commit, 서브태스크가 완료될 때마다 상태 스냅샷을 저장한다. 멜트다운이 감지되면 마지막 안정적인 체크포인트로 롤백해 재시도할 수 있다.

레이어 3 — 서브에이전트 격리

긴 루프를 작업 단위로 나눠 각 서브에이전트가 신선한 컨텍스트에서 시작하도록 한다. 서브에이전트가 완료하면 오케스트레이터에게 구조화된 결과만 반환한다. 서브에이전트의 내부 루프에서 발생한 컨텍스트 오염이 다른 서브에이전트나 오케스트레이터에게 전파되지 않는다.

세 레이어의 방어를 발동시키려면 멜트다운이 발생하고 있음을 먼저 감지해야 한다. 몇 가지 신호가 있다.

  • 반복 도구 호출: 같은 파라미터로 동일한 도구를 3회 이상 연속 호출
  • 출력 해시 불변: 연속된 이터레이션에서 에이전트 추론 출력이 거의 동일
  • 목표 이탈 판단: 현재 도구 호출 내용이 초기 목표와 의미적으로 괴리
  • 컨텍스트 비율: 입력 토큰이 컨텍스트 창의 임계 비율 초과

이 신호들을 자동으로 감지하는 “감시 루프(watchdog loop)“를 에이전트 하네스에 포함하는 것이 롱호라이즌 시스템의 표준 패턴이다.

다음 챕터에서는 이런 문제들을 각 프레임워크가 어떻게 해결하는지, LangGraph·Agents SDK·CrewAI·AutoGen·smolagents를 구조적으로 비교한다.

참고 자료