Часть 1

Основы

Прежде чем агент сможет думать, процесс должен существовать.

Глава 1: Архитектура AI-агента

Что перед вами

Традиционный CLI — это функция. Он принимает аргументы, выполняет работу и завершает работу. grep не решает дополнительно запустить sed. curl не открывает файл и не патчит его на основе того, что скачал. Контракт прост: одна команда, одно действие, детерминированный вывод.

Агентный CLI нарушает каждую часть этого контракта. Он принимает запрос на естественном языке, решает, какие инструменты использовать, выполняет их в том порядке, который требует ситуация, оценивает результаты и повторяет цикл, пока задача не выполнена или пользователь не остановит его. «Программа» не является фиксированной последовательностью инструкций — это цикл вокруг языковой модели, которая на лету генерирует собственную последовательность инструкций. Вызовы инструментов — это побочные эффекты. Рассуждение модели — это поток управления.

Claude Code — это production-реализация этой идеи от Anthropic: TypeScript-монолит почти в две тысячи файлов, превращающий терминал в полноценную среду разработки, управляемую Claude. Он был выпущен сотням тысяч разработчиков, а значит каждое архитектурное решение имеет последствия в реальном мире. Эта глава даёт вам мысленную модель. Шесть абстракций определяют всю систему. Один поток данных связывает их. Как только вы усвоите «золотой путь» от нажатия клавиши до конечного вывода, каждая последующая глава — это увеличение масштаба одной части этого пути.

Дальше следует ретроспективная декомпозиция — эти шесть абстракций не были спроектированы заранее на белой доске. Они возникли под давлением необходимости доставки рабочего агента большой базе пользователей. Понимание их такими, какие они есть, а не такими, какими их планировали, задаёт правильные ожидания для остальной книги.


Шесть ключевых абстракций

Claude Code построен на шести основных абстракциях. Всё остальное — 400+ утилитных файлов, форк рендерера терминала, эмуляция vim, трекер затрат — существует, чтобы поддерживать эти шесть.

Вот что делает каждая из них и зачем она нужна.

1. Цикл запросов (Query Loop) (query.ts, ~1,700 строк). Асинхронный генератор, являющийся сердцебиением всей системы. Он стримит ответ модели, собирает вызовы инструментов, выполняет их, добавляет результаты в историю сообщений и повторяет цикл. Каждое взаимодействие — REPL, SDK, под-агент, безголый --print — проходит через эту единственную функцию. Она отдаёт объекты Message, которые потребляет UI. Её возвращаемый тип — дискриминированный союз под названием Terminal, который точно кодирует причину остановки цикла: нормальное завершение, прерывание пользователем, исчерпание бюджета токенов, вмешательство стоп-хука, достижение максимума ходов или фатальная ошибка. Паттерн генератора — а не колбэков или эмиттеров событий — даёт естественный backpressure, аккуратную отмену и типизированные конечные состояния. В главе 5 подробно рассматриваются внутренности цикла.

2. Система инструментов (Tool System) (Tool.ts, tools.ts, services/tools/). Инструмент — это всё, что агент может сделать в мире: прочитать файл, выполнить shell-команду, отредактировать код, поискать в сети. Простота назначения скрывает значительную инфраструктуру. Каждый инструмент реализует богатый интерфейс, покрывающий идентичность, схему, выполнение, права доступа и рендеринг. Инструменты — не просто функции — они несут собственную логику разрешений, декларации о параллелизме, отчёт о прогрессе и рендер для UI. Система разбивает вызовы инструментов на параллельные и последовательные батчи, а потоковый исполнитель начинает запуск безопасных для конкуренции инструментов ещё до того, как модель завершит ответ. Глава 6 охватывает полный интерфейс инструментов и конвейер выполнения.

3. Задачи (Tasks) (Task.ts, tasks/). Задачи — это фоновые единицы работы, прежде всего под-агенты. Они следуют конечному автомату: pending -> running -> completed | failed | killed. AgentTool порождает новый генератор query() со своей историей сообщений, набором инструментов и режимом разрешений. Задачи дают Claude Code рекурсивную способность: агент может делегировать под-агентам, которые могут делегировать дальше.

4. Состояние (State) (два уровня). Система хранит состояние на двух уровнях. Изменяемый синглтон (STATE) содержит ~80 полей инфраструктуры сессии: рабочая директория, конфигурация модели, учёт затрат, счётчики телеметрии, ID сессии. Он устанавливается один раз при запуске и мутирует напрямую — без реактивности. Минимальный реактивный стор (34 строки, в стиле Zustand) управляет UI: история сообщений, режим ввода, очереди одобрений, индикаторы прогресса. Разделение намеренно: инфраструктурное состояние меняется редко и не должно триггерить перерисовки; UI-состояние меняется постоянно и должно. Глава 3 подробно рассматривает двухуровневую архитектуру.

