원문: Agent Design Is Still Hard

작성자: Armin Ronacher

작성일: 2025년 11월 21일

최근에 배운 내용들을 정리해볼 시점이 된 것 같습니다. 대부분 agent(에이전트) 구축에 관한 내용이며, agentic coding tool(에이전트 기반 코딩 도구) 사용에 대한 내용도 조금 다룹니다.

요약: Agent(에이전트) 구축은 여전히 복잡합니다. SDK 추상화는 실제 tool(도구) 사용 시 무너집니다. caching(캐싱)은 직접 관리할 때 더 잘 작동하지만, model(모델)마다 다릅니다. reinforcement(강화)는 예상보다 훨씬 많은 역할을 수행하며, 실패는 loop(루프)를 망가뜨리지 않도록 엄격하게 격리해야 합니다. file system(파일 시스템)과 유사한 계층을 통한 shared state(공유 상태)는 중요한 구성 요소입니다. output(출력) 도구는 놀랍도록 까다롭고, model(모델) 선택은 여전히 작업에 따라 달라집니다.

어떤 Agent SDK를 선택할 것인가?

자체 agent(에이전트)를 구축할 때, OpenAI SDK나 Anthropic SDK 같은 기본 SDK를 타겟으로 할 수도 있고, Vercel AI SDK나 Pydantic 같은 상위 수준의 추상화를 선택할 수도 있습니다. 저희가 얼마 전에 내린 결정은 Vercel AI SDK를 채택하되 provider(제공자) 추상화만 사용하고, 기본적으로 agent loop(에이전트 루프)를 직접 구동하는 것이었습니다. 지금 이 시점에서 저희는 그 선택을 다시 하지 않을 것입니다. Vercel AI SDK에는 전혀 문제가 없지만, agent(에이전트)를 구축하려고 할 때 원래 예상하지 못했던 두 가지 일이 발생합니다:

첫 번째는 model(모델) 간 차이가 충분히 커서 자체 agent(에이전트) 추상화를 구축해야 한다는 것입니다. 저희는 이러한 SDK들이 제공하는 솔루션 중 agent(에이전트)에 적합한 추상화를 제공하는 것을 찾지 못했습니다. 기본 agent(에이전트) 설계가 단순한 loop(루프)임에도 불구하고, 제공하는 tool(도구)에 따라 미묘한 차이가 있기 때문이라고 생각합니다. 이러한 차이는 올바른 추상화를 찾는 것이 얼마나 쉽거나 어려운지에 영향을 미칩니다(cache control(캐시 제어), reinforcement(강화)에 대한 다른 요구사항, tool prompt(도구 프롬프트), provider-side tool(제공자 측 도구) 등). 올바른 추상화가 아직 명확하지 않기 때문에, 전용 플랫폼의 원본 SDK를 사용하면 완전한 제어권을 유지할 수 있습니다. 일부 상위 수준 SDK에서는 기존 추상화 위에 구축해야 하는데, 이는 결국 원하는 추상화가 아닐 수 있습니다.

또한 Vercel SDK로 provider-side tool(제공자 측 도구)을 다룰 때 매우 어려운 점을 발견했습니다. messaging format(메시징 형식)의 통합 시도가 제대로 작동하지 않습니다. 예를 들어, Anthropic의 web search tool(웹 검색 도구)은 Vercel SDK에서 message history(메시지 기록)를 정기적으로 파괴하는데, 아직 원인을 완전히 파악하지 못했습니다. 또한 Anthropic의 경우 Vercel SDK 대신 Anthropic SDK를 직접 타겟으로 할 때 cache(캐시) 관리가 훨씬 쉽습니다. 실수했을 때 나오는 오류 메시지도 훨씬 명확합니다.

상황이 바뀔 수도 있지만, 지금 당장은 agent(에이전트)를 구축할 때 추상화를 사용하지 않을 것 같습니다. 적어도 상황이 좀 더 안정될 때까지는요. 현재로서는 이점이 비용을 상쇄하지 못합니다.

다른 분이 해결책을 찾았을 수도 있습니다. 이 글을 읽고 제가 틀렸다고 생각하신다면 메일을 보내주세요. 배우고 싶습니다.

Caching 교훈

