Архив — Память в Агент-Вэлли

1. Введение

Веспер, архивариус, за столом на первом этаже.

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

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

С ней приятно общаться. И, спустя месяц, она очень рада с вами познакомиться.

Запись — это не воспоминание.

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

ступенька

пол

что оно помнит

что его удерживает

1

стол и первый этаж

этот визит

session.state · SqliteSessionService

2

второй этаж, ваш ящик

этот посетитель

префикс user:

3

третий этаж, башня

что было сказано

Банк памяти Vertex AI

4

четвертый этаж, сезон

вся долина

BigQuery

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

Архив, разрезанный на части — пять этажей и небольшая команда, следящая за каждым из них.

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

что она говорит

Что пошло не так?

~~1~~

~~ Не могли бы вы еще раз дать мне эту ссылку? ~~

~~забыла в ходе одного разговора~~ — на третьей неделе исправила это

~~2~~

~~ Я перевела вас. Пожалуйста, начните сначала. ~~

~~История умерла вместе с процессом~~ — на третьей неделе это тоже исправили.

3

Добро пожаловать! Чем я могу вам сегодня помочь?

кому-либо, у кого открыто дело с прошлого месяца.

4

Не могли бы вы описать повреждения?

В последний раз она посмотрела на фонарь.

5

Мы никогда раньше такого не видели.

В этом сезоне вопрос задали тридцать один человек.

Третья строка — это события этой недели. Она терпит неудачу тремя разными способами: новый визит (глава 2), предложение без строки на бланке (глава 3) и архив, который существует только внутри процесса (глава 4) — и каждый способ представляет собой отдельную главу. Четвертая строка остается на ваше усмотрение: look() в archive/agent.py — это место, где она считывает отметку с фотографии. Пятая строка — это глава 5, и она необязательна.

Недели с первой по третью вам не понадобятся. Агент — это модель с инструкцией и несколькими инструментами. Всё остальное на этой неделе само собой объяснится.

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

линия

что это такое

tool_context.state[CASE] = case

Состояние записывается кодом, а никогда не в ходе разговора.

CASE = "user:case"

Префикс — это продолжительность жизни.

found = await ctx.search_memory(query)

Политика отзыва продукции — поданная заявка не запоминается.

Ещё одна строка уже доступна для ознакомления — политика записи, await ctx.add_events_to_memory(...) — а в главе 3 вы узнаете, что она делает, прежде чем написать строку, которая её использует.

Две другие ступени вообще не являются изменениями: вы строите башню (агентский движок, на котором размещается Банк памяти), и тот же код подключается к ней самостоятельно, а также загружаете хранилище данных (BigQuery), накладываете на него граф и передаете тому же агенту два управляемых запроса — один по смыслу, другой по соединению.

В каждой главе используется один и тот же цикл, и именно этот цикл вы будете использовать для своих агентов: прочитать код → написать строку → наблюдать за ней в башне. Ничего не запускается с ошибкой, ничего не перезапускается, и ничего не заменяется сочетанием клавиш Ctrl+C: каждое изменение подхватывается в следующем сообщении, а подключение башни осуществляется одной строкой в ​​файле, который Архив перечитывает при каждом запросе. Башня — это перерисовывающийся рисунок: она читает те же файлы, которые вы редактируете, и у неё есть три кнопки — 🌙 завершает день, 🗓 запускает следующий визит, 🔥 забывает вас. Всё остальное вы делаете в коде и терминале.

Два места, и они никогда не меняют своего назначения: вкладка 1 — это терминал, вкладка 2 — архив. Рабочая среда ( adk web ) появляется ровно один раз, в главе 1, для одного просмотра состояния сессии, и на этом всё. Каждый 👉 ниже указывает, что это за вкладка. 👉💻 — это вкладка 1, 👉🖥 — это вкладка 2, 👉🔬 — это рабочая среда. 👉📖 — это открытие файла и чтение (ничего менять не нужно), а 👉✏️ — это единственный тип шага, который можно редактировать. Всякий раз, когда шаг требует от вас что-то сказать Vesper, точное сообщение находится в блоке прямо под ним — скопируйте его как есть. А раздел, заголовок которого заканчивается на — только для чтения — не содержит ничего, что нужно вводить, вставлять или нажимать: код в нём уже находится в репозитории и уже выполняется, он отображается для вашего ознакомления.

