Главная / Статьи

Агент без защиты: что ломается, когда модель не защищена ничем.

Создайте тот же незащищенный агент-турагент в Claude Agent SDK и LangChain Deep Agents, затем наблюдайте за тремя сбоями, чтобы понять, какие механизмы контроля отсутствуют.

Издатель: REACTAPP.TOP9 мин чтения2152 слов

Источники: Harness Engineering: The Naked Agent: Why Your Framework Hands You a Loop, Not a Harness — I

Клиент просит агента перебронировать рейс. Модель звучит уверенно, вызов инструмента успешен, и в ответ говорится, что всё улажено. Однако кто-то замечает, что дата — 32 марта.

Именно в этот момент рушится иллюзия. У модели не было проблем с интеллектом; провалилось всё вокруг, поскольку ни один компонент не знал, когда следует вмешаться. Далее мы видим агента без всяких защит: без проверщиков, без памяти, без возможности восстановления, без резервных механизмов. Его неудача показывает, где допускаются ошибки у производственных агентов, и доказывает, что среда выполнения языковой модели важнее самой модели. Тот же упрощённый агент создаётся дважды — с помощью Claude Agent SDK и LangChain Deep Agents, — и оба раза он выходят из строя тремя конкретными способами, каждый из которых указывает на механизм контроля, необходимый настоящему агенту. Результатом становится метод, позволяющий отличить проблему самой модели от проблемы среды её работы.

Инцидент 32 марта

Представьте агента по бронированию путешествий, который получает от настоящего пользователя сообщение «32 марта». Модель передала эту строку непосредственно в API для поиска рейсов. Примерно через сорок минут и после нескольких запросов к модели стоимостью пятьдесят долларов человеку пришлось объяснять клиенту отсутствующую бронь, хотя тому гарантировали, что всё улажено.

Модель ничего не создавала. Она передавала именно то, что ей было дано, поскольку механизм управления не мог определить, следует ли это делать. В этом и заключается суть. Интеллект исходит от модели, а дисциплина — от механизма управления; поэтому, когда агент в производственной среде ведет себя ненадлежащим образом, причина почти всегда кроется в слое дисциплины. Замена более сильной моделью или улучшение формулировки запроса не обеспечат контроля, которого нет, а создание механизмов контроля начинается с понимания того, во что обходится их отсутствие. Именно поэтому исходная точка крайне примитивна: один хрупкий агент, две платформы, без механизма управления.

Доказательства доминирования времени выполнения

Данные тестирования подтверждают этот факт. Исследователи из Принстонского университета заморозили модель на уровне GPT-4 Turbo и изменили только код интерфейса, связывающего её с кодовой базой. Доля проблем из набора SWE-bench, решённых, выросла с 3,8% до 12,47%, то есть почти в три раза, при этом веса модели остались неизменными. Работа под названием «SWE-agent: Agent-Computer Interfaces Enable Automated Software Engineering» была опубликована в 2024 году и принята к публикации на конференции NeurIPS в том же году. Рост эффективности был обусловлен дизайном интерфейса, а не интеллектом модели.

У этой области есть более старое название — «механическая согласованность», что означает создание программного обеспечения, работающего в согласии с аппаратным обеспечением или платформой, а не вопреки им. Подход «Harness engineering» расширяет эту концепцию на новую среду — языковую модель с её окном контекста и лимитом внимания. Модель устанавливает предел, а механизм «Harness» определяет, насколько стабильно система может приближаться к этому пределу.

Изменение подхода влияет на действия после неудачи:

  • Раньше: после неверного ответа корректируются инструкции, добавляются ограничения, пробуется более мощная модель в надежде на улучшение
  • Теперь: после неверного ответа определяется, какой элемент управления отсутствует, и этот правил из инструкций перемещается в механизм выполнения

Инструкции используются для убеждения, а механизмы выполнения — для обеспечения соблюдения правил.

Цикл, лежащий в основе каждой платформы

