Урок 01 · Основы

Без цикла нет агента

Весь секрет Claude Code можно записать одной строкой: while stop_reason == "tool_use"

⏱ ~10 мин · 📝 3 интерактивных компонента · 🧑‍💻 На основе shareAI-lab · s01_agent_loop.py

Что на самом деле делает агент?

Когда вы запускаете 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.

Интерактив

Виджет 1 · Loop Stepper · как растёт messages[]

Поймите: результаты инструментов возвращаются в messages в специальном формате (блок tool_result с полем tool_use_id); это единственный способ, которым модель узнаёт в следующем раунде, что «инструмент отработал и вернул X».

Диалогне начат
Нажмите Step, чтобы начать →
messages[] JSONlength: 0
[]
stop_reason:
Интерактив

Виджет 2 · Break the Loop · правильно vs забыл append

Поймите: стоит пропустить одну строку messages.append(tool_results) — и агент перестаёт быть агентом: он превращается в машину, которая забывает предыдущий шаг каждый раунд.

while True:
    resp = LLM(msgs, tools)
    msgs.append({"role":"assistant",
                 "content": resp.content})
    if resp.stop_reason != "tool_use":
        return
    results = execute(resp.tool_uses)
    msgs.append({"role":"user",
                 "content": results})
Симуляция выполнения
while True:
    resp = LLM(msgs, tools)
    msgs.append({"role":"assistant",
                 "content": resp.content})
    if resp.stop_reason != "tool_use":
        return
    results = execute(resp.tool_uses)
    # BUG: forgot to append results
    # msgs.append(...)
Симуляция выполнения
Интерактив

Виджет 3 · Spot the stop_reason · определите 4 реальных ответа

Каждый фрагмент — реально возможный ответ модели. Нажмите «Продолжить» или «Завершить» и сразу узнайте, правильно ли.

Правильно: 0 / 4