Над дверью Веспер висит надпись вверху. Фраза, которой заканчивается эта неделя, является заключительной строкой всей четвёртой недели сезона:

Факты и предпочтения относятся к разным магазинам.

2. 🧰 Настройка

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

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

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

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

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

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

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

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

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

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

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

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

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

🌐 Проверьте, под какой учетной записью вы вошли — это должна быть та, что была на шаге 1:

gcloud auth list

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

💻 Клонируйте репозиторий — это архивный репозиторий, а не репозиторий первой, второй или третьей недели:

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

4 · Создайте свой проект — или используйте проект предыдущей недели (~2 мин)

💻 Один скрипт находит ваш платежный аккаунт, создает на нем проект и указывает Cloud Shell на этот проект. Независимо от того, использовали ли вы первый, второй или третий проект, скрипт сначала найдет проект Agent Valley и предложит использовать его повторно — согласитесь, и вам не придется думать о втором оплачиваемом проекте:

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

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

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

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

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

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

проект

Ищет уже имеющийся у вас проект Agent Valley — за первую, вторую или третью неделю — и предлагает использовать его повторно; в противном случае создает объект agent-valley-XXXX и связывает с ним соответствующий платежный аккаунт.

выбирает

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

ждёт

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

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

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

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

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

cd ~/agent-valley-archive
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 проекта и активирует aiplatform.googleapis.com (Vertex AI и Agent Engine, на котором размещен Memory Bank) и bigquery.googleapis.com (хранилище данных главы 5).

uv env

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

сайт

Запускает npm ci в site/ — сам архив представляет собой приложение Next.js — поэтому bash valley.sh в главе 1 больше ничего не загружает; пропускается, если пакеты уже установлены.

.env

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

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

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

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

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

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

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

cd ~/agent-valley-archive
  desk      ▢ the slip      · write_down writes nothing         chapter 1 · EDIT ONE
  floor 1   ✓ the file      · SqliteSessionService              chapter 1, read only
  floor 2   ▢ the drawer    · CASE = "case"                     chapter 2 · EDIT TWO
  floor 3   ✓ what was said · closing time files the day        chapter 3, read only
  floor 3   ▢ the asking    · recall never looks                chapter 3 · EDIT THREE
  floor 3   ▢ the tower     · InMemoryMemoryService             chapter 4 · built, not edited
  floor 3   ✓ the rules     · USER_PREFERENCES, LANTERN_CONTEXT chapter 4, read only

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

3. Сессия, Состояние, SqliteSessionService

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

РЕДАКТИРОВАНИЕ ОДИН

👉📖 Откройте archive/agent.py и найдите write_down . Прочитайте дальше 👉:

    case = dict(tool_context.state.get(CASE, {}))
    if case.get(field):
        return {"already_written": {field: case[field]}}
    case[field] = value
    # 👉 EDIT ONE — chapter 1. She heard you. Nothing was written. Delete the
    #    "# " in front of the next line, then save — the slip is session state,
    #    and state is written by code, never by the conversation:
    # tool_context.state[CASE] = case
    return {"written": {field: value}}

Функция создает запись в локальном словаре, а затем возвращает ее — и локальный словарь исчезает вместе с вызовом. Запустите это в текущем виде, и Веспер вас прекрасно услышит: срабатывают четыре вызова write_down , каждый из которых отвечает "written" , и состояние сессии остается пустым, потому что до него ничего не дошло. Она вас услышала. Ничего не было записано.

👉✏️ Всё ещё в archive/agent.py , внутри write_down : удалите символ # перед одной строкой — сам символ # и пробел после него. До:

    case[field] = value
    # 👉 EDIT ONE — chapter 1. She heard you. Nothing was written. Delete the
    #    "# " in front of the next line, then save — the slip is session state,
    #    and state is written by code, never by the conversation:
    # tool_context.state[CASE] = case
    return {"written": {field: value}}

