The Archive : la mémoire à Agent Valley

1. Introduction

Vesper, l'archiviste, au bureau du rez-de-chaussée

La première semaine, nous vous avons demandé qui dit quoi et qui fait quoi. La deuxième semaine, nous avons demandé qui décide de ce qui sera diffusé ensuite. La troisième semaine, nous vous avons demandé ce qu'une course laisse derrière elle. Cette semaine, la conversation se termine. La question est de savoir ce qui en ressort. Il s'avère que la nature de l'objet détermine où il va.

L'archive est l'endroit où la vallée se rend lorsqu'un problème est survenu. Une tour qui enregistre tout et Vesper, l'archiviste, au bureau du rez-de-chaussée. Votre lanterne s'éteint la nuit. Va demander.

Elle est agréable à côtoyer. Un mois plus tard, elle est également ravie de vous rencontrer.

Un enregistrement n'est pas un souvenir.

Cette phrase constitue l'intégralité de l'atelier. Vous allez gravir quatre niveaux, chacun correspondant à un étage de la tour :

rung

le sol

Ce dont il se souvient

ce qui le maintient

1

le bureau et le premier étage.

cette visite

session.state · SqliteSessionService

2

au deuxième étage, dans votre tiroir.

ce visiteur

le préfixe user:

3

troisième étage, tour

ce qui a été dit

Memory Bank Vertex AI

4

le quatrième étage, la saison

toute la vallée

BigQuery

Chaque échelon franchi est dû à la chute d'un élément de l'échelon inférieur.

L'Archive, ouverte en deux : cinq étages, et une petite équipe pour les entretenir tous.

Vous connaissez déjà les cinq façons d'oublier de l'archiviste, car chacune d'elles vous est arrivée lors d'un appel d'assistance :

ce qu'elle dit

ce qui n'a pas fonctionné ;

~~1~~

~~Pouvez-vous me redonner cette référence ?~~

~~Oubli dans une même conversation~~ : ce problème a été résolu au cours de la troisième semaine

~~2~~

~~Je vous ai transféré. Veuillez recommencer depuis le début.~~

~~l'historique est mort avec le processus~~ — la semaine 3 a également corrigé ce problème

3

Bienvenue ! Que puis-je faire pour vous ?

à une personne ayant une demande ouverte le mois dernier

4

Pouvez-vous décrire les dommages ?

elle a regardé la lanterne la dernière fois

5

Nous n'avons jamais vu ça.

31 personnes ont posé cette question cette saison

La ligne 3 correspond à cette semaine. Il échoue de trois manières différentes : une nouvelle visite (chapitre 2), une phrase sans ligne sur le reçu (chapitre 3) et une archive qui n'existe que dans le processus (chapitre 4). Chaque manière correspond à un chapitre. La quatrième ligne est à vous : look() dans archive/agent.py, elle lit une marque sur une photo. La ligne 5 correspond au chapitre 5 et est facultative.

Vous n'avez pas besoin des semaines 1 à 3. Un agent est un modèle doté d'une instruction et de quelques outils. Pour le reste, cette semaine parle d'elle-même.

Vous allez écrire trois lignes, et chacune d'elles est un concept de mémoire :

la ligne

Qu'est-ce que c'est ?

tool_context.state[CASE] = case

L'état est écrit par le code, jamais par la conversation.

CASE = "user:case"

un préfixe est une durée de vie

found = await ctx.search_memory(query)

la politique de rappel : le fichier n'est pas mémorisé

Une ligne supplémentaire est déjà là pour vous : la règle d'écriture, await ctx.add_events_to_memory(...). Le chapitre 3 vous explique ce qu'elle fait avant que vous n'écriviez la ligne qui l'utilise.

Les deux autres niveaux ne sont pas des modifications : vous construisez une tour (un Agent Engine qui héberge Memory Bank) et le même code s'y connecte de lui-même, et vous chargez un entrepôt (BigQuery), y superposez un graphique et remettez au même agent deux requêtes régies : une par signification et une par connexion.

Chaque chapitre exécute la même boucle, que vous utiliserez sur vos propres agents : lire le code → écrire la ligne → l'observer dans la tour. Rien ne s'exécute en premier, rien n'est redémarré et rien n'est annulé avec Ctrl+C : chaque modification est prise en compte dans le message suivant, et la connexion à la tour est une ligne dans un fichier que l'archive relit à chaque requête. La tour est un dessin qui se redessine : elle lit les mêmes fichiers que vous modifiez et comporte trois boutons : 🌙 met fin à la journée, 🗓 démarre la prochaine visite et 🔥 vous oublie. Tout le reste se fait dans le code et le terminal.

Deux emplacements, et ils ne changent jamais de tâche : l'onglet 1 est le terminal, l'onglet 2 est l'archive. Le banc d'essai (adk web) n'apparaît qu'une seule fois, au chapitre 1, pour un aperçu de l'état brut de la session. Vous n'en aurez plus besoin ensuite. Chaque 👉 ci-dessous indique le type de contenu. 👉💻 correspond à l'onglet 1, 👉🖥 à l'onglet 2 et 👉🔬 à l'atelier. 👉📖 Ouvrir un fichier et le lire : rien à modifier. 👉✏️ C'est le type d'étape qui permet de modifier un fichier. Chaque fois qu'une étape vous demande de dire quelque chose à Vesper, le message exact se trouve dans le bloc juste en dessous. Copiez-le tel quel. De plus, une section dont l'en-tête se termine par — lecture seule ne nécessite aucune saisie, aucun collage ni aucun clic : le code qu'elle contient se trouve déjà dans le dépôt et est déjà en cours d'exécution. Il est imprimé pour que vous puissiez le lire.

