Anthropic Managed Agents 원문 전체 번역, brain과 hands를 분리하는 이유

·8분 읽기

Claude Managed Agents 시작하기

Claude Managed Agents는 공식 문서를 따라 시작할 수 있다.

Anthropic Engineering Blog에서는 어떻게 하면 효과적인 에이전트를 만들 수 있는지, 그리고 장시간 실행되는 작업을 위해 하네스를 어떻게 설계해야 하는지를 계속 다뤄왔다. 이 글들 전체를 관통하는 공통된 주제는, 하네스란 결국 Claude가 스스로는 할 수 없다고 가정한 것들을 코드로 박아 넣은 구조라는 점이다. 다만 그런 가정은 모델이 발전하면서 금방 낡을 수 있기 때문에 자주 다시 점검해야 한다.

한 가지 예를 들면, 이전 작업에서 Anthropic은 Claude Sonnet 4.5가 자신의 컨텍스트 한계에 가까워진다고 느끼면 작업을 너무 일찍 마무리하려는 경향을 발견했다. 이것은 흔히 "컨텍스트 불안(context anxiety)"이라고 불리는 현상이다. Anthropic은 이를 해결하기 위해 하네스에 context reset을 넣었다. 그런데 같은 하네스를 Claude Opus 4.5에 적용해보니 이 현상은 사라져 있었다. 결국 그 reset 로직은 불필요한 짐이 된 셈이다.

Anthropic은 앞으로도 하네스가 계속 진화할 것이라고 본다. 그래서 특정 구현에 종속되지 않고, 현재 사용하는 구현을 포함해 앞으로의 구현까지 버틸 수 있는 소수의 인터페이스 위에 장기 실행 에이전트를 돌릴 수 있는 Claude Platform의 호스티드 서비스, 즉 Managed Agents를 만들었다.

Managed Agents를 만든다는 것은 컴퓨팅의 오래된 문제 하나를 푸는 일이기도 했다. 아직 생각조차 못한 프로그램들을 수용할 수 있는 시스템을 어떻게 설계할 것인가 하는 문제다. 과거 운영체제는 하드웨어를 추상화된 개념, 예를 들어 process나 file 같은 개념으로 가상화함으로써 이 문제를 풀었다. 이런 추상화는 하드웨어보다 오래 살아남는다. 예를 들어 read()는 1970년대의 디스크 팩을 읽는지, 현대 SSD를 읽는지에 상관없이 동일하게 동작한다. 위쪽 추상화는 안정적으로 유지되고, 아래 구현은 자유롭게 바뀔 수 있다.

Managed Agents도 같은 패턴을 따른다. Anthropic은 에이전트의 구성요소를 가상화했다.

  • session: 일어난 모든 일을 순서대로 쌓는 append-only 로그
  • harness: Claude를 호출하고 Claude의 tool call을 실제 인프라로 라우팅하는 루프
  • sandbox: Claude가 코드를 실행하고 파일을 수정할 수 있는 실행 환경

이렇게 하면 각 요소의 구현을 서로 방해하지 않고 교체할 수 있다. Anthropic은 각 인터페이스의 모양에는 분명한 의견을 갖고 있지만, 그 뒤에서 무엇이 돌아가는지까지 고정하려는 것은 아니다.

반려동물을 들이지 마라

Anthropic은 처음에 모든 에이전트 구성요소를 하나의 컨테이너에 넣었다. 즉 session, agent harness, sandbox가 모두 같은 환경을 공유하도록 한 것이다. 이 방식에는 장점도 있었다. 파일 편집이 곧바로 시스템 콜로 이어졌고, 별도의 서비스 경계를 설계할 필요도 없었다.

하지만 모든 것을 하나의 컨테이너에 묶는 순간, 오래된 인프라 문제 하나와 마주하게 됐다. 바로 "pet"를 키우게 된 것이다. pets vs cattle 비유에서 pet은 이름이 붙어 있고 손수 돌봐야 하며 잃어버리기 어려운 개체이고, cattle은 서로 대체 가능한 존재다. Anthropic의 경우 그 서버가 pet가 되어버렸다. 컨테이너가 실패하면 세션도 함께 사라졌고, 컨테이너가 응답하지 않으면 직접 살려내야 했다.

