Часть 2 из 5 · Серия «Архитектура Claude Code» → /claude-code-series
4. Исполнение хода: агентный цикл запросов
Когда пользователь отправляет «Fix the failing test in auth.test.ts», ввод попадает в реактивный цикл — один из возможных паттернов оркестрации для агентов программирования. Этот раздел разбирает выбор Claude Code в пользу простой while-loop-архитектуры и прослеживает один ход цикла от начала до конца, иллюстрируя три принципа из Table 1: minimal scaffolding with maximal operational harness, context as scarce resource with progressive management и graceful recovery and resilience.
4.1. Конвейер запросов (The Query Pipeline)
Каждый ход идёт по фиксированной последовательности (Figure 2, query.ts):
- Settings resolution. Функция
queryLoop()разбирает неизменяемые параметры: system prompt, user context, callback разрешений, конфигурацию модели. - Mutable state initialization. Единый объект
Stateхранит всё мутируемое состояние между итерациями: сообщения, контекст инструментов, трекинг уплотнения, счётчики восстановления. Семьcontinue-точек цикла («continue sites») каждая перезаписывает объект целиком, а не мутирует поля по одному. - Context assembly. Функция
getMessagesAfterCompactBoundary()получает сообщения после последней compact-границы, чтобы уплотнённый контент был представлен своей сводкой, а не исходными сообщениями. - Pre-model context shapers. Пять shapers выполняются последовательно (Section 4.3).
- Model call. Цикл
for awaitпоdeps.callModel()стримит ответ модели, передавая собранные сообщения (с user-context впереди), полный system prompt, конфигурацию thinking, доступный набор инструментов, abort-сигнал, спецификацию текущей модели и дополнительные опции — включая настройки fast-mode, effort-value и fallback-модель. - Tool-use dispatch. Если ответ содержит
tool_use-блоки, они идут в слой оркестрации инструментов (Section 4.2). - Permission gate. Каждый запрос инструмента проходит через систему разрешений (Section 5).
- Tool execution and result collection. Результаты инструментов добавляются в диалог как
tool_result-сообщения, цикл продолжается. - Stop condition. Если ответ не содержит
tool_use-блоков (только текст), ход завершён.
Функция queryLoop() определена как AsyncGenerator, выдающий события: StreamEvent, RequestStartEvent, Message, TombstoneMessage и ToolUseSummaryMessage. Генераторный дизайн даёт стриминговый вывод в UI-слой, сохраняя единый синхронный поток управления внутри цикла.
Реактивный цикл Claude Code следует паттерну ReAct (Yao et al., 2022): модель порождает рассуждение и вызовы инструментов, harness исполняет действия, результаты кормят следующую итерацию. Альтернативные паттерны оркестрации — явная графовая маршрутизация (LangChain, Inc., 2024), где поток управления определён как state machine с типизированными рёбрами, и методы tree-search (Zhou et al., 2023), исследующие несколько траекторий действий перед фиксацией. Собственная документация Anthropic (Schluntz and Zhang, 2024) называет пять составных workflow-паттернов: prompt chaining, routing, parallelization, orchestrator-workers, evaluator-optimizer. Claude Code преимущественно использует orchestrator-workers для делегирования субагентов (Section 8), сохраняя ядро цикла реактивным. Реактивный дизайн меняет полноту поиска на простоту и латентность: каждый ход фиксируется на одной последовательности действий без откатов.
4.2. Отправка инструментов и стриминговое исполнение
Когда ответ модели содержит tool_use-блоки, система выбирает между двумя путями исполнения. Основной путь использует StreamingToolExecutor, начинающий выполнять инструменты по мере их стриминга из ответа модели, — это снижает латентность для ответов с несколькими инструментами. Запасной путь использует runTools() в toolOrchestration.ts, итерирующий по разбиениям, созданным partitionToolCalls(). Оба пути классифицируют инструменты как concurrent-safe или exclusive. Read-only-операции могут исполняться параллельно, а изменяющие состояние (shell-команды) сериализуются.
StreamingToolExecutor (StreamingToolExecutor.ts) управляет конкурентным исполнением через два механизма координации:
- Sibling abort controller. Срабатывает, когда любой Bash-инструмент выдаёт ошибку, мгновенно завершая другие подпроцессы, а не давая им доработать.
- Progress-available signal. Будит потребителя
getRemainingResults(), когда готов новый вывод.
Результаты буферизуются и выдаются в порядке приёма инструментов, так что порядок вывода остаётся таким же, как в tool_use-запросах, даже при параллельном запуске. Это важно, потому что модель ожидает результаты инструментов в том же порядке. Такая модель конкурентного чтения и последовательной записи занимает середину между полностью последовательной отправкой и более агрессивными спекулятивными подходами вроде PASTE (Sui et al., 2026), который спекулятивно предвыполняет предсказанные будущие вызовы инструментов во время генерации, скрывая латентность через спекуляцию.
Фаза сбора результатов итерирует по обновлениям от стримингового executor или синхронного runTools()-генератора. Каждое обновление может нести результат инструмента, вложение или событие прогресса. Специальная проверка детектирует вложения hook_stopped_continuation: если PostToolUse-хук сигналит, что ход должен прерваться, устанавливается флаг shouldPreventContinuation. Результаты нормализуются для Anthropic API через normalizeMessagesForAPI(), фильтруя их до user-type-сообщений.
4.3. Pre-model context shapers
Пять context shapers выполняются последовательно в query.ts перед каждым вызовом модели, каждый работает с массивом messagesForQuery. Они идут по порядку: ранние шаги применяют более лёгкие сокращения, поздние — более широкое уплотнение.
Budget reduction (applyToolResultBudget()). Энфорсит per-message лимиты размера для результатов инструментов, заменяя превышающие выводы ссылками на контент. Исключённые инструменты (те, где maxResultSizeChars не конечное) сохраняют полный вывод. Замены контента сохраняются для источников agent- и session-запросов, чтобы обеспечить восстановление при resume. Budget reduction запускается до microcompact, потому что microcompact работает чисто по tool_use_id и никогда не инспектирует контент; эти два компонента чисто совместимы.
Snip (snipCompactIfNeeded(), управляется HISTORY_SNIP). Лёгкая обрезка, удаляющая старые сегменты истории и возвращающая {messages, tokensFreed, boundaryMessage}. Значение snipTokensFreed проводится до auto-compact, потому что главный счётчик токенов выводит размер контекста из поля usage последнего assistant-сообщения, а это сообщение переживает snip со своим pre-snip input_tokens; экономия snip невидима счётчику, если не передана явно.
Microcompact. Тонкое сжатие, всегда запускающее time-based-путь и опционально cache-aware-путь (управляемый CACHED_MICROCOMPACT). Когда cache-путь включён, boundary-сообщения откладываются до ответа API, чтобы использовать реальные cache_deleted_input_tokens, а не оценки. Возвращает {messages, compactionInfo}, где compactionInfo может включать pendingCacheEdits.
Context collapse. Управляется CONTEXT_COLLAPSE. Проекция на историю диалога во время чтения. Исходные комментарии объясняют: «Ничего не выдаётся; свёрнутое представление — проекция на полную историю REPL во время чтения. Сводные сообщения живут в collapse-хранилище, а не в массиве REPL. Вот что позволяет свёрткам сохраняться между ходами». В отличие от других shapers, context collapse не мутирует сохранённую историю REPL: он заменяет массив messagesForQuery спроектированным видом через applyCollapsesIfNeeded(), так что модель видит свёрнутую версию, а полная история остаётся доступной для реконструкции.
Auto-compact. Пятый shaper, запускающий полную сгенерированную моделью сводку через compactConversation() в compact.ts. Эта функция запускает PreCompact-хуки, создаёт запрос сводки через getCompactPrompt() и вызывает модель, чтобы та выдала сжатую сводку. Результат подаётся в buildPostCompactMessages() (compact.ts). Auto-compact срабатывает только тогда, когда после всех четырёх предыдущих shapers контекст всё ещё превышает порог давления.
4.4. Механизмы восстановления
Цикл запросов реализует несколько механизмов восстановления для краевых случаев:
- Max output tokens escalation. Когда система упирается в output-token-лимит, возможна повторная попытка с эскалированным лимитом — при условии GrowthBook-флага и отсутствия уже действующего override или env-variable-кэпа. До трёх попыток восстановления за ход (
MAX_OUTPUT_TOKENS_RECOVERY_LIMIT = 3). - Reactive compaction (управляется
REACTIVE_COMPACT). Когда контекст близок к лимиту, reactive compact сжимает столько, чтобы освободить пространство. ФлагhasAttemptedReactiveCompactгарантирует, что это срабатывает максимум раз за ход. - Prompt-too-long handling. Если API возвращает ошибку
prompt_too_long, цикл сначала пробует context-collapse, затем reactive compaction. Только если это не помогает, цикл завершается сreason: 'prompt_too_long'. - Streaming fallback. Callback
onStreamingFallbackобрабатывает проблемы стримингового API, позволяя циклу повторить с другой стратегией. - Fallback model. Параметр
fallbackModelпозволяет переключиться на альтернативную модель при сбое основной.
4.5. Условия остановки
Цикл могут остановить несколько условий:
- No tool use. Модель выдаёт только текст (основное условие остановки).
- Max turns. Достигнут настраиваемый лимит
maxTurns. - Context overflow. API возвращает
prompt_too_long. - Hook intervention. PostToolUse-хук устанавливает
hook_stopped_continuation. - Explicit abort. Срабатывает сигнал
abortController.
Конвейер хода определяет, как оркеструются и восстанавливаются запросы инструментов. Следующий раздел рассматривает ворота, решающие, будет ли каждый запрос вообще исполняться.
5. Авторизация инструментов и границы контроля
Промышленные агенты программирования принимают разные архитектуры безопасности: слоистое применение политики, OS-level-песочница или откат на основе контроля версий. Claude Code сочетает первые два, реализуя четыре принципа проектирования из Table 1: deny-first with human escalation, graduated trust spectrum, defense in depth with layered mechanisms и reversibility-weighted risk assessment.
Когда Claude решает запустить инструмент (например, npm test через BashTool для воспроизведения проваленного auth-теста), запрос попадает в конвейер разрешений, показанный на Figure 4. Каждый вызов инструмента проходит через систему разрешений, и поведение по умолчанию — отказать или спросить, а не тихо разрешить. Этот дефолт мотивирован документированным поведенческим паттерном: собственный анализ авто-режима Anthropic (Hughes, 2026) нашёл, что пользователи одобряют примерно 93% запросов на разрешение — фатигу одобрения делает интерактивное подтверждение поведенчески ненадёжным как единственный механизм безопасности. Поскольку пользователи одобряют не глядя, система должна поддерживать безопасность независимо от человеческой бдительности. Это мотивирует архитектурную приверженность deny-first-оценке, blanket-deny-префильтрации и песочнице как независимым слоям, работающим вне зависимости от внимания пользователя.
Figure 4 Permission gate overview and design principles.
Рисунок 4. Обзор ворот разрешений и принципы проектирования.
Policy Core
┌─────────────────────────────┐
│ 📋 Rules │
Tools │ ⚙ Modes │ Permission
Tool Use ─▶ │ 🪝 Hooks │ ─▶ Decision ─▶ Deny ─▶ Denied Result
└─────────────────────────────┘ │
Allow ─▶ Execution Environment
│
Allow/Deny
▼
User/Auto Classifier 👤 🤖
Ask
| Принцип | Описание |
|---|---|
| Progressive Trust | Агент начинает с минимальной автономии; пользователи расширяют её, одобряя вызовы инструментов, превращающиеся в устойчивые правила. |
| Deny-First, Ask-by-Default | Правила deny всегда побеждают — даже в более свободных режимах. Если ни одно правило не совпадает, ворота спрашивают пользователя вместо молчаливого прохода или блока. |
| Composable Policy | Политику формируют три механизма: декларативные правила, глобальные режимы доверия и программируемые хуки — каждый настраивается независимо. |
5.1. Режимы разрешений и оценка правил
В определениях типа существует семь режимов разрешений (5 внешних режимов в types/permissions.ts; auto добавляется условно; bubble в union-типе):
plan: Модель должна создать план; исполнение идёт только после одобрения пользователя.default: Стандартное интерактивное использование. Большинство операций требуют одобрения.acceptEdits: Правки внутри рабочей директории и определённые shell-команды файловой системы (mkdir,rmdir,touch,rm,mv,cp,sed) авто-одобряются; другие shell-команды требуют одобрения.auto: ML-классификатор оценивает запросы, не прошедшие fast-path-проверки (управляетсяTRANSCRIPT_CLASSIFIER).dontAsk: Не спрашивать, но правила deny всё равно энфорсятся.bypassPermissions: Обходит большинство запросов, но safety-critical-проверки и bypass-immune-правила продолжают применяться.bubble: Внутренний режим эскалации разрешений субагента в родительский терминал.
Пять внешне видимых режимов (acceptEdits, bypassPermissions, default, dontAsk, plan) определены в массиве EXTERNAL_PERMISSION_MODES. Режим auto условно включается только при активном фиче-флаге TRANSCRIPT_CLASSIFIER. Режим bubble существует в union-типе, но ни в одном из массивов режимов: используется внутренне для эскалации разрешений субагентом (Section 8).
Правила разрешений оцениваются в порядке deny-first (permissions.ts). Функция toolMatchesRule() проверяет правила deny первыми: правило deny всегда имеет приоритет над allow, даже когда allow конкретнее. Широкий запрет («deny all shell commands») нельзя перекрыть узким allow («allow npm test»). Система правил поддерживает tool-level-сопоставление (по имени инструмента) и content-level-сопоставление (по паттернам ввода, например Bash(prefix:npm)).
Семь режимов охватывают градуированный спектр автономии: от plan (пользователь одобряет все планы до исполнения) через default и acceptEdits до bypassPermissions (минимум запросов). Этот градиент отражает повторяющееся напряжение: по мере роста автономии система должна сместиться от интерактивного одобрения к автоматическим проверкам. Другие агентные системы решают это напряжение иначе: SWE-Agent и OpenHands (Yang et al., 2024; Wang et al., 2024b) используют Docker-изоляцию, песочив всю среду исполнения агента, а не оценивая отдельные вызовы инструментов. Aider (Gauthier, 2024) опирается на Git как подстраховку: все изменения обратимы через контроль версий. Подход Claude Code слоит несколько механизмов применения политики поверх опциональной контейнерной песочницы, жертвуя простотой ради тонкого контроля над отдельными действиями.
5.2. Конвейер авторизации
Полный конвейер авторизации проходит несколько стадий:
Pre-filtering. До того как запрос инструмента попадает к runtime-оценке, filterToolsByDenyRules() (tools.ts) убирает инструменты с общим deny из обзора модели полностью на этапе сборки пула инструментов. Документация говорит: «Использует тот же matcher, что и runtime-проверка разрешений, так что MCP-серверные префиксные правила вроде mcp__server убирают все инструменты этого сервера до того, как их увидит модель». Это не даёт модели даже попытаться вызвать запрещённые инструменты, так что модель не тратит вызовы на них.
PreToolUse hook. Зарегистрированные хуки срабатывают как часть конвейера разрешений. Хук PreToolUse может вернуть permissionDecision со значением deny или ask, или updatedInput, меняющий входные параметры инструмента (types/hooks.ts). Allow от хука не обходит последующих rule-based deny или safety-проверок. В интерактивном пути диалог пользователя ставится в очередь первым, а хуки запускаются асинхронно; в координаторном и похожих background-agent-путях автоматические проверки предшествуют показу диалога.
Rule evaluation. Deny-first-движок правил оценивает запрос. MCP-инструменты сопоставляются по полностью квалифицированному имени mcp__server__tool, а server-level-правила совпадают со всеми инструментами этого сервера.
Permission handler. Хендлер в useCanUseTool.tsx ветвится на один из четырёх путей в зависимости от runtime-контекста:
- Coordinator. Для режима мультиагентной координации. Пробует автоматическое разрешение (классификатор, хуки, правила) до перехода к пользовательскому взаимодействию.
- Swarm worker. Управляет worker-агентами в мультиагентном рое со своей логикой разрешения.
- Speculative classifier. Когда включён
BASH_CLASSIFIERи инструмент — BashTool, спекулятивный классификатор гонит предзапущенный результат классификации против таймаута. Если классификатор возвращает высокую уверенность, инструмент одобряется мгновенно, без пользовательского взаимодействия. - Interactive. Запасной путь. Показывает стандартный диалог одобрения через терминальный UI.
В координаторном и части background-путей автоматическое разрешение пробуется до пользовательского взаимодействия. В стандартном интерактивном пути диалог может появиться первым, а хуки или проверки классификатора продолжаются параллельно. Когда классификатор или deny-правило блокируют действие, система трактует отказ как маршрутизационный сигнал, а не жёсткий стоп: модель получает причину отказа, пересматривает подход и в следующей итерации цикла пробует более безопасную альтернативу. Событие хука PermissionDenied (Section 6) позволяет внешнему коду наблюдать и реагировать на эти отказы программно. Этот восстановительный дизайн означает, что применение разрешений формирует поведение агента, а не просто останавливает его.
5.3. Классификатор авто-режима и жизненный цикл хуков
Классификатор авто-режима (yoloClassifier.ts) участвует в решениях о разрешениях при включении. Когда включён TRANSCRIPT_CLASSIFIER, классификатор загружает три prompt-ресурса:
- Базовый system prompt.
- Внешний permissions-шаблон.
- Для Anthropic-internal — отдельный внутренний шаблон.
Классификатор оценивает предлагаемый вызов инструмента по транскрипту диалога и permission-шаблону, выдавая allow, deny или запрос ручного одобрения. Функция isUsingExternalPermissions() проверяет USER_TYPE и config-флаг forceExternalPermissions, чтобы выбрать подходящий шаблон.
Из 27 событий хуков, определённых в исходнике (coreTypes.ts), пять участвуют прямо в потоке разрешений, каждое со своей Zod-валидированной output-схемой (types/hooks.ts):
- PreToolUse. Может вернуть
permissionDecision(deny или ask, но allow не обходит последующих проверок),permissionDecisionReasonиupdatedInput(менять входы). - PostToolUse. Может подмешать
additionalContextи для MCP-инструментов вернутьupdatedMCPToolOutput, меняя результаты до того, как они попадут в контекст. - PostToolUseFailure. Может подмешать
additionalContextс error-специфичным наставлением. - PermissionDenied. Может дать
retry guidanceпосле отказов авто-режима. - PermissionRequest. Может вернуть
decision— allow или deny. В координаторном и похожих путях это может разрешить ход до пользовательского диалога. В стандартном интерактивном пути — может запуститься параллельно с диалогом.
Для не-MCP-инструментов tool_result выдаётся до срабатывания PostToolUse-хука. Для MCP-инструментов результат задерживается до отработки post-хуков, чтобы updatedMCPToolOutput мог вступить в силу.
5.4. Shell-песочница
Shell-песочница даёт дополнительный слой защиты для Bash- и PowerShell-команд (shouldUseSandbox.ts). Функция shouldUseSandbox() проверяет, включена ли песочница глобально, выбрал ли вызов opt-out и не совпадает ли команда с паттернами исключений.
При активации песочница даёт изоляцию файловой системы и сети независимо от application-level-permission-модели. Команда может быть одобрена на уровне разрешений, но всё равно посажена в песочницу — или отклонена и никогда не дойти до sandbox-проверки. Две системы оперируют разными осями: авторизация против изоляции.
Слоистая архитектура безопасности опирается на допущение независимости: если один слой падает, другие ловят нарушение. Однако несколько слоёв делят общие ограничения производительности. Исследователи безопасности (Adversa.ai, 2026) документировали, что команды с более чем 50 подкомандами падают до одного общего prompt одобрения вместо per-subcommand-проверок правил deny, потому что per-subcommand-разбор вызывал зависание UI. Этот пример показывает, что defense-in-depth может деградировать, когда слои разделяют режимы отказа, — структурное напряжение между безопасностью и производительностью разобрано далее в Section 11.3.
Конвейер разрешений управляет тем, будет ли вызван инструмент. Следующий раздел разбирает, какие инструменты вообще существуют: архитектуру расширяемости, собирающую поверхность действий модели.
← Часть 1 — Архитектура и принципы | Часть 3 — Расширяемость →