Все платформы для агентов основаны на одном пятиэтапном цикле: предоставление инструментов модели, получение запроса на их использование, выполнение запроса, возврат результата и получение следующего шага. Документация Anthropic по использованию инструментов, API Responses от OpenAI и механизм выполнения агентов от LangChain сводятся к одному и тому же принципу. SDK могут появляться и исчезать, но структура цикла остается неизменной.

В простой версии используется базовый SDK-провайдера, два инструмента для выполнения действий и полное отсутствие механизмов безопасности:

import anthropic

client = anthropic.Anthropic()  # reads ANTHROPIC_API_KEY
TOOLS = [
    {"name": "search_flights",
     "description": "Search flights between two cities for a date.",
     "input_schema": {"type": "object", "properties": {
         "origin": {"type": "string"}, "destination": {"type": "string"},
         "date": {"type": "string", "description": "YYYY-MM-DD"}},
         "required": ["origin", "destination", "date"]}},
    {"name": "book_flight",
     "description": "Book a specific flight.",
     "input_schema": {"type": "object", "properties": {
         "flight_id": {"type": "string"}, "passenger_name": {"type": "string"}},
         "required": ["flight_id", "passenger_name"]}},
]
def run_naked(user_msg: str) -> str:
    messages = [{"role": "user", "content": user_msg}]
    while True:                                   # ① no iteration cap
        resp = client.messages.create(
            model="claude-sonnet-4-6", max_tokens=1024,
            tools=TOOLS, messages=messages,
        )
        if resp.stop_reason != "tool_use":
            return resp.content[0].text
        call = next(b for b in resp.content if b.type == "tool_use")
        result = dispatch(call.name, call.input)
                                             # ② direct side effect, no check
        messages.extend([
                                             # ③ whole history, every turn
            {"role": "assistant", "content": resp.content},
            {"role": "user", "content": [{"type": "tool_result",
                "tool_use_id": call.id, "content": result}]},
        ])

Читайте это, учитывая три фактора риска:

  1. Цикл while True: не имеет ограничений. Сбойный алгоритм продолжает вызывать модель до исчерпания бюджета, поскольку количество итераций не учитывается.
  2. Функция dispatch передаёт аргументы, предоставленные моделью, непосредственно в компонент с побочными эффектами без предварительной проверки, в результате чего значение, которое корректно парсится, но не может существовать, попадает в API.
  3. Полная история сообщений расширяется и пересылается при каждом ходе, тихо увеличивая объём контекста до тех пор, пока качество не снизится.

Ни одно из свойств безопасности, которые вы хотели бы использовать, не находится внутри этого цикла. Они расположены в коде, который ещё не написан. Использование фреймворка тоже не решает проблему. Claude Agent SDK и Deep Agents предлагают лучше структурированный цикл с соответствующим протоколом инструментов и чётким путём выполнения, но по умолчанию они не включают никаких проверщиков, никакой памяти для анализа и никаких механизмов восстановления. Цикл существует таким, какой он есть, а обработка его остаётся задачей разработчика.

Вот тот же простой агент в каждом фреймворке.

Версия Claude Agent SDK

SDK оборачивает цикл за вас. Инструменты объявляются с помощью декоратора @tool, предоставляются в виде внутреннего сервера MCP и запускаются через функцию query(). Однако отсутствует любой контроль: аргументы проверяются только по схеме, ничего не сохраняется между вызовами, и нет возможности восстановления ситуации.

import asyncio
from claude_agent_sdk import (
    query, ClaudeAgentOptions, tool,
    create_sdk_mcp_server, AssistantMessage, ResultMessage,
)

@tool("search_flights", "Search flights between two cities for a date.",
      {"origin": str, "destination": str, "date": str})
async def search_flights(args):
                                              # ① no check that date exists
    hits = flights_api.search(**args)
    return {"content": [{"type": "text", "text": str(hits)}]}