La phrase au-dessus de la porte de Vesper est celle qui se trouve en haut. La phrase de cette semaine est la dernière de la quatrième semaine de la saison :

Les faits et les préférences appartiennent à des magasins différents.

2. 🧰 Configuration

  • Quoi : pointez sur un projet ou collez une clé, installez et vérifiez que la chaîne d'outils est verte.
  • Pourquoi : aucun élément n'est démarré ici. Chaque serveur démarre juste avant l'étape qui en a besoin. Ainsi, lorsqu'un écran s'ouvre, vous savez à quelle commande il appartient.
  • Comment faire : un script pour créer le projet, un script pour installer et vous orienter vers Vertex AI, et un script de prévol pour lire votre code et vous indiquer où vous en êtes.

Tout ce qui se trouve dans cet atelier s'exécute dans Cloud Shell. Python, uv et gcloud y sont déjà installés. Gemini est accessible via Vertex AI à l'aide de vos propres identifiants. Cet atelier ne contient aucune clé API et vous n'avez rien à installer sur votre ordinateur portable.

Il y a une chose que cet atelier ne peut pas vous fournir : un projet Google Cloud avec la facturation activée. L'étape 1 vous permet d'obtenir un compte qui peut payer le projet. L'étape 4 transforme ce compte en projet ou reprend le projet Agent Valley que vous avez déjà créé au cours de la première, deuxième ou troisième semaine. Effectuez-les dans cet ordre : chaque étape après la quatrième lit le projet sur lequel l'étape 4 s'arrête.

1) Obtenir un compte avec facturation (~1 min)

🌐 Faites-le avant toute autre chose. Connectez-vous avec un compte Google pour lequel la facturation est activée. N'importe quel compte de ce type fonctionne. Si ce n'est pas le cas, démarrez un essai sans frais. Vous obtiendrez ainsi un compte de facturation, ce qui est tout ce dont vous avez besoin pour l'étape 4.

2 · Ouvrir Cloud Shell (environ 1 min)

🌐 Cliquez sur Activer Cloud Shell en haut de la console Google Cloud (icône en forme de terminal dans l'en-tête).

Activer Cloud Shell depuis l'en-tête de la console Google Cloud

🌐 Cliquez sur Ouvrir l'éditeur (icône en forme de dossier ouvert avec un crayon). L'éditeur Cloud Shell s'affiche avec un explorateur de fichiers sur la gauche. C'est là que vous travaillerez chaque fois qu'une étape sera marquée 👉✏️.

Ouvrir l'éditeur Cloud Shell

🌐 Si vous êtes invité à autoriser l'accès à un moment donné, cliquez sur Autoriser pour continuer.

🌐 Ouvrez un terminal dans l'éditeur (c'est-à-dire l'onglet 1). Chaque étape 👉💻 s'y exécute, sauf indication contraire. L'onglet 2 correspond à l'archive. Vous l'ouvrez au chapitre 1, juste avant l'étape qui en a besoin. Si le terminal ne se trouve pas déjà en bas de l'écran, ouvrez-le en cliquant sur Affichage → Terminal :

Ouvrez un nouveau terminal dans l'éditeur Cloud Shell.

🌐 Vérifiez avec quel compte vous êtes connecté. Il doit s'agir de celui de l'étape 1 :

gcloud auth list

3 · Obtenir l'atelier (environ 1 min)

💻 Clonez le dépôt. Il s'agit du dépôt Archive, et non de celui de la semaine 1, de la semaine 2 ou de la semaine 3 :

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

4 · Créez votre projet ou réutilisez celui d'une semaine précédente (environ 2 min)

💻 Un script trouve votre compte de facturation, y crée un projet et pointe Cloud Shell vers ce projet. Si vous avez effectué la semaine 1, la semaine 2 ou la semaine 3, le projet Agent Valley est trouvé en premier et il vous est proposé de le réutiliser. Répondez "Oui" et vous n'aurez pas à vous soucier d'un deuxième projet facturable :

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

👀 Attendez-vous à un ✓ pour le compte de facturation qu'il a choisi, un ✓ pour le projet qu'il a créé ou réutilisé, et une fin comme celle-ci :

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

agent-valley-4271 est l'ID de votre projet. Le vôtre sera différent. Quatre choses se passent :

facturation

prend le compte [YYYY-MM-DD] GDP Credit: le plus récent, le cas échéant, ou tout autre compte de facturation ouvert

project

recherche un projet Agent Valley que vous possédez déjà (semaine 1, 2 ou 3) et propose de le réutiliser. Sinon, il crée agent-valley-XXXX et y associe ce compte de facturation.

sélectionne

écrit l'ID dans ~/project_id.txt et exécute gcloud config set project. Vous n'aurez donc plus besoin de le saisir.

waits

interroge jusqu'à ce que le projet soit réellement autorisé à diffuser des annonces, pendant deux minutes maximum.

🌐 Le même projet se trouve désormais dans la console. Son ID figure dans la fiche Informations sur le projet du tableau de bord :

L'ID de votre projet dans le tableau de bord de la console

5 : Installer et pointer vers Vertex AI (~3 min)

💻 Installez uv, exportez les trois variables qui envoient ADK à Vertex AI, puis exécutez la configuration :

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) a quelque chose à renvoyer, car l'étape 4 l'a défini. C'est pourquoi l'étape 4 vient en premier.