После:

    case[field] = value
    # 👉 EDIT ONE — chapter 1. She heard you. Nothing was written. Delete the
    #    "# " in front of the next line, then save — the slip is session state,
    #    and state is written by code, never by the conversation:
    tool_context.state[CASE] = case
    return {"written": {field: value}}

Сохранять.

Записка заполняется

👉💻 Запустите рабочую среду. Это единственный раз в лаборатории, когда вы её открываете — это единственное окно, которое отображает состояние сессии в исходном формате JSON:

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

👉🔬 Откройте рабочую среду с помощью Web Preview → Preview на порту 8000 (кнопка Web Preview — это значок глаза над окном в правом верхнем углу Cloud Shell). Выберите archive , и прежде чем что-либо вводить, нажмите State на левой панели.

👀 Пусто. Пока никто ничего не сказал.

👉🔬 А теперь скажите вот это, всё в одном сообщении:

Hi! My lantern keeps going out at night. My case number is k7f2, and there's a little q7 stamped on the base.

👀 Срабатывают четыре чипа write_down — нажмите на один и прочитайте JSON: "written": {"no": "k7f2"} . Теперь снова нажмите State .

Четыре вещи, четыре поля, одно сообщение

case   {"no": "k7f2", "item": "lantern", "mark": "q7",
        "symptom": "keeps going out at night"}

👀 Сначала пустое, потом полное сообщение. Именно это она запоминает, и оно не появилось само по себе. Вы написали строку, которая его записывает. Вот весь механизм: простая функция, которая записывает данные в tool_context.state , и инструкция, которая указывает ей использовать её всякий раз, когда посетитель говорит что-то, что помещается в строку на бланке.

👉🔬 Спросите её:

What's my case number?

👀 k7f2. Она зачитывает это с листка. Без ПЕРВОГО ИЗМЕНЕНИЯ она бы попросила вас об этом — первая строка списка поддержки, в рамках одного разговора.

Два охранника — это бланк, представляющий собой бумажный документ : четыре короткие строчки, каждая написана один раз. Они важнее, чем кажутся. Без них модель обнаруживает, что может свести весь разговор к symptom , и глава 3 — глава, смысл которой заключается в том, что в бланке нет строки для этого — тихо перестает быть истинной. Форма магазина либо обеспечивается кодом, который его создает, либо не обеспечивается.

Теперь идите к башне.

👉💻 Вы закончили работу с рабочей средой — оставьте её запущенной или закройте вкладку; остальная часть лаборатории находится в Архиве. Откройте вторую вкладку терминала (вкладка 2 — Архив) :

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

👉🖥 Откройте Архив с помощью веб-предварительного просмотра → Изменить порт , введите 3440 и нажмите Изменить и предварительный просмотр . Порта 3440 нет в списке быстрого доступа веб-предварительного просмотра — по умолчанию используется 8080 — поэтому на этот раз вам придется установить его вручную. Нажмите ▶ Пуск , затем Архив . Вы у двери. Выберите того, кто пришел спросить — имя и вид — и войдите.

Башня, разрезанная на части

👀 Одно здание. Веспер сидит за столом на первом этаже, и именно там вы разговариваете. Над ней: первый этаж, второй этаж, третий этаж и темный чердак. Четыре ступеньки — это четыре этажа. Ваш фамильяр сидит на первом этаже, потому что вы поднялись только туда. Колонна рядом с рисунком показывает, что сейчас находится на каждом этаже — те же четыре этажа, что и за столом во введении, в том же порядке. Справа нет кнопки.

👉🖥 Вставьте те же четыре строки в поле под сообщением — это же самое сообщение — и нажмите Enter:

Hi! My lantern keeps going out at night. My case number is k7f2, and there's a little q7 stamped on the base.

Четыре строки на столе и те же четыре рядом с ним.

👀 Следите за столом. Записка пишется сама собой, по одной строчке за раз — на рисунке и полностью под соседней запиской . Тот же инструмент, то же состояние: башня просто рисует то, что написал write_down , и будет продолжать это делать до конца лабораторной работы.