컨테이너를 간호해야 한다는 것은 결국 멈춰버린 세션을 디버깅해야 한다는 뜻이었다. Anthropic이 볼 수 있는 유일한 창은 WebSocket 이벤트 스트림뿐이었는데, 이것만으로는 문제가 어디서 시작됐는지 알 수 없었다. 하네스 버그인지, 이벤트 스트림의 패킷 손실인지, 컨테이너 오프라인인지가 겉으로는 동일하게 보였다. 무엇이 잘못됐는지 알아내려면 엔지니어가 컨테이너 안으로 셸을 열어야 했지만, 그 컨테이너에는 사용자 데이터가 함께 들어있는 경우가 많았기 때문에 사실상 제대로 디버깅할 수 없는 구조였다.

또 다른 문제는 하네스가 Claude가 작업하는 대상은 모두 자신과 같은 컨테이너 안에 있다고 가정했다는 점이다. 고객이 Claude를 자기 VPC의 리소스와 연결하고 싶어 하면, Anthropic 네트워크와 피어링을 하거나 Anthropic 하네스를 고객 환경 안에서 직접 실행해야 했다. 하네스에 박혀 있던 하나의 가정이 다른 인프라와 연결하려는 순간 제약으로 돌아온 것이다.

Anthropic이 도달한 해결책은, 소위 "brain"이라 부른 Claude와 그 하네스를 "hands"인 샌드박스와 각종 실행 도구, 그리고 "session"인 이벤트 로그로부터 분리하는 것이었다. 각 요소는 서로에 대해 거의 가정하지 않는 인터페이스가 되었고, 각각 독립적으로 실패하거나 교체될 수 있게 됐다.

하네스를 컨테이너 밖으로 빼내기

brain과 hands를 분리한다는 것은, 하네스가 더 이상 컨테이너 안에 살지 않는다는 뜻이었다. 하네스는 이제 컨테이너를 다른 도구와 똑같이 호출한다.

execute(name, input) -> string

컨테이너는 cattle이 된다. 컨테이너가 죽더라도 하네스는 그것을 tool-call 에러로 받아 Claude에게 돌려줄 수 있다. Claude가 재시도하기로 결정하면, 새로운 컨테이너를 provision({resources}) 같은 표준 절차로 다시 초기화하면 된다. 이제 실패한 컨테이너를 손수 간호할 필요가 없어진다.

하네스 실패에서 복구하기

하네스 역시 cattle이 된다. session 로그가 하네스 바깥에 있기 때문에, 하네스 내부에는 크래시 이후에도 살아남아야 할 것이 없다. 하나가 실패하면, 새로운 하네스를 wake(sessionId)로 부팅하고 getSession(id)로 이벤트 로그를 다시 읽은 뒤 마지막 이벤트부터 재개하면 된다. 에이전트 루프가 도는 동안 하네스는 emitEvent(id, event)로 session에 이벤트를 기록해 durable한 로그를 유지한다.

보안 경계

결합된 구조에서는 Claude가 생성한 신뢰할 수 없는 코드가 자격증명과 같은 컨테이너 안에서 실행됐다. 따라서 프롬프트 인젝션이 성공하면 공격자는 Claude에게 자기 환경 변수를 읽게 만들기만 하면 됐다. 한 번 토큰을 얻고 나면 새롭고 제한 없는 세션을 마음대로 만들고, 그 세션에 추가 작업을 위임할 수도 있다. 토큰 범위를 좁히는 것은 분명한 완화책이지만, 이것 역시 Claude가 제한된 토큰으로는 무엇을 못 할지에 대한 가정을 전제로 한다. 그런데 Claude는 점점 더 똑똑해지고 있다. 구조적인 해결책은 Claude가 생성한 코드가 도는 sandbox에서 토큰 자체에 닿지 못하도록 만드는 것이었다.

Anthropic은 이를 위해 두 가지 패턴을 사용했다. 인증 정보는 리소스와 함께 번들링하거나, sandbox 밖의 vault에 보관할 수 있다. Git의 경우 각 저장소의 access token으로 sandbox 초기화 시 repo를 clone하고, 그 토큰을 로컬 git remote에 연결한다. 그러면 sandbox 안에서는 에이전트가 토큰 자체를 직접 다루지 않고도 Git push와 pull이 동작한다. 커스텀 도구에 대해서는 MCP를 지원하고, OAuth 토큰은 안전한 vault에 저장한다. Claude는 전용 프록시를 통해 MCP 도구를 호출하고, 이 프록시는 세션과 연결된 토큰을 받아 vault에서 실제 자격증명을 조회한 뒤 외부 서비스 호출을 수행한다. 하네스는 어떤 자격증명도 직접 알지 못한다.