setup_codelab.sh est idempotent et peut être réexécuté un nombre illimité de fois. Elle effectue cinq opérations, dans l'ordre :

api

lit l'étape 4 du projet sélectionné et active aiplatform.googleapis.com (Vertex AI et Agent Engine qui héberge Memory Bank) et bigquery.googleapis.com (l'entrepôt du chapitre 5)

uv env

uv venv crée .venv ou réutilise celui qui existe déjà, puis uv sync installe exactement ce que uv.lock épingle : google-adk, google-genai et tout ce qui se trouve en dessous.

site

exécute npm ci dans site/ (l'archive elle-même est une application Next.js). Par conséquent, bash valley.sh du chapitre 1 n'a plus rien à télécharger et est ignoré lorsque les packages sont déjà présents.

.env

écrit les variables que lit l'atelier : GOOGLE_GENAI_USE_VERTEXAI, GOOGLE_CLOUD_PROJECT, GOOGLE_CLOUD_LOCATION, MEMORY_BANK_LOCATION et BQ_DATASET, et aucune clé API n'est présente.

model check

effectue un véritable appel à Gemini. Ainsi, un projet de mauvaise qualité échoue lors de la configuration, et non plus tard dans un chapitre.

6 · Prouve que c'est vert (environ 1 min)

💻 Exécutez le vol d'essai : chaque coche correspond à une véritable vérification, et non à une supposition :

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

👀 Le test avant vol pose une question au modèle pour vérifier que vos identifiants fonctionnent, vérifie que les packages de l'archive sont en place, puis lit votre code et affiche la barre de progression de la semaine :

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

Trois sont déjà cochées. Ce sont ceux que vous ne faites que lire. Trois sont des lignes que vous écrivez, une est une tour que vous construisez. Exécutez à nouveau le test avant vol à tout moment de l'atelier. Il vous indiquera où vous en êtes, car il lit vos fichiers, et non une checklist.

3. Session, État, SqliteSessionService

  • Quoi : écrivez une ligne, démarrez l'établi une fois et regardez le papier se remplir, puis faites de même dans la tour, puis lisez pourquoi il survit à la nuit.
  • Pourquoi : la première chose à savoir sur la mémoire, c'est que rien n'est mémorisé en soi. Quelqu'un l'écrit.
  • How : une ligne dans agent.py, puis ne modifiez rien d'autre.

EDIT ONE

👉📖 Ouvrez archive/agent.py et recherchez write_down. Lisez jusqu'au 👉 :

    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}}

La fonction crée le reçu dans un dict local, puis le renvoie. Un dict local disparaît avec l'appel. Exécutez-le tel quel et Vesper vous entendra parfaitement : quatre appels write_down sont déclenchés, chacun répond "written", et l'état de la session reste vide, car rien ne l'a jamais atteint. Elle vous a entendu. Aucun texte n'a été saisi.

👉✏️ Toujours dans archive/agent.py, à l'intérieur de write_down : supprimez le # devant une ligne, c'est-à-dire le # et l'espace qui le suit. Avant :

    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}}

Après :

    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}}

Économisez.

Le reçu se remplit

👉💻 Démarrez le workbench. C'est la seule fois que vous l'ouvrez dans l'atelier. Il s'agit de la seule fenêtre qui affiche l'état de la session sous forme de code JSON brut :

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

👉🔬 Ouvrez l'atelier avec Aperçu sur le Web → Aperçu sur le port 8000 (le bouton "Aperçu sur le Web" est l'icône en forme d'œil au-dessus d'une fenêtre en haut à droite de Cloud Shell). Sélectionnez archive, puis cliquez sur État dans le panneau de gauche avant de saisir quoi que ce soit.

👀 Vide. Personne n'a encore rien dit.

👉🔬 Maintenant, dis ceci en un seul message :

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

👀 Quatre chips write_down sont disponibles. Cliquez sur l'une d'elles et lisez le JSON : "written": {"no": "k7f2"}. Cliquez à nouveau sur État.

Quatre éléments, quatre champs, un message

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

👀 Vide, puis plein, dans un même message. C'est ce dont elle se souvient, et cela n'est pas apparu tout seul. Vous avez écrit la ligne qui l'écrit. C'est tout le mécanisme : une fonction simple qui écrit dans tool_context.state et une instruction qui lui dit de l'utiliser chaque fois que le visiteur dit quelque chose qui correspond à une ligne sur le bulletin.

👉 🔬 Demandez-lui :

What's my case number?

👀 k7f2. Elle lit le reçu. Sans EDIT ONE, elle vous l'aurait demandé : ligne 1 de la liste d'assistance, dans une conversation.

Les deux gardes sont le reçu qui est un formulaire papier : quatre courtes lignes, chacune écrite une seule fois. Elles sont plus importantes qu'il n'y paraît. Sans eux, le modèle découvre qu'il peut verser toute la conversation dans symptom, et le chapitre 3 – dont le but est de montrer que le bordereau ne comporte pas de ligne pour cela – cesse discrètement d'être vrai. La forme d'un magasin est appliquée ou non par le code qui l'écrit.