Она помнит события той ночи — только для чтения

Что вы написали и что это такое

линия

что это такое

где оно живет

tool_context.state[CASE] = case

Состояние сессии — записывается кодом, считывается инструкцией.

стол · session.state

SqliteSessionService(DB) — вы только читаете это.

хранилище сессий — это обращение к файлу, а не к процессу.

первый этаж · archive.db

Эта ступенька выдерживает одно посещение и остается открытой до закрытия. Следующее посещение она не выдерживает.

4. Состояние пользователя

  • Что — вернитесь через месяц; записки уже нет. Пять символов, и она преследует вас.
  • Почему? — Один сеанс — это одно посещение. Следующее посещение — это новый сеанс, и он никогда не видел предыдущего.
  • Как — пять символов в state.py .

Месяц спустя. Рад знакомству.

Нажмите здесь

👉🖥 Нажмите 🗓 через месяц . Это новая сессия для того же посетителя — так начинался новый разговор на третьей неделе.

Добро пожаловать в Архив. Чем я могу вам сегодня помочь?

👉🖥 Посмотрите на башню. На первом этаже лежит еще одна книга — о нашем визите. Ящик на втором этаже пуст. Записка на столе пустая.

Ещё одна книга на первом этаже; ящик пуст; листок чистый

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

ВТОРАЯ РЕДАКЦИЯ

👉✏️ Откройте archive/state.py . Найдите 👉. Перед:

# 👉 EDIT TWO — chapter 2. Five characters. A key with no prefix lives for one
#    visit; `user:` follows the visitor into every visit after this.
CASE = "case"

После:

CASE = "user:case"

Сохраните. Перезапуск не требуется — архив повторно импортирует ваш код для каждого сообщения.

👉🖥 Вставьте те же четыре элемента еще раз — это же самое сообщение:

Hi! My lantern keeps going out at night. My case number is k7f2, and there's a little q7 stamped on the base.

👉🖥 Затем нажмите 🗓 снова через месяц — ту же кнопку, что и раньше.

Ящик с надписью "На нём"

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

Префикс означает «на всю жизнь».

префикс

сколько он живет

какой этаж

(никто)

этот визит

стол

user:

каждый визит этого посетителя

второй этаж, ваш ящик

app:

все, навсегда

часть четвертого этажа

temp:

Только этот поворот — даже не поднимается по лестнице

—

Один стол, четыре ящика. В этой главе рассматривается один из них.

Теперь она тебя знает.

👉🖥 Этот визит совершенно новый, и вы еще ничего в нем не сказали. Спросите ее:

What did I bring in last time?

👀 У меня записано, что вы принесли фонарь, кейс k7f2. Он помечен как q7, и вы упомянули, что он гаснет ночью. Все слова были сказаны из ящика, а не из разговора — этот визит состоялся всего одно сообщением назад, и в сообщении ничего не было указано. Это работа состояния на уровне пользователя: ключ user: присутствует до того, как посетитель произнесет хоть слово, при каждом его посещении.

В ящике хранятся только четыре строчки, указанные на листке, и больше ничего. То, что вы пишете, но на листке нет строчки — что вы пробовали, почему это важно, хотите ли вы новый — никуда не девается, и через месяц этого уже нет. Ящик закрывает месяц. Предложение — нет. Глава 3 именно об этом.

Что вы написали и что это такое

линия

что это такое

где оно живет

CASE = "user:case"

Префикс обозначает срок действия — тот же ключ, но ограниченный возможностями посетителя, а не всего визита.

второй этаж · user: состояние

Эта ступенька доходит до одного посетителя. Ящик следует за вами; разговор по-прежнему не продолжается.

5. InMemoryMemoryService

  • Что — прочитайте строку, которая заполняет день, напишите строку с вопросом; затем одно время закрытия и один вопрос.
  • Почему ? Потому что этот бланк — это форма. В форме есть строки, которые вы в него записали. Люди не говорят по строкам, и заполнение того, что они сказали, — это только половина дела, чтобы это запомнить.
  • Как — прочитать file ; одну строку в recall ; затем один 🌙, один 🗓, один вопрос.

