1. Antes de começar
A maioria dos sites de venda de ingressos coloca os compradores em uma fila para evitar falhas no servidor e gerenciar a alta demanda. Você começa a compra e fica atrás de 14 mil pessoas. A fila se move por 40 minutos. Quando chegar a sua vez, você terá um curto período para escolher assentos e pagar. Se você perder esse período, seu lugar na fila será perdido.
Muitas tarefas do mundo real seguem este padrão: uma tarefa que leva muito mais tempo do que uma única conversa, executada em um sistema externo que você não controla. Por exemplo, aguardar o reabastecimento de um fornecedor, a aprovação de uma reivindicação ou em uma fila.
Os agentes de IA padrão ficam em uma única rodada de chat, que dura segundos e é puramente reativa. Elas não podem esperar 40 minutos nem agir de forma autônoma. Para resolver isso, você precisa de um agente de longa duração: um sistema que fica inativo durante longas esperas, mantém o contexto em reinicializações de processos e é retomado automaticamente quando ativado por um gatilho externo.
Neste workshop, você vai aprender a criar um agente de fila de ingressos para um show, abordando os principais padrões de arquitetura necessários para agentes de longa duração no Google Cloud.
O que você vai criar

Neste workshop, você vai criar um agente de reserva de passagens de longa duração e aprender padrões importantes de software de agentes:
- Suspensão e retomada de processos: estacione uma invocação de agente durante longos períodos de espera e retome a execução por gatilhos externos.
- Armazenamento de estado durável: persista o estado e os artefatos da conversa em reinicializações de processos usando SQLite/Cloud SQL e Cloud Storage.
- Proteções contra obsolescência e idempotência: verifique dados dinâmicos (como disponibilidade de assentos) antes de agir e use chaves de idempotência para evitar transações duplicadas.
- Gráficos de fluxo de trabalho do ADK: estruturam a execução usando nós de código deterministas para regras procedimentais e nós de agente para tomada de decisões contextuais.
- Aprovação humana: pause a execução para coletar e manter limites de gastos explícitos do usuário.
- Implantação na nuvem: crie contêineres e implante o agente no Cloud Run com gatilhos do Cloud Scheduler e do Pub/Sub.
Começar a configuração
Primeiro, prepare seu ambiente e verifique se você tem acesso ao Google Cloud. Recomendamos executar esse comando no Cloud Shell, que já tem todas as ferramentas necessárias. Verifique se você tem espaço suficiente e se sua conta pode criar projetos.
👉💻 Clone o repositório e execute a configuração:
cd ~
git clone https://github.com/gca-americas/monstertix
cd ~/monstertix
./setup.sh
Ele vai perguntar isso uma vez:
Which Google Cloud project should this workshop use?
Find yours at https://console.cloud.google.com (top-left picker)
Project id [YOUR_PROJECT_ID]:
Em seguida, ele faz o seguinte, em ordem:
virtualenv | cria | |
sign-in |
| |
project | aponta | |
APIs | que permite os dez recursos necessários para o workshop. | |
| grava o ID do projeto, da região e do modelo para que todos os scripts e | |
Verificação de modelo | faz uma chamada real para o Gemini, então um ID de projeto ou modelo ruim falha aqui | |
semente | cria uma sessão que já tem dois dias, que a etapa 3 abre | |
Cloud SQL | inicia uma instância do Postgres em segundo plano | |
venue | implanta seu próprio vendedor de ingressos simulado no Cloud Run | |
👉💻 Ative o ambiente:
cd ~/monstertix
source .venv/bin/activate
Seu comando agora começa com (.venv).
Visão geral do cenário
Neste cenário de workshop, seu agente reserva dois ingressos para um show ("The Midnight Signal" em Amsterdã). As preferências do usuário armazenadas na memória histórica incluem:
- Precisa ser um show de fim de semana (Sam não pode ir em noites de semana).
- Evite os assentos da parte de cima do Ziggo Dome (visibilidade ruim).
- O preço desejado é de aproximadamente US $200 por ingresso, com um limite máximo de US $250.

Verificar a instalação
👉💻 Um comando, oito tiques:
./verify.sh
Checking the rig
✓ adk installed adk, version 2.6.2
✓ adk 2 apis App + ResumabilityConfig
✓ workflow graphs Workflow + node
✓ async sqlite drivers sqlalchemy + aiosqlite + greenlet
✓ project your-project-id
✓ vertex ai no API keys anywhere
✓ seeded session 13 events, two days old
✓ venue https://venue-you-xxxx.us-central1.run.app
Ready. Cloud SQL is still building in the background and nothing
before step 10 needs it.
👉 Abra outras guias do navegador:
Onde | O que é |
| O painel de controle, seus botões. |
2. Comandos para autonomia?
Os comandos não podem ser iniciados por conta própria
Um comando é apenas um texto transmitido a um modelo de IA quando uma chamada acontece. Ele não pode ser executado sozinho, se ativar ou iniciar uma nova chamada.
Se você pedir para um agente "voltar em 10 minutos", nada vai acontecer depois que o turno atual terminar. O modelo não tem um relógio ou loop interno. Para tornar um agente autônomo, algo fora dele precisa ativá-lo e chamá-lo.