Maintenant, allez à la tour.

👉💻 Vous avez terminé de travailler avec l'atelier. Vous pouvez le laisser en cours d'exécution ou fermer son onglet. Le reste de l'atelier se trouve dans l'archive. Ouvrez un deuxième onglet de terminal (onglet 2 : Archive) :

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

👉 🖥 Ouvrez l'archive en cliquant sur Aperçu sur le Web → Modifier le port, saisissez 3440, puis cliquez sur Modifier et prévisualiser. 3440 ne figure pas dans la liste rapide de l'aperçu Web (8080 est la valeur par défaut). Vous devez donc le définir manuellement cette fois-ci. Appuyez sur ▶ Démarrer, puis sur L'archive. Vous êtes à la porte. Choisissez le nom et l'espèce de la personne qui est venue vous voir, puis entrez.

La tour, ouverte

👀 Un bâtiment. Vesper se trouve au bureau du rez-de-chaussée. C'est là que vous devez vous adresser à elle. Au-dessus d'elle : le premier, le deuxième et le troisième étage, ainsi qu'un grenier sombre. Les quatre barreaux correspondent aux quatre étages. Votre familier est assis au rez-de-chaussée, car c'est le seul étage que vous avez atteint. La colonne à côté du dessin indique ce que chaque étage contient actuellement : les quatre mêmes magasins que dans le tableau de l'introduction, dans le même ordre. Rien à droite n'est un bouton.

👉 🖥 Collez les quatre mêmes éléments dans la zone sous la conversation (ce message exact), puis appuyez sur Entrée :

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

Quatre lignes sur le bureau et les mêmes quatre à côté

👀 Regardez le bureau. Le bulletin se remplit automatiquement, ligne par ligne, sur le dessin et en entier sous le bulletin à côté. Même outil, même état : la tour dessine simplement ce que write_down a écrit, et elle continuera à le faire pour le reste de l'atelier.

Elle se souvient de la nuit précédente : lecture seule

Ce que vous avez écrit et ce que c'est

la ligne

Qu'est-ce que c'est ?

où il se trouve

tool_context.state[CASE] = case

État de la session : écrit par le code, lu par l'instruction

le bureau · session.state

SqliteSessionService(DB) : vous ne faites que le lire

le magasin de session : une visite dans un fichier, et non dans un processus.

premier étage · archive.db

Ce palier correspond à une visite et n'expire pas à la fermeture. Elle ne survit pas à la prochaine visite.

4. État de l'utilisateur

  • Quoi : revenez un mois plus tard, le reçu a disparu. Cinq personnages vous suivent.
  • Pourquoi : une session correspond à une visite. La prochaine visite est une nouvelle session, qui n'a jamais vu la dernière.
  • Comment : cinq caractères dans state.py.

Un mois plus tard. Enchanté de faire votre connaissance.

Appuyez ici

👉 🖥 Appuyez sur 🗓 un mois plus tard. Il s'agit d'une nouvelle session pour le même visiteur, comme Démarrer une nouvelle conversation l'était lors de la troisième semaine.

Bienvenue dans les archives. Que puis-je faire pour vous ?

👉 🖥 Regardez la tour. Au premier étage, un autre livre est posé. Le tiroir du deuxième étage est vide. Le papier sur le bureau est vierge.

Un autre livre au premier étage ; le tiroir vide ; le reçu vierge

👀 Pourquoi : le reçu est enregistré dans l'état de la session, et une session correspond à cette visite. Modifier les visites, modifier les sessions. Rien n'a été transféré, car aucune instruction n'a été donnée en ce sens. La dernière visite est toujours dans le fichier (il s'agit du premier livre du premier étage), mais le reçu a été écrit avec une clé qui n'est valable que pour une seule visite.

EDIT TWO

👉✏️ Ouvrez archive/state.py. Repérez le 👉. Avant :

# 👉 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"

Après :

CASE = "user:case"

Économisez. Pas de redémarrage : l'archive réimporte votre code à chaque message.

👉 🖥 Collez à nouveau les quatre mêmes éléments, à savoir ce message exact :

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

👉 🖥 Appuyez ensuite à nouveau sur 🗓 un mois plus tard, le même bouton que précédemment.

Le tiroir, avec un nom dessus

👀 Le tiroir du deuxième étage porte votre nom. Les quatre champs se trouvent à l'intérieur. Le papier sur le bureau comporte les mêmes quatre lignes que celui que vous avez sorti du tiroir en entrant. Votre familier est passé à l'étage supérieur. Même clé, chaque visite, une seule valeur : c'est le préfixe qui fait son travail.

Le préfixe est une durée de vie.

préfixe

la durée de vie de l'annonce.

à quel étage ;

(aucun)

cette visite

le bureau

user:

chaque visite effectuée par ce visiteur.

au deuxième étage, dans votre tiroir

app:

pour tous, pour toujours

une partie du quatrième étage

temp:

ce tour uniquement : ne monte jamais les escaliers

—

Une table, quatre tiroirs. Ce chapitre en aborde un.

Elle te connaît maintenant

👉 🖥 Cette visite est toute nouvelle et vous n'y avez encore rien dit. Demandez-lui :

What did I bring in last time?