Время закрытия. Она проходит мимо полок.

Что она делает по дороге домой — только для чтения

Поданная заявка не запоминается

Эта строка откладывает день в сторону. Она ничего с него не снимает. Полка, полная карточек, помогает ровно так же, как и пустая, если никто не смотрит — а по мере выпуска кода никто не смотрит: recall , узел, который запускается перед Vesper при каждом вопросе, ничего не спрашивает у башни. Хранилище, которое вы не можете вспомнить в нужный момент, — это не память; это журнал. Взгляд — это другая половина, и вы можете записывать в него.

ТРЕТИЙ ВАРИАНТ ИЗМЕНЕНИЯ — вопрос

👉📖 В archive/agent.py найдите функцию recall — она находится чуть выше file . Это простой узел функции, который запускается перед Vesper для каждого вопроса — модель не решает, когда искать ответ, а это значит, что случаи, когда ничего не находится, тоже видны. Читайте дальше 👉:

    if query.strip():
        try:
            # 👉 EDIT THREE — chapter 3. Filed is not remembered: the cards are on
            #    the shelf and nobody is looking at them. Delete the "# " in front
            #    of the next line, then save:
            # found = await ctx.search_memory(query)
            pass
        except ValueError:
            # no memory service wired on this runner — degrade, do not crash
            pass

👉✏️ Всё ещё в archive/agent.py , внутри recall : удалите символ # перед одной строкой — сам символ # и пробел после него. До:

        try:
            # 👉 EDIT THREE — chapter 3. Filed is not remembered: the cards are on
            #    the shelf and nobody is looking at them. Delete the "# " in front
            #    of the next line, then save:
            # found = await ctx.search_memory(query)
            pass
        except ValueError:

После:

        try:
            # 👉 EDIT THREE — chapter 3. Filed is not remembered: the cards are on
            #    the shelf and nobody is looking at them. Delete the "# " in front
            #    of the next line, then save:
            found = await ctx.search_memory(query)
            pass
        except ValueError:

Оставьте pass на месте — сейчас он ничего не делает, потому что found уже None .

Сохранение. Перезапуск не требуется.

Две строки, две политики — и вы написали одну из них. Политика записи определяет, что сохраняется и когда (время закрытия, весь день). Политика отзыва определяет, когда и с помощью чего искать (каждый вопрос, сам вопрос). ADK предоставляет вам глаголы — add_events_to_memory , search_memory — и не вмешивается в политику. Это правильное разделение, и именно поэтому вам пришлось написать этот вопрос.

Время закрытия

👉🖥 Напишите три вещи, для которых на бланке нет строчки. Одно сообщение или три:

I read by it at night, so night is exactly when I need it.
I've tried hanging it higher. No change.
I don't want a new one — it was a gift.

Нажмите здесь

👉🖥 Затем нажмите 🌙, и на сегодня всё .

Слова поднимаются

👀 Следите за столом. Три ваших сообщения отрываются от подставки, поднимаются вверх и приземляются на третьем этаже. Под карточками рядом с башней они перечислены — по одной карточке на каждое отправленное вами сообщение, помеченные как quote , ваши предложения слово в слово, и в этом процессе над ними. Лампа на столе горит до тех пор, пока они не приземлятся.

Месяц спустя она это знает.

👉🖥 Нажмите 🗓 через месяц и спросите:

What have I already tried?

👀 Вы пытались повесить её повыше — это не помогло. Она знает. В квитанции нет и никогда не было строки для этого, поэтому кассир не мог ответить на этот вопрос; recall нашла карту, записала её в ctx.state["recalled"] , и её инструкция гласит именно это. Разница всего в одной строке.

И всё это лишь плод её воображения.

Посмотрите под карточками рядом с башней: в этом процессе . Прямо сейчас память находится в InMemoryMemoryService — словаре внутри bash valley.sh , и она существует ровно столько же, сколько этот процесс. Закройте башню сегодня вечером и откройте завтра: книги на первом этаже, ящик на втором, а на третьем этаже темно. Дело сохранилось в файле. То, что вы сказали, существовало в процессе, а сам процесс исчез.

