Claude Info
Engineering·

Масштабирование управляемых агентов: отделяем мозг от рук

Как Anthropic построила Managed Agents — хостинговый сервис для долгосрочных агентов. Разбираем архитектурные решения: разделение сессии, harness и sandbox, безопасность и снижение TTFT на 60–90%.

Масштабирование управляемых агентов: отделяем мозг от рук

Начать работу с Claude Managed Agents можно, следуя нашей документации. Постоянная тема Engineering Blog — как строить эффективных агентов и проектировать harness-оболочки для длительных задач. Общая нить во всех этих материалах: harness-оболочки кодируют предположения о том, чего Claude не может делать самостоятельно. Однако эти предположения нужно регулярно пересматривать, потому что по мере улучшения моделей они устаревают.

Для примера: в предыдущих исследованиях мы обнаружили, что Claude Sonnet 4.5 преждевременно завершал задачи, когда чувствовал приближение лимита контекстного окна — поведение, которое иногда называют «контекстной тревогой». Мы решили это, добавив в harness сброс контекста. Но когда мы применили тот же harness к Claude Opus 4.5, оказалось, что такого поведения больше нет. Сбросы превратились в мёртвый груз.

Мы ожидаем, что harness-оболочки продолжат эволюционировать. Поэтому мы создали Managed Agents — хостинговый сервис на платформе Claude, который запускает долгосрочных агентов от вашего имени через небольшой набор интерфейсов, рассчитанных на то, чтобы пережить любую конкретную реализацию — включая те, что мы используем сегодня.

Создание Managed Agents потребовало решения старой задачи в вычислительной технике: как спроектировать систему для «программ, которые ещё не придуманы». Десятилетия назад операционные системы решили эту задачу, виртуализировав аппаратное обеспечение в абстракции — процесс, файл — достаточно общие для программ, которых ещё не существовало. Абстракции пережили железо. Команда read() не знает, обращается ли она к дисковому пакету 1970-х или к современному SSD. Абстракции сверху оставались стабильными, пока реализации снизу свободно менялись.

Managed Agents следуют той же логике. Мы виртуализировали компоненты агента: сессию (append-only лог всего произошедшего), harness (цикл, который вызывает Claude и маршрутизирует его вызовы инструментов к соответствующей инфраструктуре) и sandbox (среду выполнения, где Claude может запускать код и редактировать файлы). Это позволяет менять реализацию каждого компонента, не затрагивая остальные. Мы придерживаемся определённых взглядов на форму этих интерфейсов, но не на то, что за ними стоит.

Не заводите питомцев

Мы начали с размещения всех компонентов агента в одном контейнере — сессия, harness и sandbox делили одну среду. У такого подхода были преимущества: редактирование файлов — это прямые системные вызовы, и не нужно было проектировать сервисные границы.

Но, объединив всё в один контейнер, мы столкнулись со старой инфраструктурной проблемой: мы завели питомца. В аналогии «питомцы против скота» питомец — это именной, тщательно опекаемый индивид, которого нельзя потерять, тогда как скот взаимозаменяем. В нашем случае сервер стал таким питомцем: если контейнер падал, сессия терялась. Если контейнер переставал отвечать, нам приходилось его «выхаживать».

Выхаживание контейнеров означало отладку зависших сессий. Единственным окном внутрь был поток событий WebSocket, но он не мог сказать, где именно возникли сбои: баг в harness, потеря пакета в потоке событий или отключение контейнера выглядели одинаково. Чтобы разобраться, инженер должен был открыть shell внутри контейнера, но поскольку тот же контейнер часто хранил пользовательские данные, это фактически означало отсутствие возможности нормально отлаживать.

Вторая проблема: harness предполагал, что всё, с чем работает Claude, находится в том же контейнере. Когда клиенты просили нас подключить Claude к их виртуальному частному облаку, им приходилось либо устанавливать пиринг своей сети с нашей, либо запускать наш harness в собственной среде. Предположение, зашитое в harness, становилось проблемой, когда мы хотели подключить его к другой инфраструктуре.

Отделяем мозг от рук

Решение, к которому мы пришли, — разделить то, что мы называли «мозгом» (Claude и его harness), «руками» (sandbox-среды и инструменты, выполняющие действия) и «сессией» (лог событий сессии). Каждый компонент стал интерфейсом с минимальными предположениями об остальных, и каждый мог независимо отказать или быть заменён.