5. Память (Memory) (memdir/). Устойчивая контекстная информация агента между сессиями. Три уровня: на уровне проекта (CLAUDE.md файлы в репозитории), на уровне пользователя (~/.claude/MEMORY.md) и на уровне команды (разделяется через симлинки). При старте сессии система сканирует все файлы памяти, парсит frontmatter, и LLM выбирает, какие моменты релевантны текущему разговору. Память — это то, как Claude Code «помнит» соглашения вашего кодбазы, архитектурные решения и историю отладки.

6. Хуки (Hooks) (hooks/, utils/hooks/). Пользовательские перехватчики жизненного цикла, срабатывающие при 27 различных событиях в 4 типах исполнения: shell-команды, одноразовые LLM-промпты, многоходовые беседы агентов и HTTP-webhook’и. Хуки могут блокировать выполнение инструмента, модифицировать входы, внедрять дополнительный контекст или короткозамкнуть весь цикл запросов. Система прав частично реализована через хуки — PreToolUse хуки могут отклонить вызовы инструментов ещё до появления интерактивного запроса разрешения пользователю.


Золотой путь: от нажатия клавиши до вывода

Проследите один запрос через систему. Пользователь печатает «add error handling to the login function» и нажимает Enter.

Три момента, на которые стоит обратить внимание в этом потоке.

Во-первых, цикл запросов — это генератор, а не цепочка колбэков. REPL получает сообщения через for await, что означает естественный backpressure — если UI не успевает, генератор приостанавливается. Это осознанный выбор в отличие от event-emitter’ов или observable-потоков.

Во-вторых, выполнение инструментов перекрывается со стримингом модели. StreamingToolExecutor не ждёт завершения модели перед запуском безопасных для конкурентности инструментов. Вызов Read может завершиться и вернуть результаты, пока модель всё ещё генерирует остальную часть ответа. Это спекулятивное выполнение — если итоговый вывод модели отменяет вызов инструмента (редко, но возможно), результат отбрасывается.

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


Система разрешений

Claude Code выполняет произвольные shell-команды на вашей машине. Он редактирует ваши файлы. Может порождать подпроцессы, делать сетевые запросы и изменять историю git. Без системы разрешений это катастрофа с точки зрения безопасности.

Система определяет семь режимов разрешений, упорядоченных от наиболее к наименее разрешительному:

ModeBehavior
bypassPermissionsВсё разрешено. Проверки отсутствуют. Только для внутренних/тестовых запусков.
dontAskВсё разрешено, но всё ещё логируется. Без запросов пользователю.
autoКлассификатор транскрипта (LLM) решает разрешить/отклонить.
acceptEditsИзменения файлов автo-подтверждаются; все остальные мутации запрашивают разрешение.
defaultСтандартный интерактивный режим. Пользователь подтверждает каждое действие.
planТолько чтение. Все мутации блокируются.
bubbleЭскалировать решение к родительскому агенту (режим под-агента).

Когда вызов инструмента требует разрешения, разрешение решается по строгой цепочке:

Режим auto заслуживает особого внимания. Он запускает отдельный, лёгкий LLM-вызов, который классифицирует вызов инструмента на основе транскрипта разговора. Классификатор видит компактное представление входа инструмента и решает, соответствует ли действие тому, что пользователь просил. Именно этот режим позволяет Claude Code работать полуавтономно — автоматически одобряя рутинные операции и помечая то, что отклоняется от намерения пользователя.

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


Мульти-провайдерная архитектура

Claude Code общается с Claude через четыре разные инфраструктурные дорожки, все прозрачны для остальной системы.

Ключевая мысль: Anthropic SDK предоставляет обёртки для каждого облачного провайдера, которые представляют тот же интерфейс, что и прямой API-клиент. Фабрика getAnthropicClient() читает переменные окружения и конфигурацию, определяет, какой провайдер использовать, конструирует соответствующий клиент и возвращает его. С этого момента callModel() и все остальные потребители рассматривают его как общий Anthropic-клиент.

Выбор провайдера определяется при старте и сохраняется в STATE. Цикл запросов никогда не проверяет, какой провайдер активен. Переключение с Direct API на Bedrock — это изменение конфигурации, а не кода — цикл агента, система инструментов и модель разрешений полностью провайдер-агностичны.


Система сборки

Claude Code распространяется и как внутренний инструмент Anthropic, и как публичный npm-пакет. Один и тот же код служит обоим целям, при этом флаги на этапе компиляции контролируют, что включать.

// Conditional imports guarded by feature flags
const reactiveCompact = feature('REACTIVE_COMPACT')
  ? require('./services/compact/reactiveCompact.js')
  : null