👀 J'ai noté que vous aviez apporté une lanterne, dossier k7f2. Il est marqué q7 et vous avez mentionné qu'il s'éteint la nuit. Chaque mot qui sort du tiroir, et non de la conversation : cette visite ne comporte qu'un seul message, et celui-ci n'a pas de nom. C'est le rôle de l'état au niveau de l'utilisateur : une clé user: est présente avant même que le visiteur ne dise un mot, à chaque visite.

Le tiroir contient les quatre lignes du reçu, et rien d'autre. Ce que vous dites qui n'est pas indiqué sur le reçu (ce que vous avez essayé, pourquoi cela vous intéresse, si vous en voulez un nouveau) n'est pas enregistré et disparaît un mois plus tard. Le tiroir chevauche le mois. Une phrase, en revanche, n'en a pas. Le chapitre 3 traite de ce sujet.

Ce que vous avez écrit et ce que c'est

la ligne

Qu'est-ce que c'est ?

où il se trouve

CASE = "user:case"

Un préfixe est une durée de vie : la même clé, limitée au visiteur au lieu de la visite

Étage 2 · État user:

Cette marche est atteinte par un visiteur. Le tiroir vous suit, mais pas la conversation.

5. InMemoryMemoryService

  • Quoi : lisez la ligne qui indique le jour, écrivez la ligne qui pose la question, puis indiquez une heure de clôture et posez une question.
  • Pourquoi : le bulletin est un formulaire. Un formulaire comporte les lignes que vous lui avez fournies. Les gens ne parlent pas en lignes, et enregistrer ce qu'ils ont dit ne suffit pas à s'en souvenir.
  • Comment : lisez file, une ligne dans recall, puis une 🌙, un 🗓 et une question.

Heure de fermeture. Elle repasse devant les étagères.

Ce qu'elle fait en chemin : lecture seule

Le fichier n'est pas mémorisé

Cette ligne met la journée en veille. Il ne déduit rien. Une étagère pleine de cartes est aussi utile qu'une étagère vide si personne ne la regarde. Et comme le code est expédié, personne ne la regarde : recall, le nœud qui s'exécute avant Vesper sur chaque question, ne demande rien à la tour. Un stockage dont vous ne pouvez pas vous souvenir au bon moment n'est pas une mémoire, mais un journal. L'observation est l'autre moitié, et c'est à vous de l'écrire.

TROISIÈME ÉTAPE : LA DEMANDE

👉📖 Dans archive/agent.py, recherchez recall, la fonction juste au-dessus de file. Il s'agit d'un nœud de fonction simple qui s'exécute avant Vesper pour chaque question. Ce n'est pas le modèle qui décide quand chercher, ce qui signifie que les moments où il ne trouve rien sont également visibles. Lisez jusqu'au 👉 :

    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

👉✏️ Toujours dans archive/agent.py, à l'intérieur de recall : supprimez le # devant une ligne, ainsi que le # et l'espace qui le suit. Avant :

        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:

Après :

        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:

Laissez pass à sa place. Il ne fait rien pour le moment, car found est déjà None au-dessus.

Économisez. Aucun redémarrage.

Deux lignes, deux règles, dont une que vous avez écrite. La règle d'écriture détermine ce qui est conservé et quand (heure de fermeture, toute la journée). La politique de rappel détermine quand et avec quoi effectuer la recherche (chaque question, la question elle-même). ADK vous fournit les verbes (add_events_to_memory, search_memory) et ne s'immisce pas dans la règle. C'est la bonne répartition, et c'est pourquoi vous deviez écrire la demande.

Heure de fermeture

👉🖥 Cite trois choses pour lesquelles le bordereau ne prévoit pas de ligne. Un ou trois messages :

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.

Appuyez ici

👉 🖥 Appuyez ensuite sur 🌙 C'est tout pour aujourd'hui.

Les mots s'élèvent

👀 Regardez le bureau. Les trois choses que vous avez dites se détachent du papier et flottent dans les airs, puis atterrissent au troisième étage. Sous les fiches à côté de la tour, elles sont listées : une fiche pour chaque message que vous avez envoyé, marquée quote, vos phrases mot pour mot et dans ce processus au-dessus. La lampe sur le bureau reste allumée jusqu'à ce qu'ils aient atterri.

Un mois plus tard, elle sait

👉 🖥 Appuyez sur 🗓 un mois plus tard, puis posez la question suivante :

What have I already tried?

👀 Vous avez essayé de le suspendre plus haut, mais cela n'a pas aidé. Elle le sait. Le reçu ne comporte pas de ligne pour cela et n'en a jamais comporté. La caissière n'a donc pas pu répondre à cette question. recall a trouvé la carte, l'a inscrite dans ctx.state["recalled"] et a écrit cette instruction. Une ligne correspond à la différence.

Et tout se passe dans sa tête

Regardez sous les cartes à côté de la tour : dans ce processus. Pour le moment, la mémoire est InMemoryMemoryService, un dictionnaire dans bash valley.sh, et elle existe exactement aussi longtemps que ce processus. Ferme la tour ce soir et ouvre-la demain : les livres sont au premier étage, le tiroir au deuxième et le troisième est sombre. La demande est toujours présente dans le fichier. Ce que vous avez dit faisait partie du processus, et le processus a disparu.

La persistance n'est pas de la mémoire.