플랫폼마다 caching(캐싱)에 대한 접근 방식이 매우 다릅니다. 이미 많은 이야기가 있었지만, Anthropic은 caching(캐싱)에 대해 비용을 청구합니다. cache point(캐시 포인트)를 명시적으로 관리하게 하는데, 이는 agent engineering(에이전트 엔지니어링) 수준에서 상호작용하는 방식을 실제로 바꿉니다. 처음에는 수동 관리가 꽤 바보같다고 생각했습니다. 플랫폼이 대신 해주면 안 될까요? 그러나 완전히 생각이 바뀌었고 이제는 명시적 cache(캐시) 관리를 훨씬 선호합니다. 비용과 cache(캐시) 활용도를 훨씬 더 예측 가능하게 만듭니다.

명시적 caching(캐싱)을 사용하면 다른 방법으로는 훨씬 어려운 특정 작업을 수행할 수 있습니다. 예를 들어, conversation(대화)을 분리하여 두 가지 다른 방향으로 동시에 실행할 수 있습니다. context editing(컨텍스트 편집)을 할 수 있는 기회도 있습니다. 여기서 최적의 전략은 불분명하지만, 확실히 훨씬 더 많은 제어권을 갖게 되며, 저는 그 제어권을 갖는 것을 정말 좋아합니다. 또한 기본 agent(에이전트)의 비용을 훨씬 쉽게 이해할 수 있습니다. cache(캐시)가 얼마나 잘 활용될지 훨씬 더 많이 가정할 수 있는 반면, 다른 플랫폼에서는 결과가 일관되지 않았습니다.

Anthropic에서 agent(에이전트)의 caching(캐싱)을 수행하는 방식은 꽤 간단합니다. 하나의 cache point(캐시 포인트)는 system prompt(시스템 프롬프트) 다음에 있습니다. 두 개의 cache point(캐시 포인트)는 conversation(대화) 시작 부분에 배치되며, 마지막 cache point(캐시 포인트)는 conversation(대화)의 끝부분과 함께 위로 이동합니다. 그리고 그 과정에서 할 수 있는 최적화가 있습니다.

system prompt(시스템 프롬프트)와 tool selection(도구 선택)이 대부분 정적이어야 하므로, 현재 시간과 같은 정보를 제공하기 위해 나중에 동적 메시지를 제공합니다. 그렇지 않으면 cache(캐시)가 무효화됩니다. 또한 loop(루프) 중에 reinforcement(강화)를 훨씬 더 많이 활용합니다.

Agent Loop에서의 Reinforcement

agent(에이전트)가 tool(도구)을 실행할 때마다 tool(도구)이 생성하는 데이터만 반환하는 것이 아니라 loop(루프)에 더 많은 정보를 피드백할 수 있는 기회가 있습니다. 예를 들어, agent(에이전트)에게 전체 목표와 개별 task(작업)의 상태를 상기시킬 수 있습니다. tool(도구) 호출이 실패했을 때 tool call(도구 호출)이 성공할 수 있는 방법에 대한 힌트를 제공할 수도 있습니다. reinforcement(강화)의 또 다른 용도는 백그라운드에서 발생한 state change(상태 변경)에 대해 시스템에 알리는 것입니다. parallel processing(병렬 처리)을 사용하는 agent(에이전트)가 있는 경우, 해당 상태가 변경되고 task(작업) 완료와 관련이 있을 때 모든 tool call(도구 호출) 후에 정보를 주입할 수 있습니다.

때로는 agent(에이전트)가 스스로 강화하는 것만으로도 충분합니다. 예를 들어, Claude Code에서 todo write tool(할 일 작성 도구)은 self-reinforcement tool(자체 강화 도구)입니다. 이 도구는 agent(에이전트)로부터 해야 한다고 생각하는 task(작업) 목록을 받아 입력된 내용을 그대로 출력합니다. 기본적으로 echo tool(에코 도구)일 뿐이며, 실제로 다른 작업은 하지 않습니다. 그러나 이것만으로도 context(컨텍스트) 시작 부분에 task(작업)와 subtask(하위 작업)만 주어지고 그 사이에 너무 많은 일이 발생한 경우보다 agent(에이전트)를 더 잘 추진할 수 있습니다.

또한 reinforcement(강화)를 사용하여 실행 중에 environment(환경)이 agent(에이전트)에 문제가 되는 방식으로 변경되었는지 시스템에 알립니다. 예를 들어, agent(에이전트)가 실패하고 특정 단계부터 다시 시도하지만 복구가 손상된 데이터로 작동하는 경우, 몇 단계 뒤로 물러나 이전 단계를 다시 수행하는 것이 좋을 수 있다는 메시지를 주입합니다.

