레퍼런스·설명레퍼런스

설정·키·포트

입력 형식과 범위와 반영 시점을 찾는다

계약

WERFT_API_KEY와 WERFT_BASE_URL은 서버 설정이 아니라 SDK 호출자의 설정이다. SDK 클라이언트 설정을 따른다.

설정값의 형식·필수성·반영 시점을 찾는 문서다. 실행 전 활성화·롤백 게이트는 Compose 업그레이드와 Kubernetes 업그레이드를 먼저 읽는다.

설치 상한

API와 scheduler는 아래 값 중 SESSION_TOKEN_LIMIT를 뺀 여섯 값이 없거나, 어느 값이든 형식이 틀리면 문제를 한 줄에 모두 로그로 남기고 기동하지 않는다. SESSION_TOKEN_LIMIT는 없으면 0(상한 없음)으로 읽는다. 두 프로세스는 같은 parser를 쓴다. SESSION_TOKEN_LIMIT의 0 말고는 코드에 기본값이 없고, compose의 x-installation-limits 블록이 로컬 기본값을 준다. 떠 있는 API의 /readyz는 같은 검증을 config 체크로 다시 수행한다.

변수의미넘었을 때
EXECUTION_SLOT_LIMIT동시에 슬롯을 잡는 worker 수. 0이면 접수만 받고 아무것도 띄우지 않는다입력은 거부하지 않고 queued로 둔다
QUEUED_INPUT_LIMIT_PER_SESSION세션 하나가 쌓아 둘 수 있는 queued turn 수429 RATE_LIMITED, retryable:true, Retry-After: 5
STORAGE_LIMIT_BYTES설치 전체가 보존하는 입력 message의 UTF-8 bytes. event·checkpoint object·worker 디스크는 세지 않는다(디스크는 workspace quota가 맡는다)413 STORAGE_LIMIT_EXCEEDED, retryable:false
MAX_TURN_SECONDSturn 하나의 벽시계 상한. 승인 대기도 포함한다. worker env WORKER_MAX_TURN_SEC로 전달된다turn failed(turn_timeout). 엔진이 응답하지 않으면 outcome_unknown(turn_timeout)
SESSION_COST_LIMIT_USD세션 누적 비용(egress proxy가 계측한 Messages 호출별 usage를 플랫폼 가격표로 환산한 합, 추정치)새 turn을 dispatch하지 않는다. 세션 상세 attention.code=BUDGET_EXCEEDED가 뜨고 worker는 슬롯을 반납한다. 입력은 계속 queued로 받는다
SESSION_TOKEN_LIMIT세션 누적 provider token 수. 비용 상한과 같은 호출을 세되 단가와 무관하다. 0이면 상한이 없다(기본)SESSION_COST_LIMIT_USD와 같다. 두 상한은 같은 판정(budgetExceeded)을 지나며 어느 쪽이 먼저 닿아도 BUDGET_EXCEEDED다. attention.reason은 닿은 상한만 적는다
PROVIDER_MAX_RETRIES실패한 Messages 요청을 다시 보내는 횟수. worker env WORKER_PROVIDER_MAX_RETRIES를 거쳐 SDK CLAUDE_CODE_MAX_RETRIES로 전달된다. 0이면 첫 실패에서 turn이 끝난다turn failed(api_error). turn 상세 result에 api_error_status·provider_error·last_retry_status가 남는다

API 설정

API는 설치 상한 말고도 아래 값을 기동 때 한 parser로 읽는다. 설정하지 않으면 기본값을 쓴다. 설정했는데 틀린 값이면(빈 문자열 포함) 기본값으로 돌아가지 않는다. 숫자 설정은 API·scheduler·reconciler·worker와 설치 상한이 모두 이 규칙을 따른다. 기본값이 없는 값은 설정하지 않으면 X is required로, 틀린 값은 X must be a positive integer처럼 기대한 범위를 적어 거부한다. 받은 값은 적지 않는다. 로그에 남고, 다른 변수에 잘못 넣은 비밀일 수 있어서다. 이때 Refusing to start: API settings are invalid 한 줄에 문제를 모두 남기고 기동하지 않는다. 떠 있는 API의 /readyz도 같은 검증을 config 체크로 다시 수행한다.

변수기본값허용 범위
HEARTBEAT_TTL_SEC3020 초과 86400 이하. worker는 lease가 끝나기 10초 전에 lease를 내려놓고(WORKER_LEASE_SAFETY_MARGIN_SEC), 5초마다 heartbeat를 보낸다(WORKER_HEARTBEAT_INTERVAL_SEC, 이전 버전 worker는 10초). 마진과 10초의 합 이하이면 이전 버전 worker의 attempt가 첫 heartbeat 무렵 lease를 잃는다. launcher는 두 worker 값을 넘기지 않으므로 기본값을 기준으로 판정한다
PENDING_REQUEST_TTL_SEC1800정수 1–604800. 승인·질문 요청을 받는 기간이다. 등록된 요청은 worker가 이 값만큼 기다린다. worker의 QUESTION_TIMEOUT_SEC는 gateway가 등록을 확인하기 전에만 적용된다. turn 벽시계 MAX_TURN_SECONDS에는 대기 시간도 들어가므로, 대기는 그 상한을 넘지 못한다
SSE_MAX_STREAMS · SSE_MAX_STREAMS_PER_OWNER · SSE_REPLAY_MAX_BYTES256 · 8 · 1 MiB양의 정수
LOG_LEVELinfodebug·info·warn·error(대소문자 무관, 빈 값은 info). scheduler·reconciler loop와 egress-proxy도 같은 규칙으로 기동을 거부한다. worker는 scheduler의 값을 받는다
WEB_DIST_DIR없음(웹 콘솔을 서빙하지 않음). control-host 이미지는 /app/apps/web/distindex.html이 있는 절대 경로. 웹 콘솔(apps/web)의 빌드 결과다

모든 /v1 응답은 api.request.completed를 남긴다. 실제 id가 없는 Hono route_template, method, status, 인증과 route 처리를 포함한 duration_ms, operation=api_request, outcome, reason_code가 있고 request_id는 log context에 있다. request body·credential·provider key는 넣지 않는다. terminate 요청은 같은 request_id의 Terminate control transaction completed도 남기며, 여기에는 pool checkout, binding·launch·session lock, 각 SQL, commit 또는 rollback, control transaction 전체의 millisecond 계측이 들어간다. 배포 환경에서 수락 지연을 조사할 때는 두 로그를 request_id로 묶어 어느 단계가 느렸는지 판단한다.