Il y a deux problèmes avec la troisième affirmation : elle est en cours et elle est exprimée avec vos propres mots (il s'agit d'une citation, pas d'un fait). Le chapitre suivant corrige les deux, sans aucun code.

Ce que vous lisez, ce que vous écrivez et ce que c'est

la ligne

Qu'est-ce que c'est ?

où il se trouve

await ctx.add_events_to_memory(events, custom_metadata=FILING) : vous ne faites que le lire

la règle d'écriture : une journée entière, à l'heure de fermeture.

Troisième étage : le service de mémoire

found = await ctx.search_memory(query) — MODIFIER TROIS

la politique de rappel : chaque question, avant qu'elle ne réponde

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

Cette étape consiste à identifier ce qui a été dit et à le conserver dans un dictionnaire au sein d'un processus, avec vos propres mots. Les deux moitiés de cette affirmation sont fausses, et le chapitre suivant corrige les deux sans aucun code.

6. MemoryBank

  • Quoi : un script crée une Memory Bank. Les deux lignes que vous avez déjà commencent à y écrire des faits et à les relire.
  • Pourquoi : un service de mémoire est un service. L'emplacement de l'archive est une décision de déploiement, et non une décision d'agent. Vous pouvez le modifier sans toucher à l'agent.
  • Comment faire : exécutez make_tower.py, dites trois phrases, appuyez sur 🌙, appuyez sur 🗓, posez une question. Ensuite, un aperçu en lecture seule de l'écriture et de la lecture.

Construire la tour

Le troisième étage du chapitre 3 était un dictionnaire Python à l'intérieur de bash valley.sh. Memory Bank est la version gérée de ce niveau. Elle réside sur une instance Agent Engine dans votre projet, conserve des faits extraits de la journée au lieu de vos phrases et les trouve par signification plutôt que par mots partagés. Vous n'avez pas besoin de déployer Vesper sur un Agent Engine pour l'utiliser. Une instance sans aucun déploiement héberge toujours une Memory Bank, et c'est ce que le script construit : une tour sans personne, dont le seul rôle est de conserver les cartes.

👉 💻 Dans l'onglet 1 (environ une minute) :

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

👀 À quoi s'attendre ?

  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...")

Un appel de SDK, agent_engines.create(), et une ligne écrite dans .env. L'archive relit ce fichier à chaque demande. Il n'y a donc rien à redémarrer.

La vidéo au service

👉🖥 Regardez le troisième étage : la tour se redessine d'elle-même en quelques secondes. Sous les cartes, il est désormais indiqué Memory Bank, et l'étagère est vide : les trois citations du chapitre 3 étaient en cours, et ce n'est pas le processus. Répète les trois phrases, en un ou trois messages :

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.

👉 🖥 Appuyez sur 🌙, c'est tout pour aujourd'hui. La fermeture prend environ huit secondes maintenant : la tour est en train de se rétracter. Ensuite, regarde au troisième étage.

À gauche : ce qu'elle a gardé en tête. À droite : ce que la tour conserve.

👀 Les cartes ne sont pas vos phrases. Ils disent par exemple J'utilise ma lanterne pour lire la nuit, et c'est à ce moment-là qu'elle s'éteint et J'ai déjà essayé de suspendre ma lanterne plus haut, chacun marqué fact avec une date. Distillation : Ce qui a été saisi était une conversation. Ce qui est ressorti, ce sont des faits.

👉 🖥 Appuyez sur 🗓 un mois plus tard. Il s'agit d'une nouvelle visite. Par conséquent, tout ce que vous venez de dire n'est plus dans la conversation. Posez-lui cette question, qui ne partage aucun mot avec les cartes :

Should I just get a different lamp?

👀 Comme il s'agissait d'un cadeau, vous avez indiqué que vous ne souhaitiez pas remplacer votre lanterne et que vous aviez déjà essayé de la suspendre plus haut. Chaque mot de la carte était vrai, et seule la carte aurait pu lui dire : cette visite est un message ancien. Recherche de similarités : La tour correspond à la signification, et non aux mots : la question indique lampe et différent, tandis que la carte indique lanterne et remplacer. Et il est maintenant hors du processus : éteignez la tour ce soir et les cartes seront là demain, car elles se trouvent sur l'Agent Engine, et non dans bash valley.sh.

Écriture et lecture : lecture seule

Le droit à l'oubli

Appuyez ici

👉 🖥 Appuyez sur 🔥 Oublie-moi sous la zone de message.

👀 Trois étages sont éteints, mais pas un :

ce qu'il contient.

après 🔥

troisième étage, les cartes

ce dont elle se souvient : les faits que la Banque de mémoire a extraits de vos visites.

memories.delete, une carte à la fois

au deuxième étage, dans le tiroir

user:case, le reçu qui suit ce visiteur de visite en visite

effacée

le bureau, le reçu

la copie de la demande pour cette visite ;

effacée

premier étage, les livres

les visites elles-mêmes : une session dans archive.db par visite, chaque message

inchangé : le nombre ne change pas

Les livres sont l'enregistrement : ils indiquent qu'une visite a eu lieu, le jour où elle s'est déroulée et ce qui a été dit. Les fiches et le tiroir sont la mémoire : ce qu'elle a retenu de ces visites et ce qu'elle emporte avec elle pour la prochaine. Une demande d'oubli concerne la mémoire, c'est-à-dire les faits dérivés sur une personne et l'état qui les suit. La décision de conserver ou non l'enregistrement d'une visite est distincte et repose sur des raisons différentes (piste d'audit, historique d'assistance, etc.). Ce laboratoire le conserve à dessein pour que vous puissiez voir les deux se séparer sous vos yeux : l'enregistrement et la mémoire ne sont pas la même chose, et l'oubli de l'un n'affecte pas l'autre. Il s'agit de la phrase au-dessus de la porte de Vesper, vue de l'autre côté.

L'abstraction de mémoire ADK comporte add_events_to_memory et search_memory. Il n'y a pas de bouton de suppression. L'oubli est une action de gouvernance, et non une action d'agent. Il se trouve donc dans l'application – archive/tower.py, memories.delete sur le client brut – où le modèle ne peut pas l'atteindre par accident. Vous pouvez choisir si un agent peut oublier ou non, exactement comme vous choisissez s'il peut se souvenir de quelque chose.

Ce que vous avez créé et lu, et ce que c'est

la chose

Qu'est-ce que c'est ?

où il se trouve

make_tower.py → AGENT_ENGINE dans .env : créé, non modifié, rien redémarré

le service de mémoire, en tant que service : une décision de déploiement, en dehors de l'agent

Vertex AI Memory Bank sur un Agent Engine

tower_service(engine) : vous ne faites que le lire

le même emplacement Runner(memory_service=...), mais une autre maison.

archive/service.py

"allowed_topics": TOPICS dans FILING : lecture seule

ce qui peut être conservé, décidé au moment de l'écriture, et non dans la requête.

archive/topics.py + la configuration de la tour

🔥 → memories.delete : appuyer, pas écrire

L'oubli est une action d'application. L'API de mémoire de l'ADK ne comporte pas de fonction de suppression.

archive/tower.py

Cette section reprend ce qui a été dit. Un jour passe, un fait ressort, distillé selon ce que les sujets permettent, et le livre au premier étage n'a jamais bougé.

7. GraphRAG

Facultatif.

  • Quoi : un script charge le passé de la vallée dans BigQuery, génère des embeddings à l'intérieur de l'entrepôt et superpose un graphique aux tables sans déplacer une ligne. Ensuite, deux questions sont posées à Vesper, qui dispose de deux requêtes régies : une pour ce qui se ressemble et une pour ce qui est connecté.
  • Pourquoi : "ressemble à" n'est pas la même chose que "est associé à". Un vecteur indique où chercher, un graphique indique ce qui est associé, et la réponse doit indiquer le chemin parcouru.
  • Comment : bash scripts/season.sh, une fois. Tout le reste de ce chapitre est en lecture seule.

La tour de Vesper est limitée à vous : (app_name, user_id). Les visites d'autres personnes ne font pas partie de sa mémoire, et une recherche plus approfondie ne changerait rien. Elles appartiennent à la vallée, qui les conserve dans un entrepôt. Trente et un visiteurs cette saison ont signalé qu'une lanterne Q7 s'éteignait la nuit, et l'un d'eux en a trouvé la cause. Ce chapitre charge cet entrepôt et y insère deux requêtes fixes.

RAG de graphiques

RAG signifie récupérer, puis générer : trouvez les bons enregistrements, transmettez-les au modèle et laissez-le répondre à partir de ces enregistrements. Le RAG simple récupère les résultats par similarité : il intègre la question et extrait les enregistrements les plus proches. Il trouve les éléments qui ressemblent à la question, et c'est là que s'est arrêtée la tour du chapitre 4. La génération augmentée par récupération (RAG) de graphiques ajoute une deuxième étape de récupération qui suit les arêtes (clés) qu'un entrepôt possède déjà, à partir de toute similitude trouvée, vers tout ce qui y est associé. Les deux étapes répondent à des questions différentes, et ce chapitre vous invite à vous poser les deux :

étape

la question à laquelle il répond ;

comment

dans ce chapitre

similarité

Où dois-je regarder ?

intégrer la question, VECTOR_SEARCH

season_search : lorsque le visiteur ne rapporte aucune note

traversée

Qu'est-ce qui est connecté à cela ?

MATCH est un chemin d'accès dans un graphique de propriétés.

season_known_issue : un MATCH fixe

de génération

Que dois-je dire au visiteur ?

le modèle, avec le chemin d'accès comme citation.

sa réponse

Exécuter l'application

La saison est fournie sous forme de fichiers CSV bruts dans season/ (visiteurs, articles, demandes, corrections, saisons), comme le ferait un système d'assistance. Un seul script vous permet de les transférer jusqu'à l'entrepôt. C'est la seule chose que vous exécuterez dans ce chapitre.

👉💻 Dans l'onglet 1 (environ trois minutes ; chaque étape est imprimée au fur et à mesure, et le processus s'arrête à la première étape qui échoue) :

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

