The Archive — Memory at Agent Valley

1. Introdução

Vesper, a arquivista, na mesa do térreo

Na primeira semana, perguntamos quem diz o que funciona. Na segunda semana, a pergunta foi quem decide o que vai ser executado em seguida. A terceira semana perguntou o que uma corrida deixa para trás. Esta semana, a conversa termina, e a questão é o que sobrevive a ela. Acontece que o tipo de coisa decide para onde ela vai.

O arquivo é onde o vale aparece quando algo deu errado. Uma torre que anota tudo e Vesper, o arquivista, na mesa do térreo. Sua lanterna se apaga à noite. Vá e pergunte.

Ela é uma boa companhia. Um mês depois, ela também fica feliz em conhecer você.

Um registro não é uma memória.

Essa frase é o laboratório inteiro. Você vai subir em quatro degraus, e cada degrau é um andar da torre:

degrau

o chão

o que ele lembra

o que o mantém

1

a mesa e o chão

esta visita

session.state · SqliteSessionService

2

segundo andar, sua gaveta

este visitante

o prefixo user:

3

terceiro andar, a torre

o que foi dito

Memory Bank da Vertex AI

4

andar quatro, a temporada

todo o vale

BigQuery

Cada degrau acima é porque algo caiu do degrau abaixo.

O Arquivo, aberto: cinco andares e uma pequena equipe cuidando de cada um deles.

Você já conhece as cinco maneiras de esquecer do arquivista, porque todas elas já aconteceram com você em uma ligação de suporte:

o que ela diz

o que deu errado

~~1~~

~~Pode me dar essa referência de novo?~~

~~esqueci em uma conversa~~ — a terceira semana corrigiu isso

~~2~~

~~Transferi a ligação. Comece do início.~~

~~o histórico morreu com o processo~~ — a terceira semana também corrigiu isso

3

Olá! Como posso ajudar?

para alguém com um caso em aberto do mês passado

4

Você pode descrever os danos?

ela olhou para a lanterna da última vez

5

Nunca vimos isso antes.

31 pessoas perguntaram nesta temporada

A linha três é desta semana. Ele falha de três maneiras diferentes: uma nova visita (capítulo 2), uma frase sem linha no papel (capítulo 3) e um arquivo que existe apenas dentro do processo (capítulo 4). Cada uma dessas maneiras é um capítulo. A quarta linha é com você: look() em archive/agent.py é onde ela lê uma marca em uma foto. A linha cinco é o capítulo 5 e é opcional.

Você não precisa das semanas de um a três. Um agente é um modelo com uma instrução e algumas ferramentas. Todo o resto se explica por si só.

Você vai escrever três linhas, e cada uma delas é um conceito de memória:

a linha

o que é

tool_context.state[CASE] = case

O estado é escrito por código, nunca pela conversa

CASE = "user:case"

um prefixo é uma vida útil

found = await ctx.search_memory(query)

a política de recall — o arquivo não é lembrado

Mais uma linha já está lá para você ler: a política de gravação, await ctx.add_events_to_memory(...). No capítulo 3, você lê o que ela faz antes de escrever a linha que a usa.

Os outros dois níveis não são edições: você constrói uma torre (um mecanismo de agente que hospeda o banco de memória) e o mesmo código se conecta a ela por conta própria, e você carrega um data warehouse (BigQuery), coloca um gráfico sobre ele e entrega ao mesmo agente duas consultas controladas: uma por significado e outra por conexão.

Todos os capítulos executam o mesmo loop, que é o que você vai usar nos seus próprios agentes: ler o código → escrever a linha → assistir na torre. Nada é executado primeiro, nada é reiniciado e nada é Ctrl+C: cada edição é capturada na próxima mensagem, e conectar a torre é uma linha em um arquivo que o Archive relê a cada solicitação. A torre é um desenho que se redesenha: ela lê os mesmos arquivos que você edita e tem três botões: 🌙 termina o dia, 🗓 inicia a próxima visita e 🔥 esquece você. Todo o resto é feito no código e no terminal.

Dois lugares, e eles nunca mudam de função: a guia 1 é o terminal, e a guia 2 é o arquivo. O ambiente de trabalho (adk web) aparece exatamente uma vez, no capítulo 1, para uma análise do estado bruto da sessão. Depois disso, você não precisa mais dele. Cada 👉 abaixo indica qual é. 👉💻 é a guia 1, 👉🖥 é a guia 2 e 👉🔬 é a bancada. 👉📖 é abrir um arquivo e ler, sem necessidade de mudanças, e 👉✏️ é o tipo de etapa que edita. Sempre que uma etapa pedir para você dizer algo à Vesper, a mensagem exata estará no bloco logo abaixo. Copie como está. Uma seção cujo cabeçalho termina em — somente leitura não tem nada para digitar, colar ou clicar: o código já está no repositório e em execução, impresso para você ler.