Harness покидает контейнер. Разделение мозга и рук означало, что harness больше не живёт внутри контейнера. Он обращался к контейнеру так же, как к любому другому инструменту: execute(name, input) → string. Контейнер стал скотом. Если контейнер умирал, harness перехватывал сбой как ошибку вызова инструмента и передавал её Claude. Если Claude решал повторить попытку, новый контейнер можно было переинициализировать по стандартному рецепту: provision({resources}). Нам больше не нужно было выхаживать упавшие контейнеры.

Восстановление после сбоя harness. Harness тоже стал скотом. Поскольку лог сессии хранится вне harness, ничто в нём не должно переживать крэш. Когда один harness падает, новый можно перезапустить через wake(sessionId), получить лог событий через getSession(id) и продолжить с последнего события. В процессе агентного цикла harness записывает в сессию через emitEvent(id, event), сохраняя долговременный журнал событий.

Граница безопасности. В связанной архитектуре любой недоверенный код, сгенерированный Claude, выполнялся в том же контейнере, что и учётные данные — поэтому для prompt injection достаточно было убедить Claude прочитать собственное окружение. Получив эти токены, атакующий мог порождать новые, ничем не ограниченные сессии и делегировать им работу. Узкое ограничение прав — очевидная мера защиты, но она кодирует предположение о том, чего Claude не может сделать с ограниченным токеном — а Claude становится всё умнее. Структурное решение — убедиться, что токены никогда не доступны из sandbox, где выполняется сгенерированный Claude код.

Мы использовали два паттерна для этого. Авторизация может быть привязана к ресурсу или храниться в хранилище вне sandbox. Для Git мы используем токен доступа каждого репозитория для клонирования при инициализации sandbox и прописываем его в локальный git remote. git push и git pull работают изнутри sandbox, и агент никогда не обрабатывает токен напрямую. Для кастомных инструментов мы поддерживаем MCP и храним OAuth-токены в защищённом хранилище. Claude вызывает MCP-инструменты через выделенный прокси; этот прокси принимает токен, связанный с сессией, затем извлекает соответствующие учётные данные из хранилища и делает вызов к внешнему сервису. Harness никогда не получает доступа к учётным данным.

Сессия — это не контекстное окно Claude

Долгосрочные задачи часто превышают длину контекстного окна Claude, а стандартные способы решения этой проблемы предполагают необратимые решения о том, что сохранять. Мы исследовали эти техники в предыдущих работах по контекстной инженерии. Например, компакция позволяет Claude сохранить резюме контекстного окна, а инструмент памяти позволяет Claude записывать контекст в файлы, обеспечивая обучение между сессиями. Это можно сочетать с обрезкой контекста, которая избирательно удаляет токены — например, старые результаты вызовов инструментов или блоки размышлений.

Но необратимые решения об избирательном сохранении или удалении контекста могут приводить к сбоям. Сложно знать заранее, какие токены понадобятся в будущих ходах. Если сообщения трансформируются на шаге компакции, harness удаляет скомпактированные сообщения из контекстного окна Claude, и они восстановимы только если были сохранены. В предыдущих работах исследовались способы решения этого путём хранения контекста как объекта вне контекстного окна. Например, контекст может быть объектом в REPL, к которому LLM программно обращается, написав код для фильтрации или нарезки.

В Managed Agents сессия обеспечивает ту же пользу, выступая контекстным объектом вне контекстного окна Claude. Но вместо хранения внутри sandbox или REPL, контекст долговременно хранится в логе сессии. Интерфейс getEvents() позволяет мозгу исследовать контекст, выбирая позиционные срезы потока событий. Интерфейс можно использовать гибко: мозг может продолжить с того места, где остановился, отмотать несколько событий назад до конкретного момента, чтобы увидеть предысторию, или перечитать контекст перед конкретным действием.

Любые полученные события также могут быть трансформированы в harness перед передачей в контекстное окно Claude. Эти трансформации могут быть любыми, что кодирует harness, включая организацию контекста для достижения высокого процента попаданий в кэш промптов и контекстную инженерию. Мы разделили задачи восстанавливаемого хранения контекста в сессии и произвольного управления контекстом в harness, потому что не можем предсказать, какая именно контекстная инженерия потребуется в будущих моделях. Интерфейсы переносят это управление контекстом в harness и гарантируют только то, что сессия долговременна и доступна для запросов.