👀 Lorsque le processus se termine, le quatrième étage de la tour s'allume et la légende à côté indique ouvert.

Ce qu'il a créé : lecture seule

Comment elle le lit : lecture seule

Demandez-lui

👉🖥 Le quatrième étage de la tour est ouvert. Posez-lui la question comme le ferait n'importe quel visiteur :

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

Elle répond, et la dernière ligne est le chemin

👀 Elle répond : trente et une autres personnes… la plupart ont dit que la flamme était basse et s'est éteinte au bout d'une heure… Marlow la loutre a constaté que les mèches n'étaient pas assez trempées. La dernière ligne de sa réponse est le chemin d'accès. Voici sa réponse. Lisez-la, mais ne la lui renvoyez pas :

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

Le chemin d'accès est la citation. Chaque saut est une arête du graphe, et une arête est une clé. N'importe qui peut donc le refaire et obtenir le même résultat (31). Personne ne peut revenir sur un score de similarité. Si elle indique, juste avant le chemin d'accès, que les lignes ont été renvoyées par trois jointures parce que le projet n'a pas de réservation de graphique, cela signifie que l'édition de votre projet refuse GRAPH_TABLE : les lignes et le chemin d'accès sont identiques.

👉 🖥 Maintenant, supprimez la marque et envoyez ceci :

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