A frase na porta de Vesper é a que está na parte de cima. A frase desta semana é a linha de encerramento da quarta semana da temporada:

Fatos e preferências pertencem a lojas diferentes.

2. 🧰 Configuração

  • O quê: aponte para um projeto ou cole uma chave, instale e prove que o conjunto de ferramentas está verde.
  • Por quê? Nada é inicializado aqui. Cada servidor é iniciado logo antes da etapa que precisa dele. Assim, quando uma tela é aberta, você sabe qual comando é o proprietário dela.
  • Como: um script que cria o projeto, um que instala e direciona você para a Vertex AI e um pré-lançamento que lê seu código e informa onde você está.

Tudo neste laboratório é executado no Cloud Shell. O Python, o uv e o gcloud já estão instalados lá, e o Gemini é acessado pela Vertex AI usando suas próprias credenciais. Não há chave de API em nenhum lugar deste laboratório, e não é necessário instalar nada no seu notebook.

Há uma coisa que este laboratório não pode oferecer: um projeto na nuvem do Google Cloud com faturamento ativado. A etapa 1 cria uma conta que pode pagar por isso. A etapa 4 transforma essa conta em um projeto ou retoma o projeto Agent Valley que você já criou na primeira, segunda ou terceira semana. Faça isso nessa ordem. Cada etapa depois da 4 lê o projeto que a etapa 4 define.

1 · Receba uma conta com faturamento (~1 min)

🌐 Faça isso antes de qualquer outra coisa. Faça login com uma Conta do Google que tenha o faturamento ativado. Qualquer uma dessas contas funciona. Se não tiver, inicie um teste sem custo financeiro. Ele oferece uma conta de faturamento, que é tudo o que a etapa 4 precisa.

2 · Abrir o Cloud Shell (~1 minuto)

🌐 Clique em Ativar o Cloud Shell na parte de cima do console do Google Cloud. É o ícone em forma de terminal no cabeçalho.

Ativar o Cloud Shell no cabeçalho do console do Google Cloud

🌐 Clique em Abrir editor (o ícone de pasta aberta com um lápis). Isso abre o editor do Cloud Shell com um explorador de arquivos à esquerda. É ali que você trabalha sempre que uma etapa é marcada com 👉✏️.

Abrir o Editor do Cloud Shell

🌐 Se for preciso autorizar em algum momento hoje, clique em Autorizar para continuar.

🌐 Abra um terminal no editor, que é a guia 1. Todas as etapas 👉💻 serão executadas lá, a menos que seja indicado o contrário. A guia 2 é o arquivo, e você a abre no capítulo 1, logo antes da etapa que precisa dela. Se o terminal ainda não estiver na parte de baixo da tela, abra-o com Visualizar → Terminal:

Abra um novo terminal no editor do Cloud Shell

🌐 Verifique em qual conta você fez login. Ela precisa ser a mesma da etapa 1:

gcloud auth list

3 · Faça o laboratório (~1 min)

💻 Clone o repositório. Este é o repositório de arquivos, não o da semana 1, 2 ou 3:

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

4 · Crie seu projeto ou reutilize o de uma semana anterior (~2 min)

💻 Um script encontra sua conta de faturamento, cria um projeto nela e aponta o Cloud Shell para esse projeto. Se você fez a primeira, a segunda ou a terceira semana, o projeto Agent Valley será encontrado primeiro e vai ser oferecida a opção de reutilizá-lo. Diga sim, e não haverá um segundo projeto faturável para pensar:

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

👀 Espere um ✓ para a conta de faturamento escolhida, um ✓ para o projeto criado ou reutilizado e um final como:

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

agent-valley-4271 é o ID do projeto, que terá dígitos diferentes. Quatro coisas acontecem:

billing

usa a conta [YYYY-MM-DD] GDP Credit: mais recente, se você tiver uma, e qualquer outra conta de faturamento aberta, caso contrário.

project

procura um projeto do Agent Valley que você já tem (da semana 1, 2 ou 3) e oferece a opção de reutilizá-lo. Caso contrário, cria agent-valley-XXXX e vincula a conta de faturamento a ele.

seleciona

grava o ID em ~/project_id.txt e executa gcloud config set project. Portanto, nada depois disso pede que você digite o ID.

waits

faz pesquisas até que o projeto possa veicular, por até dois minutos

🌐 O mesmo projeto agora está no console. O ID dele fica no card Informações do projeto no painel:

O ID do projeto no painel do console

5 · Instalar e apontar para a Vertex AI (~3 min)