Настойчивость — это не память.

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

Что вы прочитали, что вы написали и что это такое.

линия

что это такое

где оно живет

await ctx.add_events_to_memory(events, custom_metadata=FILING) — вы только читаете это

политика написания — целый день, до закрытия.

третий этаж · служба памяти

found = await ctx.search_memory(query) — ИЗМЕНЕНИЕ ТРИ

политика отзыва — каждый вопрос, прежде чем она на него ответит.

recall() → ctx.state["recalled"]

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

6. MemoryBank

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

Постройте башню

Третий этаж главы 3 представлял собой словарь Python внутри bash valley.sh . Memory Bank — это управляемая версия этого этажа: он находится на экземпляре Agent Engine в вашем проекте, хранит факты, полученные за день, а не ваши предложения, и находит их по смыслу , а не по общим словам. Вам не нужно развертывать Vesper на Agent Engine, чтобы использовать его — экземпляр, на котором ничего не развернуто, все равно содержит Memory Bank, и именно его и строит скрипт: башню, в которой никто не живет, и единственная задача которой — хранить карточки.

👉💻 В вкладке 1 (примерно минута):

cd ~/agent-valley-archive
uv run python scripts/make_tower.py

👀 Ожидается:

  building the tower in YOUR_PROJECT / us-central1 — about a minute...
  built, with the two topics in archive/topics.py configured on it.

  resource: projects/.../locations/us-central1/reasoningEngines/6792...
  AGENT_ENGINE written to .env

  Next — nothing to restart. The Archive reads .env on every message:
    look at floor three — under the cards it now says Memory Bank.
    (adk web, if you use it:  --memory_service_uri "agentengine://projects/.../reasoningEngines/6792...")

Один вызов SDK, agent_engines.create() , и одна строка, записанная в .env . Архив перечитывает этот файл при каждом запросе, поэтому ничего перезапускать не нужно.

Посмотрите, как это работает.

👉🖥 Посмотрите на третий этаж — башня перерисовывается сама собой в течение пары секунд. Под карточками теперь написано «Банк памяти» , а полка пуста: три цитаты из третьей главы были в процессе создания, а это не тот процесс. Повторите эти три предложения еще раз, одно сообщение или три:

I read by it at night, so night is exactly when I need it.
I've tried hanging it higher. No change.
I don't want a new one — it was a gift.

👉🖥 Нажмите 🌙, на сегодня всё . Время закрытия теперь составляет около восьми секунд — башня занимается демонтажем оборудования. Затем посмотрите на третий этаж.

Слева: то, что она хранила в своей голове. Справа: то, что хранит башня.

👀 Эти карточки — не ваши предложения. На них написано что-то вроде: «Я использую свой фонарь для чтения по ночам, и именно тогда он постоянно гаснет, и я уже пробовал повесить фонарь повыше» , — каждый fact отмечен датой. Это квинтэссенция. Внутрь вошёл разговор. Из него вышли факты.

👉🖥 Нажмите 🗓 через месяц — во время нового визита, чтобы ничего из того, что вы только что сказали, уже не осталось в разговоре — и задайте ей этот вопрос, который не имеет ни единого общего слова с карточками:

Should I just get a different lamp?

👀 Поскольку это был подарок, вы упомянули, что не хотите менять свой фонарь — и вы уже пытались повесить его повыше. Все это было взято из открытки, и только открытка могла ей сказать: этот визит был совершен всего одно сообщение назад. Поиск по сходству. Башня соответствует смыслу, а не словам — в вопросе говорилось «лампа» и «другой» , в открытке — «фонарь» и «заменить ». И теперь это исключено из процесса: выключите башню сегодня вечером, и завтра там будут открытки, потому что они хранятся в Agent Engine, а не в bash valley.sh .

Запись и чтение — только чтение

The right to be forgotten

Press it here

👉🖥 Press 🔥 forget me , under the message box.

👀 Three floors go dark and one does not:

what it holds

after 🔥

floor three, the cards