Много мозгов, много рук

Много мозгов. Разделение мозга и рук решило одну из первых жалоб наших клиентов. Когда команды хотели, чтобы Claude работал с ресурсами в их собственном VPC, единственным путём был пиринг их сети с нашей, потому что контейнер с harness предполагал, что все ресурсы находятся рядом с ним. Как только harness вышел из контейнера, это предположение исчезло. То же изменение дало выигрыш в производительности. Когда мы изначально помещали мозг в контейнер, это означало, что много мозгов требовало столько же контейнеров. Для каждого мозга инференс не мог начаться, пока контейнер не был подготовлен; каждая сессия платила полную стоимость запуска контейнера авансом. Каждая сессия, даже те, что никогда не касались sandbox, должна была клонировать репозиторий, запускать процесс, получать ожидающие события с наших серверов.

Это мёртвое время выражается в time-to-first-token (TTFT) — метрике, измеряющей, сколько сессия ждёт между принятием задачи и генерацией первого токена ответа. TTFT — это задержка, которую пользователь ощущает острее всего.

Разделение мозга и рук означает, что контейнеры подготавливаются мозгом через вызов инструмента (execute(name, input) → string) только при необходимости. Поэтому сессия, которой контейнер не нужен сразу, не ждёт его. Инференс мог начаться, как только оркестрационный слой получал ожидающие события из лога сессии. Используя эту архитектуру, наш p50 TTFT снизился примерно на 60%, а p95 — более чем на 90%. Масштабирование до множества мозгов означало просто запуск множества stateless harness-оболочек с подключением рук только при необходимости.

Много рук. Мы также хотели возможности подключать каждый мозг к множеству рук. На практике это означает, что Claude должен рассуждать о множестве сред выполнения и решать, куда отправить работу — более сложная когнитивная задача, чем работа в одном shell. Мы начали с мозгом в одном контейнере, потому что более ранние модели не были способны на это. По мере роста интеллекта одиночный контейнер стал ограничением: когда он падал, мы теряли состояние для каждой руки, в которую тянулся мозг.

Разделение мозга и рук делает каждую руку инструментом, execute(name, input) → string: на вход подаётся имя и входные данные, возвращается строка. Этот интерфейс поддерживает любой кастомный инструмент, любой MCP-сервер и наши собственные инструменты. Harness не знает, является ли sandbox контейнером, телефоном или эмулятором покемонов. И поскольку ни одна рука не привязана к конкретному мозгу, мозги могут передавать руки друг другу.

Заключение

Задача, с которой мы столкнулись, не нова: как спроектировать систему для «программ, которые ещё не придуманы». Операционные системы существуют десятилетиями, виртуализировав аппаратное обеспечение в абстракции, достаточно общие для программ, которых ещё не существовало. С Managed Agents мы стремились спроектировать систему, которая вмещает будущие harness-оболочки, sandbox-среды и другие компоненты вокруг Claude.

Managed Agents — это мета-harness в том же духе, без жёстких взглядов на конкретный harness, который понадобится Claude в будущем. Скорее, это система с общими интерфейсами, допускающими множество различных harness-оболочек. Например, Claude Code — отличный harness, который мы широко используем для разных задач. Мы также показали, что задачно-специфичные harness-оболочки превосходят в узких областях. Managed Agents может вместить любой из них, соответствуя интеллекту Claude со временем.

Проектирование мета-harness означает наличие чётких взглядов на интерфейсы вокруг Claude: мы ожидаем, что Claude потребуется возможность манипулировать состоянием (сессия) и выполнять вычисления (sandbox). Мы также ожидаем, что Claude потребуется возможность масштабироваться до множества мозгов и множества рук. Мы спроектировали интерфейсы так, чтобы они могли надёжно и безопасно работать на длительных горизонтах. Но мы не делаем никаких предположений о количестве или расположении мозгов или рук, которые понадобятся Claude.

Благодарности

Авторы: Lance Martin, Gabe Cemaj и Michael Cohen. Благодарим Nodir Turakulov и Jeremy Fox за полезные обсуждения этих тем. Отдельная благодарность команде Agents API и Jake Eaton за их вклад.