💻 Instale uv, exporte as três variáveis que enviam o ADK para a Vertex AI e execute a configuração:

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) tem algo a ser retornado porque a etapa 4 o definiu. É por isso que a etapa 4 vem primeiro.

setup_codelab.sh é idempotente e pode ser executado novamente quantas vezes for necessário. Ele faz cinco coisas, em ordem:

api

lê a etapa 4 do projeto selecionado e ativa aiplatform.googleapis.com (Vertex AI e o Agent Engine que hospeda o Memory Bank) e bigquery.googleapis.com (o depósito do capítulo 5) nele.

uv env

O uv venv cria um .venv ou reutiliza o que já existe. Depois, o uv sync instala exatamente o que o uv.lock fixa: google-adk, google-genai e tudo abaixo deles.

site

executa npm ci em site/. O próprio Archive é um app Next.js. Portanto, bash valley.sh no capítulo 1 não tem mais nada para baixar. É ignorado quando os pacotes já estão lá.

.env

grava as variáveis que o laboratório lê: GOOGLE_GENAI_USE_VERTEXAI, GOOGLE_CLOUD_PROJECT, GOOGLE_CLOUD_LOCATION, MEMORY_BANK_LOCATION e BQ_DATASET, e não há chave de API em lugar nenhum

Verificação de modelo

faz uma chamada real para o Gemini, então um projeto ruim falha aqui no momento da configuração, não depois dentro de um capítulo

6 · Prove que é verde (~1 min)

💻 Execute a simulação. Cada marcação é uma sondagem real, não uma estimativa:

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

👀 A pré-verificação faz uma pergunta ao modelo para provar que suas credenciais funcionam, verifica se os pacotes do arquivo estão no lugar e lê seu código e imprime a barra de progresso desta semana:

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

Três já estão marcadas. São aqueles que você apenas lê. Três são linhas que você escreve, e uma é uma torre que você constrói. Execute a verificação prévia novamente a qualquer momento no laboratório para saber onde você está. Ela lê seus arquivos, não uma lista de verificação.

3. Session, State, SqliteSessionService

  • O quê: escreva uma linha, inicie a bancada uma vez e veja o preenchimento do escorregador. Depois, faça o mesmo na torre. Em seguida, leia por que ela sobrevive à noite.
  • Por quê? A primeira coisa a saber sobre a memória é que nada é lembrado por si só. Alguém escreve.
  • Como: uma linha em agent.py, sem mais mudanças.

EDITAR UM

👉📖 Abra archive/agent.py e encontre write_down. Leia até o 👉:

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

A função cria o comprovante em um dicionário local e o retorna. Um dicionário local morre com a chamada. Execute o código como está, e o Vesper vai ouvir você perfeitamente: quatro chamadas write_down são disparadas, cada uma responde "written", e o estado da sessão permanece vazio, porque nada chegou a ele. Ela ouviu você. Nada foi escrito.

👉✏️ Ainda em archive/agent.py, dentro de write_down: exclua o # na frente de uma linha, o # e o espaço depois dele. Antes:

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

Depois:

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

Salvar.

O comprovante é preenchido

👉💻 Inicie o ambiente de trabalho. Esta é a única vez que você vai abrir o laboratório. É a única janela que mostra o estado da sessão como o JSON bruto:

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

👉🔬 Abra o ambiente de trabalho com Visualização da Web → Visualizar na porta 8000. O botão "Visualização da Web" é o ícone de um olho sobre uma janela no canto superior direito do Cloud Shell. Escolha arquivar e, antes de digitar qualquer coisa, clique em Estado no painel à esquerda.

👀 Vazio. Ninguém disse nada ainda.

👉🔬 Agora diga tudo isso em uma só mensagem:

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

👀 Quatro chips write_down são disparados. Clique em um e leia o JSON: "written": {"no": "k7f2"}. Agora clique em Estado novamente.

Quatro coisas, quatro campos, uma mensagem

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

👀 Vazio e depois cheio, dentro de uma mensagem. É com isso que ela se lembra, e isso não apareceu sozinho. Você escreveu a linha que a escreve. Esse é todo o mecanismo: uma função simples que grava em tool_context.state e uma instrução que diz para ela usar sempre que o visitante disser algo que se encaixe em uma linha do papel.

👉🔬 Pergunte a ela:

What's my case number?

👀 k7f2. Ela lê o que está escrito no papel. Sem o comando EDIT ONE, ela teria pedido isso a você: a primeira linha da lista de suporte, em uma conversa.