session은 Claude의 context window가 아니다

장기 실행 작업은 종종 Claude의 context window 길이를 넘긴다. 그리고 이를 해결하는 일반적인 방법들은 모두 무엇을 남기고 무엇을 버릴지에 대한 비가역적 결정을 포함한다. Anthropic은 이전에 context engineering 관련 글들에서 이런 기법을 다룬 바 있다. 예를 들어 compaction은 Claude가 자신의 context window를 요약해 저장하게 하고, memory tool은 Claude가 맥락을 파일로 기록해 세션 간 학습을 가능하게 한다. 여기에 오래된 tool result나 thinking block 같은 토큰만 골라서 지우는 context trimming을 조합할 수도 있다.

하지만 맥락을 선택적으로 보존하거나 버리는 비가역적 결정은 실패를 낳을 수 있다. 미래의 turn에서 어떤 토큰이 필요할지 알기 어렵기 때문이다. 메시지가 compaction을 거쳐 변형되면, 하네스는 그 compacted 메시지를 Claude의 context window에서 제거한다. 그리고 이것들은 따로 저장해두지 않았다면 복구할 수 없다. 이전 연구에서는 context를 context window 바깥에 존재하는 객체로 저장해 이 문제를 해결하는 방법도 탐구했다. 예를 들어 REPL 안의 객체로 저장한 뒤, LLM이 코드를 작성해 필터링하거나 슬라이스하는 방식으로 맥락에 접근하게 하는 식이다.

Managed Agents에서는 session이 같은 이점을 제공한다. session은 Claude의 context window 바깥에 존재하는 context object 역할을 한다. 다만 sandbox나 REPL 내부가 아니라, durable한 session log에 저장된다. getEvents() 인터페이스를 통해 brain은 이벤트 스트림에서 위치 기반 슬라이스를 선택해 맥락을 조회할 수 있다. 이 인터페이스는 유연하게 사용할 수 있다. 예를 들어 마지막으로 읽던 지점부터 다시 이어 읽거나, 특정 순간 직전 몇 개 이벤트를 되감아 경위를 확인하거나, 특정 액션 전에 맥락을 다시 읽는 식이 가능하다.

가져온 이벤트는 Claude의 context window에 넣기 전에 하네스에서 다시 변환할 수도 있다. 이 변환은 prompt cache hit rate를 높이기 위한 context 조직 방식일 수도 있고, 보다 일반적인 context engineering 기법일 수도 있다. Anthropic은 session이 담당하는 recoverable context storage와 harness가 담당하는 arbitrary context management를 분리했다. 미래 모델에서 어떤 종류의 context engineering이 필요할지 예측할 수 없기 때문이다. 인터페이스는 context 관리의 구체 구현을 harness 쪽으로 밀어내고, session이 durable하며 필요할 때 조회 가능하다는 점만 보장한다.

많은 brain, 많은 hands

많은 brain

brain과 hands를 분리한 것은 초기 고객 불만 중 하나를 해결해줬다. 고객 팀이 Claude를 자기 VPC 안의 리소스와 연결하고 싶을 때, 예전에는 harness가 들어 있는 컨테이너가 모든 리소스가 자신 옆에 있다고 가정했기 때문에 Anthropic 네트워크와 피어링하는 것 외에는 방법이 없었다. 하지만 하네스가 컨테이너 밖으로 나오자 그 가정은 사라졌다.

이 변화는 성능 측면에서도 이득이 있었다. 처음에 brain을 컨테이너 안에 넣었을 때는, 많은 brain이 곧 많은 컨테이너를 의미했다. 각 brain마다 컨테이너가 준비되기 전에는 어떤 추론도 시작할 수 없었다. 모든 세션이 컨테이너 셋업 비용을 선지불해야 했던 것이다. sandbox를 전혀 쓰지 않을 세션조차 repo를 clone하고 프로세스를 부팅하고, 서버에서 대기 중인 이벤트를 가져오는 비용을 감당해야 했다.

이런 죽은 시간은 time-to-first-token, 즉 TTFT로 나타난다. 이는 세션이 작업을 수락한 뒤 첫 번째 응답 토큰을 내기까지 걸리는 시간을 의미하며, 사용자가 가장 민감하게 느끼는 지연이다.

