당신의 에이전트는 반복하고 있는 것이 아닙니다. 그래서 당신은 에이전트가 헛돌고(Spinning) 있는 것을 잡지 못하는 것입니다.
요약
장기적 목표 수행(long-horizon) 에이전트가 진전 없이 무의미한 작업을 반복하는 '헛돎(Spinning)' 현상의 원인과 탐지 한계를 분석합니다. 기존의 입력 기반 중복 탐지 방식이 놓치는 정체된 에이전트의 패턴을 지적합니다.
핵심 포인트
- 에이전트의 헛돎(Spinning)은 단순 반복이 아닌 고유한 시그니처를 가진 작업의 연속으로 나타남
- 기존의 정확한 반복, 유사 중복, 헛돎 수렴 탐지 방식은 입력 측 탐지기에 국한되어 사각지대가 존재함
- 효과적인 에이전트 구축을 위해 문맥 압축, 마일스톤 설계, 헛돎 방지 메커니즘이 필수적임
저는 Zhejiang University의 ZJU-REAL 연구실에서 발표한 BEACON(arXiv:2605.06078)을 읽어왔는데, 읽으면 읽을수록 우리가 진행 중인 장기적 목표 수행(long-horizon) 에이전트 작업에 매우 직접적으로 적용 가능하다는 느낌을 받았습니다. 그래서 이를 시리즈로 연재해 보려 합니다.
프레임워크 (The framing)
실제로 작동하는 장기적 목표 수행 (long-horizon) 에이전트를 구축하려면, 두 가지가 제대로 되어야 한다고 생각합니다.
1. 에이전트가 목표를 향해 계속해서 진전을 이루어야 함
- 문맥 압축 (Context compaction) — 무엇을 잊어도 안전한가
- 마일스톤 설계 (Milestone design) — 특정 단계가 '정말로' 완료되었음을 어떻게 알 수 있는가
- 헛돎 방지 (Spin prevention) — 에이전트가 막혔을 때, 무언가가 이를 감지해야 함
2. 에이전트가 스스로 성찰하고 개선할 수 있어야 함
이 포스트는 첫 번째 항목인 헛돎 방지 (spin prevention)에 대해 다룹니다. 이것이 우리에게 가장 큰 고통을 주었기 때문에 가장 먼저 다룹니다.
증상 (The symptom)
사용자 보고가 있었고, 내부적으로도 확인했습니다. 매우 긴 작업 중 일부가 중간에 진행을 멈춰버리는 현상이 발생했습니다.
로그를 살펴보면 에이전트는 파일을 읽고, 검색하고, 명령어를 실행하며 결코 유휴 상태(idle)가 아닌, 매우 '바쁜' 상태입니다. 하지만 30분이 지나도록 아무것도 진전되지 않습니다.
저의 첫 번째 가설은 문맥 손실 (context loss)이었습니다. 압축 (compaction) 과정에서 "이 경로를 이미 시도했다"는 기록이 삭제되어, 에이전트가 다시 그 경로를 시도하게 되었다는 것이었습니다.
코드와 로그를 파헤쳐 본 결과, 그것은 이야기의 절반에 불과했습니다.
우리는 이미 방어 기제(guards)를 갖추고 있었습니다. 세 가지 계층으로 말이죠.
| 계층 (Tier) | 기준 (Criterion) | 임계값 (Thresholds) |
|---|---|---|
| 정확한 반복 (Exact repeat) | 도구 이름 + 정규화된 인자 (canonicalized args), 바이트 단위로 동일 | LOOP_WARN=3 경고 / LOOP_HARD=5 강제 중단 |
| 유사 중복 (Near-duplicate) | 가변적인 ID / 타임스탬프 필드를 제외하고 동일함 | NEAR_DUP_LOOP_WARN=6 / NEAR_DUP_LOOP_HARD=12 |
| 헛돎 수렴 (Spin convergence) | ≥2회의 압축 (compaction) 및 도구 루프 예산(tool-loop budget)의 ≥75% 소진 | SPIN_CONVERGENCE_MIN_COMPACTIONS=2 |
SPIN_CONVERGENCE_TOOL_LOOP_RATIO=0.75 |
따라서 문제는 아무도 아무것도 만들지 않았다는 것이 아니었습니다. 우리가 만든 것이 그것을 잡아낼 수 없었다는 것이 문제였습니다.
사각지대: 세 가지 모두 입력 측 탐지기(input-side detectors)입니다.
toolCallSignature = 도구 이름 + 정규화된 인자 (canonicalized args)
그 시그니처(signature)는 정확히 한 가지 질문에 답합니다: 당신은 똑같은 일을 두 번 하고 있는가?
하지만 정체된 유능한 모델은 호출을 반복하지 않습니다. 대신 다음과 같이 행동합니다:
파일 A 읽기 → grep X → 다른 라인 범위로 A 다시 읽기
→ 약간 다른 명령 실행 → 파일 B 읽기 → 다시 grep 수행 …
모든 개별 호출은 고유한 시그니처를 가집니다. 세 가지 계층(tiers) 모두 침묵을 유지합니다.
그리고 그 전체 구간 동안, 검증 가능한 상태 변화(verifiable state changes)의 수는 0입니다. 실제로 다시 작성된 파일도 없고, 새로운 결과를 생성하는 명령도 없습니다. 되돌릴 수 없는(irreversible) 일이 전혀 일어나지 않고 있습니다.
한 줄로 요약하자면:
루프 탐지(Loop detection) ≠ 정체 탐지(stall detection). 전자는 입력을 보고, 후자는 출력을 보아야 합니다.
논문은 정확히 그러한 출력 측면의 정의를 제공합니다
BEACON의 세그먼트 내 보상(in-segment reward)은 다음과 같습니다:
r_t = R_ms · γ^(t_k − t) 만약 세그먼트가 마일스톤(milestone)으로 끝난다면
= 0 그렇지 않다면
마일스톤으로 종료되지 않는 세그먼트는 그 안에 얼마나 많은 행동(action)이 포함되었는지와 관계없이 정확히 0을 얻습니다. 행동 횟수는 공식에 전혀 포함되지 않습니다.
이것은 제가 본 것 중 _바쁨(busy) ≠ 진전(progressing)_을 가장 깔끔하게 공식화한 방식입니다.
베이스라인(baseline) 계층은 훨씬 더 가혹합니다. 베이스라인은 그룹 평균의 단계별(per-step) 리턴(return)이므로, 만약 당신의 세그먼트가 그룹 평균인 5단계보다 긴 8단계를 소요했다면, 세그먼트 전체의 어드밴티지(advantage)는 음수 쪽으로 치우치며, 페널티는 초과한 정도에 비례하여 커집니다.
헛도는 것(Spinning)은 단순히 보상을 받지 못하는 것에 그치지 않습니다. 비례적으로 적극적인 페널티를 받게 됩니다.
논문의 그림 8(Figure 8)에는 마지막 두 행동이 동일하게 −2.20을 받는 실패한 궤적(trajectory)이 있습니다. 그 꼬리 부분 — 즉, 마지막 마일스톤 이후 다음 마일스톤에 도달하지 못하는 구간 — 이 바로 헛도는 것(spinning)의 수학적 형태입니다.
이것이 얼마나 흔할까요? 그들의 데이터에 따르면: 최소 하나 이상의 하위 목표(subgoal)를 완료했지만 작업(task)에는 실패한 궤적은 샘플의 39–47% 수준에서 일정하게 유지됩니다.
"단계 수에 따른 분할" 아이디어를 무력화하는 하나의 어블레이션(ablation)
이것은 제가 가장 가치 있다고 느낀 수치입니다:
| 분할 방식 (Partitioning) | 점수 (Score) | 베이스라인 (72.8) 대비 |
|---|---|---|
| 무작위 5방향 분할 (Random 5-way split) | 74.2 | +1.4 |
| 실제 마일스톤 (Real milestones) | 91.4 | +17.2 |
제품 관점에서 번역하자면: 임의의 단계 수(step counts)로 분할하는 것은 거의 가치가 없습니다. 실제 구조(real structure)에 따라 분할하는 것은 매우 가치가 큽니다.
이제 우리의 세 번째 계층 기준인 _"도구 루프(tool-loop) 예산의 75% 소모"_를 다시 살펴보십시오. 이것은 임의의 단계 수 트리거(arbitrary-step-count trigger)입니다. 이는 당신이 무엇을 달성했는지가 아니라, 얼마나 소모했는지를 묻습니다.
올바른 형태는 다음과 같습니다:
❌ 만약 단계(steps) > N → 개입 (intervene)
✅ 만약 단계(steps) > N AND 검증된 마일스톤이 0개 → 개입 (intervene)
두 번째 방식은 진행 중인 정당하게 긴 작업(진행 과정에서 마일스톤을 달성함)에 대해 잘못 작동(misfire)하지 않습니다. 첫 번째 방식은 그렇게 할 것입니다.
또한 양의 피드백 루프(positive feedback loop)가 존재합니다
몇 가지 상수들을 나란히 놓아보겠습니다:
컨텍스트(context)가 82%에 도달 → 압축(compaction) 트리거
2회의 압축 + 루프 예산의 75% 소모 → 그제서야 헛돎(spinning) 플래그 표시
헛돎(Spin) → 컨텍스트가 가득 참 → 압축(compaction) 실행 → 지속 가능한 상태(durable state)가 요약되어 사라짐 → 손실된 내용을 다시 유도(re-derive)함 → 더 많이 헛돎.
헛돎 감지기(spin detector)는 압축이 반복적으로 발생했다는 점을 관찰함으로써 헛돎을 추론합니다 — 하지만 압축은 바로 그 건망증(amnesia)을 유발하는 단계입니다. 이는 하류 증상(downstream symptom)을 감지하는 것이며, 루프가 두 번 돌아갈 때까지 기다려야만 합니다.
설상가상으로, 그 개입 방식은 _모델이 자신의 지속 가능한 상태(durable state)에 다시 고정(re-anchor)하도록 유도하는 것_입니다. 만약 지속 가능한 상태가 정확히 압축되어 사라진 것이라면, 다시 고정할 대상이 아무것도 남지 않게 됩니다.
이것은 상태 계층(state-layer)의 문제에 대한 프롬프트 계층(prompt-layer)의 패치입니다.
우리가 변경하려는 것
네 번째 계층을 추가합니다: 출력 측 정체 감지(output-side stall detection). 도구 관찰(tool observations)로부터 기계적으로 도출되는 두 개의 카운터입니다. 모델의 판단은 필요하지 않습니다.
1. 마지막 검증된 마일스톤 이후 경과된 단계 수 (Steps since the last verified milestone)
2. 중복 제거된 새로운 상태 변화 (Deduplicated new state changes) ← 더 유용한 것
- 동일한 해시(hash)로 파일 읽기 → 새로운 정보가 아님 (다른 줄 범위에서 동일한 파일을 다시 읽어도 해시는 변하지 않음)
- 명령어 + 종료 코드(exit code) + 표준 출력(stdout) 해시가 동일함 → 새로운 정보가 아님
afterHash가 변하지 않은 상태에서의 쓰기(Write) → 실제로 아무것도 쓰이지 않음
이 카운터는 시그니처 매칭(signature matching)이 놓치는 바로 그 사례를 목표로 합니다: 모든 동작은 다르지만, 정보 이득(information gain)은 제로인 경우. 이 기준은 기계적입니다. 즉, 의미론적 이해(semantic understanding)가 필요하지 않습니다.
그다음, 일회성 자극이 아닌 지속적인 압박을 가하십시오:
모델이 볼 수 있도록 문맥 내에서 카운터를 드러냄
→ 계획 수정 강제 (이 경로가 막혔음을 인정)
→ 사용자에게 질문
...
마지막 단계가 중요합니다: 멈출 생각이라면, 당신이 얻은 것을 붙들고 있지 마십시오 — 이는 앞서 언급한 39~47%라는 수치와 맥을 같이 합니다.
한 가지 솔직한 단점 (One honest discount)
BEACON은 훈련(training) 방법입니다. 이는 훈련된 정책(policy)이 방황할 가능성을 줄이도록 그래디언트(gradients)를 형성하지만, 자체적인 런타임 탐지 또는 개입 메커니즘은 없습니다.
BEACON은 진행(progress)에 대한 정의를 제공할 뿐, 컨트롤러(controller)를 제공하는 것이 아닙니다. 임계값(Thresholds), 단계적 에스컬레이션(escalation ladders), 중단 시점 등은 모두 직접 설계해야 합니다. 논문은 이 부분에 대해서는 도움을 주지 않습니다.
또한, 단계별 베이스라인(per-step baseline)은 참조값으로 그룹 평균 세그먼트 길이(group-average segment length)를 필요로 합니다. 실제 운영 환경(production)에서 특정 사용자 작업은 보통 단 한 번만 실행되므로 그룹이 존재하지 않습니다. 과거의 유사한 작업들로 근사치를 구할 수밖에 없는데, 이는 상당히 노이즈가 많습니다.
하지만 가장 먼저 해야 할 일은 수정하는 것이 아니라 — 측정하는 것입니다
측정 도구(Instrument)만 갖추십시오. 결정 로직은 변경하지 마십시오. 현재 우리가 답할 수 없는 질문에 답하십시오:
운영 환경에서 발생하는 스핀(spins) 중, 기억 상실 유형(amnesia-type)과 그래디언트 부재 유형(no-gradient-type)은 각각 얼마나 되는가?
- 주로 _호출 시그니처(call signatures)는 모두 다르지만 정보 이득이 제로인 상태_를 동반함 → 그래디언트 부재 유형(no-gradient-type). 기존의 3단계 구조로는 이를 구조적으로 잡아낼 수 없습니다. 출력 측면의 탐지(output-side detection)가 필요합니다.
- 주로 _이미 압축되어 사라진 콘텐츠를 다시 읽는 상태_를 동반함 → 기억 상실 유형(amnesia-type). 수정이 필요한 부분은 압축(compaction) 과정에서 무엇을 보존할 것인가 하는 점입니다.
이 두 가지 방향은 완전히 다른 투자를 요구합니다. 먼저 설계하는 것보다 먼저 측정하는 것이 더 저렴합니다.
다음 포스트: 왜 AI의 자기 성찰 (self-reflection)은 항상 "더 주의를 기울여라"라는 결론으로 끝나는가.
만약 당신이 장기 실행 에이전트 (long-running agents)를 구축하고 있다면 — 에이전트가 막혔을 때 어떤 모습인가요? 동일한 행동을 반복하고 있나요, 아니면 아무것도 변하지 않으면서 바쁜 척만 하고 있나요? 다른 분들은 무엇을 보고 계시는지 궁금합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기