Os dois guardas são o comprovante sendo um formulário em papel: quatro linhas curtas, cada uma escrita uma vez. Elas são mais importantes do que parecem. Sem elas, o modelo descobre que pode despejar toda a conversa em symptom, e o capítulo 3, cujo objetivo é o slip não tem uma linha para isso, deixa de ser verdadeiro. O formato de uma loja é aplicado ou não pelo código que a grava.

Agora vá até a torre

👉💻 Você concluiu o laboratório. Deixe a bancada em execução ou feche a guia. O restante do laboratório está no arquivo. Abra uma segunda guia do terminal (guia 2: o arquivo):

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

👉🖥 Abra o arquivo com Visualização na Web → Alterar porta, digite 3440 e clique em Alterar e visualizar. 3440 não está na lista rápida da prévia da Web. O padrão é 8080. Portanto, você precisa definir manualmente uma vez. Pressione ▶ Iniciar e depois O arquivo. Você está na porta. Escolha quem veio perguntar (um nome e uma espécie) e entre.

A torre, cortada

👀 Um prédio. Vesper está na mesa do térreo, e é lá que você vai conversar. Acima dela: primeiro, segundo e terceiro andares, além de um sótão escuro. Os quatro degraus são os quatro andares. Seu familiar está no térreo, porque é até onde você subiu. A coluna ao lado do desenho mostra o que cada andar tem agora: as mesmas quatro lojas da tabela na introdução, na mesma ordem. Nada à direita é um botão.

👉🖥 Cole as mesmas quatro coisas na caixa abaixo da conversa (esta mensagem exata) e pressione "Enter":

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

Quatro linhas no espaço de trabalho e as mesmas quatro ao lado

👀 Observe o espaço de trabalho. O papel se escreve, uma linha de cada vez, no desenho e por completo em o papel ao lado. Mesma ferramenta, mesmo estado: a torre está apenas desenhando o que write_down escreveu, e vai continuar fazendo isso pelo resto do laboratório.

Ela se lembra durante a noite — somente leitura

O que você escreveu e o que é

a linha

o que é

onde ele está

tool_context.state[CASE] = case

estado da sessão: escrito pelo código, lido pela instrução

a mesa · session.state

SqliteSessionService(DB): você apenas lê

o armazenamento de sessão: uma visita em um arquivo, não em um processo

primeiro andar · archive.db

Esta barra alcança uma visita e sobrevive ao horário de fechamento. Ele não sobrevive à próxima visita.

4. Estado do usuário

  • O quê: volte um mês depois, e o escorregão terá desaparecido. Cinco personagens que seguem você.
  • Por quê: uma sessão é uma visita. A próxima visita é uma nova sessão e nunca viu a última.
  • Como: cinco caracteres em state.py.

Um mês depois. Prazer em conhecer você.

Clique aqui

👉🖥 Pressione 🗓 um mês depois. Essa é uma nova sessão para o mesmo visitante, assim como iniciar uma nova conversa na terceira semana.

Este é o arquivo. Como posso ajudar?

👉🖥 Olhe para a torre. O primeiro andar tem outro livro — esta visita. A gaveta do segundo andar está vazia. O papel no espaço de trabalho está em branco.

Outro livro no primeiro andar; a gaveta vazia; o recibo em branco

👀 Por quê: o encarte é gravado no estado da sessão, e uma sessão é esta visita. Mudar visitas, mudar sessões. Nada foi transferido porque nada foi informado. A última visita ainda está no arquivo (o primeiro livro do primeiro andar), mas o recibo foi escrito com uma chave que dura uma visita.

EDITAR DOIS

👉✏️ Abra archive/state.py. Encontre o 👉. Antes:

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

Depois:

CASE = "user:case"

Salvar. Sem reinicialização: o Archive reimporta seu código em todas as mensagens.

👉🖥 Cole as mesmas quatro coisas novamente: esta mensagem exata:

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

👉🖥 Em seguida, pressione 🗓 um mês depois de novo, o mesmo botão de antes.

A gaveta, com um nome nela

👀 A gaveta do segundo andar tem seu nome e os quatro campos dentro. O papel na mesa tem as mesmas quatro linhas, tirado da gaveta quando você entrou. Seu familiar subiu um andar. Mesma chave, todas as visitas, um valor: esse é o prefixo fazendo o trabalho dele.

O prefixo é um tempo de vida.

prefixo

quanto tempo ele dura

qual andar

(nenhum)

esta visita

a mesa

user:

cada visita feita por esse visitante

segundo andar, sua gaveta

app:

para todos, para sempre

parte do quarto andar

temp:

esta curva apenas, nunca sobe as escadas

—

Uma mesa, quatro gavetas. Este capítulo aborda um deles.

Ela conhece você agora

👉🖥 Esta visita é nova e você ainda não disse nada nela. Pergunte a ela:

What did I bring in last time?

