OpenClaw 에이전트 체인이 긴 스텝에서도 멈추지 않게: 토큰 예산 동적 분배 전략
멀티스텝 에이전트 파이프라인을 처음 운영해본 분이라면 한 번쯤은 이 상황을 경험했을 겁니다. 로컬 테스트에서는 문제없이 돌아가던 에이전트 체인이 운영 환경에서 30~40스텝 즈음에 갑자기 컨텍스트 초과 오류를 뿜으며 죽어버리는 것. 저도 처음엔 "아, 그냥 컨텍스트 긴 모델로 바꾸면 되겠지"라고 생각했는데, 그게 근본적인 해결책이 아니더라고요.
문제의 핵심은 에이전트가 매 스텝마다 이전 도구 출력과 히스토리를 프롬프트에 누적한다는 것입니다. ReAct 루프 기반 에이전트가 긴 체인에서 도구 출력을 쌓다가 컨텍스트 한계에 도달하는 패턴은 실제로 흔한 실패 모드로 보고되고 있습니다. 더 큰 컨텍스트 윈도우는 문제를 미룰 뿐이고, 체인이 길어질수록 매 호출의 입력 토큰이 누적되기 때문에 단계당 비용이 선형보다 가파르게(super-linear) 증가하는 구조입니다.
이 글의 목표는 명확합니다. 토큰 예산을 1급 엔지니어링 관심사로 다루는 방법, 즉 컨텍스트 초과가 발생한 뒤 대응하는 게 아니라 미리 막는 전략을 OpenClaw 파이프라인에 적용할 수 있는 패턴 중심으로 풀어볼게요.
왜 컨텍스트가 터지는가, 그리고 왜 더 일찍 터지는가
토큰 소비의 구조적 분포
OpenClaw 세션에서 토큰이 어디에 쓰이는지 대략적인 분포를 살펴보면 이런 모양입니다. 아래 수치는 특정 파이프라인 조건에서 관찰되는 경향치이며, 실제 비율은 모델, 도구 유형, 프롬프트 크기에 따라 크게 달라집니다.
| 구성 요소 | 비중 추정 (경향치) |
|---|---|
| 대화 히스토리 | 40~50% |
| 도구 출력 | 20~30% |
| 시스템 프롬프트 · 스킬 문서 | 나머지 |
일부 분석 사례에서는 20개 규모의 순차 에이전트 파이프라인이 최종 답변 이전에 수만 토큰 단위의 누적을 보고하기도 합니다(측정 조건이 글마다 다르므로 자기 환경 수치는 직접 측정해야 합니다).
소프트/하드 임계값: 오류 전에 개입하는 구조
표준적인 컨텍스트 관리 패턴은 단일 임계값이 아니라 레이어드 구조를 씁니다. 중요한 점은 같은 임계값에서 매 스텝 경고가 반복 발화되지 않도록 상태 플래그를 둬야 한다는 것입니다.
경고 → 요약 트리거 → 차단으로 이어지는 이 레이어드 구조는 Gravitee AI Gateway(2026-10 기준 접근 가능)를 비롯한 여러 도구에서 공통 패턴으로 자리잡고 있습니다.
단계별 예산 할당: 특정 영역이 예산을 독식하지 않게
처음엔 그냥 "토큰이 얼마 남았나" 체크만 했는데, 그것만으론 부족했습니다. 히스토리가 예산의 80%를 잡아먹어서 도구 출력을 담을 공간이 없어지거나, 반대로 도구 출력 하나가 폭발적으로 커져서 나머지를 밀어내는 상황이 자주 생기더라고요.
핵심은 섹션별로 예산을 명시적으로 배분하는 것입니다. 아래는 OpenClaw의 실제 API가 아니라 패턴을 설명하기 위한 개념적 예시입니다.
# 개념적 예시: 단계별 예산 배분 패턴
class TokenBudgetAllocator:
def __init__(self, total_budget: int):
self.total = total_budget
self.allocations = {
"goal": int(total_budget * 0.10),
"history": int(total_budget * 0.40),
"tools": int(total_budget * 0.30),
"output": int(total_budget * 0.20),
}
self.consumed = {k: 0 for k in self.allocations}
def can_fit(self, section: str, token_count: int) -> bool:
remaining = self.allocations[section] - self.consumed[section]
return token_count <= remaining
def record(self, section: str, token_count: int):
self.consumed[section] += token_count
def usage_ratio(self, section: str) -> float:
alloc = self.allocations[section]
return self.consumed[section] / alloc if alloc > 0 else 1.0
def rebalance(self):
# 재분배 대상이 아닌 섹션(goal, output)의 slack만 수집해야
# 총합이 total_budget을 초과하지 않습니다.
donors = ("goal", "output")
slack = sum(
max(0, self.allocations[k] - self.consumed[k])
for k in donors
)
if slack <= 0:
return
# 공여 섹션의 할당 축소
for k in donors:
free = max(0, self.allocations[k] - self.consumed[k])
self.allocations[k] -= free
# 수요 섹션에 재분배 (history 우선)
self.allocations["history"] += int(slack * 0.6)
self.allocations["tools"] += slack - int(slack * 0.6)이 패턴의 핵심은 rebalance()입니다. 목표 섹션이 할당량보다 적게 썼다면, 그 여유분을 히스토리나 도구 출력에 동적으로 재분배할 수 있어요. 단, slack을 자기 자신이 포함된 집합에서 거두면 예산 총합이 초과되므로, 공여 섹션과 수요 섹션을 분리해야 합니다.
80% 임계값 체크포인트: 오류 전에 요약 압축하기
context-budgeting-v2 스킬(2026-10 기준 접근 가능 여부 확인 필요)이 설명하는 핵심 패턴은 이렇습니다. 사용량이 80%를 넘으면 자동으로 체크포인트 루틴을 실행하는데, 최근 5~10턴은 원본 형태로 유지하고 이전 내용만 요약 압축합니다. 최신 정보를 날리지 않으면서 컨텍스트를 확보하는 거죠.
# 개념적 예시: 80% 임계값 체크포인트 패턴
def checkpoint_if_needed(history: list[dict], allocator: TokenBudgetAllocator) -> list[dict]:
if allocator.usage_ratio("history") < 0.80:
return history
RECENT_TURNS = 8
recent = history[-RECENT_TURNS:]
older = history[:-RECENT_TURNS]
if not older:
return history
summary = summarize_turns(older) # 실제 구현은 LLM 요약 호출
compressed_history = [
{"role": "system", "content": f"[이전 대화 요약]\n{summary}"}
] + recent
new_token_count = estimate_tokens(compressed_history)
allocator.consumed["history"] = new_token_count
return compressed_history한 가지 주의할 점: 요약 과정에서 중요 정보가 손실될 위험이 있습니다. 무작위로 자르는 방식은 오히려 에이전트 성능을 떨어뜨릴 수 있어요. 요약 품질이 체크포인트 전략의 핵심 변수입니다.
서브에이전트 스폰 전 비용 게이팅
개인적으로 특히 유용했던 패턴입니다. 서브에이전트를 스폰하기 전에 예상 비용을 계산하고, 임계값을 넘으면 승인 게이트를 발동하는 방식인데요. 비슷한 아이디어를 구현한 커뮤니티 스킬이 몇 개 공개되어 있습니다(cost-governor 스킬, 2026-10 기준 접근 가능 여부 확인 필요).
무제한 서브에이전트 파생 시나리오는 비용 폭주의 주요 원인 중 하나입니다. 이 게이팅 패턴 하나만으로도 예측 불가능한 API 비용을 상당히 통제할 수 있어요.
프로그레시브 디스클로저: 처음부터 다 로드하지 않기
데이터베이스 질의 에이전트를 예로 들면, 처음엔 스키마 요약본만 컨텍스트에 로드하고 에이전트가 실제로 특정 테이블에 접근할 때 전체 스키마를 로드하는 전략입니다. 초기 토큰 소비를 크게 줄이는 "온디맨드 로딩"이에요.
아래 코드는 특정 라이브러리 API가 아닌 **의사코드(pseudocode)**입니다. SQLAlchemy를 쓴다면 sqlalchemy.inspect(engine)과 Table.columns를 조합해 실제 구현해야 합니다.
# 의사코드: 프로그레시브 스키마 공개 패턴
class ProgressiveSchemaLoader:
def __init__(self, db_metadata):
# db_metadata는 사용 중인 ORM에 맞춰 추상화한 래퍼라고 가정
self.metadata = db_metadata
def get_summary(self) -> str:
# 테이블명과 컬럼 수만 요약 — 토큰 최소화
lines = [
f"- {t.name} ({len(t.columns)} columns)"
for t in self.metadata.list_tables()
]
return "DB 스키마 요약:\n" + "\n".join(lines)
def get_table_detail(self, table_name: str) -> str:
# 실제 접근이 필요할 때만 전체 스키마 반환
return self.metadata.describe_table(table_name)
def load_for_context(self, needed_tables: list[str]) -> str:
if not needed_tables:
return self.get_summary()
details = [self.get_table_detail(t) for t in needed_tables]
return "\n\n".join(details)이 아이디어는 스킬 문서에도 그대로 적용됩니다. 처음엔 스킬 목록과 한 줄짜리 설명만 로드하고, 실제 호출할 때 전체 문서를 가져오는 방식으로 초기 컨텍스트를 크게 아낄 수 있어요.
모델 폴백: 예산 소진 시 그레이스풀 종료
예산 소진 상황에서 "그냥 에러"로 끝내지 않고 다른 모델로 전환하거나 요청을 차단하는 정책을 미리 정의해두는 패턴입니다. 각 정책 가지에 조건 레이블을 명시해야 "세 가지가 동시에 실행되는 것처럼" 오해를 피할 수 있어요.
폴백 대상 모델 선택은 트레이드오프입니다. 저렴한 소형 모델로 폴백하면 비용은 줄지만 품질이 낮아질 수 있고, 대형 컨텍스트 모델로 폴백하면 안정성은 높지만 비용이 급등할 수 있어요.
Task Budgets API 같은 모델 자체 예산 인식 기능
2026년 기준으로, Anthropic이 공개한 Task Budgets API(베타)처럼 모델이 자체적으로 남은 예산을 인식하고 우선순위를 재조정하는 어드바이저리 메커니즘이 등장하고 있습니다. 모델은 에이전트 루프 전체에서 남은 예산을 카운트다운하면서, 남은 범위 안에서 마무리를 시도하는 식입니다.
지원 모델명은 공식 문서를 반드시 확인하세요. 2026년 10월 기준 Anthropic은 Claude 5 계열(Opus 5.5, Sonnet 5.5 등)을 운영하고 있으며, 베타 기능의 지원 모델은 계속 바뀝니다.
사용 시 중요한 주의점이 하나 있습니다. 클라이언트가 자체적으로 예산을 추가 차감하면 안 됩니다. 서버와 클라이언트에서 이중으로 차감하면 모델이 지나치게 일찍 종료를 시도하는 부작용이 생길 수 있습니다.
# Task Budgets API 베타 헤더 (정확한 문자열은 공식 문서 참조)
anthropic-beta: <공식 문서에 명시된 베타 식별자>플레이스홀더를 그대로 복사해 쓰면 당연히 동작하지 않으므로, 베타 식별자는 공식 문서(2026-10 기준 URL은 변동 가능)에서 확인해야 합니다.
트레이드오프: 알고 쓰는 게 중요한 부분들
| 항목 | 장점 | 주의사항 |
|---|---|---|
| 80% 체크포인트 요약 | 컨텍스트 확보, 장시간 실행 가능 | 요약 품질에 따른 정보 손실 위험 |
| 단계별 예산 배분 | 특정 섹션의 예산 독식 방지 | 섹션 경계 정의가 까다로울 수 있음 |
| 서브에이전트 게이팅 | 비용 폭주 방지 | 과도한 게이팅은 파이프라인 지연 유발 |
| 모델 폴백 | 그레이스풀 종료 | 폴백 모델 품질 저하 가능성 |
| 프로그레시브 디스클로저 | 초기 토큰 절약 | 지연 로딩 로직 구현 복잡도 증가 |
| 모델 자체 예산 인식 API | 모델 자율 우선순위 조정 | 베타 단계, 클라이언트 이중 차감 금지 |
흔한 실수 두 가지:
첫째, "4자 = 1토큰" 휴리스틱을 그대로 쓰는 것. 이 방식은 부정확합니다. 모델별 전용 토크나이저를 써야 정확한 예산 계산이 됩니다.
둘째, 보안 측면을 간과하는 것. 악성 스킬이 도구 호출 체인을 조작해 토큰 소비를 비정상적으로 증폭시키는 "토큰 소진 공격" 유형이 연구 문헌에서 지적되고 있습니다(해당 URL의 유효성은 각자 확인 바랍니다). 커뮤니티 스킬을 도입할 때는 출처와 코드를 반드시 검증하세요.
현재 상황: 솔직한 진단
이 글의 전제를 먼저 분명히 해두자면, 현재 OpenClaw에는 지금까지 설명한 동적 예산 분배 기능이 공식으로 내장되어 있지 않습니다. 지금 당장 쓸 수 있는 공식 도구는 /context, /context detail, /compact, /usage tokens 정도입니다. 나머지 패턴은 커뮤니티 스킬에 의존해야 하는데, 스킬의 품질과 보안이 검증되지 않은 경우가 많다는 점을 감안해야 합니다. 글 초반의 "80스텝에서도 멈추지 않게"라는 목표는 공식 기능 한 줄로 해결되는 게 아니라, 위에서 설명한 패턴들을 조합해서 쌓아올려야 달성할 수 있다는 뜻입니다.
현실적인 접근은 이렇습니다. 우선 /context detail로 파이프라인의 토큰 소비 분포를 파악하고, 가장 빠르게 적용할 수 있는 수동 전략(/compact 타이밍 조정, 프로그레시브 디스클로저)부터 시작해보세요. 그 다음 단계에서 검증된 커뮤니티 스킬을 검토하는 흐름이 안전합니다.
그래서, 내일 뭐부터 할까요
오늘 글을 다 읽고 뭔가 해보고 싶다면, 거창한 리팩터링이 아니라 지금 돌아가는 가장 긴 파이프라인에 /context detail을 한 번 돌려보는 것부터 추천합니다. 전체 토큰 중 히스토리가 몇 퍼센트, 도구 출력이 몇 퍼센트를 쓰는지만 알아도, 위 패턴 중 어디부터 손대야 할지가 자연스럽게 눈에 들어옵니다. 히스토리가 60%를 넘기고 있다면 체크포인트 요약을, 도구 출력이 들쭉날쭉하다면 프로그레시브 디스클로저를, 서브에이전트가 많다면 스폰 게이팅을. 측정 없이 패턴부터 꽂으면 아마 엉뚱한 곳을 최적화하게 될 거예요.
참고 자료
아래 링크는 2026년 10월 기준으로 작성 시점에 참조한 자료들입니다. 외부 URL의 유효성과 접근 가능 여부는 시점에 따라 달라질 수 있으니, 끊어진 링크가 있다면 각 도구의 공식 저장소나 문서에서 최신 위치를 확인하세요.
- Anthropic Task Budgets 공식 문서:
https://platform.claude.com/docs/en/build-with-claude/task-budgets(URL은 변동 가능) - Context Overflow Failure Modes - FutureAGI
- Token Budget Controls for AI Agents - Gravitee
- OpenClaw Multi-Agent Ecosystem Analysis - AI Cloud Weekly
context-budgeting-v2커뮤니티 스킬 (Playbooks.com) — 접근 가능 여부 확인 필요Token Budget Guard,cost-governor커뮤니티 스킬 (ClawKit) — 접근 가능 여부 확인 필요- 토큰 소진 공격 관련 arXiv 프리프린트 — 식별자 및 유효성 각자 확인 권장