what she remembered — the facts Memory Bank distilled from your visits

gone — memories.delete , one card at a time

floor two, the drawer

user:case , the slip that follows this visitor from visit to visit

очищено

the desk, the slip

this visit's copy of the case

очищено

floor one, the books

the visits themselves — one session in archive.db per visit, every message of it

untouched — the count does not move

The books are the record : that a visit happened, on which day, and what was said in it. The cards and the drawer are the memory : what she made of those visits and carries into the next one. A request to be forgotten is about the memory — the facts derived about a person, and the state that follows them around. Whether the record of a visit also goes is a separate decision, made for separate reasons — an audit trail, a support history — and this lab keeps it on purpose, so you can watch the two come apart in front of you: the record and the memory are not the same thing, and forgetting one does not touch the other. That is the sentence over Vesper's door, seen from the other side.

The ADK memory abstraction has add_events_to_memory and search_memory . It has no delete. Forgetting is a governance action, not an agent action, so it lives in the application — archive/tower.py , memories.delete on the raw client — where the model cannot reach it by accident. Whether an agent can forget is something you design, exactly like whether it can remember.

What you built and read, and what it is

the thing

что это такое

where it lives

make_tower.py → AGENT_ENGINE in .env — built, not edited, nothing restarted

the memory service, as a service: a deployment decision, outside the agent

Vertex AI Memory Bank on an Agent Engine

tower_service(engine) — you only read it

the same Runner(memory_service=...) slot, a different home

archive/service.py

"allowed_topics": TOPICS in FILING — read only

what may be kept, decided at write time, not in the prompt

archive/topics.py + the tower's own config

🔥 → memories.delete — pressed, not written

forgetting is an application action; ADK's memory API has no delete

archive/tower.py

This rung reaches what was said. A day goes in; a fact comes out — distilled to what the topics allow — and the book on floor one never moved.

7. GraphRAG

Необязательный.

  • What — one script loads the valley's past into BigQuery, generates embeddings inside the warehouse and lays a graph over the tables without moving a row; then two questions to Vesper, who has two governed queries for it: one for what sounds alike, one for what is connected.
  • Why — sounds alike is not the same as is connected. A vector finds where to look; a graph finds what is attached; and the answer has to carry the path it walked.
  • How — bash scripts/season.sh , once. Everything else in this chapter is read only.

Vesper's tower is scoped to you — (app_name, user_id) — by construction. Other people's visits are not her memory, and searching it harder would not change that. They are the valley's, and the valley keeps them in a warehouse. Thirty-one visitors this season came about a q7 lantern going out at night, and one of them found the cause. This chapter loads that warehouse and hands her two fixed queries into it.

Graph RAG

RAG is retrieve, then generate : find the right records, hand them to the model, let it answer from them. Plain RAG retrieves by similarity — embed the question, pull the nearest records. That finds things that sound like the question, and it is where chapter 4's tower stopped. Graph RAG adds a second retrieval step that follows edges — keys, which a warehouse already has — from whatever similarity found, to whatever is attached to it. The two steps answer different questions, and this chapter makes you ask both:

шаг

the question it answers

как

в этой главе

сходство

where should I look?

embed the question, VECTOR_SEARCH

season_search — when the visitor brings no mark

traversal

what is connected to that?

MATCH a path through a property graph

season_known_issue — one fixed MATCH

поколение

what do I tell the visitor?

the model, with the path as its citation

her answer

Run it

The season ships as plain CSVs in season/ — visitors, items, asks, fixes, seasons — the way a support system would export them. One script takes them all the way into the warehouse, and it is the only thing you run in this chapter.

👉💻 In tab 1 (about three minutes; it prints each step as it goes, and stops on the first one that fails):

cd ~/agent-valley-archive
bash scripts/season.sh

👀 When it ends, floor four on the tower lights up, and the legend beside it says open .

What it built — read only

How she reads it — read only

Спроси её

👉🖥 Floor four is open on the tower. Ask her the way any visitor would:

Has anyone else had a q7 lantern go out at night?

She answers, and the last line is the path

