Без цикла нет агента
Весь секрет Claude Code можно записать одной строкой: while stop_reason == "tool_use"
Что на самом деле делает агент?
Когда вы запускаете Claude Code в терминале и просите его «собрать все TODO-комментарии в список», вы видите следующее: он сам решает сначала запустить grep, затем cat несколько файлов, а потом вывести Markdown. Модель не выполняет код напрямую — она лишь запрашивает его выполнение. То, что делает её «руками и ногами», — это примерно 30 строк клеевого кода вокруг неё.
В этом уроке мы разберём этот клей по частям. Он называется agent loop (цикл агента), и его структура такова:
while response.stop_reason == "tool_use": response = LLM(messages, tools) # 1. ask the model execute_tools(response.tool_calls) # 2. run what it asked for messages.append(tool_results) # 3. feed results back
Вот и всё. Продакшн-версия Claude Code добавляет поверх этого разрешения, хуки, дочерние агенты, изоляцию worktree, сжатие памяти — но ядро по-прежнему эти четыре строки.
Ключевая идея: каждый ответ модели — это либо «хочу вызвать инструмент», либо «я закончил». Пока первое — цикл продолжается; как только второе — цикл завершается. Сигнал — поле stop_reason в ответе.
Как messages[] растёт шаг за шагом
Следующий виджет симулирует задачу: «Какие файлы в текущей директории? Прочитай package.json». Нажимайте Step — каждое нажатие продвигает на одно действие внутри цикла. Слева — диалог в человекочитаемом виде, справа — настоящий массив messages[], который передаётся модели; обратите внимание, как он растёт.
Результаты инструментов должны возвращаться в историю сообщений
Самая частая ошибка новичков — воспринимать «выполнение инструмента» как побочный эффект: запустили и готово. Но в следующем раунде модель видит только messages[]; то, чего там нет, для неё не существовало. Пропустишь один append — весь цикл сломается.
Ошибки бывают разные. Две самые распространённые:
- Забыть сделать append: в следующем раунде модель не видит результат и запрашивает тот же инструмент снова — получается бесконечный цикл.
- Append есть, но потеряли
tool_use_id: Anthropic API сразу вернёт ошибкуtool_result must have tool_use_id, и цикл сломается на вызове API.
Та же задача: «Посчитай, сколько Python-файлов в проекте». Два варианта рядом — смотрите на разницу в результатах.
Как читать stop_reason
Каждый ответ модели содержит поле stop_reason. Именно оно определяет, продолжать цикл или нет:
tool_use— модель хочет вызвать инструмент, цикл продолжается.end_turn— модель решила, что закончила, выходим из цикла.max_tokens— достигнут лимит токенов, генерация обрезана, выходим из цикла (обычно обрабатывается как ошибка, нужно уведомить пользователя о неполном выводе).stop_sequence— обнаружена пользовательская строка остановки, выходим из цикла.
Типичные баги: «продолжать, пока нет tool_use» — или игнорировать max_tokens и трактовать его как нормальный end_turn.
Запустить самому
Клонируйте репозиторий shareAI-lab/learn-claude-code локально:
git clone https://github.com/shareAI-lab/learn-claude-code cd learn-claude-code pip install -r requirements.txt cp .env.example .env # fill in ANTHROPIC_API_KEY python agents/s01_agent_loop.py
Попросите его что-нибудь сделать: помоги мне посчитать, сколько .py-файлов в этом репозитории. Вы увидите, как он отправит ls -R, посмотрит на вывод, затем отправит find . -name "*.py" | wc -l и скажет вам ответ. Весь этот процесс и есть работающий agent loop.