Ваш первый агент — призыв в Долине Агентов

1. Введение

Долина Агентов — пять районов, один открытый.

Вы прибыли в Долину Агентов. Один район открыт: Роща Призывателей. К концу работы в этой лаборатории там будет жить созданное вами существо, и вы будете точно знать, какая часть машины его создала.

Моти, появившийся из «чего-то уютного, что любит дождь».

Это Моти. Всего четыре слова — «что-то уютное, что любит дождь».

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

глава

добавленный вами элемент

провод

1

модель с инструкцией

Пока никого нет — оно может только говорить.

2

инструмент

модель → инструмент

4

второй инструмент

инструмент → агент · общий словарь

5

обратный звонок

ты → провод · протянуть руку и поменять его

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

Глава 3 намеренно отсутствует в этой таблице. Как только ваш первый инструмент заработает, отложите код и идите поиграть в готовой долине пять минут. Вы вернетесь с двумя вопросами, и главы 4 и 5 — это ответы.

Вам нужно будет внести две правки . Обе — в одну строку. На этом всё, что вы будете печатать в этой лабораторной работе — всё остальное вы будете читать, запускать и смотреть.

Здесь всё — ADK 2 .

Что вам понадобится: аккаунт Google с доступной оплатой и браузер. Предварительно создавать проект Google Cloud не требуется — программа установки создаст его автоматически. Всё работает в Cloud Shell — Python, uv и gcloud уже установлены там, а доступ к Gemini осуществляется через Vertex AI с использованием ваших собственных учетных данных. В этой лабораторной работе нет API-ключа.

2. Настройка

В этой лабораторной работе все работает в Cloud Shell — Python, uv и gcloud уже установлены там, а доступ к Gemini осуществляется через Vertex AI с использованием ваших собственных учетных данных. В этой лабораторной работе нет API-ключа, и ничего не нужно устанавливать на ваш ноутбук.

В этой лабораторной работе вы не получите один готовый проект Google Cloud с возможностью оплаты. Шаг 1 позволяет получить учетную запись для оплаты проекта; шаг 4 превращает эту учетную запись в проект. Выполняйте шаги именно в таком порядке — каждый шаг после 4 считывает проект, созданный на шаге 4.

1. Зарегистрируйте аккаунт с возможностью выставления счетов (~1 мин.)

🌐 Сделайте это в первую очередь. Войдите в систему с помощью учетной записи Google, в которой включена оплата. Подойдет любая такая учетная запись. Если ваша не подходит, начните бесплатный пробный период ; он предоставит вам учетную запись для оплаты, чего и требуется на шаге 4.

2 · Открытие облачной оболочки (~1 мин)

🌐 Нажмите «Активировать Cloud Shell» в верхней части консоли Google Cloud — это значок в виде терминала в заголовке.

Активируйте Cloud Shell из заголовка консоли Google Cloud.

🌐 Нажмите «Открыть редактор» (значок открытой папки с карандашом). Откроется редактор Cloud Shell с файловым менеджером слева — именно там вы работаете, когда шаг отмечен ✏️.

Откройте редактор Cloud Shell.

🌐 Если сегодня вам будет предложено авторизоваться, нажмите «Авторизовать» , чтобы продолжить.

🌐 Откройте терминал внутри редактора — это вкладка 1 , где выполняется каждый 💻 шаг. Если он еще не находится внизу экрана, откройте его с помощью меню «Вид» → «Терминал» :

Откройте новый терминал в редакторе Cloud Shell.

💻 Проверьте, под какой учетной записью выполнен вход в Cloud Shell:

gcloud auth list

👀 Ожидайте, что ваш аккаунт будет отмечен звездочкой * , и это должен быть тот же аккаунт, с которым вы вошли в систему на шаге 1.

3 · Получите лабораторную работу (~1 мин)

💻 Клонируйте репозиторий:

cd ~
git clone https://github.com/cuppibla/agent-valley-lab.git
cd ~/agent-valley-lab

4 · Создайте свой проект (~2 мин)

💻 Один скрипт находит ваш платежный аккаунт, создает на нем проект и указывает Cloud Shell на этот проект:

cd ~/agent-valley-lab
./setup_project.sh

👀 Ожидайте ✓ за выбранный платежный аккаунт, ✓ за созданный проект и концовку примерно такую:

Project ready: agent-valley-4271
  Next:  ./setup_codelab.sh