실패 격리

code execution(코드 실행) 중에 많은 실패가 예상되는 경우, context(컨텍스트)에서 해당 실패를 숨길 수 있는 기회가 있습니다. 이는 두 가지 방식으로 발생할 수 있습니다. 하나는 반복이 필요할 수 있는 task(작업)를 개별적으로 실행하는 것입니다. subagent(하위 에이전트)에서 성공할 때까지 실행하고 성공과 작동하지 않은 접근 방식에 대한 간략한 요약만 보고합니다. agent(에이전트)가 subtask(하위 작업)에서 작동하지 않은 것을 학습하는 것이 유용한데, 이를 다음 task(작업)에 피드백하여 해당 실패를 피할 수 있기 때문입니다.

두 번째 옵션은 모든 agent(에이전트)나 foundation model(기초 모델)에 존재하지 않지만, Anthropic에서는 context editing(컨텍스트 편집)을 수행할 수 있습니다. 지금까지 context editing(컨텍스트 편집)에서 많은 성공을 거두지 못했지만, 더 탐구하고 싶은 흥미로운 것이라고 생각합니다. 다른 사람들이 성공했는지도 배우고 싶습니다. context editing(컨텍스트 편집)의 흥미로운 점은 iteration loop(반복 루프) 아래쪽을 위해 token(토큰)을 보존할 수 있어야 한다는 것입니다. loop(루프)의 성공적인 완료로 이어지지 않고 실행 중 특정 시도에만 부정적인 영향을 미친 특정 실패를 context(컨텍스트)에서 제거할 수 있습니다. 그러나 앞서 언급한 점과 마찬가지로, agent(에이전트)가 작동하지 않은 것을 이해하는 것도 유용하지만, 모든 실패의 전체 상태와 전체 출력이 필요하지 않을 수도 있습니다.

안타깝게도 context editing(컨텍스트 편집)은 자동으로 cache(캐시)를 무효화합니다. 이를 피할 방법이 없습니다. 따라서 이를 수행하는 trade-off(절충안)가 cache(캐시)를 무효화하는 추가 비용을 보상하는지 불분명할 수 있습니다.

Sub Agent / Sub Inference

이미 이 블로그에서 여러 번 언급했듯이, 저희의 대부분의 agent(에이전트)는 code execution(코드 실행)과 code generation(코드 생성)을 기반으로 합니다. 이는 agent(에이전트)가 데이터를 저장할 공통 장소가 필요합니다. 저희의 선택은 file system(파일 시스템)입니다. 저희의 경우 virtual file system(가상 파일 시스템)이지만, 이를 액세스하기 위한 다양한 tool(도구)이 필요합니다. 이는 subagent(하위 에이전트)나 subinference(하위 추론) 같은 것이 있는 경우 특히 중요합니다.

막다른 길이 없는 agent(에이전트)를 구축하려고 노력해야 합니다. 막다른 길은 task(작업)가 구축한 sub-tool(하위 도구) 내에서만 계속 실행될 수 있는 곳입니다. 예를 들어, image(이미지)를 생성하는 tool(도구)를 구축할 수 있지만, 해당 image(이미지)를 다른 하나의 tool(도구)에만 피드백할 수 있습니다. 이는 문제입니다. 왜냐하면 code execution tool(코드 실행 도구)을 사용하여 해당 image(이미지)를 zip archive(압축 파일)에 넣고 싶을 수도 있기 때문입니다. 따라서 image generation tool(이미지 생성 도구)이 code execution tool(코드 실행 도구)이 읽을 수 있는 동일한 위치에 image(이미지)를 쓸 수 있도록 하는 시스템이 필요합니다. 본질적으로 그것이 file system(파일 시스템)입니다.

당연히 반대 방향으로도 작동해야 합니다. code execution tool(코드 실행 도구)을 사용하여 zip archive(압축 파일)의 압축을 풀고 inference(추론)로 돌아가 모든 image(이미지)를 설명한 다음, 다음 단계에서 code execution(코드 실행)으로 돌아가는 식입니다. file system(파일 시스템)은 이를 위해 사용하는 메커니즘입니다. 그러나 이를 위해서는 tool(도구)가 virtual file system(가상 파일 시스템)에 대한 file path(파일 경로)를 사용할 수 있는 방식으로 구축되어야 합니다.

