개요
프로젝트 단위 AI 사용량 예산 상한(budget cap) 도입. 운영자가 "이 프로젝트는 월 N 토큰(또는 환산 금액)까지"를 설정하면, 오케스트레이터가 기간 누적 사용량을 추적하고 상한 도달 시 신규 디스패치와 진행 중인 런의 다음 턴을 멈춘다.
한 문장: 토큰은 지금도 세고 있지만, 아무도 멈추지 않는다. 세는 것에 기간 원장 + 게이트를 얹는다.
배경
사업화 시 AI 크레딧(모델 API 비용)은 고객 부담으로 설계됨. 고객이 통제할 수 없는 변동 청구서는 이탈의 1순위 원인이므로, 고객이 스스로 상한을 정하고 시스템이 그것을 강제하는 기능이 사업화의 전제 조건이다. 동시에 폭주하는 단일 런(무한 루프, 거대 컨텍스트)에 대한 안전장치 역할도 한다.
현재 상태 (2026-08-26 조사)
집계 경로는 이미 존재한다:
- 런타임 → 워커:
agent.tokenUsageUpdated 이벤트 (packages/core/src/runtime/events.ts), 워커가 절대값/델타 처리 (packages/worker/src/index.ts extractAbsoluteTokenUsage, 스펙 §13.5 준수)
- 워커 → 오케스트레이터: 채널 이벤트의
tokenUsage + WORKSPACE_RUNTIME_DIR/token-usage.json 아티팩트 (packages/worker/src/token-usage.ts)
- 오케스트레이터:
OrchestratorRunRecord.tokenUsage (런 단위) + snapshot-builder.ts의 이슈별 누적·codexTotals (전체 합계)
- 표면:
repo status, 대시보드, 컨트롤플레인 API에 토큰 수치 표시
없는 것:
- 상한 설정 자체가 없음.
WorkflowAgentConfig는 maxConcurrentAgents/maxTurns/재시도만 있고 (packages/core/src/workflow/config.ts), OrchestratorProjectConfig에도 예산 필드 없음. SYMPHONY_MAX_TOKENS는 docs/configuration.md에 "legacy token budget context"로만 남아 있고 워커 시작 시 클리어됨.
- 기간 개념이 없음. 집계는 전부 all-time 합계. "이번 달" 누적을 구할 수 없고, 재시작 후에도 이어지는 기간 원장이 없다.
- 게이트가 없음. 디스패치 루프(
packages/orchestrator/src/service.ts ~L1455–1510)는 슬롯·상태별 동시성·재시도 유예·실패 억제만 검사한다. 런 안에서 세션을 멈추는 유일한 한도는 maxTurns뿐.
- Claude 런타임은 사용량을 보고하지 않음.
agent.tokenUsageUpdated를 emit하는 곳은 runtime-codex뿐 (packages/runtime-codex/src/runtime.ts L353, L389). runtime-claude는 usage를 agent.rateLimit 페이로드에 실어 보낼 뿐 토큰 이벤트를 내지 않는다 (packages/runtime-claude/src/events.ts). → Claude 런은 예산 집계에서 빠진다. 선행 과제.
설계 방향
- 정책 위치: 프로젝트(workspace) 단위. 표현은 두 곳 —
WORKFLOW.md front matter agent.budget.* (repo 정책) 과 OrchestratorProjectConfig.budget (운영자 오버라이드, standalone 프로젝트 모델과 정합). 트래커 비의존, core에 타입 정의.
- 단위: 1차는 토큰. 금액은
cost_per_mtoken(입력/출력 분리) 환산값을 표시용으로만 제공 — 모델 가격표를 core가 소유하지 않는다.
- 기간:
period: monthly + anchor_day(청구 기준일), 확장 여지로 rolling_30d. 원장은 .runtime/orchestrator/workspaces/<id>/budget-ledger.json에 기간별 누적을 영속 (런 레코드 재집계 fallback 가능).
- 게이트 지점 두 곳:
- 디스패치 루프 — 상태별 동시성 체크 옆에 예산 체크. 초과 시 후보 스킵 + 구조화 이벤트.
- 턴 리스 발급 — 워커는 매 턴 오케스트레이터에서 리스를 받는다(
[worker] acquired turn lease). 리스 발급을 거부하면 워커 코드 변경 최소로 진행 중 런도 다음 턴에서 멈춘다. 워커는 거부를 budget_exhausted 종료로 분류(비실패, 재시도 큐 진입 금지).
- 동작 모드:
action: pause | warn. 임계 이벤트 budget_threshold(80%), budget_exhausted(100%). 스냅샷 degraded 사유에 반영. 기간 경과 시 자동 해제, 운영자 수동 해제 명령 제공.
- 런 단위 안전장치:
max_tokens_per_run — 월 상한과 별개로 단일 런 폭주 차단.
- 스펙 관계: 상위 스펙 §13.5는 집계 규칙만 정의하고 예산/게이트는 없다 → 명시적 확장으로 ADR 기록.
하위 이슈 후보
순서
runtime-claude usage → core 타입 → 원장 → {디스패치 게이트, 리스 게이트} → 워커 종료 분류 → 표면(CLI/대시보드) → extension 통보 → docs/E2E
완료 기준
- 프로젝트에 월 상한을 설정하면 Codex·Claude 런타임 모두의 사용량이 원장에 누적되고, 100% 도달 시 신규 디스패치와 진행 중 런의 다음 턴이 멈춘다 (Docker E2E 블랙박스 통과)
- 오케스트레이터 재시작 후에도 기간 누적이 유지된다
- 상한 미설정 프로젝트는 동작 변화 없음 (회귀 없음)
repo status/대시보드에서 "이번 기간 사용량 / 상한 / 잔여"를 확인할 수 있다
- 스펙 확장 ADR 병합
개요
프로젝트 단위 AI 사용량 예산 상한(budget cap) 도입. 운영자가 "이 프로젝트는 월 N 토큰(또는 환산 금액)까지"를 설정하면, 오케스트레이터가 기간 누적 사용량을 추적하고 상한 도달 시 신규 디스패치와 진행 중인 런의 다음 턴을 멈춘다.
한 문장: 토큰은 지금도 세고 있지만, 아무도 멈추지 않는다. 세는 것에 기간 원장 + 게이트를 얹는다.
배경
사업화 시 AI 크레딧(모델 API 비용)은 고객 부담으로 설계됨. 고객이 통제할 수 없는 변동 청구서는 이탈의 1순위 원인이므로, 고객이 스스로 상한을 정하고 시스템이 그것을 강제하는 기능이 사업화의 전제 조건이다. 동시에 폭주하는 단일 런(무한 루프, 거대 컨텍스트)에 대한 안전장치 역할도 한다.
현재 상태 (2026-08-26 조사)
집계 경로는 이미 존재한다:
agent.tokenUsageUpdated이벤트 (packages/core/src/runtime/events.ts), 워커가 절대값/델타 처리 (packages/worker/src/index.tsextractAbsoluteTokenUsage, 스펙 §13.5 준수)tokenUsage+WORKSPACE_RUNTIME_DIR/token-usage.json아티팩트 (packages/worker/src/token-usage.ts)OrchestratorRunRecord.tokenUsage(런 단위) +snapshot-builder.ts의 이슈별 누적·codexTotals(전체 합계)repo status, 대시보드, 컨트롤플레인 API에 토큰 수치 표시없는 것:
WorkflowAgentConfig는maxConcurrentAgents/maxTurns/재시도만 있고 (packages/core/src/workflow/config.ts),OrchestratorProjectConfig에도 예산 필드 없음.SYMPHONY_MAX_TOKENS는docs/configuration.md에 "legacy token budget context"로만 남아 있고 워커 시작 시 클리어됨.packages/orchestrator/src/service.ts~L1455–1510)는 슬롯·상태별 동시성·재시도 유예·실패 억제만 검사한다. 런 안에서 세션을 멈추는 유일한 한도는maxTurns뿐.agent.tokenUsageUpdated를 emit하는 곳은runtime-codex뿐 (packages/runtime-codex/src/runtime.tsL353, L389).runtime-claude는usage를agent.rateLimit페이로드에 실어 보낼 뿐 토큰 이벤트를 내지 않는다 (packages/runtime-claude/src/events.ts). → Claude 런은 예산 집계에서 빠진다. 선행 과제.설계 방향
WORKFLOW.mdfront matteragent.budget.*(repo 정책) 과OrchestratorProjectConfig.budget(운영자 오버라이드, standalone 프로젝트 모델과 정합). 트래커 비의존, core에 타입 정의.cost_per_mtoken(입력/출력 분리) 환산값을 표시용으로만 제공 — 모델 가격표를 core가 소유하지 않는다.period: monthly+anchor_day(청구 기준일), 확장 여지로rolling_30d. 원장은.runtime/orchestrator/workspaces/<id>/budget-ledger.json에 기간별 누적을 영속 (런 레코드 재집계 fallback 가능).[worker] acquired turn lease). 리스 발급을 거부하면 워커 코드 변경 최소로 진행 중 런도 다음 턴에서 멈춘다. 워커는 거부를budget_exhausted종료로 분류(비실패, 재시도 큐 진입 금지).action: pause | warn. 임계 이벤트budget_threshold(80%),budget_exhausted(100%). 스냅샷degraded사유에 반영. 기간 경과 시 자동 해제, 운영자 수동 해제 명령 제공.max_tokens_per_run— 월 상한과 별개로 단일 런 폭주 차단.하위 이슈 후보
result.usage→agent.tokenUsageUpdatedemit (선행; 없으면 Claude 런은 예산 밖)BudgetPolicy타입 + WORKFLOW.mdagent.budget파싱 +OrchestratorProjectConfig.budget오버라이드budget-ledger.json) — 런 tokenUsage 델타 누적, 기간 롤오버, 재시작 복원, 런 레코드 재집계 fallbackbudget_threshold/budget_exhausted구조화 이벤트, 스냅샷 degraded 사유budget_exhausted종료 분류(exit-classification), 재시도 미진입;max_tokens_per_runrepo config budget설정·해제 명령configuration.md+ changeset + Docker E2E (상한 도달 → 디스패치 중단 → 기간 롤오버 후 재개 블랙박스)순서
runtime-claude usage → core 타입 → 원장 → {디스패치 게이트, 리스 게이트} → 워커 종료 분류 → 표면(CLI/대시보드) → extension 통보 → docs/E2E
완료 기준
repo status/대시보드에서 "이번 기간 사용량 / 상한 / 잔여"를 확인할 수 있다