@tool("book_flight", "Book a specific flight.",
      {"flight_id": str, "passenger_name": str})
async def book_flight(args):
                                               # ② destructive, ungated
    confirmation = flights_api.book(**args)
    return {"content": [{"type": "text", "text": confirmation}]}
server = create_sdk_mcp_server("travel", tools=[search_flights, book_flight])
async def main():
    options = ClaudeAgentOptions(
        mcp_servers={"travel": server},
        allowed_tools=["mcp__travel__search_flights",
                       "mcp__travel__book_flight"],
    )
    async for msg in query(prompt="Rebook this customer for March 32nd.",
                           options=options):
        if isinstance(msg, AssistantMessage):
            for b in msg.content:
                if hasattr(b, "text"):
                    print(b.text)
        elif isinstance(msg, ResultMessage):
            print("done:", msg.subtype)
                                              # ③ no state survives this run

Выделяются три момента:

  1. search_flights принимает любые данные, предложенные моделью и соответствующие схеме, поэтому дата, которая не может существовать, попадает в тело инструмента.
  2. book_flight является разрушительным методом, и его ничто не защищает. Декоратор подключает его к циклу без каких-либо механизмов проверки или утверждения.
  3. После того как поступает ResultMessage и возвращается результат от query(), сессия завершается, и ничто из прошлого запуска не сохраняется для следующего вызова.

SDK обеспечивает простой цикл обработки. Он не контролирует некорректные даты, двойные списания или то, что требует сохранения.

Версия Deep Agents

Deep Agents обрабатывает цикл иначе. Обычные инструменты LangChain передаются в функцию create_deep_agent(), которая возвращает скомпилированную структуру графа для вызова через invoke. Различия заключаются в том же: отсутствует проверщик, и поскольку не указывается чекпоинт, граф забывает всё, что происходило между вызовами.

from langchain.tools import tool
from deepagents import create_deep_agent

@tool
def search_flights(origin: str, destination: str, date: str) -> str:
    """Search flights between two cities for a date (YYYY-MM-DD)."""
    return str(flights_api.search(origin, destination, date))
                                                # ① no date check
@tool
def book_flight(flight_id: str, passenger_name: str) -> str:
    """Book a specific flight."""
    return flights_api.book(flight_id, passenger_name)
                                                 # ② ungated side effect
agent = create_deep_agent(
                                                 # ③ the loop, no controls
    model="anthropic:claude-sonnet-4-6",
    tools=[search_flights, book_flight],
)
result = agent.invoke({"messages": [{"role": "user",
    "content": "Rebook this customer for March 32nd."}]})
print(result["messages"][-1].content)
# Ask a follow-up in a second invoke, and it starts from zero: no thread,
# no memory.

Ещё три момента:

  1. В функции search_flights API вызывается с необработанной строкой date, и ничто не подтверждает её достоверность до отправки запроса.
  2. book_flight выполняет бронирование сразу по выбору модели, без какого-либо промежуточного этапа между предложением и его последствиями.
  3. create_deep_agent возвращает граф, который выполняет цикл и немногое больше; при отсутствии контрольных точек между вызовами нет ни потоков, ни памяти.

Три способа сбоя у «голого» агента

Два фреймворка предоставили два простых цикла без какой-либо дисциплины — именно так выглядит «голый» агент в 2026 году. Это не упрощённая модель; это форма десятков демонстраций агентов, публикуемых на GitHub еженедельно. При реальном объёме работы он сбивается тремя способами, причём каждый сбой соответствует мере контроля, добавляемой надлежащей системой управления.

Невозможный ввод данных. При указании «32 марта» модель ведет себя как обычно и заполняет поле date строкой, которая имеет правильную структуру, но не несет никакого смысла. Схема лишь подтверждает, что date — это строка, и не может определить, существует ли такой день. В результате функция book_flight выполняется с относительно вымышленным маршрутом, а модель сообщает о успехе.