👀 She answers — thirty-one others... most said the flame sat low and went out after an hour... Marlow the otter found the wicks were dipped too shallow — and the last line of her answer is the path. This is her output — read it, do not send it back to her:

mark q7 → 31 visits this season → 10 different things said → the wicks in the q7 batch were dipped too shallow — they drown in the oil and starve in an hour

The path is the citation. Every hop in it is an edge in the graph, and an edge is a key, so anyone can walk it again and get the same thirty-one. A similarity score cannot be walked again by anyone. If she says, just before the path, that the rows came back through three joins because the project has no graph reservation, that is your project's edition refusing GRAPH_TABLE — the rows and the path are the same.

👉🖥 Now leave the mark out — send this:

Has anyone else had a lantern that keeps dying once the sun is down?

👀 She gets to the same thirty-one, and the same path. What changed is the road: season_search ran first, found five visitors who sound alike — each carrying q7 — took that mark, and only then walked. Similar is not connected ; similar is where to look.

What was loaded, and what it is

the thing

что это такое

where it lives

bq load with explicit schemas — run, not written

a warehouse: columns with names and types

archive.visitors / items / asks / fixes / seasons

ML.GENERATE_EMBEDDING + VECTOR_SEARCH

the similarity step — where to look

archive.ask_embeddings , the embedder model

CREATE PROPERTY GRAPH — no row moved

the traversal step — what is connected; tables become nodes and arrows

archive.season_graph

GQL in archive/season/__init__.py — one parameter

the only walk she may take; the path is the citation

season_known_issue(mark)

A few dozen visitors is not a reason to buy a warehouse. What this chapter teaches is the shape of the query — not scale. Grow this graph a thousand times and the MATCH does not change by a character.

8. Close

Three lines, one tower, one warehouse — four floors. Nothing restarted.

the rung

что ты сделал

what it bought

ОДИН

tool_context.state[CASE] = case — an edit

a slip that exists at all: state is written by code

ДВА

CASE = "user:case" — an edit

a drawer that follows the visitor

the filing

await ctx.add_events_to_memory(..., custom_metadata=FILING) — read, not written

what was said, kept

ТРИ

found = await ctx.search_memory(query) — an edit

what was kept, asked for — filed is not remembered

башня

make_tower.py , then AGENT_ENGINE in .env — built and connected by one line, nothing restarted

a tower that distills, and survives the night

правила

allowed_topics in FILING — read, not written

only what should be kept

сезон

season.sh — loaded, embedded in place, a graph laid over it, walked with one MATCH

everyone else's visits, by meaning and by connection

What each piece replaced

ADK 2

what you would write instead

what that costs you

session.state + one tool, kept by SqliteSessionService

a dict beside the chat loop, pickled to disk yourself

the model cannot read it, and you write the resume and the locking

the user: prefix

a users table and a join on every turn

deciding where a key belongs, every single time

add_events_to_memory + search_memory in a function node

a cron that dumps transcripts, and the model deciding when to look

it dumps, nothing distills — and you never see the times it didn't look

VertexAiMemoryBankService + allowed_topics

a vector store, an extraction prompt, a consolidation job, and "please don't remember trivia" in the prompt

three services, and the rule holds until the model feels otherwise

VECTOR_SEARCH + one fixed GQL MATCH

export, embed elsewhere, a graph database, a sync job, and the model writing SQL

the data leaves the warehouse to be understood, and you find out which joins it guessed when a customer does

Three questions, stop at the first yes

  1. Will it matter tomorrow? No → keep nothing. It lives and dies in the conversation.
  2. Can a function check it? Yes → the drawer. Code writes it, every turn reads it, and no sentence can change it.
  3. Otherwise → the tower. Distilled from what was said, looked up when it is asked for, revised by what is said next.

The one sentence

A record is not a memory. A memory is a record plus two decisions — what to keep, and when to look — and what kind of thing it is decides which floor it goes on.

Facts and preferences belong in different stores.

👀 Every floor is lit. Your familiar is at the top. The Archive stamp is in the passport, and week five opens the Night Market — where Nix runs the stage out loud, in real time, and everything the tower knows comes with you.