운영 패턴 · 루프 & 예산 거버넌스 (L6) · 새 측정이 아니라 설계
에이전트 루프: 천장 점검이 “그만”이라고 말한 뒤, 실제로 루프를 멈추는 건 누구인가
짝이 되는 천장 점검 글은 깔끔하게 끝납니다. 도구 에이전트 품질이 천장에 닿으면 추론 강도를 더 올려도 점수는 오르지 않고 지연 시간과 비용만 늘어납니다 — 그러니 천장에 닿는 가장 낮은 강도로 라우팅하라는 결론입니다. 호출 하나라면 정확히 맞는 조언입니다. 하지만 실제 에이전트는 호출 하나로 끝나는 경우가 드뭅니다. 계획하고, 도구를 부르고, 결과를 읽고, 다시 추론하며 루프를 돕니다. 작업이 루프가 되는 순간 비용은 가격표에서 읽어 낼 수 있는 숫자가 아니라 단계에 대한 적분이 됩니다. 모두가 먼저 만지는 호출 단위 강도 조절은 정작 돈을 쓰는 대상에 더는 닿지 못합니다. 이 글은 천장 점검의 나머지 절반입니다. “이 호출에 강도를 더 줄 가치가 있나”가 아니라 “루프는 언제 멈추고, 누가 그렇게 정했나”입니다.
이 패턴이 존재하는 이유
이 주제가 필요한 오후는 대개 이런 모습입니다. 에이전트가 모든 데모를 통과합니다. 그러던 어느 날 평범한 지원 티켓 하나가 루프를 만들어 냅니다 — 검색하고, 다시 읽고, 다른 도구를 부르고, 조금 더 깊이 생각하고, 다시 시도하고 — 그렇게 마흔 번의 도구 호출 끝에 답을 내놓습니다. 답은 정확합니다. 그런데 그 작업에 잡아 둔 예산의 마흔 배가 들었고, 아무도 월말 청구서를 보기 전까지 알아차리지 못합니다. 실패한 것은 없습니다. 예외도, 500도, 알림도 없습니다. 그래서 폭주하는 루프가 위험합니다. 청구서를 읽기 직전까지는 정상 동작하는 에이전트처럼 보이기 때문입니다.
그 에이전트가 기대에 못 미치면 본능은 익숙한 쪽으로 향합니다 — 조금만 더 돌게 하자, 조금만 더 생각하게 하자. 짝이 되는 천장 점검 글은 호출 하나에서 “더 생각하기”가 더는 값을 못 하는 지점을 이미 보여 줬습니다. 이 글은 호출 단위 레버가 건드릴 수 없는 부분, 곧 호출의 개수를 다룹니다. 이런 워크로드를 운영해 봤다면, 호출마다 강도를 조절하는 설정에 이미 손을 대 봤고 그것만으로는 충분히 먹히지 않는다는 것도 느꼈을 것입니다.
reasoning_effort(레버 L4)는 한 호출의 추론 토큰을 제한합니다. 루프가 호출을 몇 번 하는지에 대해서는 아무 말도 하지 않습니다.max_output_tokens(레버 L5)는 한 호출의 출력을 제한합니다. 반복에 걸친 누적 지출은 제한하지 못합니다.- 프롬프트 캐시 안정성(레버 L3)은 하나의 접두부 입력 비용을 낮춥니다. 루프에서는 단계마다 대화 기록이 자라기 때문에, 청구액이 가장 클 때 정확히 캐시 가능한 비율이 줄어듭니다.
그렇다고 호출 단위 레버가 틀린 것은 아닙니다. 단일 호출에서는 여전히 가장 먼저 만질 올바른 수단입니다. 다만 루프보다 한 층 아래에서 작동할 뿐입니다. 역량 질문 — 모델이 루프를 돌 수 있나, 백만 토큰을 담을 수 있나 — 은 애초에 어려운 문제가 아니었습니다. 어려운 쪽은 루프를 다스리는 일입니다. 임계값, 개입, 평가, 추적성, 그리고 소유입니다.
질문
작업이 모델 호출의 루프일 때, 누적 비용을 제한하는 것은 무엇이고, 그 경계는 누가 소유하는가?
패턴
운영 레버 L6: 단계 상한과 비용 천장, 페일클로즈 차단기, 평가 기반 단계 상승, 단계별 비용 추적성.
짝 근거
측정값: benchmark-03의 1.97–2.00 품질 천장 — 비용은 $0.002762 → $0.003499, 지연 시간은 2.9초 → 3.8초로 올랐습니다. 루프가 존중해야 할 단계별 발견입니다.
결정
루프를 돌리기 전에 제한하고, 차단기는 닫힘 우선(페일클로즈)으로 만들고, 모든 천장은 핫픽스에서 누군가 슬쩍 올리는 상수가 아니라 커밋·검토되는 정책으로 다루세요.
폭주 비용은 숫자가 아니라 적분이다
루프가 사람을 놀라게 하는 이유는 청구액이 미리 가격을 매길 수 있는 수치가 아니라 단계마다 쌓이는 합이기 때문입니다.
total_cost ≈ Σ_step ( input_tokens_step × input_rate
+ reasoning_tokens_step × output_rate
+ output_tokens_step × output_rate )
그 합을 누구의 예상보다도 빠르게 키우는 것은 세 가지입니다. 첫째, 반복 횟수에
한계가 없습니다. 하드 캡이 없으면 수렴하지 못하는 루프 — 불안정한 도구, 모호한
하위 목표, “맞을 때까지 계속 다듬어라”는 지시 — 는 과금되는 호출을 계속
쏘아 대고, 모델 안에는 그것을 멈추는 장치가 없습니다. 둘째, 단계마다 컨텍스트가
자랍니다. 매 반복이 이전 대화 기록에 새 도구 출력을 더해 다음 입력으로 다시
보내므로, 뒤쪽 단계가 가장 비싼 단계가 됩니다. 접두부의 단 한 바이트 변화만으로도
캐시가 비워져 그 단계들은 전체 입력 단가로 과금됩니다. 셋째, 강도가 루프 안에서
곱해집니다. 높은 reasoning_effort 기본값은 단계별 추론 토큰에 단계 수를
곱하는 셈이라, 틀렸을 때 가장 비싼 위치입니다.
호출 단위 레버는 그 합의 한 항씩을 줄입니다. 그것들이 할 수 없는 일은 항의 개수나 누적 합계를 제한하는 것입니다. 그것이 루프가 여는 틈이고, 아래 다섯 영역이 그 틈을 막는 방법입니다.
임계값 — 단계 상한과 누적 비용 천장
한 작업이 이웃의 40배를 조용히 쓰는 첫 순간에 이걸 만납니다.
메커니즘. 루프에는 단일 호출이 결코 필요로 하지 않는 두 경계가 필요합니다. 최대 반복 횟수, 그리고 작업 전체에 대한 누적 토큰/비용 천장입니다. 둘 중 하나만으로는 샙니다. 단계 상한만 있으면 각 단계가 커질 때 비싼 폭주를 여전히 허용하고, 비용 천장만 있으면 값싼 무한 회전을 여전히 허용합니다.
행동. 작업 종류별로 max_iterations 단계 상한과
hard_ceiling_usd 누적 천장을 선언하세요. 천장은 안전해 보이는
둥근 수가 아니라 대표 표본의 p99 — 단계 수와 작업당 비용 — 에서 끌어내세요.
둘 다 보수적으로 잡고, 근거가 있을 때만 올리세요.
저장소 내 근거. scripts/run_benchmark.py가
이미 정확히 이렇게 합니다. 사전 추정(estimate_experiment_cost_usd)을
MAX_COST_PER_BENCHMARK_USD로 게이트하고, 실행 중
hard_ceiling_usd를 BudgetTracker 데이터클래스로
추적하며, 도구 루프 max_iterations 상한
(agent.max_tool_iterations)을 둡니다.
docs/05-methodology.md §6 “Budget guards”는 이것을 위생이 아니라
방법론으로 다룹니다. “조용한 예산 초과는 곧 조용한 실험 범위 변경이다.” 같은
문장이 운영에서도 그대로 성립합니다 — 조용한 루프 초과는 그 작업이 써도 되는 비용이
조용히 바뀐 것입니다.
개입 — 페일클로즈로 끊는 차단기
루프가 에러를 내는 대신 밤새 도는 첫 순간에 이걸 만납니다.
메커니즘. 임계값이 루프 중간에 넘어가면, 루프는 결정론적이고 눈에 보이게 멈춰야 합니다 — 최선의 부분 답에 “예산 초과”라는 타입 있는 신호를 함께 돌려주고 — 결코 조용히 계속 돌거나 조용히 잘라 내지 않아야 합니다. 그 선택이 제한된 비용과 폭주를 가르는 전부입니다.
행동. 다음 중 무엇이든에서 루프를 중단하는 차단기를 만드세요.
단계 상한 도달, 누적 비용이 hard_ceiling_usd에 도달, 또는
무진전 탐지기 발동 — 예컨대 같은 도구 호출의 반복. 중단 시에는 종료 사유가 담긴
타입 있는 종결 레코드를 남기고, 호출자가 그래도 쓸 만한 것을 받도록 마지막으로
답만 내는 호출을 한 번 합니다. 닫힘 우선(페일클로즈)으로 설계하세요. 의심스러우면 기본값은
계속이 아니라 멈춤입니다.
저장소 내 근거. 러너는 타입 있는
BudgetExceededError(“사전 추정 또는 진행 합계가 예산 가드를 넘을 때
발생”)를 던지고, BudgetTracker.is_halted는 합계가 천장을 넘는 순간
다음 호출 이전에 정지를 강제하며, 프로세스는 전용 EXIT_BUDGET 코드로
종료합니다. 도구 루프는 같은 발상의 깔끔한 모형입니다.
iterations >= max_iterations이면 모델이 최종 답을 내도록
tools= 없이 마지막 호출을 한 번 쏘고,
tool_loop_terminated="iteration_cap"을 기록하며, 그 복구 구간을
궤적에 덧붙입니다. 사유를 남기는 페일클로즈 차단기이니, 그대로 운영에 가져오세요.
평가 — 값을 하는 곳에서만 단계를 올려라
“그냥 더 생각하게 하자”가 도움은 멈추고 과금은 계속되는 날에 이걸 만납니다.
메커니즘. 이 저장소의 핵심 결과는, 중간 범위를 넘겨 강도를 올려도 품질은 오르지 않고 비용만 늘어난다는 것입니다. benchmark-03에서 품질은 강도 단계 전체에 걸쳐 1.97–2.00 근처 천장에 머물렀고, 그동안 요청당 비용은 $0.002762에서 $0.003499로, 지연 시간은 2.9초에서 3.8초로 올랐습니다. 루프에서는 같은 발견이 계속 결정을 다스립니다. 한 단계 더, 혹은 다음 호출의 더 높은 강도는 제값을 해야 합니다.
행동. 단계 상승 — 한 번 더 반복하거나, 다음 호출의
reasoning_effort를 올리는 것 — 은 값싼 평가 뒤에 두세요. 루브릭
점수, 자기 일관성이나 신뢰도 신호, 명시적 정지 점검 같은 것들입니다. 기본값을 멈춤으로 두고,
평가는 계속해야 할 이유를 증명하게 하세요. 그러면 끝이 없는 “끝날 때까지 루프”가
“평가가 값을 하지 못할 때까지 루프”라는 제한된 형태가 됩니다 — 단일 호출 라우팅 결정을
루프 형태로 옮긴 것일 뿐입니다.
저장소 내 근거. scripts/run_judge.py는 이미 모든
벤치마크에서 품질을 채점하는 루브릭 심사자이고, 같은 모양이 자연스러운 계속
게이트가 됩니다. 1.97–2.00 천장(results/summary.md §3;
benchmarks/03-tool-using-agent/analysis.md)은, 게이트 없는
“더 생각하고 다시 루프” 기본값이 회수 지점을 한참 지나서도 계속 지출한다는 측정된
근거입니다.
추적성 — 루프별, 단계별로 비용을 귀속하라
40달러짜리 작업을 마주하고도 어느 단계가 그랬는지 말할 수 없는 첫 순간에 이걸 만납니다.
메커니즘. 귀속할 수 없는 것은 다스릴 수 없습니다. 작업 끝에 남은 단일 숫자는 어느 단계가 폭주했고 왜 그랬는지를 가립니다. 거버넌스에는 안정적인 루프 id에 연결된 단계별 레코드가 필요합니다. 각 레코드는 그 단계의 사용량, 진행 중인 누적 비용, 남은 예산 여유를 담습니다.
행동. docs/14-observability-schema.md의 요청당
캡처를 루프 범위 필드로 확장하세요. 안정적인 loop_id / 작업 id,
단조 증가하는 step_index, 단계별 usage 블록(이미
잡고 있음 — reasoning_tokens, cached_tokens, …),
cumulative_cost_usd, 그리고 budget_remaining_usd.
단계마다 JSONL 한 줄을 내보내면 루프의 청구액은 그 합이고,
budget_remaining_usd가 처음 0 이하로 떨어지는 줄이 당신의 개입
지점입니다. 종료 시점 step_index의 평균이 아니라 분포에
알람을 거세요 — 폭주는 꼬리에 숨어 있습니다.
범위 메모(정직한 경계). 그 루프 범위 필드들은 이미 출하된 것이
아니라 다음 증분입니다. 이미 request_idx와 전체
usage 블록을 담고 있는 기존 PTURequestRecord와
schemas/ptu_request_record.schema.json 위에 얹힙니다. 러너는 오늘도
이미 단계별 usage 객체를 셀 단위 딕트 하나로 합치고 단계별 궤적을
기록합니다. 루프 추적성은 그 합산을 일급 데이터로 끌어올리는 일입니다 — 합만 남기지 말고
단계별 줄을 남기세요.
거버넌스 — 예산은 소유되는 계약이다
그리고 이건 누군가 핫픽스에서 천장을 슬쩍 올렸는데 청구액이 흔적도 없이 따라 움직일 때 옵니다.
메커니즘. 단계 상한과 비용 천장은 마법의 숫자가 아니라 정책입니다. 시스템이 무엇을 해도 되는지 정하는 다른 모든 계약처럼, 소유자가 있고, 버전 관리되고, 검토를 거쳐 바뀌어야 합니다. 천장 인상은 범위 변경이고, 실제로 그렇게 느껴져야 합니다.
행동. 작업 종류별 예산 정책을 여러 서비스에 흩어진 상수가 아니라
커밋된 설정에 선언하세요. 모든 천장 변경을 검토하고, 초과율 — 차단기가 얼마나
자주 발동하는지 — 을 일급 SLO로 추적하며, 천장 인상에는 방법론 변경과 같은 절차를
부여하세요. docs/16-release-tiers-and-redaction-policy.md의 릴리스
거버넌스 규율은 같은 형태를 공개 데이터에 적용한 것이고, 이 글은 그것을 지출에
적용합니다.
저장소 내 근거. GOVERNANCE.md는 예산 정책이 따르는
소유된 계약 변경 통제 모형이고, docs/05 §6의 “조용한 초과 = 조용한
범위 변경” 규칙은 천장 변경을 일상이 아니라 검토 대상으로 만드는 기준입니다.
출처와 근거 경계
이 글에서는 두 가지가 일어나고 있고, 둘은 서로 다른 Tier에 속합니다. 측정된 앵커 — benchmark-03 천장 — 는 감사할 수 있는 공개 결과입니다. 루프 거버넌스 패턴은 운영 설계입니다. 이 저장소에 이미 존재하는 원시 요소를 재사용하지만, 그것이 막아 내려는 폭주 행동 자체는 여기서 측정되지 않았습니다. 아래 Tier는 문서화된 입력이 끝나고 설계 추론이 시작되는 지점을 표시합니다.
- [1] Tier 1 — 방법론 계약: 이 저장소,
docs/05-methodology.md§6 “Budget guards”. 이 패턴은 사전 추정 + 실행 중 하드 천장 중단을 일반화합니다. “조용한 예산 초과는 곧 조용한 실험 범위 변경이다”라는 규칙이 §5로 이어지는 거버넌스 입장입니다. - [2] Tier 2 — 측정된 짝(benchmark-03): 이 저장소,
results/summary.md§3 및benchmarks/03-tool-using-agent/analysis.md. 측정된 천장의 출처입니다 — 강도 단계 전체에서 품질은 1.97–2.00 안에 머물렀고 요청당 비용은 $0.002762 → $0.003499, 지연 시간은 2.9초 → 3.8초로 올랐습니다. 루프의 단계 상승 게이트가 존중해야 할 단계별 발견입니다. - [3] 운영 패턴(측정된 Tier가 아니라 설계): 루프 통제는
scripts/run_benchmark.py에서 그대로 가져왔습니다 —BudgetTracker/hard_ceiling_usd,BudgetExceededError,EXIT_BUDGET,MAX_COST_PER_BENCHMARK_USD로 게이트되는estimate_experiment_cost_usd,agent.max_tool_iterations, 그리고tool_loop_terminated="iteration_cap"— 그리고scripts/run_judge.py의 루브릭 게이트입니다. 정본 문서는 docs/18이고,docs/09-operator-guide-one-page.md에 운영 레버 L6으로 소개되어 있습니다. - [4] 제안된 다음 증분(미구현): §4의 루프 범위 필드 —
loop_id,step_index,cumulative_cost_usd,budget_remaining_usd— 는docs/14-observability-schema.md와schemas/ptu_request_record.schema.json을 확장합니다. 설계일 뿐이며, 이 글의 어디에서도 이것이 출하되었다고 주장하지 않습니다. - [5] 근거 대시보드: 측정된 짝은 렌더링된 benchmark-03 품질·비용 차트로 감사할 수 있습니다.
이 글이 세우는 것과 세우지 않는 것: 이 저장소에서 이미 도는 가드로 루프를 제한하는
방법을 보여 주고, 계속할지 정할 때 측정된 천장을 존중합니다. 루프가 얼마나 자주
폭주하는지, 작업 형태별 단계 수의 분포, 혹은 롱컨텍스트 비용 스케일링 곡선은
측정하지 않습니다 — docs/04 §4가 여기 어떤 벤치마크도
롱아웃풋 / 롱컨텍스트 워크로드를 측정하지 않는다고 이미 기록합니다. 그것들은
조용한 주장으로가 아니라, 미래의 폭주 특성화 벤치마크가 닫을 정직한 빈칸으로
다루세요.
근거가 말하게 해 주는 것
루프 작업에서 쓸모 있는 문장은 “더 생각하게 하자”나 “더 오래 돌게 하자”가 아닙니다. “루프를 돌리기 전에 제한하고, 그 경계를 당신이 소유하는 것으로 만들어라”입니다.
실전 규칙
- 모든 다중 호출 작업에는 경계를 하나가 아니라 둘 두세요.
max_iterations단계 상한 그리고hard_ceiling_usd누적 천장입니다. 둘 다 대표 p99에서 크기를 잡으세요. - 차단기를 닫힘 우선(페일클로즈)으로 만드세요 — 상한, 천장, 무진전 중 무엇에서든 멈추고, 타입 있는 사유를 기록하고, 최선의 부분 답을 돌려주세요.
- 모든 단계 상승을 값싼 평가 뒤에 두고, 측정된 천장을 존중하세요. 중간 범위를 넘으면 강도를 올려도 점수는 안 오르고 비용만 늘었습니다.
- 작업 끝의 합만이 아니라 단계별 줄을 남겨, 폭주한 단계를 짚을 수 있게 하세요.
- 예산을 커밋·검토되는 정책으로 쥐고, 초과율을 SLO로 추적하며, 천장 인상에 범위 변경의 절차를 부여하세요.
실전 규칙: L1–L5는 각 호출을 제한하고, L6은 루프를 제한합니다. 루프가 돌기 전에 언제 멈출지 정하고, 그 결정을 모델이 기본값으로 도달하는 무언가가 아니라 당신이 소유하는 무언가로 만드세요.
다음: PTU/PAYG 교차점: 용량 결과가 아니라 계획용 선 — 루프를 제한하고 워크로드 규모를 잡고 나면, 프로비저닝된 처리량은 언제 PAYG를 이기나요?