Функция feature() приходит из bun:bundle, встроенного API бандлера Bun. На этапе сборки каждый feature-флаг сводится к булевому литералу. Дальше оптимизация удаления мёртвого кода бандлера полностью устраняет вызов require(), когда флаг ложен — модуль никогда не загружается, не включается в бандл и не доставляется.

Паттерн последовательный: внешний feature()-гард, оборачивающий require()-вызов. require() используется вместо import специально потому, что динамический require() можно полностью убрать бандлером, когда гард ложен, тогда как динамический import() возвращает Promise, который бандлер должен сохранить.

Здесь есть ирония: исходные карты (sourcemaps), опубликованные с ранними npm-релизами, содержали sourcesContent — полный оригинальный TypeScript-код, включая внутренние пути кода. Флаги фич успешно удалили runtime-код, но оставили исходники в картах. Так исходный код Claude Code стал общедоступным.


Как куски соединяются

Шесть абстракций образуют граф зависимостей:

Память подаётся в цикл запросов как часть системного промпта. Цикл запросов запускает выполнение инструментов. Результаты инструментов возвращаются обратно в цикл запросов как сообщения. Задачи — это рекурсивные циклы запросов с изолированными историями сообщений. Хуки перехватывают цикл запросов в определённых точках. Состояние читается и записывается всем — реактивный стор мостит в UI.

Цикличная зависимость между циклом запросов и системой инструментов — определяющая характеристика системы. Модель генерирует вызовы инструментов. Инструменты выполняются и дают результаты. Результаты добавляются в историю сообщений. Модель видит результаты и решает, что делать дальше. Цикл продолжается, пока модель не перестанет генерировать вызовы инструментов или внешнее ограничение (бюджет токенов, максимум ходов, прерывание пользователем) не завершит его.

Вот как они связаны с главами далее: золотой путь от ввода к выводу — это нить, которая проходит через всю книгу. Глава 2 прослеживает, как система загружается до момента, когда этот путь может выполниться. Глава 3 объясняет двухуровневую архитектуру состояния, которую путь читает и пишет. Глава 4 охватывает слой API, который вызывает цикл запросов. Каждая последующая глава увеличивает масштаб одной из частей пути, который вы только что увидели целиком.


Примените на практике (Apply This)

Если вы строите агентную систему — любую систему, где LLM решает, какие действия предпринимать во время выполнения — вот шаблоны из архитектуры Claude Code, которые переносятся.

Паттерн генераторного цикла. Используйте асинхронный генератор как цикл агента, а не колбэки или event-emitter’ы. Генератор даёт естественный backpressure (потребители тянут данные в своём темпе), аккуратную отмену (.return() у генератора) и типизированное возвращаемое значение для терминальных состояний. Решаемая проблема: в циклах на основе колбэков трудно понять, когда цикл «окончен» и почему. Генераторы делают терминальное состояние частью типов.

Самоописывающийся интерфейс инструментов. Каждый инструмент должен объявлять свою безопасность к конкурентному выполнению, требования по разрешениям и поведение рендеринга. Не помещайте эту логику в центральный оркестратор, который «знает о» каждом инструменте. Проблема, которую это решает: центральный оркестратор превращается в god-объект, который нужно менять каждый раз при добавлении инструмента. Самоописывающиеся инструменты масштабируются линейно — добавление инструмента N+1 не требует изменений в существующем коде.

Разделяйте инфраструктурное состояние и реактивное состояние. Не всё состояние должно триггерить UI-обновления. Конфигурация сессии, учёт затрат и телеметрия принадлежат простому изменяемому объекту. История сообщений, индикаторы прогресса и очереди одобрений — в реактивном сторе. Проблема, которую это решает: сделать всё реактивным добавляет накладные подписки и усложняет состояние, которое меняется один раз при старте и читается тысячу раз. Два уровня соответствуют двум шаблонам доступа.

Режимы разрешений, а не разбросанные проверки. Определите небольшой набор именованных режимов (plan, default, auto, bypass) и решайте каждое решение о разрешении через режим. Не разбрасывайте if (isAllowed) проверки по реализациям инструментов. Проблема, которую это решает: непоследовательное применение разрешений. Когда каждый инструмент проходит через одну и ту же цепочку на основе режима, вы можете рассуждать о безопасности системы, зная активный режим.

Рекурсивная архитектура агентов через задачи. Под-агенты должны быть новыми экземплярами того же цикла агента со своей историей сообщений, а не специальными ветками кода. Эскалация разрешений идёт вверх через режим bubble. Проблема, которую это решает: логика под-агента, которая расходится с главным циклом агента, приводит к тонким различиям в поведении и обработке ошибок. Если под-агент использует тот же цикл, он наследует все те же гарантии.