👀 Elle arrive au même trente et un, et au même chemin. Ce qui a changé, c'est le chemin : season_search a couru en premier, a trouvé cinq visiteurs qui semblaient identiques (chacun portant q7), a pris cette marque, puis a marché. "Similaire" n'est pas connecté, mais c'est là qu'il faut chercher.

Ce qui a été chargé et ce que c'est

la chose

Qu'est-ce que c'est ?

où il se trouve

bq load avec des schémas explicites : exécuté, non écrit

un entrepôt : colonnes avec des noms et des types

archive.visitors / items / asks / fixes / seasons

ML.GENERATE_EMBEDDING + VECTOR_SEARCH

Étape de similarité : où chercher

archive.ask_embeddings, le modèle embedder

CREATE PROPERTY GRAPH : aucune ligne déplacée

l'étape de parcours : ce qui est connecté ; les tableaux deviennent des nœuds et des flèches

archive.season_graph

GQL dans archive/season/__init__.py : un paramètre

la seule promenade qu'elle puisse faire ; le chemin est la citation

season_known_issue(mark)

Quelques dizaines de visiteurs ne justifient pas l'achat d'un entrepôt. Ce chapitre porte sur la forme de la requête, et non sur l'échelle. Agrandissez ce graphique mille fois et le MATCH ne changera pas d'un caractère.

8. Fermer

Trois lignes, une tour, un entrepôt : quatre étages. Aucun redémarrage n'a eu lieu.

le barreau

ce que vous avez fait ;

ce qu'il a acheté.

ONE

tool_context.state[CASE] = case : une modification

une erreur qui existe : l'état est écrit par le code

DEUX

CASE = "user:case" : une modification

un tiroir qui suit le visiteur ;

la demande

await ctx.add_events_to_memory(..., custom_metadata=FILING) : lu, pas écrit

ce qui a été dit, conservé

TROIS

found = await ctx.search_memory(query) : une modification

ce qui a été conservé, demandé ; ce qui a été classé n'est pas mémorisé

la tour

make_tower.py, puis AGENT_ENGINE dans .env : créé et connecté par une ligne, rien n'a été redémarré

une tour qui distille et survit à la nuit.

les règles

allowed_topics dans FILING : lu, pas écrit

uniquement ce qui doit être conservé.

la saison

season.sh : chargé, intégré, un graphique superposé, parcouru avec un MATCH

les visites des autres utilisateurs, par signification et par connexion.

Éléments remplacés

ADK 2

ce que vous écririez à la place.

ce que cela vous coûte

session.state + un outil, conservé par SqliteSessionService

un dict à côté de la boucle de chat, que vous avez vous-même sérialisé sur le disque ;

le modèle ne peut pas le lire, et vous écrivez le CV et le verrouillage.

le préfixe user:

un tableau des utilisateurs et une jointure à chaque tour.

décider où une clé doit être placée, à chaque fois

add_events_to_memory + search_memory dans un nœud de fonction

un cron qui génère des transcriptions et le modèle qui décide quand regarder.

il déverse, rien ne distille ; et vous ne voyez jamais les moments où il n'a pas regardé.

VertexAiMemoryBankService + allowed_topics

un magasin de vecteurs, une invite d'extraction, un job de consolidation et "ne retiens pas les anecdotes" dans l'invite.

trois services, et la règle tient jusqu'à ce que le modèle en décide autrement.

VECTOR_SEARCH + un MATCH GQL fixe

exporter, intégrer ailleurs, une base de données de graphiques, un job de synchronisation et le modèle écrivant du code SQL

Les données quittent l'entrepôt pour être comprises, et vous découvrez les jointures qu'il a devinées lorsqu'un client

Trois questions, arrêtez-vous à la première réponse positive

  1. Est-ce que cela aura de l'importance demain ? Non → ne rien conserver. Il naît et meurt dans la conversation.
  2. Une fonction peut-elle vérifier cela ? Oui → le panneau. Le code l'écrit, chaque tour le lit, et aucune phrase ne peut le modifier.
  3. Sinon : la tour. Distillée à partir de ce qui a été dit, recherchée lorsqu'elle est demandée, révisée en fonction de ce qui est dit ensuite.

La phrase

Un enregistrement n'est pas un souvenir. Une mémoire est un enregistrement plus deux décisions : ce qu'il faut conserver et quand regarder. Le type de chose détermine à quel étage elle se trouve.

Les faits et les préférences appartiennent à des magasins différents.

👀 Chaque étage est éclairé. Votre familier est en haut de la liste. Le tampon "Archive" se trouve dans le passeport, et la semaine 5 ouvre le marché nocturne, où Nix anime la scène à voix haute, en temps réel, et tout ce que la tour sait vous accompagne.