재사용 가능한 에이전트 스킬에는 호출 전 런타임 체크가 필요합니다
요약
에이전트 스킬이 재사용 가능한 워크플로로 진화함에 따라, 잘못된 스킬이 반복 실행되어 자원을 낭비하는 문제를 방지하기 위한 런타임 체크의 중요성을 설명합니다. 모델 호출 전 단계에서 실행 허용 여부를 결정하는 가드(Guard) 로직 도입을 제안합니다.
핵심 포인트
- 에이전트 사용 패턴이 일회성 프롬프트에서 재사용 가능한 워크플로로 이동 중
- 느슨한 스킬 지침은 실패와 자원 낭비를 상속시키는 위험이 있음
- 런타임 경계(Runtime boundaries)를 설정하여 무한 루프 및 비용 폭증 방지 필요
- 모델 호출 전 단계에서 예산, 단계 수, 진행 상태 등을 검토하는 결정 로직 도입 권장
OpenAI의 최근 Codex 연구에는 에이전트를 구축하는 개발자들에게 중요한 세부 사항이 포함되어 있습니다:
사용자의 26.6%는 복잡한 워크플로 (workflows)를 위한 지침을 공유하는 데 스킬 (skills)을 사용하며, 10% 이상의 사용자가 매주 어느 시점에 3개 이상의 Codex 에이전트를 동시에 관리합니다.
이는 에이전트 사용 방식이 일회성 프롬프트 (one-off prompts)에서 재사용 가능한 워크플로 (reusable workflows)로 이동하고 있음을 의미합니다.
이는 좋은 현상입니다.
하지만 이는 실패 또한 재사용될 수 있음을 의미하기도 합니다.
문제점
잘못된 프롬프트 (prompt) 하나는 모델 호출 (model call) 한 번을 낭비할 수 있습니다.
잘못된 에이전트 스킬 (agent skill) 하나는 수많은 실행 (runs)을 낭비할 수 있습니다.
스킬에는 다음과 같은 내용이 인코딩될 수 있습니다:
재시도 (retry) 방법
도구 (tools) 호출 방법
파일 검사 방법
오류로부터 복구하는 방법
얼마만큼의 컨텍스트 (context)를 추가할지
언제 계속할지
언제 중단할지
만약 이러한 규칙들이 느슨하다면, 모든 실행은 그 느슨함을 상속받게 됩니다.
이 부분이 바로 개발자들이 주의 깊게 다뤄야 할 부분입니다.
재사용 가능한 에이전트 동작 (agent behavior)에는 재사용 가능한 런타임 경계 (runtime boundaries)가 필요합니다.
순진한 에이전트 스킬 (A naive agent skill)
실패하는 테스트를 수정하기 위한 코딩 에이전트 스킬을 가정해 봅시다.
지침은 다음과 같을 수 있습니다:
const skill = {
name: "fix-failing-tests",
instructions: 실패하는 테스트를 검사합니다. 관련 파일을 찾습니다. 수정 사항을 적용합니다. 테스트를 다시 실행합니다. 테스트가 통과할 때까지 반복합니다. ,
};
이것은 괜찮게 들립니다.
하지만 런타임 제한 (runtime limits)이 없다면 "테스트가 통과할 때까지 반복합니다"라는 문구는 위험합니다.
만약 테스트 실패가 환경적인 요인 때문이라면 어떻게 될까요?
만약 에이전트가 관련 없는 파일들을 계속 수정한다면 어떻게 될까요?
만약 시도할 때마다 프롬프트가 거의 변하지 않는다면 어떻게 될까요?
만약 매 재시도마다 더 많은 컨텍스트가 추가된다면 어떻게 될까요?
만약 폴백 모델 (fallback model)의 가격이 불분명하다면 어떻게 될까요?
스킬은 유용합니다.
하지만 런타임 (runtime)은 명확하게 정의되지 않았습니다.
호출 전 결정 추가 (Add a pre-call decision)
모든 프로바이더 (provider) 호출 전에, 런타임은 해당 호출이 여전히 허용되는지 결정해야 합니다.
type BeforeCallInput = {
runId: string;
workflowId?: string;
model: string;
prompt: string;
stepCount: number;
retryCount: number;
budgetRemaining: number;
previousPrompts: string[];
progressState: {
testsImproved?: boolean;
errorsChanged?: boolean;
filesChanged?: boolean;
};
};
type GuardDecision =
|
{ allowed: true }
|
{
allowed: false;
reason:
|"unknown_model_pricing"
|"budget_exceeded"
|"max_steps_exceeded"
|"retry_storm_detected"
|"similar_prompt_loop"
|"no_progress";
error: Error;
};
그러면 제공자(provider) 호출 전에 사용합니다:
const decision = guard.beforeCall({
runId,
workflowId,
model,
prompt,
stepCount,
retryCount,
budgetRemaining,
previousPrompts,
progressState,
});
if (!decision.allowed) {
return {
status: "stopped",
reason: decision.reason,
error: decision.error,
};
}
const response = await provider.call({
model,
prompt,
});
정확한 API는 중요하지 않습니다.
중요한 것은 배치(placement)입니다.
체크는 제공자 호출 전에 발생합니다.
가드(guard)는 무엇을 체크해야 할까요?
- 알려진 모델 가격 책정 (Known model pricing)
런타임이 모델의 가격을 산정할 수 없다면, 예산을 강제할 수 없습니다.
if (!pricingCatalog.has(model)) {
return {
allowed: false,
reason: "unknown_model_pricing",
error: new Error(Unknown pricing for model: ${model}),
};
}
추측하지 마세요.
실패 시 안전하게 닫으세요 (Fail closed).
- 남은 예산 (Budget remaining)
에이전트 워크플로우는 태스크 수준의 예산을 가져야 합니다.
if (estimatedNextCallCost > budgetRemaining) {
return {
allowed: false,
reason: "budget_exceeded",
error: new Error("Agent run budget exceeded"),
};
}
사소한 버그 수정과 긴 마이그레이션이 같은 예산을 공유해서는 안 됩니다.
- 최대 단계 (Max steps)
에이전트는 단계 제한(step limits)이 필요합니다.
if (stepCount >= maxSteps) {
return {
allowed: false,
reason: "max_steps_exceeded",
error: new Error("Maximum agent steps exceeded"),
};
}
이것은 기본적인 프로덕션 위생입니다.
워크플로우가 합리적인 단계 수 내에서 완료될 수 없다면 중단되어야 합니다.
- 재시도 폭풍 (Retry storms)
재시도는 유용합니다.
맹목적인 재시도는 비용이 많이 듭니다.
if (retryCount >= maxRetries && recentErrorsAreSimilar(errors)) {
return {
allowed: false,
reason: "retry_storm_detected",
error: new Error("Retry storm detected"),
};
}
목표는 재시도를 금지하는 것이 아닙니다.
목표는 반복되는 실패를 중단하는 것입니다.
- 프롬프트 루프 (Prompt loops)
에이전트가 거의 동일한 질문을 계속해서 던질 때 프롬프트 루프 (Prompt loop)가 발생합니다.
if (similarToRecentPrompt(prompt, previousPrompts)) {
return {
allowed: false,
reason: "similar_prompt_loop",
error: new Error("Similar prompt loop detected"),
};
}
단순한 유사도 검사만으로도 명백한 루프를 잡아낼 수 있습니다.
- 진전 없음 (No progress)
실행 (Run)이 활성화된 상태임에도 불구하고 개선이 이루어지지 않을 수 있습니다.
진전 신호 (Progress signals)를 추적하세요:
- 테스트 결과가 개선되었는가?
- 에러가 변경되었는가?
- 파일이 의미 있게 변경되었는가?
- 체크리스트 항목이 완료되었는가?
- 에이전트가 불확실성을 줄였는가?
진전 없이 여러 단계가 지나간다면 중단하십시오.
if (!madeProgress(progressState, recentSteps)) {
return {
allowed: false,
reason: "no_progress",
error: new Error("Agent run is not making progress"),
};
}
동시성 (Concurrency) 때문에 이것이 더 중요해지는 이유
OpenAI의 Codex 연구에 따르면, 사용자 중 10% 이상이 매주 어느 시점에 3개 이상의 에이전트를 동시에 관리합니다.
이는 리스크의 성격을 변화시킵니다.
에이전트 하나가 몇 번의 호출 (Call)을 낭비하는 것은 눈에 띕니다.
하지만 여러 에이전트가 각각 몇 번씩 호출을 낭비하는 것은 정상적인 상황처럼 보일 수 있습니다.
로컬 루프 (Local loop)가 글로벌 예산 문제 (Global budget problem)로 변하는 것입니다.
병렬 워크플로 (Parallel workflows)를 위해서는 공유 예산 체크 (Shared budget checks)를 추가하십시오:
if (estimatedNextCallCost > workflowBudgetRemaining) {
return {
allowed: false,
reason: "workflow_budget_exceeded",
error: new Error("Workflow budget exceeded"),
};
}
각 에이전트는 자신만의 제한 (Limit)이 필요합니다.
워크플로는 공유 제한 (Shared limit)이 필요합니다.
둘 다 중요합니다.
AI CostGuard가 위치하는 곳
AI CostGuard는 제가 이 문제를 해결하기 위해 구축하고 있는 로컬 우선 (Local-first) TypeScript / Node.js 런타임 안전 계층 (Runtime safety layer)입니다.
이 도구는 AI 에이전트 프로젝트를 위한 호출 전 보호 (Pre-call protection)에 집중합니다:
- 재시도 폭풍 (Retry storms)
- 프롬프트 루프 (Prompt loops)
- 최대 단계 폭발 (Max-step explosions)
- 폭주하는 실행 (Runaway execution)
- 알 수 없는 모델 가격 책정 (Unknown model pricing)
- 예산 초과 (Budget overruns)
- 통제되지 않는 제공자 호출 (Uncontrolled provider calls)
이것은 과금 원장 (Billing ledger)이 아닙니다.
이것은 엄격한 보안 경계 (Security boundary)가 아닙니다.
이것은 제공자 대시보드 (Provider dashboards)를 대체하지 않습니다.
목표는 명백히 위험한 호출이 실행되기 전에 중단하는 것입니다.
요점 (Takeaway)
재사용 가능한 에이전트 스킬 (Reusable agent skills)은 훌륭한 추상화 (abstraction)입니다.
하지만 단순히 지침 (instructions)만을 패키징해서는 안 됩니다.
런타임 정책 (runtime policy) 또한 상속받아야 합니다.
모든 프로바이더 (provider) 호출 전에 질문하십시오:
이 호출이 여전히 실행되어야 하는가?
이 단 하나의 질문이 많은 비용이 발생하는 에이전트 실패를 API 사용으로 이어지기 전에 잡아냅니다.
Tags: ai, agents, typescript, devtools
https://github.com/salimassili62-afk/ai-costguard
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기