brain과 hands를 분리하면 컨테이너는 필요할 때만 brain이 tool call, 즉 execute(name, input) -> string을 통해 provision한다. 따라서 당장 컨테이너가 필요 없는 세션은 그것을 기다릴 필요가 없다. 오케스트레이션 레이어가 session log에서 pending event만 가져오면 추론을 바로 시작할 수 있다. Anthropic은 이 아키텍처로 p50 TTFT를 약 60%, p95 TTFT를 90% 넘게 줄였다고 말한다. 많은 brain으로 확장하는 일도 단순히 많은 stateless harness를 띄우고, 필요할 때만 hands를 연결하는 문제로 바뀌었다.

많은 hands

Anthropic은 각 brain이 많은 hands에 연결될 수 있기를 원했다. 현실에서는 Claude가 여러 실행 환경을 이해하고, 어떤 작업을 어디로 보낼지 결정해야 한다는 뜻이다. 이는 단일 셸에서만 동작하는 것보다 인지적으로 더 어려운 작업이다. Anthropic이 처음 brain을 단일 컨테이너에 넣었던 이유는 당시 모델들이 아직 이런 복잡한 상황을 다룰 만큼 충분히 강하지 않았기 때문이다. 하지만 모델 지능이 향상되자 이번에는 단일 컨테이너가 병목이 됐다. 그 컨테이너가 실패하면 brain이 접근하던 모든 hand의 상태를 함께 잃게 되었기 때문이다.

brain과 hands를 분리하면 각 hand는 하나의 도구가 된다.

execute(name, input) -> string

이름과 입력이 들어가고 문자열이 나온다. 이 인터페이스는 어떤 커스텀 도구든, 어떤 MCP 서버든, Anthropic 자체 도구든 지원할 수 있다. 하네스는 sandbox가 컨테이너인지, 휴대폰인지, 포켓몬 에뮬레이터인지 알 필요가 없다. 그리고 어떤 hand도 특정 brain과 결합되어 있지 않기 때문에, 여러 brain이 hands를 서로 넘겨가며 사용할 수도 있다.

결론

Anthropic이 마주한 도전은 오래된 문제다. 아직 상상하지 못한 프로그램을 수용할 시스템을 어떻게 설계할 것인가. 운영체제는 존재하지 않던 프로그램들까지 수용할 만큼 일반적인 하드웨어 추상화를 만들어 수십 년 동안 살아남았다. Managed Agents에서도 Anthropic은 Claude 주변의 미래 하네스, 샌드박스, 혹은 기타 구성요소를 수용할 수 있는 시스템을 설계하고자 했다.

Managed Agents는 같은 정신의 meta-harness다. 미래에 Claude에게 어떤 하네스가 필요할지 특정하지 않는다. 대신 서로 다른 하네스를 수용할 수 있는 일반 인터페이스의 시스템을 제공한다. 예를 들어 Claude Code는 Anthropic 내부에서 다양한 작업에 널리 사용하는 훌륭한 하네스다. 또한 특정 영역에 특화된 하네스가 좁은 도메인에서는 뛰어난 성능을 낸다는 점도 이미 보여주었다. Managed Agents는 이런 다양한 하네스를 모두 수용하면서, 시간이 흐르며 향상되는 Claude의 지능과 발맞출 수 있다.

meta-harness 설계란 Claude 주변 인터페이스에 대해 분명한 관점을 갖는다는 뜻이다. Anthropic은 Claude가 상태를 조작할 수 있는 능력, 즉 session이 필요하고, 계산을 수행할 수 있는 능력, 즉 sandbox가 필요하다고 본다. 또한 Claude가 많은 brain과 많은 hands로 확장될 수 있어야 한다고 본다. 그래서 이 요소들이 장시간에 걸쳐 안정적이고 안전하게 실행될 수 있도록 인터페이스를 설계했다. 다만 미래의 Claude가 몇 개의 brain과 hands를 필요로 할지, 그것들이 어디에 위치할지는 가정하지 않는다.

감사의 말

이 글은 Lance Martin, Gabe Cemaj, Michael Cohen이 작성했다. Nodir Turakulov와 Jeremy Fox에게 유익한 대화에 대해 감사드린다. 또한 Agents API 팀과 Jake Eaton의 기여에도 특별히 감사한다.

원문