terminate가 이미 사라진 execution에서 즉시 끝나거나 scheduler/reconciler가 execution 부재를 확인하면 control.result가 operation=terminate, receipt id인 control_id, outcome, reason_code, durable 수락부터 확인까지의 effect_ms(duration_ms와 같음)를 남긴다. 기한을 넘긴 receipt는 먼저 outcome=unknown·reason_code=deadline_exceeded를 남기며, 이후 execution 부재를 확인해 receipt를 성공으로 올리면 outcome=succeeded·reason_code=execution_gone 결과를 추가로 남긴다.

WEB_DIST_DIR가 있으면 API가 웹 콘솔을 같은 origin에서 서빙한다. /v1·/internal 밖의 GET·HEAD는 그 디렉터리의 파일을 돌려주고, 파일이 없으면 index.html을 돌려준다(SPA fallback). 그래서 /login·/bootstrap을 새로고침해도 화면이 열린다. /v1·/internal 아래는 fallback하지 않는다. 인증된 /v1/nope는 JSON 404 NOT_FOUND다. /assets/ 아래 파일은 content hash가 붙은 이름이라 immutable로 캐시하고, 없는 asset은 페이지 대신 404를 준다. index.html은 no-cache이며 inline script를 허용하지 않는 CSP(script-src 'self')를 붙인다. control-host 이미지는 web build stage에서 apps/web을 빌드하고 dist/만 담는다. 별도 web 이미지는 없다. 로컬에서 웹만 고칠 때는 bun run --cwd apps/web dev(Vite, /v1을 VITE_API_TARGET 기본 http://127.0.0.1:3000으로 proxy)를 쓴다.

AUTH_MODE=none은 egress authorizer(EGRESS_AUTHORIZER_PORT)와 함께 쓸 수 없다. authorizer가 켜져 있다는 것은 worker가 proxy를 거쳐 이 API에 닿는다는 뜻이다. none 모드에서는 worker 안의 코드가 X-Owner-Id로 아무 owner나 행세할 수 있으므로 기동을 거부한다. AUTH_MODE 값 자체(none|api-key)는 readiness가 판정한다.

worker도 기동 때 교차 검사를 한다. WORKER_PROVIDER_MAX_RETRIES가 없으면 기동하지 않는다. 코드 기본값이 없는 설치 상한이고, scheduler가 항상 넘긴다. WORKER_NEXT_INPUT_WAIT_SEC가 WORKER_REQUEST_TIMEOUT_SEC 이상이어도 기동하지 않는다. 그 설정에서는 모든 long poll이 요청 timeout에 잘린다. scheduler와 reconciler는 빈 DATABASE_URL을 설정하지 않은 것으로 보고 QUEUE_DATABASE_URL을 쓴다.

API와 slack role은 WORKER_IMAGE_RUNTIME과 WORKER_IMAGE_PREVIOUS_RUNTIME으로 현재·직전 worker 이미지의 런타임 조합을 읽는다. 값은 손으로 적지 않고, images.yml이 남긴 worker digest JSON의 runtime 필드를 그대로 넣는다({"engine":…,"sdkVersion":…,"cliVersion":…,"profileDigestScheme":…}). engine은 claude와 codex이고, chart는 다른 이름을 렌더링 때 images.worker.runtime.engine must be one of: claude, codex로 거절한다. 직접 빌드한 이미지에는 그 JSON이 없다. images.yml과 같은 명령으로 이미지에서 읽는다.

docker run --rm -i --network none --entrypoint bun <worker-image> \
  run apps/worker/src/image-runtime.ts <<<'{}' | jq -c 'del(.profiles)'

worker 이미지 하나는 엔진 하나를 담는다. apps/worker/Dockerfile은 빌드 인자 WORKER_RUNTIME으로 엔진을 고른다. 비워 두면 Claude 이미지이고, --build-arg WORKER_RUNTIME=codex_app_server를 주면 Codex 이미지다(빌드 로그에 codex-cli 0.167.0이 찍히고, 다른 버전이면 빌드가 실패한다). CI는 Codex 이미지를 werft-worker-codex라는 별도 이미지로 만든다. Codex를 쓰는 배포는 WORKER_IMAGE(chart는 images.worker)를 그 이미지로 두고 위 명령으로 읽은 runtime(engine은 codex)을 함께 넣는다. 로컬 스택은 infra/compose.codex-worker.yml 오버레이로 같은 이미지를 빌드한다. Codex 세션이 OpenAI Responses API를 부르는 배포는 proxy의 EGRESS_CREDENTIAL_ALLOWLIST에 api.openai.com:443도 넣는다. Codex 엔진에는 자체 예산이 없다. 세션 비용·토큰 한도(SESSION_COST_LIMIT_USD, SESSION_TOKEN_LIMIT)는 worker gateway가 provider 호출마다 저장된 사용량으로 집행하고, 한도를 넘은 세션의 다음 호출은 403 BUDGET_EXCEEDED로 막힌다. 한계: 사용량은 proxy의 보고로 gateway에 닿는다. 보고가 재시도(1s·5s·15s) 끝에 유실되면 저장된 비용이 낮게 남고, 그 세션의 이후 provider 호출은 proxy 프로세스 안의 차단(프로세스 하나 단위, 재시작하면 사라진다)으로만 막힌다. 그래서 이 한도는 저장된 사용량에 기대는 소프트 한도다. 보고가 유실된 뒤 proxy가 여러 개이거나 재시작하면 초과액에 상한이 없다. 보고가 정상이어도 한 번의 긴 응답, 병렬 호출, 보고가 늦는 동안 나가는 다음 호출은 한도를 넘을 수 있다(Claude 엔진의 예산도 완료된 호출 뒤에 검사한다). 영속적인 사용량 기록으로 이 구멍을 닫는 일은 후속 작업이다.

엔진을 바꾸는 방법. 한 배포가 두 엔진의 이미지를 동시에 고르는 길은 없다. 새 세션은 언제나 현재 이미지로 시작하고, 직전 이미지는 체크포인트를 이어 받는 데만 쓰인다. 그래서 설치할 때 엔진을 정하는 것이 가장 단순하다. 이미 도는 배포의 WORKER_IMAGE를 다른 엔진의 이미지로 바꾸려면 업그레이드 검사(upgrade-check)의 규칙을 그대로 따른다. 새 설정의 WORKER_IMAGE_PREVIOUS가 지금 배포된 이미지여야 하고, 검사가 이어 받을 수 없다고 출력하는 세션(엔진이 다른 체크포인트뿐 아니라 SDK·CLI 버전이나 digest 방식이 맞지 않는 것, 런타임이 기록되지 않은 것도 포함한다)은 모두 upgrade.approvedSessions에 정확히 그 출력 목록으로 승인해야 한다. 체크포인트는 엔진 사이에서 옮길 수 없으므로, 전환 뒤에는 직전 이미지(옛 엔진)에서 이어 받던 세션이 끝나야 직전 이미지를 비울 수 있다.

WORKER_IMAGE_RUNTIME과 WORKER_IMAGE_PREVIOUS_RUNTIME이 둘 다 없으면 런타임 조합을 읽지 않으므로 아래 pin 유지도 적용되지 않는다. WORKER_IMAGE_PREVIOUS_RUNTIME만 있거나 값을 읽을 수 없으면 기동하지 않는다. 설정하면 profile의 runtime_version을 다음 이미지 버전으로 올려도, 직전 이미지 버전으로 만들어진 열린 세션의 profile pin이 유지된다. 현재 이미지가 싣지 않은 runtime_version의 profile은 새 세션을 받지 않는다. 다만 내장 도구 지원 목록이 현재 이미지의 목록과 같은 이전 버전은 싣는 것으로 본다(업그레이드 뒤에도 그 profile이 새 세션을 시작한다). 목록이 다른 SDK를 올리는 릴리스는 상한마다 runtime_version을 올린 새 버전을 함께 만든다(콘솔 또는 POST /v1/installation-profiles).

GitHub Packages bot token(결정)을 다른 host로 보내야 할 때만 아래 값을 둔다. GitHub Enterprise Server나 테스트용 가짜 레지스트리가 그런 경우다. 값은 http(s) URL이고 userinfo·query·fragment가 있으면 API가 기동하지 않는다. 레지스트리 host는 proxy의 EGRESS_CREDENTIAL_ALLOWLIST(사설 주소면 EGRESS_CREDENTIAL_PRIVATE_ALLOWLIST)에도 있어야 한다.

변수기본값의미
GITHUB_PACKAGES_API_URLhttps://api.github.com등록하는 token의 scope를 묻는 곳(GET /user)
GITHUB_PACKAGES_NPM_URLhttps://npm.pkg.github.comgithub-npm route의 upstream
GITHUB_PACKAGES_RUBYGEMS_URLhttps://rubygems.pkg.github.comgithub-rubygems route의 upstream

scheduler·worker 컨테이너·egress 설정

scheduler가 worker 컨테이너를 만들 때 읽는 값이다. 없으면 기본값을 쓰고, 형식이 틀리면 기동하지 않는다. user, HOME·workspace 경로, tmpfs 크기, credential 포트, stop timeout은 werft.isolation 지문에 들어간다(실행 기반과 worker 격리). 그래서 바꾸면 실행 중인 worker가 교체된다. CPU·메모리·PID 한도는 다음 launch부터 적용된다.

WORKER_MEMORY_MB는 transcript 상한(MAX_TRANSCRIPT_BYTES)과 함께 정한 값이다. 둘의 관계, 실측, 노드당 세션 수, 바꾸는 방법은 용량 관리에 있다.

scheduler는 WORKER_IMAGE와 WORKER_IMAGE_RUNTIME으로 현재 이미지를, WORKER_IMAGE_PREVIOUS와 WORKER_IMAGE_PREVIOUS_RUNTIME으로 직전 릴리스 이미지를 받는다. 런타임 값은 API와 같은 값이다(위 API 설정). 직전 이미지는 두 값을 함께 설정하고, 하나만 있으면 기동하지 않는다. WORKER_IMAGE_RUNTIME이 없으면 모든 launch가 WORKER_IMAGE로 뜬다. 설정하면 launch마다 세션의 checkpoint 포인터가 기록한 조합(engine·SDK·CLI·profile digest scheme)과 같은 이미지를 고르고, 둘 다 맞으면 현재 이미지를 쓴다. 새 세션, checkpoint가 없는 세션, 조합을 기록하지 않은 옛 checkpoint는 현재 이미지로 뜬다. 어느 이미지와도 맞지 않는 세션은 worker를 띄우지 않고 recovery_required(incompatible_checkpoint)로 멈추며, 세션 이벤트 checkpoint_restore_failed에 detected_at: "launch"가 남는다. retry_restore로 다시 시도할 수 있다.

worker는 claim 때 자기 이미지가 실제로 싣는 조합을 보낸다. 설정한 조합과 다르면 API가 claim을 거절하고(LAUNCH_FAILED, reason_code worker_image_mismatch), 그 이미지를 그 설정값에 대해 거부한 것으로 기록한다. 이후 그 이미지로 뜰 세션은 launch되지 않고 기다리며, scheduler pass는 images_refused를 error 로그로 남기고 exit 1로 끝난다. 이미지 설정을 바로잡으면(값이 달라지면) 다음 pass부터 다시 launch한다. 세션 자체는 실패로 바뀌지 않는다.

배포 순서: 새 worker는 claim에 image_runtime을 싣는다. 이 필드를 모르는 옛 API는 요청을 거절하므로, API를 먼저 올린 뒤 scheduler의 WORKER_IMAGE를 새 이미지로 바꾼다.

변수기본값의미
WORKER_CPUS1worker 하나의 CPU 몫. 0.01 이상
WORKER_MEMORY_MB4608worker 하나의 메모리 한도(MiB). 압축 없는 512MiB transcript를 resume한 엔진이 한도의 절반 아래에 들도록 잡은 값이다. 개발용 .env.example의 2048은 이 기준을 충족하지 않으므로 큰 transcript 복원 시험에서는 그 줄을 지우거나 4608로 바꾼다
WORKER_PIDS_LIMIT512worker 하나의 PID 한도. compose는 넘기지 않는다
EXECUTION_DOCKER_USER1000:1000worker의 숫자 uid[:gid]. uid나 gid가 0이면 기동을 거부한다. worker 이미지의 /workspace 소유자와 같아야 한다
EXECUTION_DOCKER_HOME_DIR/home/workertmpfs로 마운트하는 HOME. 절대 경로
EXECUTION_DOCKER_WORKSPACE_DIR/workspace세션 volume을 마운트하는 곳. 절대 경로이고 HOME과 달라야 하며 공백·역슬래시를 쓸 수 없다. worker에는 WORKER_WORKSPACE_DIR로 간다
EXECUTION_DOCKER_TMPFS_SIZE_MB256HOME과 /tmp tmpfs 각각의 크기(MiB)
EXECUTION_DOCKER_STOP_TIMEOUT_SEC120SIGTERM에서 SIGKILL까지의 유예. 30 미만이면 기동을 거부한다. worker에는 WORKER_STOP_GRACE_SEC로 가고, worker는 drain을 그 안에 맞춘다
EXECUTION_DOCKER_REQUEST_TIMEOUT_SEC30Docker Engine API 요청 하나의 기한
EXECUTION_DOCKER_COMMAND없음이미지 entrypoint 대신 돌릴 명령(공백으로 나눈다). 테스트용이다
DOCKER_HOSTunix:///var/run/docker.sockDocker daemon 주소
DOCKER_API_VERSIONv1.44요청에 쓰는 Engine API 버전
EXECUTION_EGRESS_CREDENTIAL_PORT3129proxy credential route의 포트. proxy의 EGRESS_CREDENTIAL_PORT와 같아야 하고, EXECUTION_EGRESS_PROXY_URL의 포트와는 달라야 한다

EXECUTION_DOCKER_NETWORK·EXECUTION_DOCKER_NETWORK_ALLOWLIST는 더 이상 읽지 않는다. 값이 있으면 기동을 거부한다.

egress-proxy와 API의 authorizer listener는 아래 값을 읽는다. 목록 변수는 기본값에 덧붙이지 않고 기본값을 통째로 대체한다. GitHub·bucket host를 넣을 때 provider와 패키지 registry 등 필요한 항목을 모두 다시 적는다. 공개 credential 목록(EGRESS_CREDENTIAL_ALLOWLIST)은 proxy와 API에서 같은 값으로 맞춘다. private 목록(EGRESS_CREDENTIAL_PRIVATE_ALLOWLIST)은 proxy가 거절하는 기준이고 API는 콘솔 경고에만 사용한다. private 목록도 같게 두기를 권장하지만 강제되지는 않으며 core·local-model 레이어의 역할별 기본값도 다르다.

변수기본값Helm values의미와 기동 제약
EGRESS_PROXY_HOST0.0.0.0별도 키 없음proxy의 forward·credential listener 주소
EGRESS_PROXY_PORT3128별도 키 없음forward proxy 포트
EGRESS_CREDENTIAL_PORT3129별도 키 없음credential route 포트
LOG_LEVELinfoconfig.logLevel로그 수준. API와 같은 형식
EGRESS_ALLOWLIST빈 목록config.egress.allowlistforward public 목적지의 쉼표 구분 host:port. credential·redirect 목록과 중복 금지
EGRESS_PRIVATE_ALLOWLISTCompose: api:3000,host.docker.internal:3000. chart: API Service의 3000config.egress.privateAllowlist(비면 API Service)forward private 목적지. link-local·reserved 주소는 허용하지 않음. 두 forward 목록이 모두 비면 proxy 기동 거부
EGRESS_CREDENTIAL_ALLOWLISTCompose(core·local): api.anthropic.com:443,api.openai.com:443,registry.npmjs.org:443,rubygems.org:443,proxy.golang.org:443,sum.golang.org:443. 단일 서버(test-ops): 기본값 없음, 필수. chart: core·local과 같은 기본값config.egress.credentialAllowlistAPI·proxy의 credential public 목적지. test-ops는 값이 없으면 Compose가 실패함. 값이 기본값을 통째로 대체하므로 GitHub·checkpoint bucket host를 넣을 때 필요한 항목을 모두 다시 적음
EGRESS_CREDENTIAL_PRIVATE_ALLOWLISTchart·Compose core proxy: 빈 목록. Compose core API·local proxy 레이어: fake-git:3000,fake-messages:4010,fake-mcp:4020,localstack:4566config.egress.credentialPrivateAllowlistproxy 거절·API 콘솔 경고의 private 목적지. test-ops LocalStack 경로는 localstack:4566이 필수이며 예시도 이 값만 사용. local-model 오버레이는 fake-git:3000,localstack:4566,ollama.internal:11434,litellm:4000. 운영 설치는 시험 host를 사용하지 않음
EGRESS_PACKAGES_REDIRECT_ALLOWLIST빈 목록config.egress.packagesRedirectAllowlistpackages redirect와 Git LFS 객체의 public 목적지. 자격 증명 없이 받으며 forward 목록과 중복 금지
EGRESS_AUTHORIZER_HOST0.0.0.0별도 키 없음API authorizer listener 주소
EGRESS_AUTHORIZER_PORTAPI 코드: 미설정 시 listener 없음. Compose·chart: 3100자동 생성API에서는 토큰과 함께 설정하거나 둘 다 생략. AUTH_MODE=none과 함께 쓰면 기동 거부
EGRESS_AUTHORIZER_URLproxy 코드: 없음. Compose: http://api:3100, chart: API authorizer Service자동 생성http/https URL. proxy에서는 토큰과 함께 설정하거나 둘 다 생략. API listener 포트와 일치해야 함
EGRESS_AUTHORIZER_TOKEN운영 기본값 없음Secret의 같은 이름 keyAPI·proxy가 공유하는 최소 32자 비밀. API에서는 PORT, proxy에서는 URL과 짝으로 필수. 운영에서는 설치별로 생성하며 로컬 시험 값을 쓰지 않음

compose의 세 포트와 두 내부 URL은 infra/compose.core.yml 상단의 x-egress-ports 한 곳에서 정한다. proxy의 EGRESS_PROXY_PORT·EGRESS_CREDENTIAL_PORT·EGRESS_AUTHORIZER_URL, api의 EGRESS_AUTHORIZER_PORT, scheduler의 EXECUTION_EGRESS_PROXY_URL·EXECUTION_EGRESS_CREDENTIAL_PORT는 모두 그 anchor를 쓴다. 포트를 바꿀 때는 같은 블록의 listener와 URL 짝을 함께 바꾼다. 시험이 docker compose config 결과에서 두 URL의 포트와 세 listener가 맞는지 확인한다.

API 컨테이너의 메모리는 checkpoint bundle 검증이 정한다. 검증은 동시에 두 개까지 돌고, 검증마다 git fetch와 index-pack이 각자 CHECKPOINT_GIT_MEMORY_MB의 주소 공간 상한 아래에서 함께 돈다. 두 값은 함께 바꾼다. 상한만 올리면 OOM killer가 무엇을 죽일지 정하게 된다. 계산은 infra/compose.core.yml의 api 서비스 주석에 있다.

변수기본값의미
API_MEMORY_MB7168compose api 컨테이너의 mem_limit(MiB)
CHECKPOINT_GIT_MEMORY_MB1536bundle 검증 git 프로세스 하나의 주소 공간 상한(MiB). 512 미만이면 기동을 거부한다

compose의 Postgres는 POSTGRES_USER(postgres)·POSTGRES_PASSWORD(dev)·POSTGRES_DB(sessions)로 만들고, 모든 서비스가 같은 값으로 DSN을 만든다.

worker는 아래 값도 env로 읽는다. 하지만 scheduler가 넘기지 않으므로 운영에서는 코드 기본값으로 고정이다. WORKER_ANSWER_POLL_SEC(1), WORKER_ATTACH_FETCH_TIMEOUT_SEC(300, 세션 도중 붙인 저장소 하나를 받아 오는 시간), WORKER_ATTACH_FETCH_MAX_MB(2048, 그 저장소를 받은 scratch mirror의 크기), WORKER_CLAIM_TIMEOUT_SEC(60), WORKER_DRAIN_TIMEOUT_SEC(100, stop 유예 안으로 줄어든다), WORKER_HEARTBEAT_INTERVAL_SEC(5), WORKER_IDLE_TIMEOUT_SEC(1800), WORKER_LEASE_SAFETY_MARGIN_SEC(10), WORKER_NEXT_INPUT_RETRY_SEC(60), WORKER_NEXT_INPUT_WAIT_SEC(20), WORKER_REQUEST_TIMEOUT_SEC(30), WORKER_STARTUP_TIMEOUT_SEC(3600), QUESTION_TIMEOUT_SEC(1800), WORKER_CLAUDE_CONFIG_DIR($HOME/.claude).

감독 루프

설정기본의미
RECONCILER_INTERVAL_SEC10pass가 끝난 뒤 다음 pass까지 쉬는 시간
RECONCILER_PASS_TIMEOUT_SEC60이 시간을 넘긴 pass는 SIGTERM, 10초 뒤 SIGKILL로 끝내고 실패로 센다. DB가 멈춘 pass가 pool timeout으로 스스로 끝나는 약 45초보다 크게 둔다
RECONCILER_MAX_CONSECUTIVE_FAILURES3실패 pass가 이만큼 이어지면 루프가 exit 1 하고 restart: unless-stopped가 재시작한다. 그보다 적으면 다음 pass가 곧 재시도다
RECONCILER_HEALTH_STALE_SEC90healthcheck는 마지막으로 끝난 pass가 실패했거나, 진행 중인 pass가 제한 시간을 넘겼거나, 이 시간 동안 성공한 pass가 없으면 unhealthy다. 성공 직후 멈춘 pass도 제한 시간에서 바로 unhealthy가 된다. RECONCILER_INTERVAL_SEC + RECONCILER_PASS_TIMEOUT_SEC보다 커야 기동한다
RECONCILER_STATUS_FILE/tmp/reconciler-status.json루프가 pass마다 갱신하는 상태(lastSuccessAt·lastDegradedAt·lastFailureAt·lastFailureReason·consecutiveFailures·lastPassDurationMs, 진행 중인 pass의 passDeadlineAt). healthcheck가 읽는다
RECONCILER_BATCH_SIZE100pass의 각 단계(orphan·lease·interrupt 회수, 기한을 넘긴 interrupt·terminate receipt의 unknown 마감, 입력 대기 알림)가 한 번에 처리하는 row 수 상한. 남은 row는 다음 pass가 처리한다. scheduler의 terminate 기한 sweep은 이 값을 읽지 않고 pass마다 고정 상한 100개씩 처리한다
scheduler 설정기본제약
SCHEDULER_INTERVAL_SEC5pass 종료 뒤 대기
SCHEDULER_PASS_TIMEOUT_SEC180Docker stop+request timeout보다 큼. Kubernetes는 150초 초과
SCHEDULER_MAX_CONSECUTIVE_FAILURES3연속 실패 뒤 loop 종료
SCHEDULER_HEALTH_STALE_SEC200interval+timeout보다 큼
SCHEDULER_STATUS_FILE/tmp/scheduler-status.json디렉터리 전체가 쓰기 가능해야 함

compose는 RECONCILER_STATUS_FILE, SCHEDULER_STATUS_FILE, RECONCILER_DRY_RUN을 전달하지 않는다. 상태 확인은 각 status file과 --health를 사용한다. reconciler dry-run은 복구 작업에서 직접 1회 실행한다. HEARTBEAT_TTL_SEC는 API만 읽으며 reconciler에 넣으면 기동을 거부한다.

Kubernetes env와 values

scheduler는 EXECUTION_BACKEND로 backend를 고른다. 값은 local_docker(기본값, 비워 둬도 같다)와 kubernetes 둘이고, 그 밖의 값이면 그 값을 적은 오류로 기동을 거부한다. kubernetes이면 Docker 설정(DOCKER_HOST, EXECUTION_DOCKER_*, EXECUTION_WORKSPACE_QUOTA)을 읽지 않고 Docker socket도 쓰지 않는다. Docker 전용 preflight인 egress proxy 격리 확인과 workspace quota probe도 돌지 않는다. kubernetes에서 worker 격리는 chart의 NetworkPolicy가 맡고, workspace는 PVC 크기를 요청하지만 실제 상한 강제는 저장소에 달려 있다. 정지·요청 timeout은 고정값(120s + 30s)이어서 SCHEDULER_PASS_TIMEOUT_SEC는 150보다 커야 한다. migrate-workspace는 Docker volume을 옮기는 도구라서 local_docker에서만 돈다.

readOnlyRootFilesystem인 Pod에서는 status file을 쓰기 가능한 경로에 둔다. 기본 경로 /tmp/scheduler-status.json·/tmp/reconciler-status.json은 /tmp에 emptyDir를 마운트하면 그대로 쓸 수 있다. 다른 경로라면 SCHEDULER_STATUS_FILE·RECONCILER_STATUS_FILE로 바꾼다. 루프는 status file과 같은 디렉터리에 임시 파일(<status file>.<pid>.tmp)을 쓴 뒤 rename한다. scheduler는 그 옆에 .quota-verified 기록도 둔다. 그래서 파일 하나가 아니라 디렉터리 전체가 쓰기 가능해야 한다.

공유 env(WORKER_IMAGE, WORKER_GATEWAY_URL, EXECUTION_EGRESS_PROXY_URL, EXECUTION_EGRESS_CREDENTIAL_PORT, EXECUTION_INSTALLATION_ID, EXECUTION_WORKSPACE_QUOTA_MB, EXECUTION_WORKSPACE_GC_MIN_AGE_SEC, S3_BUCKET, AWS_REGION)는 Docker backend와 같다. kubernetes 모드에서는 다음 두 값에 제약이 더 붙는다.

  • WORKER_IMAGE는 repository@sha256:<digest> 형식이어야 한다.
  • EXECUTION_INSTALLATION_ID는 32자 이하의 소문자 DNS label이어야 한다.

kubernetes 모드에서만 읽는 설정은 다음과 같다.

EXECUTION_WORKSPACE_ENFORCED_BY_STORAGE는 미설정이면 false이며 정확히 true/false만 받는다. 빈 값·공백·대문자·1/0·on/off는 원본 값을 노출하지 않는 오류로 기동을 거부한다. Helm의 worker.workspace.enforcedByStorage(기본 false)에서 생성하며, 이 설치의 모든 workspace PVC(기존 것 포함)에 저장소가 요청 바이트를 하드로 강제한다는 운영자 선언이다. StorageClass가 비면 클러스터 기본값이므로 각 PVC의 강제를 운영자가 확인한다. Werft는 검증하지 않는다. 전용 쓰기 가능 마운트(/ 아님, rw 있고 ro 없음), 파일시스템 총량이 요청의 0.95~1.05배, PVC capacity가 요청 이상이라는 휴리스틱을 통과한 선언 경로만 바이트 hard/inode unknown으로 판별한다. 저장소와 상한을 따른다. 선언 변경은 PVC·isolation stamp·기존 Pod env를 바꾸지 않고 새 Pod만 새 값을 받는다.

변수뜻
EXECUTION_K8S_NAMESPACEworker Pod·Secret·PVC를 두는 namespace. 기본값은 scheduler 자신의 namespace다. 클러스터 밖에서 돌리면 반드시 줘야 한다.
EXECUTION_K8S_WORKER_SERVICE_ACCOUNTworker Pod의 ServiceAccount. token은 늘 마운트하지 않는다.
EXECUTION_K8S_STORAGE_CLASSworkspace PVC의 StorageClass. 비우면 클러스터 기본값을 쓴다.
EXECUTION_K8S_IMAGE_PULL_SECRETS선택. 쉼표로 구분한 Secret 이름.
EXECUTION_K8S_WORKER_NODE_SELECTOR선택. JSON object.
EXECUTION_K8S_WORKER_TOLERATIONS선택. JSON array.
EXECUTION_K8S_WORKER_EPHEMERAL_STORAGE_MBworker 컨테이너의 ephemeral-storage requests·limits(MiB). 기본 1024. 넘으면 kubelet이 Pod를 evict한다.

실패한 worker의 종료 사유와 로그 tail(마지막 8 KiB)은 scheduler 로그의 Execution resource failed; log tail kept 줄에 남는다. 저장 위치와 보존 기간은 저장소의 Kubernetes 실행 어댑터 README(종료 사유와 로그 tail 절)에 있다.

chart가 넣는 값

Helm chart(deploy/helm/werft, 설치 절차는 Kubernetes 설치 (Helm chart))로 설치하면 위 env를 직접 적지 않는다. chart가 values에서 만든다.

envvalues비고
EXECUTION_BACKEND없음늘 kubernetes
EXECUTION_K8S_WORKER_SERVICE_ACCOUNT없음chart가 만든 <fullname>-worker. token은 마운트하지 않는다
EXECUTION_K8S_NAMESPACE없음넣지 않는다. scheduler 자신의 namespace(release namespace)가 된다
EXECUTION_K8S_STORAGE_CLASSworker.storageClass비면 넣지 않는다
EXECUTION_K8S_IMAGE_PULL_SECRETSimages.pullSecrets제어 영역 Pod에도 같은 Secret을 쓴다
EXECUTION_K8S_WORKER_NODE_SELECTOR, EXECUTION_K8S_WORKER_TOLERATIONSworker.nodeSelector, worker.tolerationsJSON으로 바꿔 넣는다
WORKER_IMAGEimages.registry, images.worker.*<registry>/<repository>@<digest>. digest가 없으면 render가 실패한다
WORKER_GATEWAY_URL, EXECUTION_EGRESS_PROXY_URL, EXECUTION_EGRESS_CREDENTIAL_PORT없음api Service의 3000, egress-proxy Service의 3128, 3129
EXECUTION_INSTALLATION_IDinstallationIdworker NetworkPolicy의 label 값이기도 하다
EXECUTION_WORKSPACE_QUOTA_MB, EXECUTION_WORKSPACE_GC_MIN_AGE_SEC, EXECUTION_WORKSPACE_ENFORCED_BY_STORAGEworker.workspace.quotaMb, worker.workspace.gcMinAgeSec, worker.workspace.enforcedByStorage요청 크기·회수 최소 나이·설치 전체 강제 선언
WORKER_CPUS, WORKER_MEMORY_MBworker.cpus, worker.memoryMb
EXECUTION_K8S_WORKER_EPHEMERAL_STORAGE_MBworker.ephemeralStorageMb기본 1024
설치 상한(EXECUTION_SLOT_LIMIT 등)config.limitsapi와 scheduler에 같은 값
SCHEDULER_*scheduler.intervalSec 등SCHEDULER_PASS_TIMEOUT_SEC(scheduler.passTimeoutSec, 기본 180)는 150보다 커야 한다
SLACK_ROLE_INTERVAL_SEC, SLACK_ROLE_PASS_TIMEOUT_SEC, SLACK_ROLE_MAX_CONSECUTIVE_FAILURES, SLACK_ROLE_HEALTH_STALE_SEC, SLACK_ROLE_BATCH_SIZE, SLACK_ROLE_LEASE_SECslack.intervalSec, slack.passTimeoutSec, slack.maxConsecutiveFailures, slack.healthStaleSec, slack.batchSize, slack.leaseSec기본 2, 60, 3, 90, 10, 60. slack.enabled: false면 slack Deployment를 그리지 않는다
PLATFORM_ALLOW_UNATTENDED_PERMISSIONS(slack)api.extraEnvslack은 api와 같은 카탈로그를 읽으므로 api의 값을 따른다. slack.extraEnv에 적으면 그 값이 이긴다

어느 Deployment에나 extraEnv로 env를 더할 수 있다. Secret에서 오는 값(DATABASE_URL, EGRESS_AUTHORIZER_TOKEN, AWS key, 카탈로그 credential)은 Kubernetes 설치 (Helm chart)의 준비 절의 표에 있다.

probe

구성요소readinesslivenessstartup
apiGET /readyzGET /healthzGET /healthz(5초 × 24)
schedulerbun run apps/control-host/src/main.ts scheduler --health같음같음. 실패 허용 횟수는 (intervalSec + passTimeoutSec + 39) / 10
reconcilerbun run apps/control-host/src/main.ts reconciler --health같음scheduler와 같은 식
slackbun run apps/control-host/src/main.ts slack --health같음scheduler와 같은 식
egress-proxybun run /app/src/healthcheck.ts같음없음

/healthz는 HTTP를 받는지만 보고 DB를 보지 않는다. 그래서 liveness에 쓰고, DB·schema·설정을 보는 /readyz는 readiness에만 쓴다. DB가 잠시 끊겨도 api Pod는 재시작되지 않고 트래픽에서만 빠진다. /livez는 없다. scheduler·reconciler·slack의 --health는 loop가 쓰는 status file을 읽고, 직전 pass가 실패했거나 기한을 넘겼거나, SCHEDULER_HEALTH_STALE_SEC·RECONCILER_HEALTH_STALE_SEC·SLACK_ROLE_HEALTH_STALE_SEC 안에 끝난 pass가 없으면 실패한다. startup probe는 첫 pass가 끝날 때까지 기다린다.

chart 리소스

리소스수비고
api Deployment + Service(3000) + authorizer Service(3100)23100은 egress-proxy만 닿는다(NetworkPolicy). Ingress는 3000의 /v1·/docs·/healthz·/readyz만 route한다. worker용 /internal은 열지 않는다. 웹 콘솔 /는 ingress.console.enabled로 더한다(접속 경로)
scheduler Deployment + Role/RoleBinding1, Recreateworker Pod·nonce Secret·workspace PVC를 같은 namespace에서만 만들고 지운다
reconciler Deployment1
slack Deployment1Slack inbox를 비운다. slack.enabled: false면 없다. 바깥으로는 DNS(53), 443, DB 포트(networkPolicy.slack.databasePort, 기본 5432)로만 나간다
egress-proxy Deployment + Service(3128, 3129)2worker의 유일한 바깥 경로
migrate Jobinstall·upgrade마다 1pre-install,pre-upgrade hook
upgrade-check Jobupgrade마다 1pre-upgrade hook, migrate 앞. scheduler.enabled: false면 없다(업그레이드 전 검사)
checkpoint GC CronJob주 1회checkpointGc.schedule
session archive CronJob주 1회sessionArchive.schedule(기본 0 5 * * 0)·afterDays(기본 180, 0=끔, 30–3650)·dryRun(기본 true). 오래 쉰 세션의 이벤트를 bucket으로 옮긴다(data-retention.md)
NetworkPolicy5worker는 egress-proxy로만 나간다. 제어 영역 Pod 중 바깥 연결을 막는 것은 slack 하나다
Ingress, ExternalSecret선택ingress.enabled, secrets.externalSecret.enabled
metrics Deployment + Service(9464) + NetworkPolicy선택, 1, Recreatemetrics.enabled. 플랫폼 지표를 낸다(플랫폼 지표)

Secret과 AWS values

키읽는 곳
DATABASE_URLapi, scheduler, reconciler, slack, migrate, checkpoint GC, metrics, upgrade-check Job, session archive CronJob
EGRESS_AUTHORIZER_TOKENapi, egress-proxy(같은 값)
AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEYapi, checkpoint GC, session archive CronJob. secrets.objectStoreKeys: true일 때만. false면 AWS 기본 credential chain(IRSA)으로 서명한다
secrets.catalogEnv의 이름(기본 ANTHROPIC_API_KEY)api만. 설정 문서가 value_env로 가리키는 provider·저장소 자격 증명을 환경 변수로 주는 대안이다. 모델 key의 기본 경로는 설치 뒤 콘솔의 설정 > 에이전트 연결 > 모델 연결 > 연결 추가이고, 그렇게 하면 secrets.catalogEnv에서 이 이름만 빼고 BOOTSTRAP_TOKEN은 남긴다(["BOOTSTRAP_TOKEN"]). secrets.catalogEnv나 api.extraEnv 같은 어떤 경로로도 BOOTSTRAP_TOKEN을 주입하지 않으면 프로세스마다 토큰이 자동 생성된다. 기본 API replica 2개로 첫 설치(bootstrap)를 하려면 같은 토큰을 주입해야 한다. 다른 경로 주입이 없는데 목록을 []로 비우면 replica마다 다른 토큰이 생겨 일부 요청이 401이 된다. replica가 1개이면 자동 생성 토큰도 쓸 수 있다. 기본값을 두면 Secret에 그 키가 없을 때 Pod가 뜨지 않는다. slack은 받지 않는다
PLATFORM_SECRET_KEYapi, slack. 필수. 64자 hex master key. 콘솔에서 저장한 자격 증명을 봉인한다. Secret에 없으면 Pod가 뜨지 않는다
값무엇을 바꾸나
images.registry, images.*.digestECR host와 그 이미지의 digest
secrets.objectStoreKeys: false, serviceAccounts.api.annotations정적 AWS key 대신 IRSA. api, checkpoint GC, session archive CronJob이 api ServiceAccount의 이 role로 S3·Secrets Manager에 서명한다. scheduler와 worker는 S3 권한이 없다
secrets.externalSecretExternal Secrets Operator가 Secrets Manager의 remoteKey(JSON, 위 Secret 키 전부. IRSA면 AWS key 둘은 뺀다)로 Secret을 쓴다. 운영자 CRD가 필요하다
ingressALB Ingress. scheme은 internal이다(위). healthcheck-path: /readyz, SSE를 위해 idle timeout 300초. 공개 경로(/v1·/docs·/healthz·/readyz)만 route하고 /internal과 authorizer(3100)는 열지 않는다
ingress.console.enabled기본 false. 켜면 웹 콘솔 /와 /internal 404 규칙을 더한다. 켜는 조건은 접속 경로
config.egress.credentialAllowlist값이 기본값을 통째로 대체하므로 GitHub·checkpoint bucket host를 넣을 때 provider와 패키지 registry 등 필요한 항목을 모두 다시 적는다. worker의 checkpoint upload·restore가 credential route로 나간다
worker.storageClass, worker.nodeSelector, worker.tolerationsworker 전용 node group과 workspace PVC의 StorageClass(gp3)
networkPolicy.enabledVPC CNI의 network policy agent가 켜져 있어야 효력이 있다

정적 S3 key를 써야 하면 secrets.objectStoreKeys: true로 두고 Secret에 AWS key 둘을 넣는다. 둘 다 있으면 정적 key가 IRSA보다 우선한다.

사용량과 비용 계측

  • 세션 비용은 credential route가 센다. proxy는 2xx /v1/messages 응답(JSON과 SSE 모두)에서 usage를 읽는다. 응답이 끝나거나 끊기면 authorizer listener의 POST /usage로 보고한다. count_tokens와 오류 응답은 세지 않는다.
    • 응답이 usage를 다 말하지 않으면 많게 추정한다. message_stop 전에 끊긴 stream은 전달한 content 한 글자를 token 하나로 쳐서 output에 넣고, 전달한 web search 결과 블록 하나를 검색 한 번으로 친다. usage를 읽지 못한 JSON 응답(끊김, 8 MiB 초과, JSON 아님)은 요청으로 센다. 요청 1 byte를 input token 하나로, max_tokens를 output 전부로 본다. 이런 호출은 proxy가 Provider usage estimated 경고를 남기고 ledger에 estimated=true로 적힌다.
    • API는 가격표로 USD를 구해 micro-dollar 단위로 올림한다. 그 금액을 provider_usage에 교환 하나당 한 줄로 쓰고, 같은 트랜잭션에서 세션 비용에 더한다. 그래서 ledger 합과 세션 비용이 맞는다.
    • engine의 호출과 도구가 engine의 token으로 직접 부른 호출을 똑같이 센다.
    • 보고에는 proxy가 교환마다 만든 id가 붙는다. authorizer가 답하지 못하면 1·5·15초 뒤에 다시 보내고, 같은 id는 한 번만 센다. 끝내 보고하지 못하면 proxy가 Provider usage went unreported 오류 로그를 남기고 그 교환은 세지 않는다.
  • turn 종료 때 SDK가 알려 준 total_cost_usd 증분은 turn 결과(cost_usd)에만 남고 세션 비용에는 더하지 않는다. engine의 turn별 maxBudgetUsd는 그대로 SDK 값으로 판정한다. proxy 계측 전에 만든 세션은 그때까지 SDK 값을 더한 비용을 이어받는다.
  • 가격표는 플랫폼 코드가 소유한다. 모델 id별로 input, output, cache write(5분 1.25배, 1시간 2배), cache read 단가를 둔다. 모델이 추가되거나 가격이 바뀌면 릴리스와 함께 고친다.
    • fast mode는 따로 둔 fast 단가표로 센다. 지금은 Opus 5.5($8/$40)와 Opus 5·Opus 4.8($10/$50)만 있다. cache 배수는 fast 단가 위에 그대로 곱한다. speed는 응답의 usage.speed를 따르고, 응답이 말하지 않으면 요청의 speed를 따른다. 둘 다 없으면 standard다.
    • 표에 없는 모델, fast 단가가 없는 모델의 fast 응답, 이름이 아닌 speed 값은 항목마다 두 표를 통틀어 최고 단가로 센다. authorizer가 Provider usage priced at the fallback rate 경고를 남기고, ledger에는 priced_by=fallback으로 적힌다.
    • 응답 usage.server_tool_use의 web search 한 번마다 $0.01(1,000회 $10)을 더한다. web fetch는 token 말고 요금이 없다. code execution은 더하지 않는다. web search·web fetch와 함께 쓰면 요금이 없고, 아니면 호출 수가 아니라 컨테이너 시간으로 과금되며 조직 무료 시간이 있기 때문이다. 세 횟수와 speed는 ledger(provider_usage)에 함께 적힌다.
    • 4.6 이후 모델은 US 전용 추론(inference_geo: "us")이면 token 단가 전부(fast·cache 포함)에 1.1배를 곱한다. web search 요금에는 곱하지 않는다. geo는 응답의 usage.inference_geo를 따르고, 응답이 말하지 않으면 요청의 inference_geo를 따른다. 둘 다 없으면 unknown이다. workspace 기본값이 us일 수 있어서다. global과 us가 아닌 값은 최고 단가에 1.1배로 센다(priced_by=fallback). geo는 ledger에 inference_geo로 적힌다. inference_geo를 모르는 옛 API는 이 필드가 붙은 보고를 400으로 거절하고, proxy는 그 교환을 세지 못한다(Provider usage went unreported). 그래서 API(migration 포함)를 egress proxy보다 먼저 올려야 한다. 실행 순서는 Compose 업그레이드와 Kubernetes 업그레이드를 따른다. 새 API는 이 필드가 없는 옛 proxy의 보고도 받는다.
    • 4.6 이전 모델(Opus 4.5·4, Sonnet 4.5·4, Haiku 4.5)은 inference_geo를 받지 않으므로 geo와 상관없이 표준 단가로 센다. 이 모델들의 창은 200K이고 공개된 long-context 단가가 없다. 그래서 input·cache write·cache read 합이 200K를 넘는 호출은 최고 단가로 센다(priced_by=fallback). 4.6 이후 모델은 1M 전체가 표준 단가다.
    • batch는 credential route가 /v1/messages/batches를 열지 않아서 해당이 없다.
  • 토큰 상한은 provider_usage 한 줄의 input_tokens + cache_creation_input_tokens + cache_read_input_tokens + output_tokens를 sessions.provider_tokens에 더해 센다. 더하는 일은 provider_usage insert trigger가 같은 트랜잭션에서 한다. 그래서 rollout 중이나 롤백 뒤에 0127 이전 API가 기록한 호출도 빠지지 않는다. cache read도 모델이 처리한 token이라 센다. 많게 추정한 보고는 추정치 그대로 더한다. 이 누계를 더한 migration은 기존 세션의 값을 provider_usage 합으로 채운다. 그 전에 SDK 값으로만 비용을 셌던 세션의 token은 채울 수 없다. 누계는 2^53-1(Number.MAX_SAFE_INTEGER)에서 멈춘다(migration 0129). trigger는 numeric으로 더하므로 비정상적으로 큰 token 값이 와도 오류로 usage 기록과 비용을 되돌리지 않는다.
    • SDK에는 token 예산 옵션이 없다. 그래서 turn 도중의 토큰 상한은 아래 egress 거절과 재인가만 맡는다. 상한에 닿은 뒤 열린 Messages 호출은 거절되어 turn이 failed(budget_exceeded)로 끝나고, 다음 turn부터는 BUDGET_EXCEEDED로 dispatch되지 않는다. proxy는 authorizer의 BUDGET_EXCEEDED 거절을 Messages 오류 형식(message가 BUDGET_EXCEEDED:로 시작)으로 전한다. 사유는 이 문구로 정하지 않는다. 엔진은 이 403을 api_error로 보고하고, 재인가에서 끊긴 교환도 재시도가 남지 않았으면(PROVIDER_MAX_RETRIES=0) 끊긴 연결 그대로 api_error로 보고한다. finalize는 api_error로 끝난 turn의 세션이 이미 상한을 넘었으면 사유를 budget_exceeded로, receipt 오류를 BUDGET_EXCEEDED로 기록하고, 엔진의 provider 진단(provider_error)은 비운다. 상한 아래에서 난 403은 api_error로 남는다.
  • 비용 상한과 토큰 상한은 호출이 끝난 뒤에 판정한다. 동시에 열린 호출은 모두 인가를 통과할 수 있고, 진행 중인 turn은 상한을 넘을 수 있다.
  • 누적 비용이나 누적 token이 상한 이상인 세션의 provider egress token은 authorizer가 403 BUDGET_EXCEEDED로 거절한다. 새 provider 교환은 곧바로 거절되고, 이미 열린 교환은 다음 재인가(30초 주기)에서 끊긴다. repository·object store route는 거절하지 않는다.

지표 역할

설정기본값뜻
METRICS_PORT9464/metrics와 /healthz를 받는 포트. chart는 metrics.port로 넣는다
DATABASE_URL없음(필수)읽기 전용으로 여는 DB 주소. chart는 다른 역할과 같은 Secret key를 넣는다
config.limits의 상한설정 레퍼런스launch 대기열을 scheduler와 같은 비용·토큰 상한으로 센다. chart는 scheduler와 같은 값을 넣는다
LOG_LEVELinfo읽기 실패를 남기는 로그 수준

이 페이지