Вызывается функция book_flight с выдуманным идентификатором рейса и именем пассажира, и она возвращает сообщение «Бронирование выполнено». Действие было выполнено, но ничто в цикле не проверяло, действительно ли это было необходимо.

book_flight(flight_id=”AC-PHANTOM”, passenger_name=”J. Moffatt”)
# -> “Booked.” The action fired. Nothing in the loop asked whether it should.

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

Чрезмерный контекст. Простой цикл пересылает всю историю на каждом шаге. Длинный процесс перебронирования, содержащий результаты поиска, текст правил и попытки повтора, может превысить пятьдесят тысяч токенов, и ничто не сокращает его объем. Наступает то, что в литературе называют «порчей контекста», и агент перестает выполнять шаги, которые ранее успешно выполнял в том же запуске. Это не вызывает ошибок. Агент просто постепенно становится хуже.

Здесь нехватало рабочей памяти и механизма сбора контекста, которые относятся к категории памяти и долговременного состояния, а также к процессу сбора контекста.

Сглаженные ошибки. Инструмент возвращает данные с ошибкой, но модель, чаще, чем кто-либо мог ожидать, пишет вежливое резюме, игнорирующее сбой. В одном анализе производственных инцидентов агенты были описаны как уверенные лжецы: база данных, возвращающая нуль строк, воспринимается как фактический ответ, а не как сбой. Выбор правильного инструмента никогда не был сложной задачей. Сложно определить, инструмент действительно работает или убедительно имитирует работу. Здесь не наблюдается обратной связи, а её наличие относится к возможностям наблюдения и обнаружения отклонений.

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

Рабочий цикл для инженерии harness

При создании любого механизма контроля повторяется четырехэтапный процесс:

  1. Определяется функция крепления и сбой, который она контролирует.
  2. Она реализуется в обоих фреймворках в виде отдельного фрагмента кода.
  3. Инициируется сбой для подтверждения того, что механизм контроля функционирует.
  4. Проверяется реакция крепления на этот сбой.

Вышеописанные этапы были выполнены три раза без специального обозначения: выявление «голого» соединения, инициирование сбоя, который оно вызывает, и указание механизма контроля, к которому оно относится. Затем происходит поочередное закрытие этих соединений.

Попробуйте сами

Эту проблему можно заметить без дальнейшего чтения. В редакторе несколько быстрых экспериментов покажут это:

  • Направьте «голого» агента в любом из фреймворков на инструкцию о перебронировании билета для клиента на 32 марта и посмотрите, как невозможная дата попадает в функцию book_flight.
  • Ограничьте количество итераций пустого цикла пятью. Это первый реально написанный механизм управления, и он относится к части среды выполнения, а не к интерфейсу ввода
  • Пусть функция book_flight возвращает {"is_error": true, ...}, и проверьте, соответствует ли итоговое резюме информации о сбое или же игнорирует её
  • Перед началом исправьте одну версию библиотеки claude-agent-sdk и одну версию библиотек deepagents или langchain, поскольку обе API могут менять версии, и рассматривайте названия моделей (claude-sonnet-4-6, anthropic:claude-sonnet-4-6) как обозначения текущих версий
  • Каждая платформа предоставляет аккуратный, хорошо спроектированный цикл, но ни одна из них не предлагает инструменты для управления им. Это особенность ситуации, а не недостаток самих инструментов; именно здесь и заключается настоящая работа. Claude Code, OpenCode, Codex и Deep Agents считаются инструментами для управления агентами, и разработчик, которому трудно с ними работать, отличается от того, кто получает надежные результаты, создавая механизмы контроля, отсутствующие в стандартном цикле. Модель уже давно является простой частью процесса. Надежность формируется благодаря дисциплине, основанной на использовании модели, через постепенное устранение ошибок, причем первым естественным шагом является определение границ действий — первое правило, позволяющее выйти за рамки вводного текста и работать во время выполнения программы.