agent-valley-4271 — это идентификатор вашего проекта; у вас он будет содержать другие цифры. В нём происходит четыре вещи:

выставление счетов

Использует самый новый [YYYY-MM-DD] GDP Credit: если он у вас есть, и любой другой открытый счет для выставления счетов в противном случае.

проект

Создает agent-valley-XXXX и связывает с ним соответствующий платежный аккаунт.

выбирает

Записывает идентификатор в файл ~/project_id.txt и запускает команду gcloud config set project , поэтому после этого вам больше не потребуется его вводить.

ждёт

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

🌐 Тот же проект теперь отображается в консоли — его идентификатор находится в карточке информации о проекте на панели управления:

Идентификатор вашего проекта на панели управления консоли.

5. Установите и наведите на Vertex AI (~3 мин).

💻 Установите uv , экспортируйте три переменные, которые отправляют ADK в Vertex AI, и запустите setup:

cd ~/agent-valley-lab
curl -LsSf https://astral.sh/uv/install.sh | sh
source ~/.local/bin/env
export GOOGLE_GENAI_USE_VERTEXAI=True
export GOOGLE_CLOUD_PROJECT=$(gcloud config get project)
export GOOGLE_CLOUD_LOCATION=global
./setup_codelab.sh

$(gcloud config get project) ` возвращает что-то, потому что шаг 4 это установил. Именно поэтому шаг 4 выполняется первым.

setup_codelab.sh идемпотентен — его можно безопасно запускать любое количество раз. Он выполняет четыре действия в указанном порядке:

API

Считывает выбранный проект на шаге 4 и включает для него API Vertex AI.

uv env

uv venv создает файл .venv или использует уже существующий, после чего uv sync устанавливает именно то, что закрепил uv.lock — google-adk , google-genai и все, что находится под ними.

.env

Записывает три переменные, которые считывает лаборатория — ключ API нигде не указан.

проверка модели

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

6. Докажите, что он зелёный (~1 мин)

💻 Проведите предполетную проверку — каждый тик является реальным зондированием, а не предположением:

cd ~/agent-valley-lab
uv run python scripts/preflight.py

👀 Ожидайте, что каждая строка будет работать — включая Vertex AI · project — заканчивается на PREFLIGHT GREEN . Одна строка будет гласить grove · 0 tools ← no edits yet ; это правильно. Этот отсутствующий инструмент — вся суть второй главы.

Подробнее (по желанию)

3. 1 · Ваш первый агент

Деталь: модель с инструкцией. Проволока: пока отсутствует — и в этом-то и суть.

  • Что ж — познакомиться с самым маленьким агентом, который существует, и прочитать всё это.
  • Почему — всё, что будет происходить дальше в этой лаборатории, — это этот файл плюс ещё одна вещь.
  • Как — одна команда, один файл, два набранных предложения.

👉💻 На вкладке 1 запустите пользовательский интерфейс разработчика ADK:

cd ~/agent-valley-lab
uv run adk web . --allow_origins="*"

👉🔬 Откройте пользовательский интерфейс разработчика через веб-предварительный просмотр → Изменить порт → 8000. Нажмите «Выбрать приложение» . Отобразятся два приложения: grove и grove_locked . Выберите grove . Заблокированное приложение — это готовый агент; вы выполните тот же запрос к нему в главе 5.

Прочитайте весь текст агента.

👉📖 Откройте grove/agent.py . Не обращайте внимания на комментарии — важны всего четыре строки :

cloudshell edit ~/agent-valley-lab/grove/agent.py
root_agent = LlmAgent(
    name="grove",
    model="gemini-3-flash-preview",
    instruction=INSTRUCTION,
)

Это агент. Всё это.

  • name — то, как оно отображается в пользовательском интерфейсе разработчика.
  • model — какой мозг. Здесь, Gemini 3 Flash.
  • instruction — её краткое изложение. 👉📖 Прокрутите вверх и прочитайте INSTRUCTION : семь простых предложений, сообщающих, что у неё есть кузница, что всё, что описывает путешественник, всегда превращается в милое животное, и что кузница умеет только призывать существ.

👀 Никаких инструментов. Никакой памяти. Только модель и описание задачи.

Поговори с ним

👉🔬 Введите это:

Summon a fox familiar for me.

Вы увидите нечто прекрасное. Вот настоящий ответ от этого же агента:

Кузница мягко, туманно-голубым светом освещается, когда из неё появляется пухлая, покрытая мхом лягушка в крошечном вязаном свитере, держащая в руках дымящуюся шапочку из желудя, наполненную чаем.

В нем описывается вызов, который так и не был выполнен.

👉🔬 Задайте очевидный дополнительный вопрос:

Show me it.

Вы должны увидеть больше красивых слов — и это всё, что есть. Ни изображения. Ни файла. Ни лягушки.

👉🔬 Нажмите на вкладку «Штат» . Она пуста.

👀 Это не ошибка и не плохая модель. Единственный выходной канал модели — текст. Поэтому утверждение об актёрской игре и фактическое действие выглядят идентично .

Разговор с агентом без инструментов — это очень хорошо подготовленный диалог.

Подробнее (по желанию)

4. 2 · Ваш первый инструмент

Деталь: функция. Проволока: модель → инструмент.

  • Что — дать агенту одну функцию, а затем отслеживать, что происходит, когда он её использует.
  • Почему ? — В этом и разница между описанием лягушки и её созданием.
  • Как это сделать — удалить два символа, перезапустить игру и прочитать цепочку событий.

Отредактировать один

👉📖 Снова откройте grove/agent.py . Инструменты уже импортированы вверху — они всё это время находились там. Просто агент не держал ни один из них.

cloudshell edit ~/agent-valley-lab/grove/agent.py

👉✏️ ПЕРВОЕ ИЗМЕНЕНИЕ — удалите символ # в начале строки tools= , затем сохраните:

root_agent = LlmAgent(
    name="grove",
    model="gemini-3-flash-preview",
    instruction=INSTRUCTION,

    # 👉 EDIT ONE — delete the "# " at the start of the next line, then save.
    tools=[cast_candidates],
)

Один инструмент. Одно имя, одна функция, которую теперь может выполнять этот агент. В 4-й главе добавляется второй инструмент — вся лаборатория.

👉💻 Интерфейс разработчика загрузил агента при запуске, поэтому задайте ему новый. На вкладке 1 нажмите Ctrl+C , затем запустите его снова:

cd ~/agent-valley-lab
uv run adk web . --allow_origins="*"

👉🔬 Новая сессия в grove . Задайте тот же вопрос, что и раньше:

something cozy that likes rain and drinks tea

Вы должны увидеть, как оно перестанет говорить и начнет работать: чип cast_candidates , пауза примерно в двенадцать секунд, а затем настоящий портрет существа, которого не существовало, когда вы нажали Enter.

Модель выбирает инструмент и сама записывает аргумент.

Что такое вызов инструмента?

👉🔬 Нажмите на кнопку «Открыть список cast_candidates ».

Четыре сообщения, один ход

👀 Четыре сообщения. Обратите внимание на второй шаг: на выходе модели по-прежнему только сообщение. Она назвала вашу функцию и записала аргумент — вы сказали «уютно, дождь, чай», и она составила полное визуальное описание от вашего имени — но ничего не запустила. Это сделала среда выполнения.

Контракт

👉📖 Откройте forge/agent/tools.py и найдите cast_candidates :

cloudshell edit ~/agent-valley-lab/forge/agent/tools.py
async def cast_candidates(description: str, tool_context: ToolContext) -> dict:
    """Draw the familiar a traveler described.

    Args:
        description: What the creature looks like — species, colours, mood, era.
    """

👀 Название, сигнатура и этот docstring — это полное описание этой функции, которое когда-либо увидит модель. Именно поэтому она написана простым английским языком. Пишите её для читателя, а не для компилятора.

Откуда на самом деле взялась эта фотография

Теперь проследите всю цепочку действий. Это та часть, которую большинство руководств пропускают.

Откуда на самом деле берется изображение

👀 Три вещи, на которых стоит остановиться:

  1. Ваш агент ничего не нарисовал. grove — это текстовая модель — текст на входе, текст на выходе. Изображение было получено из второй модели , gemini-2.5-flash-image , которая вызывается изнутри вашей функции.
  2. Художественный стиль задается в виде изображений. 👉📖 Откройте forge/agent/backends.py и найдите _style_refs() . С каждым запросом отправляются четыре примера изображений. Это такой художественный стиль, который вы не смогли бы задать в качестве подсказки.
    cloudshell edit ~/agent-valley-lab/forge/agent/backends.py
    
  3. Модель никогда не видит изображение. Пиксели сохраняются с помощью save_artifact() . В ответ модель получает строку "candidate_0.png" — достаточно маленькую, чтобы поместиться в подсказку.

Ваш агент не стал мультимодальным. Вы открыли ему дверь к тому, что уже существовало.

Вы уже протянули руку. Прежде чем протянуть вторую — сходите и посмотрите, какие ощущения вызовет готовый результат.

Подробнее (по желанию)

5. 3 · Долина

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

👉💻 Оставьте adk web запущенным. Откройте вторую вкладку терминала (вкладка 2) с помощью кнопки «+» на панели инструментов терминала:

cd ~/agent-valley-lab
bash valley.sh

👉🔬 Откройте долину через веб-предварительный просмотр → Сменить порт → 3200 , нажмите ▶ Start , затем The Summoning Grove .

Роща

👉🔬 Выберите вид, введите заклинание и призовите. Дайте ему имя. Заберите его.

👉🔬 Затем оденьте его: коснитесь предложенного варианта, как золотой короны , и наблюдайте, как он появляется в поле запроса в виде предложения. Измените слова, если хотите. Нажмите ✦ отправить . Сделайте это три или четыре раза. Не торопитесь; это та часть лаборатории, которая просто доставляет удовольствие.

Форма для запросов, предложения и справочная полоса.

Три вещи во время игры

  1. Вы уже ознакомились с кодом, лежащим в основе этого. Ваше предложение отправляется агенту, который вызывает функции cast_candidates или generate_look — те же самые функции, документацию к которым вы читали пять минут назад.
  2. Предложение вводит слова, а не нажимает кнопку. Это сделано намеренно. Кнопка не несёт в себе никакого смысла, поэтому модели нечего читать. Смысл несёт в себе предложение — и это единственная причина, по которой агент вообще может находиться в этом цикле.
  3. Здесь ничего не требовало вашей правки. В Valley работают собственные агенты ( forge/agent/lanes.py ). Всё работало до того, как вы что-либо изменили.

Две вещи, которые вы пока не можете объяснить.

Оба варианта реальны. На оба есть ответы. Ни на один из них нет ответа в этой главе.

👉🔬 Следите за панелью справа, пока отправляете запрос.

На каждом шагу что-то записывается.

before_model   inject character_sheet
after_model    guard: pass
before_tool    identity lock → candidate_0.png
after_tool     + a golden crown  ·  outfit: a golden crown

👀 Шесть строк в одном предложении. Что-то где-то записывается , и что-то выбирает картинку от вашего имени. Что именно, и куда это отправится?

👉🔬 Теперь посмотрите на полоску под заголовком "работайте, исходя из этой картинки" и добавьте пятый элемент. И шестой.

Четыре запроса, одно лицо — и наряд продолжает подсчитывать.

👀 Ничего не заменило. OUTFIT · 4 , а график справа показывает, что список пополняется одним элементом за раз — a golden crown → a golden crown, a starry cape → и так далее. Каждый рендер строится из всего списка, поэтому корона все еще там спустя четыре хода после того, как вы ее запросили.

👀 На этом снимке две детали. Один из предметов одежды, который вы уже носите, отмечен галочкой ( ✓ ) — эти галочки просто показывают тот же список. И каждый предмет отмечен ✕ : снятие чего-либо — это кнопка , а не предложение. Здесь нечего интерпретировать, поэтому модели ничего делать не нужно — это точное отражение принципа «предложение пишет слова, а не нажимает кнопку», описанного выше.

👀 Это всё ещё ваше творение. Вы так и не переописали его — вы напечатали четыре слова о шляпе. Что-то всё ещё цепляется за первоначальный образ. Что это делает, и можно ли это отключить?

Отложите оба вопроса. Первый — это следующая глава. Второй — последний.

👀 Оставьте долину работать — в пятой главе мы к этому вернемся.

Подробнее (по желанию)

6.4 · Второй инструмент — и как они взаимодействуют

Помните ту строчку на правой панели долины — after_tool · + Crown · outfit: Crown ? Вот откуда она взялась.

Деталь: еще одна функция. Провод: инструмент → агент. Эта глава экономит людям недели.

  • Что ж , давайте добавим второй инструмент и выясним, как он узнает, что сделал первый.
  • Почему ? — Эти две функции никогда не вызывают друг друга. Данные передаются чем-то другим.
  • Как — еще одно имя, одно предложение и вкладка «Штат».

Редактировать два

cloudshell edit ~/agent-valley-lab/grove/agent.py

👉✏️ ВТОРОЕ ИЗМЕНЕНИЕ — в файле grove/agent.py добавьте еще одно имя внутри скобок, затем сохраните:

    tools=[cast_candidates, lock_candidate],

Это последняя правка в этой лабораторной работе. Добавление инструмента по сути сводится к добавлению имени в список.

👉📖 Прочитайте то, что вы только что передали — lock_candidate в forge/agent/tools.py :

async def lock_candidate(index: int, name: str, tool_context: ToolContext) -> dict:
    """Lock one candidate as the canonical character. Spends no model — a pure state write."""
    candidates = tool_context.state.get("temp:candidates", [])
    ref = candidates[index]
    tool_context.state["character_ref"] = ref

👀 Обратите внимание, чего нет в его подписи: ни имени файла, ни изображения, ни списка. Все, что модель может ему предоставить, — это номер и имя.

👉💻 Вернитесь во вкладку 1 , перезапустите пользовательский интерфейс разработчика, чтобы он подхватил нового агента — Ctrl+C , а затем запустите его снова:

cd ~/agent-valley-lab
uv run adk web . --allow_origins="*"

Одно предложение, два инструмента

👉🔬 Новая сессия в grove . Запросите сразу две вещи:

summon something cozy that likes rain, and call it Mochi

На этот раз вы должны увидеть два чипа в указанном порядке — cast_candidates , затем lock_candidate — и затем вашего фамильяра с указанным именем.

Два инструмента за один ход

Вопрос

👉🔬 Откройте чип lock_candidate . Модель вызвала его именно с таким именем:

lock_candidate(index=0, name="Mochi")

👀 Итак, откуда взялся заблокированный файл?

Не из модели. Модель передала число. Имя файла было получено из temp:candidates , которое cast_candidates записал несколькими секундами ранее.

Модель не переносила данные из одного инструмента в другой. Это делал словарь.

Словарь

👉📖 Посмотрите на последние строки файла cast_candidates в том же файле:

tool_context.state["temp:candidates"] = filenames
tool_context.state["character_sheet"] = {"name": "", "description": description}
tool_context.state["provisional_ref"] = filenames[0]
tool_context.state["temp:provisional_ref_png"] = pngs[0][0]

👀 tool_context.state — это один словарь , общий для всех инструментов в сессии. Один инструмент записывает в него данные, другой — считывает. Между ними нет передачи сообщений и обратного канала. Общая комната.

👀 Первая из этих четырех операций записи — это передача прав, которую вы только что наблюдали: lock_candidate считывает temp:candidates и извлекает из него индекс 0. Последние две — это предварительное закрепление : портрет уже существует, поэтому уже есть что-то, к чему можно привязать последующий рисунок, даже если никто его не заявлял и у него еще нет имени. Ничто не является каноном, пока следующий инструмент не скажет об этом.

И агент тоже это читает.

👉📖 Теперь о третьей стороне. Откройте forge/agent/character_forge.py и посмотрите на его инструкцию:

cloudshell edit ~/agent-valley-lab/forge/agent/character_forge.py
Character card: {character_sheet?}

👀 Эти фигурные скобки — не украшение. ADK заполняет их данными из состояния перед каждым вызовом модели. Инструмент записал character_sheet ; запрос считывает его обратно. Обмен данными завершен, и ни один из этих трех файлов не импортирует другой.

Как инструмент взаимодействует с пользователем

Посмотрите!

👉🔬 Нажмите вкладку «Штат» . Оба инструмента запущены, поэтому это вся запись:

__session_metadata__   {...}
character_sheet        {"name": "Mochi", "description": "A cozy red panda...", "style_notes": ""}
provisional_ref        ""
character_ref          "candidate_0.png"
look_history           []

👀 __session_metadata__ — это собственная бухгалтерия пользовательского интерфейса разработчика. Остальные четыре — ваши. cast_candidates записал character_sheet с пустым именем и закрепил его портрет как provisional_ref ; lock_candidate заполнил имя, записал character_ref и обнулил временный пин, вернув его к "" . Эта пустая строка — это глава в миниатюре: после cast_candidates идет ссылка , изображение, к которому можно привязать что-либо. Только после lock_candidate идет персонаж — с именем, канонический, character_ref . Один и тот же портрет в обоих случаях; lock_candidate — это шаг, который проходит между ними, а пустая строка — это его подтверждение. Одна привязка в записи, а не две.

👀 Оба инструмента также записывали байты , и ни один из этих наборов не входит в этот список: temp:provisional_ref_png и temp:character_ref_png . В главе 5 рассказывается о том, почему существуют эти ключи, а следующее правило объясняет, почему вы их не видите.

👀 Два правила, которые стоит запомнить:

  • Большие объекты не помещаются в состояние. Изображение размером 1,3 МБ было помещено в артефакты ; короткая строка, содержащая его имя, — в состояние . Каждый запрос представляет собой текст, поэтому всё, что должна видеть модель, должно помещаться внутри него.
  • Префикс действует всю жизнь. temp: длится один ход. Ни один префикс не сохраняется на протяжении всего разговора. user: действует вечно — то есть 4-ю неделю.

Реальный результат работы инструмента — это не предложение, которое он произносит после себя. Это то, что он оставил в неизменном состоянии.

Подробнее (по желанию)

7. 5 · Обратный вызов — попытка проникнуть в провод

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

Фрагмент: ваш код в определенный момент времени. Провод: вы → провод.

  • Что — разместить собственный код между запросом модели и запуском вашего инструмента.
  • Почему? — Инструкция — это просьба. Так создаются правила.
  • Как — один файл для чтения, одно предложение, отправленное двум агентам, и один переключатель в долине.

Пять моментов

👉📖 Откройте forge/agent/callbacks.py . Пять функций, пять именованных моментов:

cloudshell edit ~/agent-valley-lab/forge/agent/callbacks.py

Пять мест, где вы можете стоять

👀 Функция обратного вызова — это просто ваш собственный код, выполняющийся в тот момент, когда ADK присваивает ему имя. Она выполняется независимо от того, хочет это модель или нет.

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

png = (state.get("temp:_last_output_png") if mode == "latest"
       else state.get("temp:character_ref_png")
            or state.get("temp:provisional_ref_png"))
if png is None:                       # a later turn: the temp tier is gone
    png = await artifact_png(tool_context, seed)
state["temp:active_reference_png"] = png

👀 Посмотрите, к чему это прикасается: state . Словарь из главы 4. Коллбэк — это то место, где вы подключаетесь к этому проводу. or это порядок разрешения двух контактов — сначала заблокированная ссылка, затем временная из cast_candidates — так что всегда есть возможность отправить сигнал.

👉📖 Прочитайте описание функции прямо над ней, _apply_identity_lock . Именно её использует Долина Скалистых гор:

picked = state.get("requested_reference") or canon
if state.get("identity_lock", True):
    return canon, canon_png, f"identity lock → overruled {picked}, anchored to {canon}"
return picked, picked_png, f"lock off → honoured {picked}"

👀 Перечитайте первую строку ещё раз. picked фотография, которую вы сделали в долине. А следующая строка может её выбросить.

Что на самом деле решает эта одна строка?

Каждый рисунок после первого содержит ровно две вещи: одно изображение и одно предложение. Строка выше выбирает изображение.

👀 Есть только два способа заполнить этот конверт. Та же модель, тот же шаблон подсказки, по три хода для каждого:

  • ⚓ Закрепленный отправляет P0 , портрет, который вы заявили, на каждом шагу. Предложение переносит весь наряд на данный момент — «теперь в короне, шарфе, фонаре» . Ничего не накапливается, кроме слов.
  • ◈ unpinned отправляет результат последнего хода — P0 , затем P1 , затем P2 — и предложение содержит только самый новый элемент. Фотокопия фотокопии.

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

Смотрите, как оно держится.

grove — это модель и два инструмента. grove_locked — это то же самое, плюс третий инструмент и пять коллбэков, которые вы только что прочитали — один и тот же файл, объединенные вместе. Отправьте обоим одно и то же предложение.

👉💻 Эта часть вернулась в пользовательский интерфейс для разработчиков, а не в Valley. Если вкладка 1 все еще работает, оставьте ее запущенной; если вы ее остановили, запустите снова:

cd ~/agent-valley-lab
uv run adk web . --allow_origins="*"

👉🔬 В grove попросите сменить одежду. Если Моти уже занят в этой сессии, отправьте его сразу же; если сессия новая или пустая, сначала заявите фамильяра: summon something cozy that likes rain, and call it Mochi . Затем отправьте:

give it a golden crown

Вы должны увидеть другое существо в короне. grove нет generate_look . Единственный инструмент, который у него есть и который рисует, — это cast_candidates , и он каждый раз при запуске создает нового фамильяра — таким образом, он призывает нового. Вы получаете корону и теряете Мочи. Ничто здесь никогда не просило держать это лицо.

👉🔬 Выберите приложение →

grove_locked . Это новое приложение, поэтому для него создается новая сессия — заявите здесь тоже о своем фамильяре: summon something cozy that likes rain, and call it Mochi . Затем отправьте то же самое предложение:

give it a golden crown

👀 То же лицо, новый наряд. Открываем чип: запущена программа generate_look .

Вот один реальный случай появления этой же пары, но с другим фамильяром: сначала тюлень в жёлтом дождевике, затем в красном шарфе . Тот же тюлень вернулся в шарфе — та же морда, те же глаза, тот же капюшон.

👉🔬 Откройте вкладку «Штат» :

current_look   {"form": "a golden crown", "artifact": "look_0.png", "anchored_to": "canon", "identity_hue": null}

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

Теперь выключите его.

👉🔬 Вернувшись в долину, нажмите на замок, чтобы отобразилось 🔓 блокировка личности · выключена . Отправьте еще три или четыре запроса, позволяя полосе следовать за вами каждый раз.

Это не авария. Это утечка.

before_tool   lock off → honoured look_3.png

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

👀 P0 → P1 → P2 → P3 — цепочка копирования, описанная ранее, работающая на вашем собственном фамильяре.

👉🔬 Нажмите кнопку блокировки и отправьте еще раз. Она вернется в исходное положение — Canon никогда не был потерян. Он был записан в штате, ожидая, когда кто-нибудь снова на него укажет.

Три ступеньки, а не две

👀 Вы уже использовали все три способа управления этим устройством, и они не равноценны:

кто это гарантирует

инструкция — «всегда милое животное»

модель, если ей так захочется

ваш выбор — клик по ссылке

Никто. Это всего лишь просьба, и её можно отклонить.

обратный вызов — блокировка идентификации

Ваш код. На каждом шагу. Без исключений.

Инструкция — это запрос. Обратный вызов — это правило.

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

8. Закрыть

Ещё один тест

👉🔬 Выполните принудительную перезагрузку приложения (Cmd/Ctrl + Shift + R).

Одежда вернулась. Следа не осталось.

👀 Ваш фамильяр всё ещё на месте, всё ещё с именем, всё ещё одет — но лента Инспектора оказалась пустой . Ещё секунду назад оба объекта были «в приложении». Один был записан, другой — нет. Определить, какие данные к чему относятся, — это большая часть работы.

👉🔬 Нажмите ✨ «Готово» , затем ⤓ «Сохранить паспорт» .

паспорт

Что вы на самом деле узнали

Агент состоит из элементов, плюс связи между ними:

произведение

провод

модель с инструкцией

оно умеет только говорить

инструмент

Модель запрашивает, среда выполнения запускается.

второй инструмент

Один инструмент записывает состояние, другой его считывает.

обратный звонок

ваш код, проникающий в этот провод

И в итоге получается такой цикл: слушать → принимать решение → действовать → состояние меняется. Затем снова слушать. Claude Code редактирует ваши файлы с его помощью. Deep Research просматривает страницы с его помощью. Gemini CLI запускает вашу оболочку с его помощью. Тот же цикл — разные инструменты за дверью.

Модель может только говорить. Программа может только следовать своему сценарию. Агент — это союз, и инструмент меняет лишь состояние.

Куда отправиться дальше?

  • Вторая неделя открывает Buildyard. Сегодня все задания выполнял один агент. На следующей неделе проблема в том, что один агент не может выполнить всю работу целиком — и решение в том, чтобы их стало больше, а не чтобы задания были более масштабными. Все пять недель: goo.gle/agent101
  • Сравните два агента. forge/agent/character_forge.py — это grove/agent.py плюс третий инструмент и пять коллбэков, которые вы только что наблюдали. Всё добавленное — это управление.
  • Третий инструмент. generate_look(form) — это тот, который вы никогда не подключали к grove — он сохраняет то же самое лицо только потому, что функция обратного вызова закрепляет ссылку на него. Вот почему он находится там, где находятся функции обратного вызова.