👀 Tenho anotado que você trouxe uma lanterna, estojo k7f2. Ele está marcado com q7 e você mencionou que ele continua saindo à noite. Cada palavra que saiu da gaveta, não da conversa. Esta visita tem uma mensagem, e a mensagem não nomeou nada. Esse é o estado no nível do usuário fazendo o trabalho dele: uma chave user: está lá antes que o visitante diga uma palavra, em todas as visitas que ele faz.

A gaveta contém as quatro linhas do comprovante e nada mais. O que você diz que não tem uma linha no papel — o que você tentou, por que se importa, se quer um novo — não tem para onde ir e, um mês depois, desaparece. O gaveteiro cruza o mês. Uma frase não. O capítulo 3 é sobre isso.

O que você escreveu e o que é

a linha

o que é

onde ele está

CASE = "user:case"

um prefixo é um tempo de vida: a mesma chave, com escopo para o visitante em vez da visita

segundo andar · estado user:

Esta barra alcança um visitante. A gaveta segue você, mas a conversa ainda não.

5. InMemoryMemoryService

  • O quê: leia a linha que arquiva o dia, escreva a linha que pergunta e depois um horário de encerramento e uma pergunta.
  • Por quê: o comprovante é um formulário. Um formulário tem as linhas que você deu a ele. As pessoas não falam em linhas, e arquivar o que elas disseram é apenas metade da lembrança.
  • Como: leia file, uma linha em recall e depois uma 🌙, um 🗓 e uma pergunta.

Horário de fechamento. Ela volta a passar pelas prateleiras.

O que ela faz a caminho de casa — somente leitura

O campo não é lembrado

Essa linha coloca o dia na prateleira. Ele não tira nada. Uma prateleira cheia de cards ajuda tanto quanto uma vazia se ninguém olhar. E, como o código é enviado, ninguém olha: recall, o nó que é executado antes do Vesper em todas as perguntas, não pergunta nada à torre. O armazenamento que você não consegue lembrar no momento certo não é memória, é um registro. A outra metade é o olhar, e você pode escrever sobre isso.

EDIÇÃO 3: a pergunta

👉📖 Em archive/agent.py, encontre recall, a função logo acima de file. É um nó de função simples que é executado antes do Vesper em todas as perguntas, não o modelo decidindo quando procurar. Isso significa que os momentos em que ele não encontra nada também são visíveis. Leia até o 👉:

    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

👉✏️ Ainda em archive/agent.py, dentro de recall: exclua o # na frente de uma linha, o # e o espaço depois dele. Antes:

        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:

Depois:

        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:

Deixe o pass onde está. Ele não faz nada agora porque found já é None acima.

Salvar. Sem reinicialização.

Duas linhas, duas políticas, e você escreveu uma delas. A política de gravação decide o que é mantido e quando (horário de fechamento, o dia todo). A política de recall decide quando procurar e com o quê (todas as perguntas, a pergunta em si). O ADK oferece os verbos — add_events_to_memory, search_memory — e não entra na política. Essa é a divisão correta, e é por isso que a pergunta foi escrita por você.

Fecha às

👉🖥 Diga três coisas que não têm uma linha no comprovante. Uma mensagem ou três:

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.

Clique aqui

👉🖥 Depois, pressione 🌙 Por hoje é só.

As palavras surgem

👀 Observe o espaço de trabalho. As três coisas que você disse saem do escorregador e flutuam para cima, pousando no terceiro andar. Em os cards ao lado da torre, eles são listados: um card para cada mensagem enviada, marcada como quote, suas frases palavra por palavra e neste processo acima delas. A luminária de mesa fica acesa até que eles aterrissem.

Um mês depois, ela sabe

👉🖥 Pressione 🗓 um mês depois e pergunte:

What have I already tried?

👀 Você tentou pendurar mais alto, mas não ajudou. Ela sabe. O comprovante não tem uma linha para isso e nunca teve, então a gaveta não poderia ter respondido. recall encontrou o cartão, escreveu em ctx.state["recalled"] e a instrução dela diz isso. Uma linha é a diferença.

E tudo isso está na cabeça dela

Confira os cards ao lado da torre: neste processo. No momento, a memória é InMemoryMemoryService, um dicionário dentro de bash valley.sh, e dura exatamente o mesmo tempo que esse processo. Feche a torre hoje à noite e abra amanhã: os livros estão no primeiro andar, a gaveta está no segundo andar e o terceiro andar está escuro. O caso foi mantido no arquivo. O que você disse estava no processo, e o processo acabou.

Persistência não é memória.

Há duas coisas erradas com o terceiro andar: ele está em processo e é uma citação, não um fato. O próximo capítulo corrige os dois problemas sem código algum.