기본적으로 ExecuteCode tool(도구)은 동일한 virtual file system(가상 파일 시스템)의 파일에 대한 path를 사용할 수 있는 RunInference tool(도구)과 동일한 file system(파일 시스템)에 액세스할 수 있습니다.

Output Tool의 사용

저희가 agent(에이전트)를 구성한 방식의 흥미로운 점 하나는 chat session(채팅 세션)을 나타내지 않는다는 것입니다. 결국에는 user(사용자)나 외부 세계에 무언가를 전달하지만, 그 사이에 보내는 모든 message(메시지)는 일반적으로 공개되지 않습니다. 질문은: 어떻게 해당 message(메시지)를 생성할까요? 저희에게는 output tool(출력 도구)라는 하나의 tool(도구)이 있습니다. agent(에이전트)는 이를 명시적으로 사용하여 사람과 소통합니다. 그런 다음 prompt(프롬프트)를 사용하여 해당 tool(도구)을 언제 사용할지 지시합니다. 저희의 경우 output tool(출력 도구)은 이메일을 보냅니다.

그러나 이는 몇 가지 다른 문제를 야기합니다. 하나는 main agent loop(메인 에이전트 루프)의 text output(텍스트 출력)을 user(사용자)와 대화하는 메커니즘으로 사용하는 것과 비교하여 해당 output tool(출력 도구)의 wording(표현)과 tone(톤)을 조정하는 것이 놀랍도록 어렵다는 것입니다. 왜 그런지 말할 수 없지만, 아마도 이러한 model(모델)이 훈련되는 방식과 관련이 있다고 생각합니다.

잘 작동하지 않은 한 가지 시도는 output tool(출력 도구)이 Gemini 2.5 Flash와 같은 다른 빠른 LLM을 실행하여 tone(톤)을 저희가 선호하는 방식으로 조정하도록 하는 것이었습니다. 그러나 이는 latency(지연 시간)를 증가시키고 실제로 output(출력)의 품질을 떨어뜨립니다. 부분적으로는 model(모델)이 올바르게 표현하지 못하고 subtool(하위 도구)에 충분한 context(컨텍스트)가 없기 때문이라고 생각합니다. main agentic context(메인 에이전트 컨텍스트)의 더 많은 부분을 subtool(하위 도구)에 제공하면 비용이 많이 들고 문제를 완전히 해결하지도 못했습니다. 또한 때때로 final output(최종 출력)에 최종 결과로 이어진 단계와 같이 원하지 않는 정보를 공개합니다.

output tool(출력 도구)의 또 다른 문제는 때때로 tool(도구)를 호출하지 않는다는 것입니다. 이를 강제하는 방법 중 하나는 output tool(출력 도구)이 호출되었는지 기억하는 것입니다. output tool(출력 도구) 없이 loop(루프)가 끝나면 output tool(출력 도구)을 사용하도록 권장하는 reinforcement(강화) 메시지를 주입합니다.

Model 선택

전반적으로 model(모델) 선택은 지금까지 크게 변하지 않았습니다. Haiku와 Sonnet이 여전히 사용 가능한 최고의 tool caller(도구 호출자)라고 생각하므로 agent loop(에이전트 루프)에서 탁월한 선택입니다. RL이 어떤 모습인지에 대해서도 어느 정도 투명합니다. 다른 명백한 선택은 Gemini model(모델)입니다. 지금까지 main loop(메인 루프)에 GPT 계열 model(모델)에서 많은 성공을 거두지 못했습니다.

부분적으로 inference(추론)가 필요할 수도 있는 개별 sub-tool(하위 도구)의 경우, 대용량 문서를 요약하거나 PDF 등을 작업해야 하는 경우 현재 선택은 Gemini 2.5입니다. 이는 image(이미지)에서 정보를 추출하는 데도 꽤 좋은 model(모델)입니다. 특히 Sonnet 계열 model(모델)이 성가실 수 있는 safety filter(안전 필터)에 자주 걸리기 때문입니다.