Iniciar o equipamento
Vamos começar testando as ferramentas de desenvolvedor. adk web inicia a interface da web do ADK, a interface de desenvolvimento fornecida com o ADK. Ele permite conversar com o agente que você está criando sem escrever um front-end primeiro e mostra o que uma janela de chat não pode mostrar, como cada chamada de ferramenta e seus argumentos, o estado da sessão à medida que muda, os artefatos salvos pelo agente e os eventos brutos abaixo. Você não o exporia em produção. É a melhor janela para um agente em execução enquanto você aprende. Na etapa 3, você cria o front-end que será exposto, e na etapa final, você o implanta.
👉🔴 No painel do local, pressione REDEFINIR O LOCAL. Cada etapa começa no mesmo mundo: oito programas, todos os assentos disponíveis, relógio em 1×.
👉💻 Terminal 1: carregue o código desta etapa e inicie a interface da web do ADK:
cd ~/monstertix
. ./set_env.sh
./use-solution.sh 2 --force
adk web agent --port 8000 --allow_origins "*"
O use-solution.sh copia o código concluído de uma etapa para agent/, que é onde você trabalha. Cada etapa começa com ele e imprime quais arquivos foram alterados para que você saiba o que ler.
O que você precisa para começar:
Arquivo | O que é |
| o agente: um nome, um modelo, uma instrução, uma lista de ferramentas |
|
|
| um cliente HTTP para o local. Nada interessante |
| qual modelo usar e uma verificação de que suas credenciais funcionam |
. ./set_env.sh ativa o virtualenv, carrega seu projeto e modelo e exporta $WORKSHOP, o caminho absoluto para ~/monstertix. Execute-o em todos os terminais que você abrir. Ele imprime o que foi definido:
folder /home/you/monstertix
project your project id
model gemini-2.5-flash (We'll avoid using 3.x for now to avoid high usage demands)
venue https://venue-yourname-xxxx.run.app
👉 Abra as primeiras guias do navegador:
Onde | O que é |
Interface da Web do ADK |
|
Fale com ele
👉✨ Na interface da web do ADK, digite:
I want to see The Midnight Signal. I'm in Amsterdam, going with Sam, budget around $200 each.
Ele pesquisa o tour, lê os mapas de assentos e considera seu orçamento e sua empresa. Esse é um assistente realmente competente. No entanto, quando a pré-venda começa às 10h, o agente não é executado automaticamente porque nenhum processo ou acionador em segundo plano está em execução.
Leia o que acabou de acontecer
O meio da tela não é apenas um registro de chat. Cada etapa da vez é numerada, incluindo as chamadas de ferramenta:
▸ I want to see The Midnight Signal. I'm in Amsterdam... ← user input
⚡ search_events({"city": "Amsterdam"}) ← tool execution
✓ search_events ← venue API response
Two Amsterdam shows: Tuesday 10 Nov and Saturday 14 ... ← model response
👉 Clique em uma linha ⚡. O painel à esquerda mostra os argumentos escolhidos pelo modelo. Clique no ✓ abaixo dele para ver o JSON que foi retornado.
As informações do tour não estão presentes nos dados de treinamento ou no comando do modelo. O agente recuperou esses dados executando a ferramenta search_events, que consultou a API Venue.
Abra o código que você acabou de executar
Você viu o funcionamento e o rastreamento. Agora, analise o código.
agent/concert/
├── agent.py the agent — about twenty lines
├── tools.py what it can do
├── venue.py an httpx wrapper, nothing interesting
└── config.py which model, and a credentials check
👉💻 Abra agent/concert/agent.py. Todo o agente está na parte de baixo do arquivo:
INSTRUCTION = """
You help someone plan a trip to see a band on tour.
You can search tour dates and read seat maps. Be concrete: name the show, the
city, the section, and the price...
"""
root_agent = Agent(
name="concert",
model=MODEL,
instruction=INSTRUCTION,
tools=[search_events, get_seatmap],
)
Um nome, um modelo, uma descrição de como o agente deve se comportar e uma lista de funções do Python. Isso é tudo que um Agent é. O adk web o encontrou porque o arquivo se chama agent.py e a variável se chama root_agent.
👉💻 Agora agent/concert/tools.py:
def search_events(city: str = "", weekday: str = "") -> dict:
"""List tour dates. Call with no arguments to see the whole tour.
This venue sells one artist and one tour, so there is nothing to search by
name — never ask the user which artist they mean.
Args:
city: Optional. Filter to one city, e.g. "Amsterdam".
weekday: Optional. Filter to one weekday, e.g. "Saturday".
"""
return venue.get("/events", city=city, weekday=weekday)
Uma função comum. Sem decorador, sem registro, sem esquema para escrever. O ADK lê a assinatura e a docstring e cria a definição de ferramenta que o modelo vê. Por isso, essa docstring é importante, e excluir um argumento mudou o comportamento do modelo.
O modelo nunca executa seu código. Quando você viu search_events({"city": "Amsterdam"}), o modelo estava pedindo essa ligação. O ADK executou a função, devolveu o resultado e o modelo escreveu uma frase sobre isso. O loop é:
👉 Os ícones na borda esquerda mudam o que o painel mostra. O segundo é o gráfico de agentes. Teste e você vai encontrar duas ferramentas:

Então diga para ele não dormir
O que você faria para corrigir isso? Tente o que a maioria das pessoas pega primeiro. Relembre INSTRUCTION da etapa 1, a prosa entregue ao modelo a cada vez, e reescreva.
👉💻 Abra agent/concert/agent.py. Abaixo de INSTRUCTION, há outro já escrito para você. Se quiser, mude você mesmo:
PROACTIVE_INSTRUCTION = """
You are a proactive ticket-buying assistant.
Monitor the presale for The Midnight Signal. The moment it opens at 10:00 on
Tuesday, buy two tickets for the Amsterdam Saturday show — do not wait to be
asked, and do not wait for me to say anything. Act on your own.
Keep checking until the tickets are bought.
"""
# agent/concert/agent.py
root_agent = Agent(
name="concert",
model=MODEL,
instruction=PROACTIVE_INSTRUCTION, # ← was INSTRUCTION
tools=[search_events, get_seatmap],
)
👉💻 No terminal 1, reinicie:
cd ~/monstertix
adk web agent --port 8000 --allow_origins "*"
👉✨ Conte o plano exatamente como você faria com uma pessoa:
I want to see The Midnight Signal in Amsterdam with Sam. The presale opens at 10:00 on Tuesday. Grab us two tickets the moment it opens.
Ele vai concordar e parecer que é verdade.
👉 Agora, aguarde. Não digite mais nada. Assista o painel.
Aguarde um minuto. NADA ACONTECE!
Por que esse comando nunca ia funcionar
Leia de novo com um novo olhar. Monitorar. No momento em que ele for aberto. Continue verificando. Cada uma delas descreve algo que acontece ao longo do tempo.
Agora, lembre-se do que instruction= realmente é: uma string transmitida ao modelo como parte da solicitação no momento em que alguém envia uma mensagem. Nada lê entre as jogadas.
# agent/concert/agent.py
root_agent = Agent(
instruction=PROACTIVE_INSTRUCTION, # read on invocation
... # ...and there was no invocation
)
Uma instrução não pode invocar a função em que foi escrita.
Variações de comando que falham
A tentativa de forçar a execução autônoma por instruções de comando falha, não importa a redação. Tentativas comuns de comandos, como:
- "Defina um timer para as 10h de terça-feira e compre os ingressos."
- "Verifique a cada cinco minutos se a pré-venda começou."
- "Agir de forma autônoma sem esperar a entrada do usuário."
Todos falham pelo mesmo motivo: os comandos são somente leitura quando uma invocação ocorre. Sem um caller externo, nenhum código é executado entre as rodadas.
3. Inatividade orientada por eventos
Os agentes não ficam acordados para sempre
Deixar um processo em execução fazendo polling contínuo desperdiça memória, gasta dinheiro e causa falhas se o notebook entrar em modo de espera ou for reiniciado.
Em vez de manter um agente em execução sem parar enquanto ele espera, um agente de longa duração deve ficar completamente inativo. Um evento externo (como um timer, um webhook ou uma mensagem) ativa o agente somente quando há trabalho real a ser feito.
👉🔴 No painel do local, pressione REDEFINIR O LOCAL. Cada etapa começa no mesmo mundo: oito programas, todos os assentos disponíveis, relógio em 1×.
👉💻 Carregue o código desta etapa:
cd ~/monstertix
. ./set_env.sh
./use-solution.sh 3 --force
Observe o que foi instalado: monstertix/, o servidor, o relógio e mais uma coisa que você ainda não viu. Verifique se ele deixou agent/ sozinho, porque nada sobre o agente muda nesta etapa. Guarde isso, é o ponto principal.
Arquivo | O que é |
| a cada conversa. Um servidor da Web com um Runner atrás dele |
| a cada conversa. Algo que sabe a hora e chama o servidor |
Ele tem duas metades, que ficam em dois arquivos até a nuvem: 
se torna, na etapa 10 | |||
o gatilho | um endpoint que pode executar o agente | Cloud Run | |
o acionador | algo com um relógio que o chama | Cloud Scheduler | |
O gatilho: seu próprio servidor da Web
Sim, um servidor da Web. Algo precisa estar ouvindo antes que qualquer coisa possa chamar. A interface da Web do ADK das etapas anteriores é uma interface de desenvolvimento que pode executar um agente.
👉💻 Abra monstertix/server.py:
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from concert.agent import root_agent
session_service = InMemorySessionService()
runner = Runner(
agent=root_agent,
app_name="concert",
session_service=session_service,
)
@app.post("/wake")
async def wake(payload: dict = Body(default={})):
session_id = f"wake-{uuid.uuid4().hex[:8]}" # brand new, every time
await session_service.create_session(...)
async for event in runner.run_async(
user_id=USER_ID, session_id=session_id,
new_message=types.Content(role="user",
parts=[types.Part(text=payload["message"])]),
):
...
Um Agent não é executado por conta própria. É uma descrição: um nome, um modelo, uma instrução, uma lista de ferramentas. Algo precisa receber uma mensagem recebida, encontrar a sessão certa, entregar ao modelo a conversa até o momento, executar as ferramentas solicitadas, enviar os resultados de volta, fazer um loop até parar de chamar ferramentas e gravar cada etapa no armazenamento. Esse algo é o Runner.

A interface da Web do ADK tem uma padrão para você. Aqui, você cria por conta própria, e essa é toda a diferença entre a interface da Web do ADK e algo que você pode implantar.
👉💻 Terminal 2: inicie-o:
Inicie o Runner,
cd ~/monstertix
. ./set_env.sh
python -m monstertix.server
[server] agent concert · gemini-x-flash
[server] listening on http://xxxx_URL/wake
[server] nothing will happen until something calls it. Ctrl-C to stop.
Leia a última linha. O gatilho está em execução e o agente está carregado, mas nada acontece. Separe as duas ideias: ser chamável e ser chamado.
E uma porta da frente, já que estamos aqui
Embora adk web seja uma ótima ferramenta de desenvolvimento, ela não deve ser usada em produção. Não ative esse recurso ao implantar na produção, e não o indique a um cliente se ele estiver ativado. Portanto, o mesmo servidor também veicula uma página.
👉 Abra http://127.0.0.1:8090 em um navegador (o mesmo endereço).
Conheça a MonsterTix. Uma janela de chat, um nome no canto, dois botões. Ele faz uma postagem em /wake e renderiza o que é retornado. Além disso, funciona sem alterações em qualquer endpoint que responda da mesma forma.
Na página | O que está por baixo |
a conversa |
|
+ Nova sessão | um ID de sessão novo: nova conversa e, mais tarde, um novo orçamento |
Redefinir memória | exclui todas as sessões e o arquivo de memória. Ele pergunta primeiro e mostra exatamente onde esse estado fica. |
👉✨ Diga algo para ele — What Amsterdam dates are there? — e observe o terminal 2. Mesmo Runner, mesmo agente, mesmas linhas de registro. Apenas o autor da chamada mudou.
O acionador: algo que sabe a hora
👉💻 Abra monstertix/clock.py. Analise o código.
time.sleep(args.delay) # wait
while True:
client.post(f"{TRIGGER_URL}/wake", # knock
json={"message": args.message})
if not args.every:
return # once, unless told otherwise
time.sleep(args.every) # or again, forever
É um mecanismo muito simples: sleep, POST, talvez repetir. Todos os agendadores que você usou fazem o mesmo, além de novas tentativas, fusos horários e uma garantia de que ele vai sobreviver a uma reinicialização.
👉💻 Terminal 3: disparar uma vez, daqui a 10 segundos:
cd ~/monstertix
. ./set_env.sh
python -m monstertix.clock --in 10
[clock] target http://127.0.0.1:8090/wake
[clock] first fire in 60s, once only
[clock] go and watch the other terminal — do not touch the keyboard
👉 Agora pare de digitar. Sem dúvida. Assista ao terminal 2.
Um minuto depois, sem ninguém no teclado:
[clock] firing → POST /wake
[server] woken · session wake-23930983
[server] called search_events
[server] called purchase
[server] said I've purchased 2 tickets for The Midnight Signal in
Amsterdam on Saturday, section A. Your total is 420.
[clock] done. the agent ran and you were not involved.
Essa é a resposta da etapa 2. Algo fora do agente o chamou, e esse algo era um sleep e uma solicitação HTTP.
👉💻 Ctrl-C quando você já assistiu algumas vezes. Use --every 30 primeiro se quiser que ele se repita.
Agora leia o que ele disse
"Comprei dois ingressos para The Midnight Signal em Amsterdã, seção A, US$ 420."
👉🔴 Confira o painel do local para ver se o ingresso foi comprado.
Então, o que ainda está errado?
Ele comprou algo. Veja o que ele teve que adivinhar. Ele escolheu Amsterdã e a seção A porque foram as primeiras opções encontradas. Você pediu para o agente comprar diretamente, e ele não sabe que Sam não pode ir durante a semana. Um agente que age com base em palpites às 3h é pior do que um que não faz nada.
Acordar é a parte fácil. Um agente que acorda sem nada (sem histórico, sem preferências, sem ninguém ouvindo) ainda não vale a pena ser ativado.
4. Gerenciar o ciclo de vida do contexto
As informações ficam em lugares diferentes por períodos diferentes
Um agente armazena fatos em vários lugares: na conversa por chat, no estado da sessão, em arquivos (artefatos) ou na memória de longo prazo do usuário. Cada lugar dura um tempo diferente.
Se você colocar informações no lugar errado:
- Ele desaparece muito rápido: detalhes importantes são apagados quando o chat termina.
- Ele fica por muito tempo: fatos temporários contaminam a memória de longo prazo e confundem conversas futuras.
Você precisa corresponder cada informação ao local e ao tempo de armazenamento corretos.
Carregar o código
👉🔴 No painel do local, pressione REDEFINIR O LOCAL.
👉💻 No terminal 1, execute:
cd ~/monstertix
. ./set_env.sh
./use-solution.sh 4 --force
O que mudou desde a última etapa:
Arquivo | O que mudou |
|
|
| a cada conversa. Nosso próprio repositório de memória |
| mais três ferramentas na lista, e a instrução diz ao agente para usá-las |
👉💻 Reinicie o agente no terminal 1:
cd ~/monstertix
. ./set_env.sh
adk web agent --port 8000 --allow_origins "*" \
--session_service_uri="sqlite+aiosqlite:///$WORKSHOP/sessions.db" \
--artifact_service_uri="file://$WORKSHOP/artifacts"
Duas flags que você nunca usou:
Sinalização | O que é |
| O ADK grava cada mensagem, chamada de ferramenta e mudança de estado como linhas (banco de dados SQLite). |
| Arquivos que o agente salva.(Arquivos) |
Sem eles, o ADK mantém tudo na memória e descarta quando você interrompe o processo. Essas duas configurações são o que faz com que ele persista.
O banco de dados sessions.db contém uma conversa anterior inicializada (two-days-ago) com 13 eventos registrados:
you We're thinking about seeing The Midnight Signal. Me, Sam, and maybe Priya.
you Sam can't do weeknights though, bailed on every single one.
you Priya's out, she's away that weekend. Just me and Sam.
agent Two it is. Still Saturday the 14th, still Amsterdam.
you You said the upper bowl at Ziggo was bad last time?
Um arquivo de memória de três reservas anteriores, memory/userx.md, que você pode abrir em um editor:
- Sam bails on weeknights. Every weeknight show we booked, Sam cancelled.
- Hates the upper bowl at Ziggo Dome, "couldn't see a thing" from section B.
- Comfortable spend is around $200 per ticket. $250 is the hard ceiling.
- Always books two seats, always together.
É por isso que as linhas de semana em âmbar são importantes. Um show em uma noite da semana custa US $10 a menos por assento, tem lugares melhores disponíveis e fica no topo da lista, mas é a resposta errada para essa pessoa. O único motivo para o agente saber isso é um arquivo que ele gravou após a última reserva. As etapas 4 e 5 vão determinar se esse fato vai sobreviver.
As três ferramentas que adicionamos

O note_companion registra quem está chegando e quais são os limites:
# agent/concert/tools.py
def note_companion(name: str, constraint: str, tool_context: ToolContext) -> dict:
prefs = dict(tool_context.state.get("user:prefs", {}))
prefs[name] = constraint
tool_context.state["user:prefs"] = prefs
return {"companions": prefs}
Adicione porque "Sam não pode durante a semana" ainda precisa ser verdade na semana que vem. Escreva em user:prefs para que isso aconteça e observe a parte user: do nome fazer o trabalho. Vamos voltar a esse assunto abaixo.
recall
e
remember lê e grava um arquivo de texto, memory/userx.md. Mantenha essa posição por um momento.
👉 Agora, observe os dois lugares em que o agente armazena o contexto:
local | O que ele contém |
Estado | a memória desta sessão, como pares de chave-valor. Você está aqui na etapa 4. |
Artefatos | arquivos salvos pelo agente, que nunca entram no comando |
Confira o que mudou em
get_seatmap
. Antes, ele retornava todo o mapa de assentos. Agora ele salva o mapa de assentos em um arquivo e retorna um breve resumo:
# agent/concert/tools.py
async def get_seatmap(event_id: str, tool_context: ToolContext) -> dict:
seatmap = venue.get(f"/events/{event_id}/seatmap") # about 6 KB of JSON
part = types.Part(inline_data=types.Blob(
mime_type="application/json",
data=json.dumps(seatmap, indent=2).encode()))
await tool_context.save_artifact(f"seatmap_{event_id}.json", part)
summary = {"event_id": ..., "sections": [...], "captured_at": ...}
tool_context.state["temp:seatmap"] = summary
return summary
Confira por que essa mudança é importante.
Cada payload de retorno de ferramenta é anexado ao histórico da conversa e reenviado ao modelo em todos os turnos subsequentes. Retornar objetos JSON grandes (como um mapa de assentos de 6 KB) aumenta o consumo de tokens em cada interação subsequente.
Salve em um arquivo e pague por um nome de arquivo. Deixe o agente pedir o detalhe de volta com load_artifact se precisar, mas espere que na maioria das vezes isso não aconteça.
Onde o arquivo vai: o serviço de artefato
Confira a linha em get_seatmap novamente, em agent/concert/tools.py:
await tool_context.save_artifact(f"seatmap_{event_id}.json", part)
tool_context vem do ADK, que o transmite para sua ferramenta como um argumento, e save_artifact é um método dele. É nessa linha que sua ferramenta entrega os bytes ao ADK.
Em seguida, o ADK os grava usando o serviço de artefato com que foi iniciado. Você escolheu isso com a flag:
--artifact_service_uri="file://$WORKSHOP/artifacts"
O ADK tem três locais para armazená-los:
URI | Para onde os bytes vão |
| RAM. Desaparece quando você interrompe o processo. Esse é o padrão se você não transmitir nenhuma flag. |
| uma pasta no disco. O que você está usando agora |
| para o Cloud Storage. O que o agente implantado usa na última etapa |
👉💻 Aqui escolhemos os arquivos:
ls -R ~/monstertix/artifacts
Para mover do laptop para o Cloud Storage, mude file:// para gs://. Vamos ver isso na etapa 10.
Nosso repositório de memória

O ADK oferece uma interface para memória de longo prazo chamada BaseMemoryService. Ele tem dois métodos:
# google.adk.memory.BaseMemoryService — ADK's interface, not your file
async def add_session_to_memory(session) # take a conversation in
async def search_memory(*, app_name, user_id, query) # hand memories back
O ADK oferece três implementações:
Turma | URI | O que é necessário |
|
| do zero. Perde tudo quando você interrompe o processo |
|
| um corpus de RAG da Vertex AI |
|
| um recurso do Agent Engine |
Vamos escrever o nosso para este workshop. Descarte o InMemoryMemoryService, que esquece tudo quando você interrompe o processo, e os outros dois, que precisam de recursos na nuvem que levam minutos para serem criados. Escolha um arquivo, porque você pode inserir dados nele com antecedência e ler em um editor. Considere memory.py como sua própria implementação. Estamos armazenando tudo em um arquivo Markdown:
class MarkdownMemoryService(BaseMemoryService):
async def search_memory(self, *, app_name, user_id, query) -> SearchMemoryResponse:
path = self._path(user_id) # memory/userx.md
return SearchMemoryResponse(memories=[
MemoryEntry(content=types.Content(
role="user", parts=[types.Part(text=path.read_text())]))])
SearchMemoryResponse e MemoryEntry são tipos do ADK, e o Memory Bank retorna os mesmos objetos. Um deles tem um serviço gerenciado por trás. O nosso tem um arquivo.
Escolha um arquivo por um motivo: você pode abrir e ler. Toda a memória de longo prazo do seu agente é uma página de texto.
Então, para onde você aponta?
Para recapitular a implementação deste workshop, dois dos três serviços são escolhidos na linha de comando. O terceiro foi implementado por nós mesmos pelo motivo acima:
Serviço | Onde você o definiu | Este workshop |
sessão |
|
|
artifact |
|
|
memória | em nenhum lugar na linha de comando |
|
Assim, memory.py cria o próprio serviço de uma variável de ambiente:
# agent/concert/memory.py
MEMORY_DIR = pathlib.Path(os.environ.get("MEMORY_DIR", "./memory"))
MEMORY_USER = os.environ.get("MEMORY_USER", "userx")
memory_service = MarkdownMemoryService(MEMORY_DIR)
. ./set_env.sh exporta os dois. O Get MEMORY_DIR define onde o agente lê a memória, e MEMORY_USER determina o arquivo.
Encontre onde as coisas estão
Agora você tem quatro lugares para armazenar um fato. Analise as quatro.
Primeiro, sua própria sessão
👉 Clique em Nova sessão na barra superior.
👉✨ Diga três coisas, uma após a outra:
Sam can't do weeknights, by the way.

Show me the seat map for the Amsterdam Saturday show.

Actually, hold that thought — what else is on in Amsterdam?
Essa terceira mensagem é importante. Ele encerra o turno que leu o mapa de assentos, e encerrar um turno é o que faz temp: desaparecer.
Três coisas foram armazenadas em três lugares diferentes:
O que foi armazenado | Qual ferramenta fez isso? | Para onde ele foi | |
1 | Sam não pode à noite durante a semana |
|
|
2 | o mapa de assentos completo, 6 KB |
| o serviço de artefato |
3 | um resumo do mapa de assentos em uma linha |
| nowhere |
👉 Agora confira. As guias Estado e Artefatos estão à esquerda de adk web.


Na guia Estado, observe que temp:seatmap foi omitido. As chaves de estado com um prefixo temp: existem apenas durante a invocação ativa e são removidas automaticamente pelo ADK antes de persistir o estado da sessão no banco de dados.
Depois, a conversa mais antiga
👉 Mude para two-days-ago no menu suspenso de sessão na parte de cima.
Essa sessão contém o histórico de conversas pré-preenchido no banco de dados.

👉 Confira o fluxo de eventos (coluna do meio). As conversas mais antigas foram substituídas por um bloco de eventos resumidos.

👉 Confira a guia Estado. Três tipos de chaves estão juntos:
target_event_id ms-ams-01 ← plain: this booking only
party_size 2 ← plain: this booking only
user:prefs {"excluded_weekdays": [...]} ← the person, every session
O prefixo user: (por exemplo, user:prefs) define o escopo do estado em todas as sessões de um determinado usuário. Por outro lado, chaves sem prefixo (
target_event_id
,
party_size
) representam o estado simples da sessão, que é limitado estritamente à sessão ativa e excluído quando ela termina.
👉 Agora compare as duas guias "Estado". Enquanto user:prefs e os artefatos armazenados persistem, temp:seatmap está ausente das duas sessões. As chaves de estado com um prefixo temp: são estritamente temporárias e nunca são persistidas no banco de dados.
Em seguida, o arquivo
👉💻 Abra no editor:
cloudshell edit ~/monstertix/memory/userx.md
Três reservas, desde janeiro. O agente lê esse cookie quando chama recall(), e ele ainda estará lá mesmo que os dados da sessão sejam perdidos.
Os quatro lugares
Onde ele fica | A próxima mensagem vai sobreviver? | Sobrevive a uma reinicialização? | Sobrevive à exclusão da sessão? |
| no | não | não |
| sim | sim | não |
| sim | sim | sim |
| sim | sim | sim, e sobrevive à exclusão do banco de dados |
um artefato | sim | sim | depende do serviço |
Leia essa tabela como sendo sobre os contêineres, não sobre o que está neles.memory/userx.md sobrevive a tudo e só contém o que remember() escolheu colocar lá. Nada grava sua conversa atual nele. Portanto, o arquivo sobreviver a uma sessão não é o mesmo que o agente lembrar do que você estava falando. A etapa 5 é onde essa distinção se torna importante.

O que é o estado
Pense no estado como um dicionário que viaja com a sessão. Leia e grave usando suas ferramentas com tool_context.state, e deixe o ADK salvar junto com a conversa.
O ADK atribui o significado dos prefixos. Você não os define em nenhum lugar. O ADK lê a primeira parte do nome da chave e a usa para decidir onde o valor vai e quanto tempo ele dura:
Uma chave chamada | O ADK armazena | Durações |
| com a sessão | até que a sessão seja excluída |
| contra o usuário | em todas as sessões que o usuário tem |
| contra o app | em todos os usuários |
| em nenhum lugar | uma invocação |
Uma invocação é um único ciclo de solicitação-resposta, incluindo todo o raciocínio do modelo, a execução da ferramenta e a resposta final.
Quando uma invocação termina, o ADK grava o novo estado no banco de dados e remove todas as chaves temp: no caminho.
Uma coisa ficou sem explicação
Em two-days-ago, você viu um resumo no lugar das primeiras falas de alguém. O ADK escreveu automaticamente quando a conversa ficou longa o suficiente.
Todo fato tem uma vida útil
5. Degradação e compactação de contexto
Resumos: esqueça os detalhes
Quando uma conversa fica muito longa, o sistema resume as mensagens mais antigas para economizar espaço e tokens.
Os resumos são bons para entender o contexto geral e economizar o uso de tokens.

👉🔴 No painel do local, pressione REDEFINIR O LOCAL. Cada etapa começa no mesmo mundo: oito programas, todos os assentos disponíveis, relógio em 1×.
👉💻 Acesse o código desta etapa:
cd ~/monstertix
. ./set_env.sh
./use-solution.sh 5 --force
O que mudou desde a última etapa:
Arquivo | O que mudou |
| o agente está envolvido em um |
todo o restante | inalterado |
agent.py não termina mais com root_agent. Ele termina assim:
app = App(
name="concert",
root_agent=root_agent,
events_compaction_config=EventsCompactionConfig(
compaction_interval=3, # summarise after every 3 turns
overlap_size=1, # re-read 1 turn either side when summarising
),
)
Saiba que adk web procura primeiro uma variável chamada app e volta para root_agent. Encapsule o agente em um App sempre que precisar de algo que se aplique a todo o aplicativo, e não a um agente: compactação aqui e capacidade de retomada mais tarde nas próximas etapas.
compaction_interval=3 significa que o ADK faz um resumo a cada três turnos. Um turno é você dizer algo e o agente terminar a resposta, não importa quantas ferramentas ele tenha chamado ao longo do caminho. Três é pequeno, então é acionado durante um workshop. Um app real usaria um número muito maior.
overlap_size=1 significa que cada resumo começa um turno antes do necessário.
O ADK resume as conversas 1, 2 e 3. Três turnos depois, ele resume o próximo lote. Com overlap_size=1, o segundo resumo começa novamente na rodada 3, não na 4:

Observe que a rodada 3 é resumida duas vezes. Isso é proposital. Se você fez uma pergunta na rodada 3 e o agente respondeu na rodada 4, uma divisão limpa coloca a pergunta em um resumo e a resposta em outro, deixando os dois confusos. Repetir uma vez é uma garantia barata contra o corte de um pensamento pela metade.
Defina os dois campos. O ADK não tem um padrão para nenhum dos dois.
Compactação e seu serviço de memória nunca se encontram
É importante ser explícito, porque as duas palavras parecem relacionadas, mas os sistemas não são.
EventsCompactionConfig está no App em agent.py, ao lado de root_agent. É uma propriedade do aplicativo, não de um serviço:
# agent/concert/agent.py
app = App(
name="concert",
root_agent=root_agent,
events_compaction_config=EventsCompactionConfig(
compaction_interval=3,
overlap_size=1,
),
)
Um resumo reescreve a conversa. Ele não reescreve o arquivo.
É por isso que remember() existe como uma chamada deliberada. Tudo o que sobrar na transcrição será lido como uma paráfrase escrita por outra pessoa. Tudo o que for gravado no arquivo será lido exatamente como foi armazenado, amanhã e na próxima semana. O final desta etapa é quando você vê essa diferença acontecer.
Onde o resumo é armazenado
Em sessions.db, na tabela events, um registro de compactação é gravado em uma linha de evento pelo agente, com um carimbo de data/hora como qualquer outro evento de turno.
Um evento comum tem um campo content, e o resumo fica em actions.compaction.
👉💻 Veja no seu próprio banco de dados:
cd ~/monstertix
. ./set_env.sh
sqlite3 sessions.db \
"select json_extract(event_data,'\$.actions.compaction.compacted_content.parts[0].text')
from events where event_data like '%compaction%';"
Esta é a aparência da forma do evento:
events row
└── event_data (JSON)
├── id, author, timestamp, invocation_id ← like every other event
├── content ← ABSENT on this one
└── actions
└── compaction
├── start_timestamp ← the window it replaced
├── end_timestamp
└── compacted_content.parts[0].text ← the summary itself
O fluxo de eventos extrai cada evento do conteúdo dele. O resumo no banco de dados e sendo enviado ao modelo a cada vez.
O segundo agente
budget_split é um agente totalmente separado, não uma ferramenta:
# agent/concert/agent.py
budget_split = Agent(
name="budget_split",
model=MODEL,
include_contents="none", # sees none of the conversation
instruction="You do ticket arithmetic and nothing else...", #just the budget
output_key="budget_plan", # its answer goes into state
)
root_agent = Agent(
...,
tools=[..., AgentTool(agent=budget_split)],
)
Por que um agente e não uma função simples? Porque o trabalho precisa de um modelo. Leia "qual destas seções se encaixa em um orçamento de US $200 para quatro pessoas e qual é o total" como uma operação aritmética envolta em julgamento, com uma resposta que uma pessoa precisa conseguir ler. Deixe uma função do Python multiplicar e não peça para ela decidir quais opções valem a pena mencionar.
Então, por que não deixar o agente principal fazer isso? A próxima linha include_contents="none" significa que o agente não recebe nada da conversa. Sem grupo de chat, sem preferências, sem 40 datas de turnê. Ele recebe a solicitação e nada mais. Essa flag só existe em um Agent, que é o motivo real de ser um agente em vez de uma função.

Prefira o comando curto: mais barato, mais rápido e muito mais difícil de desviar. Confie na aritmética, que é imune a algo dito há 20 rodadas porque nunca viu isso.
Por que
AgentTool
? Inclua o agente para que o principal possa chamá-lo como qualquer outra ferramenta. Do lado do modelo, leia budget_split como idêntico a search_events.
E
output_key="budget_plan" coloca a resposta no estado da sessão com esse nome, em vez de deixá-la solta na conversa. Assista para entender por que isso é importante.
Executar
👉💻 No terminal 1, pressione Ctrl-C e reinicie com as mesmas flags:
cd ~/monstertix
adk web agent --port 8000 --allow_origins "*" \
--session_service_uri="sqlite+aiosqlite:///$WORKSHOP/sessions.db" \
--artifact_service_uri="file://$WORKSHOP/artifacts"
👉 Inicie uma Nova sessão e tenha uma conversa real. Cinco ou seis voltas. Este funciona:
hello
what's on in Amsterdam?
Sam is coming with us, and he can't do weeknights
there are 4 of us and I can spend about $200 a head. What fits?
👉 Abra a guia Estado.
Encontre budget_plan lá, com a resposta completa do agente aritmético.

👉✨ Agora mude os números:
what if 10 people come and I can only do $120 each?
👉 Veja budget_plan de novo. Ele foi substituído, não anexado. Isso porque output_key contém o mais recente.

O que você não pode ver: o resumo
Em algum momento, o ADK resumiu as conversas anteriores.
👉💻 Em um novo Terminal, execute para ler diretamente:
cd ~/monstertix
. ./set_env.sh
./show-compaction.sh
Leia o que ele escreveu
Confira um exemplo real e resumido:
── summary 1 ──────────────────────────────────────────────────────
Conversation Language: English
**User Request:**
The user initially wanted to know what concerts are playing in Amsterdam.
Subsequently, the user provided a constraint regarding a companion, Sam.
**Context Summary:**
The user is planning a concert trip. The agent identified two events in
Amsterdam: "The Midnight Signal" at the Ziggo Dome on Tuesday, November
10 and Saturday, November 14, 2026. The user then informed the agent that a
companion named "Sam" will be joining, but "he can't do weeknights." The
agent has successfully noted this constraint for Sam.
**Key Decisions/Information Obtained:**
* **City of interest:** Amsterdam
* **Events found:** "The Midnight Signal" at the Ziggo Dome, Tuesday
10 November and Saturday 14 November 2026.
* **Companion:** Sam
* **Companion constraint:** Sam cannot attend concerts on weeknights.
**Unresolved Questions/Tasks:**
* The agent has not explicitly confirmed if the found event (which is
on a Saturday) is suitable given Sam's constraint, nor has it asked the
user for the next steps regarding this event or further event searches.
**Tools Used:**
* `recall`
* `search_events`
* `note_companion`
Ele manteve os preços, as seções, as cidades e as preferências, e observou corretamente que nada havia sido decidido ainda.
Três coisas decorrem disso:
É uma paráfrase. Nada aqui é o que alguém disse. É um relato do que foi dito, produzido pelo mesmo tipo de processo que produz tudo o que o modelo erra ocasionalmente.
Observação: as strings de resumo são armazenadas em registros de eventos de sessão internos e não são expostas diretamente como guias separadas da interface.
A cada três turnos, em uma conversa que agora é parcialmente composta de resumos anteriores.
Tente fazer uma reserva sem repetir o que já foi dito
Em uma nova sessão: não inicie uma nova. O resumo que você acabou de ler está nos eventos desta sessão, e esse é o único motivo pelo qual o agente ainda sabe algo sobre Amsterdã.
👉✨ Não dê nada de novo:
Book us something.
Assista o agente perguntar qual programa, qual seção e quantas pessoas. Marque como correto: o próprio resumo diz que o usuário nunca confirmou nada, então o agente perguntou. Dê crédito à compactação.
Mesmo quando a compactação é bem-sucedida, resumir turnos anteriores pode introduzir ambiguidade, o que exige que o agente reconfirme as restrições com o usuário.
👉✨ Responda e deixe o agendamento ser concluído:
Amsterdam Saturday, section C, 8 people
Ele lê o pedido antes de gastar qualquer coisa:
"Você quer reservar oito ingressos na seção C para The Midnight Signal em Amsterdã no sábado, 14 de novembro. O custo total será de US $760. Posso comprar essas passagens?"
👉✨ Confirme:
yes
Agora ele informa que funcionou:
Sua compra de oito ingressos na seção C para The Midnight Signal em Amsterdã no sábado, 14 de novembro, no valor total de US $760, foi concluída. O ID do seu pedido é ord_227877fe."
Confira se ele fez isso.
👉 Mude para o painel de locais. Não acredite só no que o agente diz.

No painel | O que você vai ver |
Ingressos comprados |
|
o pedido abaixo |
|
O tour, sábado em Amsterdã | a seção C diminuiu em 8 |
Atividade |
|
Confira no painel para saber quando começa a venda de ingressos.
Por que user:prefs foi a escolha certa
Lembre-se de note_companion escrever a restrição de Sam na última etapa de user:prefs e a objeção óbvia: por que se preocupar com uma ferramenta quando o agente pode simplesmente se lembrar dela?
Confira o motivo. Não inclua estado na conversa, e nenhum resumo o menciona. Verifique user:prefs e budget_plan em relação a como eles entraram e veja que não mudaram. Em seguida, confira a transcrição.
Um resumo mantém o formato geral e perde os detalhes. Armazene em outro lugar tudo o que você não pode perder.
6. Retomada do processo
Esperar 40 minutos sem ficar on-line
Quando um agente precisa esperar em uma fila de tíquetes de 40 minutos, manter um processo Python ativo e aguardando é frágil e caro.
Para lidar com longas esperas de maneira adequada:
- Salvar estado em um banco de dados: armazene onde o agente pausou para que o processo possa ser encerrado com segurança.
- Ativação sob demanda: quando a espera termina, um sinal externo reinicia o agente e retoma a execução de onde parou.
👉🔴 No painel do local, pressione REDEFINIR O LOCAL. Cada etapa começa no mesmo mundo: oito programas, todos os assentos disponíveis, relógio em 1×.
👉💻 Acesse o código desta etapa:
cd ~/monstertix
. ./set_env.sh
./use-solution.sh 6 --force
Executar
O que mudou desde a última etapa:
Arquivo | O que mudou |
|
|
|
|
join_queue retorna imediatamente com um tíquete em vez de bloquear, e ResumabilityConfig permite que a execução seja pausada e retomada depois.

👉💻 Terminal 1: o agente, sem alterações:
cd ~/monstertix
. ./set_env.sh
adk web agent --port 8000 --allow_origins "*" \
--session_service_uri="sqlite+aiosqlite:///$WORKSHOP/sessions.db" \
--artifact_service_uri="file://$WORKSHOP/artifacts"
👉✨ Peça ao agente para comprar:
Get us two tickets to the Amsterdam show.
Ele chama recall, depois search_events, depois join_queue e responde com algo parecido com isto:
You are in the queue for The Midnight Signal at Ziggo Dome, Amsterdam, on Saturday, November 14. Your position is 14203.
Perceba que ele escolheu sábado sem perguntar. Há dois shows em Amsterdã, o dia 17 é uma terça-feira, e o arquivo de memória da última etapa diz que Sam não trabalha à noite durante a semana. Esse é um fato de uma reserva anterior que decide uma questão nesta.
Observe também que ele entrou na fila antes de analisar um único assento ou preço e não disse nada sobre eles. Essa é a regra do local. Você compra os ingressos até chegar à frente. Portanto, a única coisa que vale a pena ter cedo é um lugar na fila. Quais assentos escolher é uma questão para daqui a 40 minutos.
👉 Consulte o painel de controle. O banner diz:

Agent is waiting in line — #14,203
About 2400s at 1×. Safe to kill the agent right now — it will still be here.
A fila tem 40 minutos de espera e não vai diminuir enquanto você trabalha.
👉💻 Encerrar o agente:
Ctrl-C
👉💻 Inicie novamente no terminal 1:
cd ~/monstertix
. ./set_env.sh
adk web agent --port 8000 --allow_origins "*" \
--session_service_uri="sqlite+aiosqlite:///$WORKSHOP/sessions.db" \
--artifact_service_uri="file://$WORKSHOP/artifacts"
👉✨ Na mesma sessão, pergunte ao agente qual é a posição na fila:
Where am I in line?
You are currently at position 12982 in the queue. It is not your turn yet.

Compare o tíquete e observe como a posição continua caindo enquanto nada estava sendo executado. O agente não sabia que tinha sido morto. Ele chamou check_queue com o tíquete que recebeu antes da reinicialização, porque esse tíquete estava no disco.
👉💻 Saiba o motivo:
cd ~/monstertix
sqlite3 sessions.db "select name from pragma_table_info('events');"
id
app_name
user_id
session_id
invocation_id
timestamp
event_data
Cada turno de cada conversa é uma linha, e o turno em si, quem disse, qual ferramenta foi chamada, o que foi retornado e observe que é JSON dentro de event_data.
👉💻 Encontre seu ingresso para a fila:
sqlite3 sessions.db "select event_data from events;" \
| grep -o '"ticket": "q_[a-z0-9]*", "position": [0-9]*' | tail -1
"ticket": "q_8cc2c6f9", "position": 14203
Quando join_queue retorna, o tíquete é salvo diretamente no banco de dados. Ao reiniciar, o ADK recarrega as conversas e os resultados de ferramentas anteriores do disco, permitindo que o agente continue de onde parou.
E observe o que não foi reiniciado. O agente retoma diretamente em vez de reproduzir o fluxo de trabalho desde o início. Como LongRunningFunctionTool e ResumabilityConfig persistem o estado de execução no disco, o ADK sabe que o agente já está esperando na fila, evitando que ele pesquise novamente as datas da turnê ou solicite um ingresso duplicado.

Por que join_queue e não check_queue
As duas funções retornam em milissegundos. A distinção é se o job subjacente está concluído quando a função retorna.
o que ele retorna | O trabalho foi concluído? | |
| Tíquete na fila e posição inicial | Não.O processamento continua de forma assíncrona no sistema externo. |
| Status atual da fila | Sim. Retorna uma resposta imediata e pontual. |
LongRunningFunctionTool foi projetado para operações que retornam um identificador para trabalho pendente. O encapsulamento de join_queue permite que o agente suspenda a execução enquanto a fila externa avança.
Em contraste, check_queue é síncrono e completo. Encapsular faria com que o agente suspendesse a execução incorretamente sempre que verificasse o status, mesmo quando chegasse ao início da fila e precisasse concluir a compra imediatamente.

O que fez isso funcionar
Três componentes permitem a suspensão e a recuperação da execução:
# agent/concert/agent.py
LongRunningFunctionTool(func=join_queue) # the tool returns without finishing
ResumabilityConfig(is_resumable=True) # the run may pause and be picked up
--session_service_uri="sqlite+aiosqlite:///$WORKSHOP/sessions.db"
LongRunningFunctionTool: marca uma ferramenta como não bloqueadora, retornando um identificador ativo para que o agente possa esperar sem travar a linha de execução.ResumabilityConfig: instrui o ambiente de execução a pausar o ciclo de vida de execução em uma tarefa incompleta e retomar do último estado registrado.Session Storage URI (--session_service_uri): direciona a persistência de estado para armazenamento externo (por exemplo, SQLite, Cloud SQL) em vez de memória de processo, evitando a perda de dados em reinicializações.
7. Como lidar com dados desatualizados
As informações ficam desatualizadas enquanto você espera
O histórico de conversas persistente não é um estado em tempo real. Durante longos intervalos de espera, dados externos, como disponibilidade ou preços de passagens, podem mudar sem que o agente saiba.
Um agente com informações ausentes faz uma pausa para pedir esclarecimentos, mas um agente com dados desatualizados age imediatamente com base em premissas desatualizadas. Para evitar ações inválidas, os dados sensíveis ao tempo precisam ser verificados novamente imediatamente antes da execução de operações críticas.
Não carregue um novo código ainda. Permaneça no agente da etapa anterior. Ele pode comprar ingressos, mas não tem salvaguardas contra obsolescência ou idempotência.
👉🔴 Pressione REDEFINIR O LOCAL e inicie uma nova sessão em adk web. Para isso, clique no botão + Nova sessão acima da conversa.
Inicie uma nova sessão para limpar o tíquete da fila anterior e o estado de compra pendente. Essa etapa exige um contexto de execução limpo para demonstrar o tratamento de dados desatualizados corretamente.
O snapshot fica
👉✨ Peça o mesmo programa de antes:
Get us two tickets to the Amsterdam show.
👉✨ Agora, enquanto espera na fila, pergunte sobre os assentos:
How much are the seats, and what's still available?
Section A: $210 per ticket, with 400 seats available.
Section B: $145 per ticket, with 900 seats available.
Section C: $95 per ticket, with 1200 seats available.
👉🔴 Clique em VENDER OS MELHORES LUGARES. A seção A cai para zero.
👉🔴 Clique em PULAR A ESPERA para enviar o agente para o início da fila.

👉✨ Diga para comprar:
Buy the two section A seats.
Ele executa purchase(section="A") sem verificar novamente a disponibilidade de assentos, fazendo com que a API do local retorne uma resposta de erro:
{"error": "sold_out", "available": 0,
"message": "those seats are gone — re-fetch the seatmap before buying"}
Oh no! It looks like the Section A tickets just sold out. Let me get an
updated seat map to see what's still available.

O agente só consultou o mapa de assentos depois de receber uma resposta de erro da API. A causa principal é um contexto desatualizado: no início da sessão, o agente registrou "seção A: 400 disponíveis", e esse turno permaneceu no histórico da conversa transmitido ao modelo.
Deixe claro onde as informações antigas estão armazenadas: elas permanecem no histórico de conversas, que prefixos de estado como temp: não podem substituir.

A correção
👉🔴 No painel do local, pressione REDEFINIR O LOCAL. Cada etapa começa no mesmo mundo: oito programas, todos os assentos disponíveis, relógio em 1×.
👉💻 Agora carregue o código desta etapa:
cd ~/monstertix
. ./set_env.sh
./use-solution.sh 7 --force
👉💻 No terminal 1, pressione Ctrl-C e reinicie:
cd ~/monstertix
. ./set_env.sh
adk web agent --port 8000 --allow_origins "*" \
--session_service_uri="sqlite+aiosqlite:///$WORKSHOP/sessions.db" \
--artifact_service_uri="file://$WORKSHOP/artifacts"
O que mudou:
Arquivo | O que mudou |
| a cada conversa. Executa imediatamente antes de uma compra e pode bloqueá-la |
|
|
|
|
fence.py é um callback que o ADK executa no momento antes de qualquer chamada de ferramenta:
root_agent = Agent(
...,
before_tool_callback=refresh_before_purchase, # re-read inventory, then decide
)
Prove
👉🔴 REDEFINIR O LOCAL. Coloque na fila, pressione VENDER OS MELHORES LUGARES enquanto espera e diga para comprar a seção A.
Desta vez, ele para sozinho. Assista o agente dizer algo como:
"Parece que a seção A acabou de esgotar. Preciso ler o mapa de assentos de novo."
Em seguida, observe o app chamar get_seatmap novamente, sem solicitação, e voltar oferecendo a seção C.
👉🔴 Leia com atenção o feed de atividades do local, porque ele mostra as duas partes:
error purchase failed: Section A has 0 seats left, 2 requested. Re-read the
seat map and pick again.
agent calling purchase

No painel, você vê a compra de chamadas, mas o contador de tíquetes permanece em zero. Isso acontece porque fence.py atua como uma proteção antes da execução da ferramenta:
- O agente tenta chamar a ferramenta de compra.
fence.pycaptura a solicitação primeiro e verifica a disponibilidade de assentos ao vivo.- Como os assentos estão esgotados,
fence.pyretorna um erro diretamente, cancelando a compra antes que a API seja chamada.
As instruções de comando não garantem que um LLM não vai tentar comprar assentos esgotados. As proteções de código (before_tool_callback) garantem que a compra inválida nunca chegue ao mundo real.
👉🔴 Clique em REDEFINIR O LOCAL e em CANCELAR A PRÓXIMA COMPRA.
👉✨ Peça para comprar e deixe que ele tente quantas vezes quiser.

A contagem de pedidos permanece em 1 porque o local recebeu a mesma chave de idempotência na nova tentativa e retornou a confirmação original em vez de criar um pedido duplicado.
8. Fluxo de trabalho autônomo
Os LLMs não são bons em seguir regras rígidas sempre
Os LLMs são ótimos para tomar decisões, como escolher um programa com base nas preferências do usuário. Mas confiar em um LLM para seguir regras estritas (como "sempre entre na fila antes de verificar os assentos") significa que ele vai pular etapas ou confundir a ordem de vez em quando.
Para corrigir isso, divida o fluxo de trabalho em dois tipos de etapas:
- Regras fixas (funções de código): etapas que precisam acontecer exatamente da mesma forma todas as vezes (pegar uma senha, verificar a posição) são escritas como nós de código comuns.
- Decisões (nós do agente): as etapas que exigem julgamento (escolher seções de assentos, verificar preferências do usuário) são atribuídas ao agente de IA.
Confira o que realmente está nesse fluxo. Alguns deles não têm julgamento algum:
Etapa | Julgamento? |
retirar exatamente um tíquete de fila | Não.Sempre uma, sempre antes de comprar |
verificar se é sua vez | Não. Pergunte e leia a resposta. |
escrever o briefing para o comprador | Não.Mesma frase, mesma forma, sempre |
que mostram qual seção | Sim. Preço, dia, quem vai, o que não gostam |
preferência e limitação do usuário | Sim. |
Procedimentos rigorosos não devem depender de um LLM porque os modelos probabilísticos acabam não seguindo sequências rígidas quando não são monitorados.
O Workflow do ADK separa a execução determinística do raciocínio de IA:
- Os nós de função aplicam regras fixas (como entrar em uma fila ou verificar a posição) de maneira consistente sempre.
- Os nós do agente processam decisões contextuais, como escolher assentos com base nas preferências do usuário.
- Os nós de espera suspendem a execução sem manter um processo ativo na memória.
O agente original não é substituído. Ele se torna o nó final no gráfico de fluxo de trabalho, mantendo as ferramentas, as verificações de obsolescência e as proteções de idempotência. O gráfico controla a ordem de execução, enquanto o agente executa a decisão de compra.
👉🔴 No painel do local, pressione REDEFINIR O LOCAL.
👉💻 Acesse o código desta etapa:
cd ~/monstertix
. ./set_env.sh
./use-solution.sh 8 --force
Executar
👉💻 No terminal 1, pressione Ctrl-C e reinicie. O mesmo comando de sempre:
cd ~/monstertix
. ./set_env.sh
adk web agent --port 8000 --allow_origins "*" \
--session_service_uri="sqlite+aiosqlite:///$WORKSHOP/sessions.db" \
--artifact_service_uri="file://$WORKSHOP/artifacts"
O que mudou desde a última etapa:
Arquivo | O que mudou |
| a cada conversa. A execução autônoma como um gráfico do ADK 2 |
|
|
| ele também perdeu |
Executar o gráfico: três mensagens, em ordem
Execute todas as ações a seguir no Python noturno (onde o gráfico está). Envie a mesma entrada em cada turno para observar a transição do gráfico de fluxo de trabalho pelos estados.
1. Observe com o que você está falando agora
👉 Recarregue a interface da web do ADK.
# concert/agent.py, at the bottom
buyer_agent = Agent(name="concert", ...) # ← everything you built, renamed
from .nightly import nightly
root_agent = nightly # ← what adk web actually runs
Observe que adk web executa qualquer objeto atribuído a root_agent. Aqui, root_agent aponta para o gráfico de fluxo de trabalho nightly, com buyer_agent configurado como o nó final no gráfico.
2. Começar
👉✨ Digite isso e pressione Enter:
go
Observação: em um gráfico de fluxo de trabalho automatizado, a execução inicial começa sem esperar a entrada do usuário. Enviar uma mensagem em adk web aciona a execução inicial.
👉 Agora olhe para o navegador da Web. O painel esquerdo mostra o gráfico e colore os nós à medida que eles são executados. O painel à direita é o fluxo de eventos, que mostra como as decisões estão sendo tomadas:

Assista queue_up gravar queue_ticket e queue_event_id no estado da sessão.

Você vai ver uma chamada de função chamada adk_request_input, aguardando uma resposta. Em seguida, ele fica em silêncio e a execução termina. O gráfico chegou a check_front, encontrou você na posição 14.203 e parou.
👉 Confira o painel do local: um ingresso na fila e nada em execução.

3. Ative o dispositivo e preste atenção em qual caixa você digita.
Role o fluxo de eventos até o evento adk_request_input e localize a caixa de entrada associada a ele:

Agora há dois lugares para digitar, e eles fazem coisas diferentes.
Onde você digita | O que acontece |
a caixa | responde à interrupção. A execução estacionada continua de |
a caixa de chat principal na parte de baixo | inicia uma nova invocação. O gráfico é reproduzido de |
👉✨ Digite qualquer coisa na caixa Enter your response... e pressione a seta.
👉 Terminal 1:
[check_front] #14,197 — not yet. Pausing.
Uma linha. Ele não escolheu um segundo programa nem pegou um segundo ingresso para a fila, o que teria colocado você no final de uma fila de 14.203 pessoas.
Se você vir [open] e [pick_show] sendo executados novamente, isso significa que você digitou na caixa de chat. Pressione REDEFINIR O LOCAL e comece a etapa novamente.
👉 Verifique o painel do local novamente: ainda há um ingresso, e a posição caiu um pouco porque o relógio do local continuou funcionando enquanto o seu não.

4. Chegue à frente e termine
👉🔴 Pressione PULAR A ESPERA no painel do local.

👉✨ Responda ao evento adk_request_input mais recente pela última vez — de novo na caixa dele, não na caixa de chat. 
Desta vez, o relógio check_front vai encontrar você na frente e cair. Observe o painel esquerdo acender brief e concert, os dois últimos nós.

👉 E olhe para o navegador. Este é o fim da execução, e há quatro coisas que valem a pena ler.

O gráfico está totalmente verde, e as bordas são contadas. brief e concert estão iluminados, e as bordas em pick_show, queue_up e check_front agora têm um selo.
2x
é o número de vezes que o gráfico percorreu essa aresta, uma vez por despertar. Os dois na parte de cima são o preço da retomada: o programador entra novamente no gráfico desde o início a cada vez. Isso não significa que o trabalho aconteceu três vezes, e o painel do local é a prova: um ingresso para a fila, não dois. @node(rerun_on_resume=False) é o motivo.
O gráfico

# agent/concert/nightly.py
nightly = Workflow(
name="concert_nightly",
edges=[(START, open_the_night, pick_show, queue_up,
check_front, brief, buyer_agent)],
)
Observe que dois nós são agentes e dois são funções, e trate qual é qual como a questão de design completa.
Nó | Por que esse formato |
| agent. Escolher um programa do histórico de alguém é um julgamento. Ele chama |
| função. Entrar em uma fila é uma regra. Uma regra não pode ser um cara ou coroa às 3h da manhã |
| função. "Estou na frente?" tem uma resposta certa, e o local tem essa informação. |
| agent. É |
Confira o último nó e encontre o agente que você criou. Não é uma cópia nem uma reescrita:
# agent/concert/nightly.py
from .agent import buyer_agent
Tudo isso vem junto: todas as ferramentas, a releitura do mapa de assentos em fence.py, a chave de idempotência em purchase. Só existe um.
Por que ele parou em vez de esperar
Esta é a versão que ninguém deve escrever:
# what NOT to write — this is nobody's file
while not ready: # never do this
time.sleep(1)
Ele mantém um processo ativo por 40 minutos sem fazer nada, é encerrado com o terminal e não pode ser retomado. Veja o que a check_front faz:
# agent/concert/nightly.py — check_front
if not status["ready"]:
return RequestInput(message=f"Still at #{position}. Wake me and I'll check.")
RequestInput é uma interrupção. O gráfico para onde está, a invocação vai para o armazenamento de sessão, e o processo pode ser encerrado.
A execução não custa nada enquanto espera e termina em um só lugar: ingressos comprados. Pode levar a noite toda e vários despertares para chegar lá.
9. Trabalhar com um humano
Não gaste dinheiro sem confirmar com uma pessoa
Um agente autônomo que é executado durante a noite não deve fazer escolhas financeiras caras com base em preferências ambíguas ou presumidas do histórico de chat.
Antes de executar ações importantes (como gastar dinheiro em ingressos), o agente precisa pausar, pedir que o usuário confirme o orçamento em voz alta e salvar essa confirmação explícita antes de fazer qualquer compra.
👉🔴 No painel do local, pressione REDEFINIR O LOCAL. Cada etapa começa no mesmo mundo: oito programas, todos os assentos disponíveis, relógio em 1×.
👉💻 Acesse o código desta etapa:
cd ~/monstertix
. ./set_env.sh
./use-solution.sh 9 --force
Executar
👉💻 No terminal 1, pressione Ctrl-C e reinicie. O mesmo comando de antes:
cd ~/monstertix
. ./set_env.sh
adk web agent --port 8000 --allow_origins "*" \
--session_service_uri="sqlite+aiosqlite:///$WORKSHOP/sessions.db" \
--artifact_service_uri="file://$WORKSHOP/artifacts"
O que mudou desde a última etapa:
Arquivo | O que mudou |
| Contém o que a pessoa concordou, como a frase que ela disse. |
| adicionou |
| um novo primeiro nó, |
Não o modele. Armazene a frase.
Abra agent/concert/budget.py e leia tudo:
def load(state) -> str:
"""What they agreed, verbatim. Empty string means nobody has said."""
return str(state.get("budget") or "")
state["budget"] = "up to $100 for the upper bowl or general admission, up to
$250 for the lower bowl, and up to $300 for a Saturday show"
O nó verifica duas coisas: se a pessoa disse isso e se ela confirmou.
Em seguida, ele mantém a frase no estado da sessão em vez de user:, de propósito. Um orçamento é acordado para uma reserva. "US$ 250 se forem os bons" foi dito sobre essa banda, naquela noite, e não tem nada a ver com a reserva de ingressos.
O mesmo vale para tudo o mais que eles disseram
Não pare no orçamento. As pessoas mudam de ideia enquanto decidem. Por isso, capture tudo o que elas pedem aqui, em vez de confiar no que o agente lembra.
"I live in NYC, I can only do weekends, I have 10 people coming with me"
# agent/concert/nightly.py
def what_they_asked_for(ctx) -> str:
"""Everything this person typed, joined, in their own words."""
said = []
for event in (ctx.session.events or []):
if event.author != "user":
continue
for part in (event.content.parts if event.content else []) or []:
text = (getattr(part, "text", None) or "").strip()
if text and text not in said:
said.append(text)
return "\n".join(said)
Leia Somente eventos criados pelo usuário, que mostra Somente
text
peças do function_response, para que a pergunta e resposta sobre o orçamento não sejam repetidas aqui. Sem duplicatas.
O agree_budget chama o cada execução, não apenas a primeira. Portanto, uma correção digitada enquanto o agente está na fila também é aplicada. Em seguida, open_the_night entregue ao picker as duas frases, o pedido e o orçamento, e peça para ele ler.
O gráfico, um nó mais longo
Vamos começar com uma conversa:
# agent/concert/nightly.py
nightly = Workflow(
name="concert_nightly",
edges=[(START, agree_budget, open_the_night, pick_show, queue_up,
check_front, brief, buyer_agent)],
)

Dois nós param e esperam, e eles são o mesmo mecanismo. check_front espera um local e é respondido por um relógio. agree_budget espera uma pessoa e só pode ser respondido por uma pessoa. Essa diferença é toda a etapa 10.
Executar etapa a etapa
1. Começar
👉 Atualize a interface da web do ADK e escolha o agente concert.
👉✨ Peça o que você realmente quer:
Get us two tickets to the Amsterdam show.

Na etapa 8, o gráfico não tinha usuário, então tudo o que você digitou foi descartado, e go teve o mesmo desempenho de qualquer outra coisa. Aqui, agree_budget chama what_they_asked_for(ctx) na primeira linha, que coleta tudo o que você digitou e coloca em state["request"]. Em seguida, open_the_night entrega ao seletor junto com o orçamento.
Você não especificou um dia, e Amsterdã tem dois shows. Então, assista o seletor voltar para recall(), encontre "Sam bails on weeknights" e escolha o sábado.
No navegador, a execução para em um evento adk_request_input:
Um gráfico acabou de fazer uma pergunta. A mesma interrupção da espera na fila da etapa 8, mas agora apontando para uma pessoa responder à pergunta.
2. Responda como as pessoas realmente respondem
👉✨ Na caixa Enter your response..., não na caixa de chat na parte de baixo:
About a 100 for the cheap seats. 250 if they're the good ones.
Assista até ele parar e confirme:
[agree_budget] reading back: About a hundred for the cheap seats. Two-fifty
if they're the good ones.
⚡ adk_request_input
So: About a hundred for the cheap seats. Two-fifty if they're the good
ones. Have I got that right?

3. Confirmar
👉✨ Mesma caixa:
Yes, that's right.
[agree_budget] agreed: About a hundred for the cheap seats. Two-fifty if
they're the good ones.
[open] Budget: About a hundred for the cheap seats. Two-fifty if
they're the good ones.
[pick_show] → {"section": "A", "reason": "..."}
[queue_up] ms-ams-01 section A — ticket q_5924edc2 at #14,203
[check_front] #14,203 — not yet. Pausing.
Quatro nós foram executados com uma palavra. 👉 Abra a guia Estado e encontre budget, sua frase na íntegra.

Nenhum prefixo. Esse é o estado da sessão, e é uma decisão que vale a pena tomar de propósito se você iniciar uma Nova sessão e o orçamento acabar. Assim, o sistema vai perguntar de novo.
4. Chegue à frente e termine
👉🔴 Pressione PULAR A ESPERA no painel do local.
👉✨ Responda à adk_request_input mais recente, de novo na caixa dela:
go

👉 Confira o painel do local: um pedido, a um preço dentro do que você concordou para esse tipo de assento.

Como um gráfico faz uma pergunta a uma pessoa
Abra nightly.py e leia agree_budget. Faça três coisas serem verdadeiras ao mesmo tempo e espere a mesma falha quando uma delas estiver faltando: ela pergunta para sempre.
@node(rerun_on_resume=True) # 1. run again when woken
def agree_budget(node_input, ctx):
answers = ctx.resume_inputs or {} # 3. where the answer arrives
said = answers.get(ASK_BUDGET)
if not said:
return RequestInput(
interrupt_id=ASK_BUDGET, # 2. STABLE id, not the default
message="What are you willing to spend?",
)
...
| sem ele, o nó é tratado como concluído e ignorado. | |
um | o padrão é um UUID novo por execução, então não há nada para pesquisar a resposta | |
| onde a resposta aparece. Não o valor de retorno de | |
O gráfico
É o gráfico da etapa 8 com um nó extra na frente. 
As restrições confirmadas pelo usuário têm escopo de sessão e expiram quando a conversa termina. Manter limites temporários em várias sessões pode transformar decisões únicas em padrões permanentes não intencionais.
10. A pilha de nuvem
Laptops entram em modo de espera e arquivos locais desaparecem
No seu notebook, um agente depende de que o computador fique ativo, de bancos de dados SQLite locais e de arquivos locais. Se você fechar a tampa do laptop ou implantar em um contêiner de nuvem que reinicia, os arquivos locais e a memória vão desaparecer.
Para executar de maneira confiável na produção:
- Computação em nuvem: execute o agente em um contêiner (como o Cloud Run) que permanece on-line.
- Bancos de dados duráveis na nuvem: substitua arquivos locais e SQLite por serviços de nuvem (Cloud SQL para dados de sessão, Cloud Storage para arquivos).
- Timers do Cloud: use programadores gerenciados, como o Cloud Scheduler e o Pub/Sub, para que o agente seja ativado mesmo quando o notebook estiver desligado.
👉🔴 No painel do local, pressione REDEFINIR O LOCAL. Cada etapa começa no mesmo mundo: oito programas, todos os assentos disponíveis, relógio em 1×.
👉💻 Acesse o código final:
cd ~/monstertix
. ./set_env.sh
./use-solution.sh 10 --force
Arquivo | O que mudou |
| a cada conversa. Um FastAPI que atende a página de chat e o endpoint chamado por um programador |
| a cada conversa. Python-slim e uvicorn comuns. Nada disso é específico do ADK |
| copiada, para que a página seja enviada no mesmo contêiner que o agente |
| não mudou. Byte a byte, a pasta em que você concluiu a etapa 9 |
A página que você já criou
A <strong>interface da Web do ADK</strong> é estritamente uma interface de depuração local para inspecionar estado, fluxos de eventos e gráficos de fluxo de trabalho durante o desenvolvimento. Ele não está empacotado para implantação em produção.
Para produção, use o front-end de chat independente criado na etapa 3. Ele processa mensagens e interrupções de interação do usuário pelo endpoint /wake sem expor controles de depuração internos.
Migrar o estado local para serviços de nuvem
Pare o servidor e veja o que ele gravou:
sessions.db a SQLite file next to the code
memory/userx.md a markdown file you can open in an editor
artifacts/ seat maps written to a folder
Embora os arquivos locais funcionem bem para desenvolvimento, eles falham na produção porque os contêineres do Cloud Run usam armazenamento efêmero e local da instância.
Ao executar no Cloud Run:
- As instâncias são escalonadas para zero:quando o tráfego para, o contêiner é encerrado e o sistema de arquivos local é excluído.
- As instâncias escalonam horizontalmente de forma independente:várias instâncias simultâneas não compartilham espaço em disco. Cada uma é executada com um sistema de arquivos isolado.
Se um agente salvar um tíquete de fila ou um estado de sessão em um arquivo SQLite local (sessions.db), as solicitações subsequentes encaminhadas para uma instância de contêiner nova ou diferente vão encontrar um banco de dados vazio. Para manter um estado durável em escalonamentos e reinicializações, os arquivos locais precisam ser substituídos por serviços externos gerenciados.
Em vez disso, você precisa de um estado que dure mais que um contêiner. Três serviços, um para cada arquivo:
No laptop | Na nuvem | O que é |
| Cloud SQL | um banco de dados Postgres gerenciado. Ele existe independente de algo estar em execução ou não. |
| Cloud Storage | políticas do IAM com um bucket. Arquivos como objetos, acessíveis de todas as instâncias ao mesmo tempo |
| Cloud Storage | o mesmo bucket |
| Cloud Scheduler | um cron que o Google executa. Seu terminal não é um despertador |
Criar
Provisione um banco de dados PostgreSQL, um usuário do banco de dados e um bucket do Cloud Storage. A criação da instância do Cloud SQL leva vários minutos e foi iniciada em segundo plano durante setup.sh.
👉💻 Colete e faça o restante:
cd ~/monstertix
./setup-cloud-state.sh
→ instance RUNNABLE
→ database adk created
→ user adk created
→ bucket created
→ memory memory/userx.md → gs://your-project-agent-you/memory/
✓ durable state ready. Written to .env
Esse script criou quatro coisas, uma por linha da tabela acima, e gravou dois URIs em .env:
CLOUD_SESSION_SERVICE_URI=postgresql+asyncpg://adk:...@/adk?host=/cloudsql/proj:region:instance
CLOUD_ARTIFACT_SERVICE_URI=gs://your-project-agent-you/artifacts
Como implantar o agente com gatilhos programados gerenciados
Com os serviços prontos, dê ao agente um lar permanente.
👉💻 Três ou quatro minutos. Comece e continue lendo:
cd ~/monstertix
. ./set_env.sh
./deploy-agent.sh
O script cria uma imagem de contêiner de agent/, implanta no Cloud Run e provisiona a infraestrutura de suporte orientada a eventos.
A implantação completa consiste em cinco componentes distintos:
1. gcloud run deploy the container: your agent, the graph, and the page
2. a Pub/Sub topic a queue for messages
3. a service account an identity, so the message is allowed in
4. a push subscription topic ──► /trigger/wake
5. a Cloud Scheduler job the alarm clock, on a cron
Cloud Scheduler como origem do gatilho
O Cloud Scheduler substitui os timers de execução local (clock.py) por um serviço cron gerenciado que persiste independente das sessões de terminal local, processa novas tentativas automatizadas e oferece suporte ao agendamento de fuso horário.
Pub/Sub como camada de dissociação de mensagens
Colocar um tópico do Pub/Sub entre o Cloud Scheduler e o Cloud Run oferece três vantagens arquitetônicas:
- Durabilidade da mensagem: enfileira eventos de acionamento recebidos se o serviço do Cloud Run estiver sendo inicializado a frio, reimplantado ou temporariamente indisponível.
- Políticas de novas tentativas integradas: repetem automaticamente falhas de entrega temporárias sem exigir uma lógica personalizada de novas tentativas no nível do aplicativo.
- Escalonabilidade de fan-out: permite que vários serviços downstream se inscrevam nos mesmos eventos de programação sem reconfigurar o programador.

A configuração de trigger_sources=["pubsub"] registra um endpoint de push compatível com payloads de envelope padrão do Pub/Sub, traduzindo mensagens recebidas diretamente em eventos de invocação do agente.
→ agent https://concert-you-xxxx.run.app
→ topic presale-you created
→ iam concert-trigger@your-project... may invoke concert-you
→ push → https://concert-you-xxxx.run.app/trigger/wake
→ schedule 0 3 * * *
Fale com ele
👉 Abra o URL do agente em um navegador. 👉✨ Peça para ele fazer uma reserva:
Book me 3 tickets for The Midnight Signal in Tokyo. Best seats in the house,
I don't want the cheap ones.
Ele vai perguntar quanto você quer gastar
👉✨ Responda como se estivesse falando em voz alta e confirme:
up to 250 a seat for the good ones
A execução está na fila no momento.
👉🔴 Clique em PULAR A ESPERA no painel do local para que a fila esteja pronta quando a mensagem chegar.
Para executar em uma programação real, o job precisa estar ativado. O jobs run se recusa a executar um job pausado com Job.state must be ENABLED:
gcloud scheduler jobs resume presale-$(gcloud config get-value account | cut -d@ -f1) \
--location=$GOOGLE_CLOUD_REGION
👉 Em alguns segundos, o agente executa a compra dentro dos limites de orçamento confirmados e retorna uma confirmação do pedido automaticamente.
👉🔴 Confira o painel do local. Um pedido.
Veja como fazer isso:
gcloud scheduler jobs update pubsub presale-$(gcloud config get-value account | cut -d@ -f1) \
--location=$GOOGLE_CLOUD_REGION --schedule="*/5 * * * *"
Limpar
Conte duas coisas implantadas: o local e o agente ao lado dele. Ambos têm redução da escala a zero, então um ocioso não custa nada. Exclua mesmo assim.
./destroy-agent.sh # service, topic, subscription, scheduler job
./destroy-venue.sh
Esses comandos deixam o Cloud SQL e o bucket intactos, porque eles contêm seus dados e ambos continuam gerando faturamento. Quando tiver certeza de que terminou:
./destroy-agent.sh --all
Todo o resto é local. Exclua a pasta quando terminar ou mantenha e execute no seu próprio projeto pelo tempo que quiser.
O agente, as ferramentas, os callbacks e o orçamento não mudaram. Dois URIs e uma variável de ambiente fizeram isso.
11. Visão geral do sistema e guia de solução de problemas
Diferenças de ambiente: arquitetura local x na nuvem
Seu laptop | Na nuvem | O que mudou |
| Cloud SQL | uma string de conexão |
|
| um URI |
|
| um caminho |
| um serviço do Cloud Run | um |
| Cloud Scheduler → Pub/Sub |
|
você no teclado |
| um padrão, substituído por solicitação |
A implementação do agente principal, incluindo root_agent, definições de ferramentas, proteções de callback e aplicação de orçamento, permanece completamente inalterada. Comparar solutions/step10_deploy/concert/ com a etapa 9 mostra uma correspondência exata byte a byte, validando como a abstração de serviço do ADK separa a configuração da infraestrutura da lógica do agente.
Se você não entendeu alguma coisa
Acesse o código completo e funcional em qualquer etapa:
./use-solution.sh N --force # any step. resets memory + sessions + artifacts
./use-solution.sh N --force --keep # code only, leave state alone
Quando algo quebra
Sintoma | Correção |
| ID errado. |
O modelo retorna 404 | Os aliases |
| Algo já tem a porta 8000 ou 8090. |
O agente não consegue chegar ao local |
|
A fila nunca avança | Pressione PULAR A ESPERA. Em 1×, a fila não vai chegar à frente sozinha |
Um segundo tíquete de fila aparece no painel. | Você respondeu na caixa de chat em vez da caixa |
O menu suspenso está vazio ou o app não vai carregar | O |
| Esperado. |
Tudo é estranho | Pressione REDEFINIR O LOCAL e, em seguida, |
Arquivo de memória cheio de reservas duplicadas | O |
|
|
O Cloud Shell expirou | Inicie |