O que você lê, o que você escreve e o que é

a linha

o que é

onde ele está

await ctx.add_events_to_memory(events, custom_metadata=FILING): você apenas lê

a política de gravação: um dia inteiro, no horário de fechamento

terceiro andar · o serviço de memória

found = await ctx.search_memory(query) — EDIT THREE

a política de recall — todas as perguntas, antes de responder

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

Essa etapa alcança o que foi dito e o mantém em um dicionário dentro de um processo, com suas próprias palavras. As duas partes estão erradas, e o próximo capítulo corrige ambas sem código algum.

6. MemoryBank

  • O que: um script cria um Memory Bank. As duas linhas que você já tem começam a escrever fatos nele e a lê-los de volta.
  • Por quê: um serviço de memória é um serviço. O local em que o arquivo fica é uma decisão de implantação, não do agente. Você pode mudar isso sem afetar o agente.
  • Como: execute make_tower.py, diga três frases, pressione 🌙, pressione 🗓 e faça uma pergunta. Depois, uma análise somente leitura da gravação e da leitura.

Construa a torre

O terceiro andar do capítulo 3 era um dicionário Python dentro de bash valley.sh. O Memory Bank é a versão gerenciada desse recurso: ele fica em uma instância do Agent Engine no seu projeto, mantém fatos destilados do dia em vez das suas frases e os encontra por significado em vez de palavras compartilhadas. Não é necessário implantar o Vesper em um Agent Engine para usá-lo. Uma instância sem nada implantado ainda hospeda um Memory Bank, e é isso que o script cria: uma torre sem ninguém morando nela, cuja única função é guardar os cards.

👉💻 Na guia 1 (cerca de um minuto):

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

👀 O que esperar:

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

Uma chamada de SDK, agent_engines.create(), e uma linha gravada em .env. O arquivo é lido novamente pelo arquivo em cada solicitação, então não é necessário reiniciar nada.

Veja como o vídeo funciona

👉🖥 Observe o terceiro andar: a torre se redesenha sozinha em alguns segundos. Em os cards, agora aparece Memory Bank, e a prateleira está vazia: as três citações do capítulo 3 estavam no processo, e este não é o processo. Diga as três frases de novo, uma mensagem ou três:

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.

👉🖥 Pressione 🌙 por hoje é só. O tempo de fechamento agora é de cerca de oito segundos. A torre está extraindo. Depois, olhe para o terceiro andar.

À esquerda: o que ela guardou na cabeça. Direita: o que a torre mantém.

👀 Os cards não são suas frases. Elas dizem coisas como Uso minha lanterna para ler à noite, que é quando ela fica apagando e Já tentei pendurar a lanterna mais alto, cada uma marcada fact com uma data. Destilação. O que foi inserido foi uma conversa. O que saiu são fatos.

👉🖥 Pressione 🗓 um mês depois (uma nova visita, então nada do que você acabou de dizer está mais na conversa) e faça esta pergunta, que não compartilha uma palavra com os cards:

Should I just get a different lamp?

👀 Como foi um presente, você mencionou que não quer substituir a lanterna e já tentou pendurá-la mais alto. Cada palavra veio do cartão, e só ele poderia ter dito a ela: esta visita tem uma mensagem. Pesquisa por similaridade. A torre corresponde ao significado, não às palavras. A pergunta diz lâmpada e diferente, e o card diz lanterna e substituir. E ele está fora do processo agora: desligue a torre hoje à noite e os cards estarão lá amanhã, porque eles ficam no Agent Engine, não em bash valley.sh.

Gravação e leitura (somente leitura)

O direito de ser esquecido

Clique aqui

👉🖥 Clique em 🔥 esqueça-me, abaixo da caixa de mensagem.

👀 Três andares ficam escuros e um não:

o que ele contém

depois de 🔥

terceiro andar, os cards

o que ela lembrou: os fatos que o Memory Bank extraiu das suas visitas

desapareceu — memories.delete, um card por vez

segundo andar, a gaveta

user:case, o comprovante que acompanha o visitante de uma visita para outra

apagados

o espaço de trabalho, o escorregão

a cópia do caso desta visita

apagados

primeiro andar, os livros

as visitas em si: uma sessão no archive.db por visita, cada mensagem dela

inalterado: a contagem não muda

Os livros são o registro: que uma visita aconteceu, em qual dia e o que foi dito nela. Os cards e a gaveta são a memória: o que ela fez dessas visitas e leva para a próxima. Um pedido para ser esquecido se refere à memória: os fatos derivados de uma pessoa e o estado que a acompanha. A decisão de manter ou não o registro de uma visita é separada, tomada por motivos diferentes (uma trilha de auditoria, um histórico de suporte). Este laboratório mantém o registro de propósito para que você possa ver os dois se separarem: o registro e a memória não são a mesma coisa, e esquecer um não afeta o outro. Essa é a frase na porta de Vesper, vista do outro lado.