token(토큰) 비용만으로는 agent(에이전트)가 얼마나 비싼지 실제로 정의하지 않는다는 매우 명백한 깨달음도 있습니다. 더 나은 tool caller(도구 호출자)는 더 적은 token(토큰)으로 작업을 수행합니다. 오늘날 Sonnet보다 저렴한 일부 model(모델)이 사용 가능하지만, loop(루프)에서 반드시 더 저렴한 것은 아닙니다.

그러나 모든 것을 고려할 때, 지난 몇 주 동안 그다지 많이 변하지 않았습니다.

Testing과 Eval

저희는 testing(테스트)과 eval(평가)이 여기서 가장 어려운 문제라고 생각합니다. 이것은 전혀 놀라운 일이 아니지만, agentic(에이전트) 특성으로 인해 더욱 어려워집니다. prompt(프롬프트)와 달리, 외부 시스템에서 eval(평가)를 수행할 수 없습니다. 왜냐하면 너무 많이 피드해야 하기 때문입니다. 즉, observability data(관측 가능성 데이터) 또는 실제 test run(테스트 실행)을 계측하는 것을 기반으로 eval(평가)를 수행해야 합니다. 지금까지 저희가 시도한 솔루션 중 여기서 올바른 접근 방식을 찾았다고 확신시킨 것은 없습니다. 안타깝게도, 현재로서는 저희를 정말 만족시키는 것을 찾지 못했다고 보고해야 합니다. agent(에이전트)를 구축하는 데 점점 더 좌절스러운 측면이 되고 있기 때문에 이에 대한 솔루션을 찾기를 바랍니다.

Coding Agent 업데이트

coding agent(코딩 에이전트)에 대한 저의 경험과 관련하여, 실제로 그다지 많이 변하지 않았습니다. 주요 새로운 개발은 Amp를 더 많이 시험하고 있다는 것입니다. 궁금하신 경우를 위해 설명하자면: 객관적으로 제가 사용하는 것보다 더 나은 agent(에이전트)여서가 아니라, 그들이 게시하는 내용으로 볼 때 agent(에이전트)에 대해 생각하는 방식이 정말 마음에 들기 때문입니다. Oracle과 같은 다양한 sub agent(하위 에이전트)와 main loop(메인 루프)의 상호 작용이 아름답게 완성되었으며, 오늘날 다른 많은 harness(하네스)가 이를 수행하지 않습니다. 또한 다양한 agent design(에이전트 설계)이 어떻게 작동하는지 검증하는 좋은 방법입니다. Amp는 Claude Code와 마찬가지로 자신의 tool(도구)도 사용하는 사람들이 만든 제품처럼 느껴집니다. 업계의 다른 모든 agent(에이전트)가 이렇게 한다고 느끼지는 않습니다.

읽고 발견한 것들

공유할 가치가 있다고 생각하는 것들의 무작위 모음입니다:

  • MCP가 전혀 필요 없다면?: Mario는 많은 MCP server(서버)가 과도하게 엔지니어링되어 있고 많은 context(컨텍스트)를 소비하는 대규모 toolset(도구 세트)를 포함한다고 주장합니다. 그는 browser-agent use-case(브라우저 에이전트 사용 사례)를 위해 Bash를 통해 실행되는 간단한 CLI tool(도구)(예: start, navigate, evaluate JS, screenshot)에 의존하는 minimalist approach(미니멀리스트 접근 방식)를 제안하며, 이는 token(토큰) 사용량을 적게 유지하고 workflow(워크플로우)를 유연하게 만듭니다. 저는 이를 기반으로 Claude/Amp Skill을 구축했습니다.
  • “소규모” 오픈 소스의 운명: 저자는 작고 단일 목적의 오픈 소스 라이브러리 시대가 끝나가고 있다고 주장합니다. 주로 내장 플랫폼 API와 AI tool(도구)이 이제 간단한 유틸리티를 주문형으로 생성할 수 있기 때문입니다. 정말 다행입니다.
  • Tmux는 사랑. 함께 제공되는 글은 없지만, 요약하면 Tmux가 훌륭하다는 것입니다. agent(에이전트)가 작업해야 하는 interactive system(대화형 시스템)처럼 보이는 것이 있다면, Tmux skill(기술)을 제공해야 합니다.
  • LLM API는 동기화 문제. 이것은 이 글에 넣기에는 너무 긴 별도의 깨달음이어서 별도로 작성했습니다.

이 글은 ai 태그가 지정되었습니다.

마크다운으로 복사 / 보기