내 서버가 에이전트에게 힌트를 전달하는 방식 — 그리고 그 과정에 이르게 된 3번의 반복 과정
요약
에이전트에게 API를 직접 노출할 때 발생하는 토큰 낭비와 오류 문제를 해결하기 위한 반복적인 아키텍처 개선 과정을 다룹니다. MCP의 오버헤드와 raw API의 타입 안정성 문제를 지적하며, 에이전트 친화적인 인터페이스 구축의 중요성을 강조합니다.
핵심 포인트
- MCP 사용 시 발생하는 지속적인 토큰 오버헤드 문제
- 문서 품질보다 중요한 것은 에이전트의 타입 안정성 확보
- Raw API 호출 시 발생하는 에러 복구 루프와 토큰 낭비
- 에이전트의 실수를 줄이기 위한 CLI 인터페이스의 필요성
나는 첫날부터 MCP (Model Context Protocol)를 피했습니다. 스키마 오버헤드(schema overhead)도, 토큰 세금(token tax)도 없었습니다. 에이전트는 동작 사양(behavior spec)과 잘 작성된 문서를 가지고 나의 GraphQL API를 직접 호출했습니다. 나는 명확한 문서와 올바른 아키텍처(architecture)만 있다면 에이전트가 스스로 알아서 할 것이라고 생각했고, 그것으로 충분하다고 가정했습니다.
하지만 그렇지 않았습니다. 내 생각을 바꾼 결정적인 순간은 에이전트가 단 한 번의 업로드에 1,500개의 토큰을 낭비하는 것을 목격했을 때였습니다. 에이전트는 JSON 필드 형식을 계속 잘못 추측하고, 여러 페이지에 걸쳐 문서를 읽고, 재시도하며 토큰을 소모하고 있었습니다. 나는 문서를 수정했습니다. 하지만 문제는 다른 필드에서 다시 나타났습니다. 그 필드들도 수정했습니다. 하지만 문제는 계속 반복되었습니다. CLI (Command Line Interface)는 있으면 좋은 기능이 아니었습니다. 그것은 실제로 손실을 막을 수 있는 유일한 방법이었습니다. 그리고 이것은 내가 가장 흥미로운 단계에 도달하기 전 거친 세 번의 반복 중 첫 번째 단계에 불과했습니다. 바로 서버가 에이전트에게 직접 말을 걸게 하는 방식입니다.
반복 1: 첫날부터 MCP 피하기
MCP의 컨텍스트 세금(context tax)은 이미 잘 알려져 있으므로 길게 설명하지 않겠습니다. 나의 API 표면(API surface)은 34개의 명령어를 포함합니다. 이를 MCP 도구(tools)로 사용할 경우, 34개의 스키마 × 약 180개 토큰 = **매 대화 턴마다 에이전트의 사용 여부와 상관없이 약 6,120개의 토큰이 지속적인 오버헤드(overhead)**로 발생합니다.
나는 이를 제1원칙(first principles)에 근거하여 이해했고 다른 경로를 선택했습니다. 바로 SKILL.md 동작 사양(behavior spec) + 직접적인 GraphQL API 호출 방식입니다. 등록된 도구도 없고, 스키마 오버헤드도 없습니다. 에이전트는 스킬(skill)이 호출될 때 동작 사양을 한 번 읽은 다음, curl을 통해 API를 호출합니다.
아키텍처: 올바름. 문제: 해결됨?
아니요.
교훈: 올바른 아키텍처 선택이 에이전트의 올바른 동작을 보장하는 것은 아닙니다. 진짜 작업은 아키텍처가 구축된 후에 시작됩니다.
반복 2: 가공되지 않은 GraphQL에서 CLI로
에이전트는 나의 GraphQL API를 직접 호출했습니다. 복잡한 필드들(중첩된 JSON 형태의 출처 메타데이터(provenance metadata), 차단 객체(blocker objects), 모델 설정(model config))은 에이전트가 curl 명령 내에서 가공되지 않은 JSON 페이로드(payload)를 직접 구성해야 함을 의미했습니다.
그것은 끊임없이 잘못된 추측을 했습니다. 필드 타입 하나만 틀려도 → GraphQL 에러 발생 → 에이전트가 올바른 형식을 파악하기 위해 API 문서를 읽음 → 문서는 상세하며 여러 페이지에 걸쳐 나뉘어 있음 → 재시도 시도당 2개 이상의 페이지를 가져와야 함. 약 200 토큰 정도면 충분했을 단일 작업이 에러 복구 루프(error-recovery loops)로 인해 1,500개 이상의 토큰을 소모하고 있었습니다.
저는 문서를 수정하려고 시도했습니다. 더 정밀하게 만들고, 인라인 예시(inline examples)를 추가하고, 페이지를 통합했습니다. 하지만 문제는 다른 필드에서 계속해서 다시 나타났습니다. 하나를 고칠 때마다 다른 문제가 발생했습니다.
통찰: 문제는 문서의 품질이 아니었습니다. 문제는 가공되지 않은 API(raw APIs)가 에이전트로 하여금 타입 안정성(type safety) 없이 구조를 조립하도록 강제한다는 것이었으며, LLM은 근본적으로 이 작업에 취약하다는 점이었습니다.
해결책:
# 이전: curl 명령 내에서 에이전트가 가공되지 않은 JSON을 조립
curl -X POST /graphql -d '{"query":"mutation { uploadAsset(input: { shotId: \"...\", type: \"start_frame\", provenance: { method: \"ai_generated\", model: \"gpt-image-2\", prompt: \"...\" } }) { id } }"}'
...
CLI 디스패처(dispatcher)는 34개의 명령을 타입이 지정된 인자(typed arguments)를 통해 라우팅합니다. 에이전트는 필드 타입을 추측하거나 중첩된 객체(nested objects)를 조립하지 않습니다. 대신 플래그(flags)를 전달합니다.
보너스: --json 플래그는 에이전트에게 추론을 위한 구조화된 데이터(structured data)를 제공하는 반면, 기본값은 사람이 읽을 수 있는 테이블을 제공합니다. 하나의 CLI로 두 대상에게 대응합니다:
# 에이전트용: 파싱을 위한 구조화된 JSON
python3 nl.py overview <noteId> --json
...
교훈: 당신이 미리 구조화할 수 있는 것을 에이전트가 직접 조립하게 만들지 마세요. CLI 인자는 LLM에게 본질적으로 타입 안정성(type-safe)을 제공합니다. 만약 당신의 에이전트가 에러→문서→재시도 루프를 반복하고 있다면, 해결책은 더 나은 문서가 아닙니다. 조립 단계 자체를 완전히 제거하는 것입니다.
반복 3단계: 일시 정지 및 성찰(pause-and-reflect) 방법론
CLI는 실행 문제를 해결했습니다. 하지만 에이전트는 여전히 잘못된 결정을 내렸습니다. 프롬프트를 먼저 수정하지 않고 거절된 비디오를 다시 생성(re-roll)하기도 했습니다. 이미 시도했던 내용을 확인하지 않고 새로운 프롬프트를 작성하기도 했습니다. 사전 점검(preflight check)을 건너뛰고 불완전한 에셋(assets)에 생성 비용을 낭비하기도 했습니다.
이것들은 실행 실패(execution failures)가 아니었습니다. 그것들은 판단 실패(judgment failures)였습니다. 에이전트는 제가 요청한 것을 정확하게 수행했지만, 잘못된 행동을 선택했습니다.
저는 생산을 중단하고 에이전트에게 물었습니다:
"멈춰. 계속하기 전에, 방금 비효율적인 행동을 많이 했니? 문서나 스킬 명세(skill spec)가 충분히 명확하지 않아서 그랬던 거야? 그리고 모든 것을 한 번에 제공하는 새로운 API가 있었다면 도움이 되었을 법한 반복적인 시나리오가 있니?"
에이전트는 제 SKILL.md의 구체적인 공백들을 지적했습니다:
- "무언가를 변경하지 않고는 절대 다시 롤(re-roll)하지 마라"라는 명시적인 규칙이 없음
- 새로운 프롬프트(prompt)를 작성하기 전에 과거의 통찰(insights)을 확인하라는 지침이 없음
- 결정 임계값(decision thresholds) 누락 (어떤 점수가 '수정 후 재시도'를 의미하고, 어떤 점수가 '먼저 디버깅'을 의미하는가?)
저는 그 공백들을 패치했습니다. 생산을 더 진행했습니다. 다시 멈췄습니다. 다시 물었습니다. 각 사이클은 행동 명세(behavior spec)의 새로운 사각지대를 드러냈습니다.
이것은 일회성 감사(audit)가 아닙니다. 이것은 반복되는 피드백 루프(feedback loop)입니다:
생산(produce) → 성찰(reflect) → 명세 다듬기(polish spec) → 생산(produce) → 성찰(reflect) → ...
교훈: 당신의 에이전트는 당신의 행동 명세(behavior spec)의 소비자이자 가장 훌륭한 감사자(auditor)입니다. 에이전트는 명세가 자신의 어느 부분을 실패하게 만들었는지 정확히 알고 있습니다. 당신은 그저 멈춰서 물어보고, 에이전트가 말해주는 것을 실제로 수정하기만 하면 됩니다.
서버가 푸시하는 가이드라인 (Server-pushed guidance)
여러 번의 성찰(reflect) 사이클을 거친 후에도 한 종류의 실패가 지속되었고, 그것은 저에게 가장 흥미로운 교훈을 주었습니다.
에이전트가 비디오 생성 프롬프트(video generation prompt)를 작성했지만, 업로드된 에셋(assets) 중 그 어떤 것도 참조하지 않았습니다. 생성된 비디오는 프로젝트에 바로 놓여 있는 참조 프레임(reference frames)과 아무런 관련이 없었습니다. 에셋은 존재했습니다. 에이전트는 그저... 그것들을 사용하는 것을 잊어버린 것이었습니다.
실패 후, 저는 물었습니다: "만약 당신이 프롬프트를 작성하기 직전에 사용 가능한 에셋 목록과 그것들을 참조하는 방법을 나열한 메시지가 있었다면, 이 문제를 잡아낼 수 있었겠니?" 에이전트는 그렇다고 답했습니다.
그래서 저는 그것을 구축했습니다. 서버가 사전 점검(preflight)은 통과했지만 활성 프롬프트(active prompt)에 @filename 참조가 포함되어 있지 않다는 것을 감지하면, 사용 가능한 모든 에셋을 나열하는 힌트(hint)를 주입(inject)합니다:
같은 방식, 다른 실패: 에이전트가 모든 에셋을 업로드했고, 사전 점검(preflight)도 통과했지만, asset_prep 상태에서 ready로의 상태 진행을 잊은 경우입니다. 같은 질문에서 탄생한 또 하나의 힌트입니다. '여기서 약간의 자극이 이를 막았을까?'
// Preflight passed but agent forgot to advance status
ctx.pendingHints.push({
type: "ready_to_advance",
...
시스템 내 모든 고가치 힌트는 이런 방식으로 설계되었습니다. 에이전트 실패 → 저는 '무슨 힌트가 이를 막았을까?'라고 질문합니다 → 그리고 트리거를 구축합니다.
이 힌트들에서 주목할 점은, 이것들이 저(사람)를 위해 쓰인 것이 아니라는 것입니다. 에이전트를 위해 쓰였습니다. 메시지에는 CLI 명령어, @filename 규칙, 상태 값 등 에이전트가 사용하는 모든 작업 어휘가 포함되어 있습니다. 이것은 사람들을 위한 알림 시스템이 아닙니다. 서버가 자신의 언어로 에이전트에게 직접 말하는 것입니다.
그리고 가장 좋은 부분은, 설령 에이전트가 힌트를 무시하고 여전히 실수를 저지르더라도, 그 힌트는 이미 컨텍스트 창에 놓여 있다는 것입니다. 에이전트가 실패 후 디버그 모드로 진입할 때, 그것은 자연스럽게 '이에 대한 힌트가 있었다'는 것을 상기합니다. 힌트는 지켜질 필요가 없어서 유용합니다. 단지 컨텍스트 안에 존재하기만 하면 됩니다.
힌트는 모든 GraphQL 응답에서 extensions.agentHints로 전송됩니다. 클라이언트 측에서는 에이전트가 파싱하는 JSON을 오염시키지 않기 위해 stderr로 라우팅됩니다:
hints = result.get("extensions", {}).get("agentHints", [])
if hints:
for h in hints:
...
일반적인 모범 사례를 위한 확률적 힌트 풀도 있지만, 고가치 힌트는 모두 특정 에이전트 실패로부터 역설계되었으며, 추가 데이터베이스 비용 없이 구현됩니다. 왜냐하면 이들은 이미 뮤테이션(mutation)이 로드한 데이터를 기반으로 생성되기 때문입니다.
교훈: 아키텍처에서 기능으로가 아니라, 실패에서 트리거로 거꾸로 힌트를 설계하십시오. 그리고 자신을 위해서가 아니라, 에이전트를 위해 작성하십시오.
등장하게 된 아키텍처
세 개의 레이어는 각각 서로 다른 실패 모드 (failure mode)를 통해 발견되었습니다:
| 레이어 | 해결하는 문제 | 자유도 |
|---|---|---|
| SKILL.md | 잘못된 결정 | 높음: 무엇을, 언제, 왜 해야 하는가 |
| ... |
어느 한 레이어라도 제거하면 에이전트는 표류하게 됩니다. 힌트(hints)가 없는 명세(spec)는 맥락(context)의 망각을 의미합니다. 명세가 없는 힌트는 의사결정 프레임워크(decision framework)가 없음을 의미합니다. 이 둘 중 어느 것도 없는 CLI는 잘못된 결정에 대한 올바른 실행을 의미합니다.
저는 콘텐츠 크리에이터를 위한 연구 도구인 Narrative Lion을 구축하고 있습니다. 이 도구는 여러분이 학습하는 영상을 AI가 실제로 사용할 수 있는 플레이북 (Playbook)으로 변환해 줍니다. 여기에 설명된 에이전트 아키텍처 (agent architecture)는 이 서비스의 프로덕션 파이프라인 (production pipeline)을 구동합니다. narrativelion.com에서 확인해 보세요.
다른 분들은 이 문제를 어떻게 다루는지 궁금합니다. 서버에서 가이던스 (guidance)를 밀어넣으시나요, 아니면 모든 것을 행동 명세 (behavior spec)에 담아두시나요?
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기