A abstração de memória do ADK tem add_events_to_memory e search_memory. Não há exclusão. O esquecimento é uma ação de governança, não do agente. Portanto, ele fica no aplicativo — archive/tower.py, memories.delete no cliente bruto — onde o modelo não pode alcançar por acidente. Se um agente pode esquecer é algo que você projeta, assim como se ele pode lembrar.

O que você criou e leu, e o que é

a coisa

o que é

onde ele está

make_tower.py → AGENT_ENGINE em .env: criado, não editado, nada reiniciado

o serviço de memória, como um serviço: uma decisão de implantação, fora do agente

Memory Bank da Vertex AI em um Agent Engine

tower_service(engine): você apenas lê

o mesmo período Runner(memory_service=...), mas em outra casa

archive/service.py

"allowed_topics": TOPICS em FILING: somente leitura

o que pode ser mantido, decidido no momento da gravação, não no comando

archive/topics.py + a configuração da própria torre

🔥 → memories.delete — pressionado, não escrito

O esquecimento é uma ação do aplicativo. A API Memory do ADK não tem exclusão.

archive/tower.py

Esta etapa alcança o que foi dito. Um dia se passa, um fato é revelado, destilado ao que os tópicos permitem, e o livro no primeiro andar nunca se moveu.

7. GraphRAG

Opcional.

  • O quê: um script carrega o passado do vale no BigQuery, gera embeddings dentro do data warehouse e cria um gráfico sobre as tabelas sem mover uma linha. Em seguida, duas perguntas para o Vesper, que tem duas consultas controladas: uma para o que soa parecido e outra para o que está conectado.
  • Por quê: "parece igual" não é o mesmo que "está conectado". Um vetor encontra onde procurar, um gráfico encontra o que está anexado, e a resposta precisa carregar o caminho percorrido.
  • Como — bash scripts/season.sh, uma vez. Todo o resto neste capítulo é somente leitura.

A torre de Vesper é limitada a você — (app_name, user_id) — por construção. As visitas de outras pessoas não são a memória dela, e pesquisar mais não mudaria isso. Eles pertencem ao vale, que os guarda em um armazém. Nesta temporada, 31 visitantes vieram por causa de uma lanterna q7 que apagou à noite, e um deles encontrou a causa. Este capítulo carrega esse data warehouse e entrega duas consultas fixas a ele.

Graph RAG

A RAG é recuperar e gerar: encontre os registros certos, entregue-os ao modelo e deixe que ele responda com base neles. A RAG simples faz a recuperação por semelhança: incorpora a pergunta e extrai os registros mais próximos. Isso encontra coisas que parecem a pergunta, e é onde a torre do capítulo 4 parou. A RAG de grafo adiciona uma segunda etapa de recuperação que segue arestas (chaves que um data warehouse já tem) de qualquer semelhança encontrada para o que estiver anexado a ela. As duas etapas respondem a perguntas diferentes, e este capítulo faz você perguntar as duas:

etapa

a pergunta que ele responde

como

neste capítulo

similaridade

Onde devo procurar?

incorporar a pergunta, VECTOR_SEARCH

season_search: quando o visitante não traz marca

travessia

o que está conectado a isso?

MATCH um caminho em um gráfico de propriedades

season_known_issue: um MATCH fixo

geração

O que devo dizer ao visitante?

o modelo, com o caminho como citação

a resposta dela

Executar

A temporada é enviada como CSVs simples em season/ (visitantes, itens, perguntas, correções, temporadas), da mesma forma que um sistema de suporte os exportaria. Um script leva todos eles até o armazém, e é a única coisa que você executa neste capítulo.

👉💻 Na guia 1 (cerca de três minutos; ela imprime cada etapa à medida que avança e para na primeira que falha):

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

👀 Quando ele termina, o quarto andar da torre acende, e a legenda ao lado diz aberto.

O que ele criou: somente leitura

Como ela lê — somente leitura

Pergunte a ela

👉🖥 O quarto andar está aberto na torre. Pergunte a ela da mesma forma que um visitante faria:

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

Ela responde, e a última linha é o caminho

👀 Ela responde: outras 31 pessoas... a maioria disse que a chama ficou baixa e se apagou depois de uma hora... A lontra Marlow descobriu que os pavios estavam muito rasos, e a última linha da resposta dela é o caminho. Esta é a resposta dela. Leia, não envie de volta:

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

O caminho é a citação. Cada salto é uma aresta no gráfico, e uma aresta é uma chave. Assim, qualquer pessoa pode percorrer o caminho novamente e chegar ao mesmo número. Uma pontuação de similaridade não pode ser percorrida novamente por ninguém. Se ela disser, logo antes do caminho, que as linhas voltaram por três junções porque o projeto não tem uma reserva de gráfico, essa é a edição do seu projeto recusando GRAPH_TABLE. As linhas e o caminho são os mesmos.

👉🖥 Agora deixe a marca de fora — envie isto:

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

👀 Ela chega ao mesmo 31 e ao mesmo caminho. O que mudou foi o caminho: season_search correu primeiro, encontrou cinco visitantes que soam de forma semelhante (cada um carregando q7), pegou essa marca e só então caminhou. Semelhante não é conectado, mas sim onde procurar.

O que foi carregado e o que é

a coisa

o que é

onde ele está

bq load com esquemas explícitos: execução, não gravação

um depósito: colunas com nomes e tipos

archive.visitors / items / asks / fixes / seasons

ML.GENERATE_EMBEDDING + VECTOR_SEARCH

a etapa de similaridade: onde procurar

archive.ask_embeddings, o modelo embedder

CREATE PROPERTY GRAPH: nenhuma linha movida

a etapa de travessia: o que está conectado; as tabelas se tornam nós e setas

archive.season_graph

GQL em archive/season/__init__.py: um parâmetro

o único passeio que ela pode fazer; o caminho é a citação

season_known_issue(mark)

Algumas dezenas de visitantes não são motivo para comprar um armazém. O que este capítulo ensina é o formato da consulta, não a escala. Aumente esse gráfico mil vezes e o MATCH não vai mudar em um caractere.

8. Fechar

Três linhas, uma torre, um armazém: quatro andares. Nada foi reiniciado.

o degrau

o que você fez

o que foi comprado

UM

tool_context.state[CASE] = case: uma edição

um deslize que existe: o estado é escrito por código

DOIS

CASE = "user:case": uma edição

uma gaveta que segue o visitante

o envio

await ctx.add_events_to_memory(..., custom_metadata=FILING): lido, não escrito

o que foi dito, mantido

TRÊS

found = await ctx.search_memory(query): uma edição

o que foi mantido, pedido — o arquivo não é lembrado

a torre

make_tower.py e AGENT_ENGINE em .env: criados e conectados por uma linha, sem reinicialização

uma torre que destila e sobrevive à noite

as regras

allowed_topics em FILING: lido, não gravado

apenas o que deve ser mantido

a temporada

season.sh: carregado, incorporado no lugar, um gráfico sobreposto a ele, caminhado com um MATCH

as visitas de todos os outros, por significado e por conexão

O que cada peça substituiu

ADK 2

o que você escreveria em vez disso

o que isso custa para você

session.state + uma ferramenta, mantida por SqliteSessionService

um dict ao lado do loop de chat, gravado em disco por você

o modelo não consegue ler, e você escreve o currículo e o bloqueio

o prefixo user:

uma tabela de usuários e uma junção em cada turno

decidir onde uma chave pertence, sempre

add_events_to_memory + search_memory em um nó de função

um cron que despeja transcrições e o modelo decidindo quando procurar

ele despeja, nada destila, e você nunca vê as vezes em que não parecia

VertexAiMemoryBankService + allowed_topics

um repositório de vetores, um comando de extração, um job de consolidação e "não se lembre de curiosidades" no comando

três serviços, e a regra é válida até que o modelo entenda o contrário.

VECTOR_SEARCH + um GQL fixo MATCH

exportar, incorporar em outro lugar, um banco de dados de grafo, um job de sincronização e o modelo gravando SQL

os dados saem do data warehouse para serem compreendidos, e você descobre quais junções foram estimadas quando um cliente faz

Três perguntas, pare no primeiro "sim"

  1. Isso vai importar amanhã? Não → não manter nada. Ela vive e morre na conversa.
  2. Uma função pode verificar isso? Sim → a gaveta. O código escreve, cada turno lê e nenhuma frase pode mudar.
  3. Caso contrário → a torre. Destilado do que foi dito, pesquisado quando solicitado, revisado pelo que é dito em seguida.

A frase

Um registro não é uma memória. Uma memória é um registro mais duas decisões: o que manter e quando procurar. O tipo de coisa que ela é decide em qual andar ela fica.

Fatos e preferências pertencem a lojas diferentes.

👀 Todos os andares estão iluminados. Seu familiar fica na parte de cima. O carimbo do Arquivo está no passaporte, e a quinta semana abre o Mercado Noturno, onde Nix comanda o palco em tempo real, e tudo o que a torre sabe acompanha você.