1. Visão geral
O cenário da IA e da tecnologia muda mais rápido do que qualquer pessoa consegue acompanhar. Novos modelos, artigos e produtos são lançados todos os dias. Um agente de resumo que busca as manchetes de hoje, escreve resumos objetivos e gera um PDF todas as manhãs resolveria isso. No entanto, criar um significava escolher um framework, definir ferramentas em Python, escrever um loop de orquestração, empacotar um contêiner e implantar no Cloud Run. Tudo isso antes de o agente fazer uma única solicitação da Web.
Os agentes gerenciados na API Gemini mudam a equação. Você escreve dois arquivos de configuração do Markdown e um script renderizador pré-criado, faz uma chamada de API e uma sandbox do Ubuntu real é inicializada, navega na Web, escreve seus resumos e gera um PDF. Nenhum contêiner. Nenhuma implantação. Nenhum código de orquestração.
Neste codelab, você vai criar exatamente esse agente: de uma função vazia a um resumo diário funcional, um conceito por vez.
O que você criará
- Criar e executar seu primeiro agente gerenciado em uma sandbox real do Linux
- Personalize o agente com estilo editorial, fontes da Web e uma habilidade de PDF
- Adicione um hook de segurança para bloquear comandos destrutivos antes da execução
- Baixe o PDF gerado pelo agente
- Refinar o resumo em uma conversa multiturno sem buscar novamente na Web
- Salve a configuração do agente e invoque-a por ID em execuções futuras
- Enviar o resumo para sua caixa de entrada usando a API Gmail
- Programe o agente para ser executado e enviado automaticamente todos os dias
O que é necessário
- Python 3.10 ou mais recente
- Uma chave da API Gemini: aistudio.google.com/api-keys (nível sem custo financeiro incluído; faturamento recomendado para execuções ininterruptas)
2. O que são agentes gerenciados na API Gemini?
Três níveis de sistemas de IA
Antes de começar a programar, confira onde os agentes gerenciados se encaixam em relação às duas alternativas:
Nível | O que é | Quem gerencia a infraestrutura? |
LLM padrão | Você dá um comando, e ele responde com texto. Sem as mãos, sem memória, sem Internet. | N/A: não pode fazer nada por conta própria |
Agente auto-hospedado | Você conecta ADK/LangChain/AutoGen + Docker + ferramentas + memória. | Você: tudo (ou uma plataforma gerenciada como o Agent Engine) |
Agente gerenciado | Você dá um objetivo a ele. O Google provisiona um sandbox seguro. O agente escreve e executa código, lê erros, pesquisa na Web e corrige bugs de forma autônoma. | Google: tudo |
Este codelab é sobre a terceira linha. Você fornece uma tarefa e arquivos de configuração. O Google cuida de todo o resto.
O que você pode criar com o ADK e o Cloud Run
Para criar um agente de resumo de notícias que navega na Web, executa Python e gera um PDF, você precisaria de tudo isso com o ADK + Cloud Run:
# agent.py: define tools and wire up the agent
from google.adk.agents import LlmAgent
from google.adk.tools import google_search, built_in_code_execution
agent = LlmAgent(
name="digest-agent",
model=MODEL,
instruction=AGENTS_MD, # your editorial voice and rules
tools=[google_search, built_in_code_execution],
)
# app.py: serve the agent over HTTP
from google.adk.runners import FastApiRunner
runner = FastApiRunner(agent=agent)
app = runner.app
# pdf_tool.py: custom tool, install reportlab, render PDF
# scraper.py: custom tool, fetch each news source
# streaming.py: wire agent events to your SSE endpoint
# Dockerfile: package everything
FROM python:3.12
COPY . /app
RUN pip install google-adk reportlab requests
CMD ["uvicorn", "app:app", "--host", "0.0.0.0"]
# Deploy to Cloud Run
gcloud run deploy digest-agent \
--image gcr.io/your-project/digest-agent \
--set-secrets GEMINI_API_KEY=gemini-key:latest \
--memory 2Gi
Isso é antes de o agente ser executado pela primeira vez. Você ainda tem isolamento de sandbox (para que o agente não danifique seu servidor), instalação de pacotes, gerenciamento de estado entre chamadas de ferramentas e a infraestrutura de streaming para enviar eventos a um cliente.
O que os agentes gerenciados substituem
from google import genai
client = genai.Client()
stream = client.interactions.create(
agent="antigravity-preview-05-2026",
input="Generate the digest.",
stream=True,
environment={
"type": "remote",
"sources": [ # your config files, mounted at startup
{
"type": "inline",
"target": ".agents/AGENTS.md",
"content": AGENTS_MD,
},
{
"type": "inline",
"target": ".agents/skills/digest-pdf/SKILL.md",
"content": SKILL_MD,
},
{
"type": "inline",
"target": ".agents/skills/digest-pdf/scripts/generate_pdf.py",
"content": GENERATE_PDF_PY,
},
],
},
)
O que o ADK + Cloud Run exige | O que os agentes gerenciados fazem por você |
Imagem do contêiner + Dockerfile + CI/CD | Sandbox do Ubuntu totalmente gerenciado (Python 3.12, Node 22, 4 CPUs / 16 GB de RAM) |
Implantação e escalonamento do Cloud Run | Provisionado por interação, expira automaticamente após 7 dias de inatividade |
Isolamento do sandbox | Isolado por interação |
Ferramenta de PDF personalizada + | O agente instala pacotes no sandbox |
Infraestrutura de streaming de SSE |
|
Definições de ferramentas em Python | Ferramentas integradas: navegação na Web, execução de código, sistema de arquivos |
Gerenciamento de estado entre chamadas de ferramentas | Integrado ao ciclo de raciocínio do agente |
Você escreve arquivos de configuração (AGENTS.md, SKILL.md, um script pré-criado) e faz uma chamada de API. O Google cuida de todo o resto.
Como o sandbox funciona
interactions.create() call
│
▼
Google provisions Ubuntu sandbox (Python 3.12, Node 22, 4 CPU / 16 GB RAM)
│
▼
Agent reasoning loop:
plan → fetch URLs → run Python → write files → reason → repeat
│
▼
Events stream back in real time: tool calls, text chunks, completion
│
▼
interaction.completed → environment_id + interaction_id
O sandbox persiste por 7 dias de inatividade. Você pode retomar com environment_id para refinar a saída, executar tarefas de acompanhamento ou bifurcar em um agente salvo e nomeado.
3. Configurar
Opção A: Cloud Shell (recomendado)
Clique no botão abaixo para abrir este codelab no Google Cloud Shell. Todas as dependências são pré-instaladas.
Opção B: configuração local
git clone https://github.com/Saoussen-CH/tech-digest-managed-agent.git
cd tech-digest-managed-agent
Instale o uv, se necessário:
curl -LsSf https://astral.sh/uv/install.sh | sh
configurar sua chave de API
cp .env.example .env
cloudshell edit .env
Defina sua chave:
GEMINI_API_KEY=your-key-here
Instalar dependências
uv sync
4. Faça sua primeira chamada de agente
Abra o arquivo inicial
cloudshell edit run_digest.py
run_digest() tem uma tarefa TODO para preencher agora e mais três para a próxima etapa. Dois assistentes já estão pré-preenchidos acima dele:
load_source(path): lê um arquivo de.agents/relativo ao script. Você vai usá-lo no próximo exercício para montar o estilo editorial, o manual em PDF e o renderizador na sandbox.run_stream(stream): processa o fluxo de eventos e retorna(environment_id, interaction_id). Você não precisa escrever o loop de eventos.
O que adicionar
TODO 1:substitua pass por (ignore os TODOs 3 e 4 por enquanto, eles são para a próxima etapa):
from google import genai
client = genai.Client()
stream = client.interactions.create(
agent=BASE_AGENT,
agent_config={"type": "antigravity", "model": "gemini-3.7-flash"},
input="Fetch the Hacker News front page and list the top 5 stories.",
stream=True,
environment="remote",
)
environment_id, interaction_id = run_stream(stream)
print(f"\nDone. environment_id={environment_id}")
O que cada parte faz
genai.Client() lê GEMINI_API_KEY do ambiente. Todo o resto passa por esse cliente.
interactions.create() é a chamada principal. Quatro parâmetros fazem isso funcionar:
agent=BASE_AGENT: seleciona o agente do Antigravity (antigravity-preview-05-2026), um agente gerenciado de uso geral com tecnologia Gemini 3.7 Flash por padrão. É possível configurar o modelo usandoagent_config(opções:gemini-3.7-flash,gemini-3.6-flash,gemini-3.5-flash,gemini-3.5-flash-lite). Ele vem com três ferramentas integradas ativadas por padrão:code_execution(executar Bash, Python, Node.js),google_searcheurl_context(buscar e ler páginas da Web). As ferramentas do sistema de arquivos (read_file,write_file,list_files) são ativadas automaticamente quando você transmite o parâmetroenvironment. Uma chamada provisiona um ambiente Ubuntu totalmente gerenciado com Python 3.12, Node.js 22, git, pip e curl pré-instalados. Não há contêiner para criar nem implantação para executar.input: a tarefa desta execução. O agente navega pelo Hacker News e analisa os resultados.environment="remote": provisiona um novo sandbox na nuvem para essa interação.stream=True: retorna um iterável de eventos em vez de bloquear. Sem ele, a chamada aguarda de 30 a 90 segundos e retorna toda a saída de uma vez comointeraction.output_text. Com o streaming, você vê o motivo e a ação do agente conforme eles acontecem. O streaming não é um recurso avançado aqui: é o padrão certo, porque uma caixa preta de 90 segundos não dá nenhum sinal sobre se o agente está funcionando ou travado.
environment_id é um handle do sandbox que acabou de ser executado. Após interaction.completed, o sandbox não é desligado: ele fica ativo por até 7 dias. O environment_id é como você volta a ele. Transmita para uma segunda chamada interactions.create(), e o agente vai retomar no mesmo sistema de arquivos, com os mesmos arquivos e pacotes instalados, como se nunca tivesse saído. A próxima etapa usa esse valor para baixar o PDF sem executar o agente novamente, e a etapa seguinte o usa para continuar a conversa.
interaction_id é um identificador da rodada de conversa que acabou de ser concluída. Transmita como previous_interaction_id na próxima chamada, e o agente terá memória completa do que disse e fez nesta vez.
Verificar
uv run python run_digest.py
Você vai ver a saída ao vivo enquanto o agente trabalha:
[agent started]
[tool] run_code
Here are the top 5 stories currently on the Hacker News front page, retrieved via the official Hacker News API:
1. **Qwen 3.6 27B is the sweet spot for local development** (471 points)
2. **.self: A new top-level domain designed to support self-hosting** (116 points)
...
Done. environment_id=e3de58774073f75a6ef42924c6ce2e88
A API retorna um environment_id real mesmo com environment="remote". O sandbox foi executado. O que está faltando é a configuração: sem voz, sem skill, sem gerador de PDF. O agente apenas imprimiu histórias como texto e parou. A próxima etapa adiciona esses dados.
Cada linha de saída é mapeada para um evento de run_stream():
| O que é | O que |
| agente buscando um URL |
|
| agente executando código no sandbox |
|
| agente pesquisando na Web |
|
| ferramentas de arquivos e outros |
|
| texto escrito pelo agente | transmitido diretamente para stdout |
5. Personalizar o agente
O agente não tinha instruções: sem voz, sem habilidade, sem gerador de PDF. Nesta etapa, você carrega os arquivos de configuração de .agents/ e os monta no sandbox.
O que mudar
Faça quatro mudanças em run_digest.py:
TODO 2:abaixo de load_source(), adicione as três constantes no nível do módulo (elas ficam fora de run_digest(), na parte de cima do arquivo):
AGENTS_MD = load_source(".agents/AGENTS.md")
SKILL_MD = load_source(".agents/skills/digest-pdf/SKILL.md")
GENERATE_PDF_PY = load_source(".agents/skills/digest-pdf/scripts/generate_pdf.py")
Abra cada arquivo para ver o que você está carregando: AGENTS.md define o estilo editorial e as regras de fluxo de trabalho; SKILL.md é o manual em PDF com instruções detalhadas; generate_pdf.py é o renderizador pré-criado que o agente vai executar.
Agora faça mais duas mudanças em run_digest():
TODO 3:mude environment de "remote" para o dicionário de fontes e defina input como "Generate the digest.":
environment={
"type": "remote",
"sources": [
{
"type": "inline",
"target": ".agents/AGENTS.md",
"content": AGENTS_MD,
},
{
"type": "inline",
"target": ".agents/skills/digest-pdf/SKILL.md",
"content": SKILL_MD,
},
{
"type": "inline",
"target": ".agents/skills/digest-pdf/scripts/generate_pdf.py",
"content": GENERATE_PDF_PY,
},
],
},
TODO 4:adicione esta linha logo após print(f"\nDone. environment_id={environment_id}"):
save_env(ENVIRONMENT_ID=environment_id, INTERACTION_ID=interaction_id)
save_env já está definido em run_digest.py. Ele grava os dois IDs em .env para que a próxima etapa possa baixar o PDF sem executar o agente novamente.
O que cada fonte faz
Cada origem é um arquivo montado no sistema de arquivos do sandbox na inicialização, antes da execução do agente. Os caminhos target correspondem ao local em que o arnês Antigravity espera encontrá-los:
.agents/
├── AGENTS.md ← auto-loaded as global instructions
└── skills/
└── digest-pdf/
├── SKILL.md ← auto-discovered and registered as a skill
└── scripts/
└── generate_pdf.py ← pre-built renderer the agent can run
caminho | Variável | O que o arnês faz com ele |
|
| Carregadas automaticamente como instruções permanentes: estilo editorial, fluxo de trabalho, regras de execução |
|
| Descoberta e registrada automaticamente como uma capacidade nomeada. O agente a invoca pelo nome. |
|
| Renderizador de PDF pré-criado. O agente escreve |
Verificar
uv run python run_digest.py
A execução leva de 1 a 3 minutos. Você vai ver o agente lendo arquivos de configuração, gravando resumos e salvando o PDF:
[agent started]
[tool] read_file (/.agents/skills/digest-pdf/SKILL.md)
[tool] list_files (/.agents/skills/digest-pdf/scripts)
[tool] read_file (/.agents/skills/digest-pdf/scripts/generate_pdf.py)
[tool] run_code
[tool] write_file (/workspace/summaries.json)
[tool] run_code
[tool] delete_file (/tmp/test_scrape.py)
I have successfully generated today's tech news digest and saved the formatted document to /workspace/digest.pdf.
Done. environment_id=4129ffd75574e308748e9425d7ec828f
environment_id agora é um valor real: o sandbox foi executado com seus arquivos de configuração, e o agente criou digest.pdf. A próxima etapa adiciona um gancho de segurança antes do download.
6. Adicionar um gancho de segurança
Os hooks permitem executar um script dentro da sandbox antes ou depois de cada chamada de ferramenta. O agente de resumo usa code_execution para executar scripts Python. Assim, um hook pre_tool_execution pode interceptar essas chamadas e bloquear comandos destrutivos do shell antes da execução.
O ambiente de execução lê .agents/hooks.json do sandbox. Antes de cada chamada de ferramenta de correspondência, ele transmite os detalhes da chamada para o script de gate em stdin. O script imprime {"decision": "allow"} ou {"decision": "deny", "reason": "..."} em stdout. Uma negação cancela a chamada de ferramenta, e o agente vê seu motivo e se corrige.
O que adicionar
TODO 5:em run_digest.py, adicione estas duas constantes perto da parte de cima, depois das chamadas load_source atuais:
import json
HOOKS_JSON = json.dumps({
"safety-gate": {
"pre_tool_execution": [
{
"matcher": "code_execution",
"hooks": [
{
"type": "command",
"command": "python3 /.agents/hooks-scripts/gate.py",
"timeout": 10,
}
],
}
]
}
}, indent=2)
GATE_PY = """\
#!/usr/bin/env python3
import sys, json
data = json.load(sys.stdin)
cmd = str(data.get("tool_call", {}).get("args", {}))
if "rm -rf" in cmd:
print(json.dumps({"decision": "deny", "reason": "Destructive command blocked by safety gate."}))
else:
print(json.dumps({"decision": "allow"}))
"""
TODO 6:adicione mais duas entradas à lista sources em interactions.create():
{"type": "inline", "target": ".agents/hooks.json", "content": HOOKS_JSON},
{"type": "inline", "target": ".agents/hooks-scripts/gate.py", "content": GATE_PY},
Como os hooks são disparados na execução do digest
Sempre que o agente chama code_execution para executar um script Python ou um comando do shell, o ambiente de execução transmite os detalhes da chamada para gate.py primeiro. Se o comando contiver rm -rf, o hook vai retornar deny, e o agente vai receber o motivo da rejeição e tentar novamente com uma alternativa segura. Todas as outras chamadas de execução de código passam sem alterações.
Verificar
uv run python run_digest.py
A saída é idêntica à anterior: o portão de segurança permite todos os comandos normais de geração de PDF. Para confirmar que o hook é acionado, mude temporariamente a entrada do agente para pedir que ele execute rm -rf /tmp/test. O agente vai informar que o comando foi bloqueado e escolher uma alternativa.
7. Faça download do PDF
O agente gravou digest.pdf em /workspace/digest.pdf no sandbox. O snapshot do ambiente está disponível como um arquivo tar pela API Gemini Files.
Instale o requests, se necessário:
uv pip install requests
O que preencher
Abra download_pdf.py. Ele tem duas tarefas TODO.
TODO 1:preencha a chamada requests.get():
r = requests.get(
f"https://generativelanguage.googleapis.com/v1beta/files/environment-{environment_id}:download",
params={"alt": "media"},
headers={"x-goog-api-key": api_key},
allow_redirects=True,
)
r.raise_for_status()
O URL aborda o snapshot do sandbox. params={"alt": "media"} retorna bytes brutos em vez de metadados. Seu GEMINI_API_KEY atual também autentica a API Files.
TODO 2:encontre e extraia o PDF do arquivo tar:
member = next(m for m in tar.getmembers() if m.name.endswith("workspace/digest.pdf"))
tar.extract(member, path=tmp, filter="data")
O prefixo do caminho tar varia entre as execuções. Portanto, pesquise por sufixo em vez de codificar o caminho exato. filter="data" suprime o aviso de descontinuação do Python 3.13 sobre extração de tar não segura.
Verificar
uv run python download_pdf.py
Saved digest.pdf (48,231 bytes)
Abra digest.pdf no mesmo diretório. Ele contém o resumo formatado que o agente gerou de páginas da Web ativas.
8. Continuar a conversa
Você já tem o digest.pdf. Se você só queria o arquivo, está tudo pronto. Esta etapa é sobre algo diferente: pedir ao agente para mudar o resumo sem buscar novamente na Web.
O sandbox ainda está ativo. O agente ainda tem /workspace/digest.pdf e lembra de todas as matérias que resumiu. Uma segunda chamada interactions.create() envia uma mensagem de acompanhamento para essa mesma sandbox. Aqui, você pede para adicionar uma nota "Por que isso é importante" em cada matéria, e o PDF é atualizado no lugar, sem nova busca e sem novo resumo.
O que preencher
Abra refine_digest.py. Ele tem três TODOs.
TODOs 1 e 2:preencha os dois parâmetros multiturno em interactions.create():
environment=environment_id,
previous_interaction_id=interaction_id,
O environment=environment_id retoma o mesmo sandbox com os arquivos e pacotes dele. previous_interaction_id=interaction_id dá ao agente o histórico de conversas. O restante continua igual.
TODO 3:persistir o novo interaction_id de volta para .env após o loop de eventos:
save_env(INTERACTION_ID=interaction_id)
Cada chamada interactions.create() produz um novo interaction_id. Ao reescrever, a próxima execução passa esse refinamento como previous_interaction_id, encadeando as ações corretamente. O ID da sandbox nunca muda, então ENVIRONMENT_ID não precisa ser atualizado.
Os dois parâmetros que fazem o multiturno funcionar
ID | O que ele preserva | Analogia |
| Arquivos, pacotes instalados, estado do sistema: tudo no sistema de arquivos do Linux | Manter a mesma mesa de escritório entre as reuniões |
| Histórico da conversa: o que o agente disse e fez em interações anteriores | Lembrar o que foi discutido na última reunião |
Você pode transmitir qualquer um dos IDs de forma independente:
environment_idapenas: reutilize arquivos e pacotes, mas inicie uma nova conversa. Útil para uma nova tarefa no mesmo espaço de trabalho.previous_interaction_idapenas: continue o contexto da conversa, mas em uma nova sandbox (os arquivos sumiram).- Ambos: continuidade total, que é o que esta etapa usa.
Sem environment_id: sandbox em branco, sem PDF. Sem previous_interaction_id: não há contexto, o agente não pode refinar uma seção específica.
Verificar
uv run python refine_digest.py
O stream precisa ser rápido, já que o agente não está buscando nada de novo. Depois que ele terminar:
Refinement done.
Saved digest_v2.pdf (52,418 bytes)
Abra digest_v2.pdf e compare com digest.pdf. Cada matéria agora tem uma linha "Por que isso é importante".
9. Persistir uma configuração de agente gerenciado
Até agora, todas as chamadas transmitiram AGENTS.md, SKILL.md e generate_pdf.py inline. Isso funciona, mas o código de chamada carrega todo o conteúdo do arquivo em cada execução. O agents.create() incorpora a configuração a um agente nomeado salvo no lado do Google. A próxima invocação apenas transmite o ID do agente:
Inline calls: send sources on every call
Named agent: bake once → invoke by ID, no sources
O que preencher
Abra save_agent.py. Ele tem uma tarefa (TODO 1).
Observe que as constantes são importadas diretamente de run_digest.py (sem duplicação):
from run_digest import BASE_AGENT, AGENTS_MD, SKILL_MD, GENERATE_PDF_PY
TODO 1:preencha a chamada agents.create():
agent = client.agents.create(
id="my-digest",
base_agent=BASE_AGENT,
agent_config={
"type": "antigravity",
"model": "gemini-3.7-flash",
},
description="Daily tech digest with editorial voice and PDF generation.",
base_environment={
"type": "remote",
"sources": [
{
"type": "inline",
"target": ".agents/AGENTS.md",
"content": AGENTS_MD,
},
{
"type": "inline",
"target": ".agents/skills/digest-pdf/SKILL.md",
"content": SKILL_MD,
},
{
"type": "inline",
"target": ".agents/skills/digest-pdf/scripts/generate_pdf.py",
"content": GENERATE_PDF_PY,
},
],
},
)
agent_config define o modelo subjacente. gemini-3.7-flash é o padrão e a melhor opção para esse fluxo de trabalho. gemini-3.6-flash, gemini-3.5-flash e gemini-3.5-flash-lite estão disponíveis se você quiser uma execução mais leve ou de menor custo.
base_environment (não environment) é a principal diferença da chamada inline na etapa anterior: as fontes são armazenadas no lado do Google e montadas automaticamente em todas as invocações futuras. Execute uma vez, não em todas as execuções de resumo.
Verificar: salvar o agente
uv run python save_agent.py
Saved: my-digest
my-digest: Daily tech digest with editorial voice and PDF generation.
Invocar o agente salvo
Abra invoke_agent.py. Ele chama o agente salvo por ID sem fontes:
stream = client.interactions.create(
agent="my-digest",
input="Generate the digest.",
stream=True,
environment="remote",
)
Compare isso com a chamada inline: agent=BASE_AGENT é substituído por "my-digest", e o bloco environment completo com três fontes inline é substituído por environment="remote". A configuração já está integrada ao lado do Google.
Verificar: invocar o agente salvo
uv run python invoke_agent.py
Você vai ver a mesma transmissão ao vivo da execução inline, mas a chamada não terá arquivos de origem. Após a execução, ENVIRONMENT_ID e INTERACTION_ID em .env são atualizados para que você possa continuar com refine_digest.py como antes.
[agent started]
[tool] read_file
[tool] write_file
[tool] run_code
I have successfully created today's tech news digest.
Done. environment_id=9a1c3e02-...
10. Enviar pelo Gmail
O agente gerou o resumo e o salvou em /workspace/digest.pdf. Até agora, você baixou o arquivo localmente. Nesta etapa, o agente chama a API REST do Gmail na caixa de sandbox para entregar o e-mail diretamente na sua caixa de entrada.
A abordagem: você recebe um token de acesso do OAuth 2.0 localmente e o transmite ao agente no comando input. O agente usa code_execution para criar um e-mail MIME com o PDF anexado e enviá-lo por POST à API Gmail. Sem ferramentas personalizadas, sem registro do servidor MCP.
Pré-requisitos
Ative a API Gmail no seu projeto do GCP e crie um ID do cliente OAuth 2.0:
- Acesse console.cloud.google.com/apis/library/gmail.googleapis.com e ative a API Gmail.
- Acesse APIs e serviços > Credenciais > Criar credenciais > ID do cliente OAuth 2.0.
- Tipo de aplicativo: App para computador. Faça o download do JSON e salve-o como
credentials.jsonna raiz do projeto.
Adicione o e-mail do destinatário em .env:
RECIPIENT_EMAIL=you@gmail.com
Instale as bibliotecas de autenticação, se necessário:
uv sync
O que preencher
Abra send_digest.py. Ele tem duas tarefas TODO.
TODO 1:carregue ou atualize um token de acesso do OAuth 2.0:
creds = None
if TOKEN_FILE.exists():
creds = Credentials.from_authorized_user_file(TOKEN_FILE, SCOPES)
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
TOKEN_FILE.write_text(creds.to_json())
else:
flow = InstalledAppFlow.from_client_secrets_file("credentials.json", SCOPES)
creds = flow.run_local_server(port=8080, open_browser=False)
TOKEN_FILE.write_text(creds.to_json())
Remova a linha raise NotImplementedError depois de adicioná-la. Na primeira execução, isso abre um navegador para a tela de permissão OAuth. O token é armazenado em cache em .gmail_token.json para execuções futuras.
PENDENTE 2:substitua input="" pelas instruções de e-mail. O token já está no escopo como creds.token:
input=(
"Use the Gmail REST API to send an email:\n"
f" To: {recipient}\n"
" Subject: Tech Digest - <today's date in YYYY-MM-DD format>\n"
" Attachment: /workspace/digest.pdf attached as digest.pdf\n\n"
"For the body, read /workspace/summaries.json and format it as a "
"human-readable newsletter, NOT raw JSON. Use this structure:\n"
" Tech Digest - <date>\n\n"
" === <source name> ===\n"
" 1. <title>\n"
" <summary>\n\n"
"Steps:\n"
"1. Parse /workspace/summaries.json and build the formatted body text above.\n"
"2. Read /workspace/digest.pdf as bytes.\n"
"3. Build a MIME multipart message using Python's email library.\n"
"4. Base64url-encode the raw message.\n"
"5. POST to https://gmail.googleapis.com/gmail/v1/users/me/messages/send "
"with Authorization header using this token: "
f"{creds.token}"
),
O que cada parte faz
A interação retoma o mesmo sandbox em que o agente já gerou digest.pdf e summaries.json. previous_interaction_id dá ao agente o histórico de conversas.
O token de acesso é transmitido na string input. O agente lê o texto do comando e o usa no cabeçalho Authorization: Bearer ao chamar a API Gmail. Ele nunca acessa sua máquina local ou sistema de arquivos.
O agente usa code_execution para escrever e executar um script Python na sandbox: ele lê summaries.json, formata como uma newsletter, lê digest.pdf, cria uma mensagem MIME de várias partes, codifica em base64url e envia por POST para https://gmail.googleapis.com/gmail/v1/users/me/messages/send.
Verificar
uv run python send_digest.py
Sending digest...
[agent started]
[tool] read_file (/workspace/summaries.json)
[tool] run_code
[tool] run_code
Email sent successfully.
Email sent. Check your inbox.
Verifique sua caixa de entrada. O e-mail chega com o corpo formatado como uma newsletter e o digest.pdf anexado.
11. Programar execuções diárias
Até agora, cada etapa foi acionada manualmente. Com os acionadores, é possível programar a execução automática do agente nomeado em uma expressão cron. O agente é acionado no horário programado, executa o fluxo de trabalho completo de resumo, e o ambiente persiste entre as execuções. Assim, os pacotes instalados na primeira execução ficam disponíveis em todas as execuções subsequentes.
Manual: python run_digest.py → runs once, now
Trigger: client.triggers.create() → runs every morning, automatically
O que preencher
Abra create_trigger.py. Ele tem uma tarefa pendente.
TODO 1:preencha a chamada triggers.create(). O gatilho executa o fluxo de trabalho completo todos os dias: gera o resumo E envia para sua caixa de entrada. Como os tokens de acesso expiram em uma hora, ele injeta o token de atualização de .gmail_token.json como uma fonte inline para que o agente possa trocá-lo por um token novo em cada execução.
trigger = client.triggers.create(
schedule="0 9 * * *",
time_zone="UTC",
display_name="daily-tech-digest",
max_consecutive_failures=3,
execution_timeout_seconds=600,
interaction={
"agent": "my-digest",
"input": (
f"Generate the daily tech digest following AGENTS.md instructions. "
f"Then send an email to {recipient}:\n"
"- Subject: Tech Digest - <today's date in YYYY-MM-DD format>\n"
"- Body: the content of /workspace/summaries.json formatted as a readable "
"newsletter (NOT raw JSON).\n"
"- Attachment: /workspace/digest.pdf\n\n"
"For Gmail auth: read /workspace/.gmail_creds.json, POST to "
"https://oauth2.googleapis.com/token with grant_type=refresh_token "
"and the client_id, client_secret, refresh_token from the file to get an "
"access_token. Then POST to "
"https://gmail.googleapis.com/gmail/v1/users/me/messages/send "
"with Authorization: Bearer <access_token>."
),
"environment": {
"type": "remote",
"sources": [
{
"type": "inline",
"target": "/workspace/.gmail_creds.json",
"content": gmail_creds,
}
],
},
},
)
execution_timeout_seconds=600 é o tempo limite padrão. O max_consecutive_failures=3 pausa o gatilho automaticamente após três execuções com falha seguidas. O padrão da API é cinco, mas três é mais conservador para um workshop.
A lista sources injeta .gmail_creds.json na sandbox em /workspace/.gmail_creds.json. O agente lê, troca o token de atualização por um token de acesso novo e chama a API Gmail. Os tokens de atualização não expiram, então isso funciona em todas as execuções programadas sem atualização manual do token.
Remova a linha raise NotImplementedError depois de adicionar a chamada.
Verificar
uv run python create_trigger.py
Trigger created: trig_abc123
Next run: 2026-07-23T09:00:00Z
O create_trigger.py salva automaticamente o ID do gatilho em .env.
Para verificar o histórico de execução após uma execução:
uv run python check_trigger.py
Para acionar o gatilho imediatamente sem esperar pelo próximo horário programado:
uv run python fire_trigger.py
Para pausar ou excluir o gatilho:
uv run python pause_trigger.py
12. Limpeza
O sandbox expira automaticamente após 7 dias de inatividade. Não há servidores para parar. Nenhum contêiner para excluir.
Se você salvou uma configuração de agente, exclua-a:
uv run python delete_agent.py
13. Resumo
Você criou um agente gerenciado do zero, um conceito por vez. Confira o que cada exercício ensinou:
Exercício | Conceito | API Key |
Faça sua primeira chamada | Provisionar um sandbox real do Linux e transmitir os eventos ao vivo |
|
Personalizar o agente | Montar arquivos de configuração; manter IDs em |
|
Adicionar um gancho de segurança | Interceptar chamadas de ferramentas antes da execução e negar comandos destrutivos |
|
Faça download do PDF | Baixar o PDF sem executar o agente novamente | API Gemini Files |
Continuar a conversa | Continuar a conversa sem buscar novamente na Web |
|
Persistir configuração do agente | Persistir a configuração do agente; invocar por ID, sem necessidade de fontes |
|
Enviar pelo Gmail | Receba um token OAuth localmente, transmita-o ao agente, que chama a API REST do Gmail via | OAuth 2.0, |
Programar execuções diárias | Executar o agente automaticamente em uma programação cron |
|
Padrões principais
- Uma chamada, um sandbox:
interactions.create()processa toda a infraestrutura (sem contêineres para implantação, sem pacotes para instalação local). - Streaming progressivo: o
stream=Truetransforma uma caixa preta de 90 segundos em um feed publicado de chamadas de ferramentas e partes de texto. - Fontes in-line: monte
AGENTS.md,SKILL.mde scripts pré-criados no sandbox sem etapas de upload ou implantação. - Descoberta automática de arneses: os arquivos colocados em
.agents/são coletados automaticamente (não é necessário configurar o SDK). - Estado bidimensional:
environment_idrastreia arquivos e pacotes;previous_interaction_idrastreia o contexto da conversa. Qualquer um pode ser transmitido de forma independente. - Download de snapshots: o ambiente é um tarball de sistema de arquivos completo, acessível pela API Gemini Files.
- Agentes nomeados: o
agents.create()incorpora a configuração de forma permanente. As chamadas futuras transmitem apenas o ID do agente eenvironment="remote", sem fontes. - Hooks:
hooks.json+ uma ferramenta de interceptação de script de gate chama antes da execução; uma respostadenycancela a chamada e o agente se corrige - Chamadas de API externa: transmita uma credencial no comando
input. O agente escreve e executa o código de integração no sandbox usandocode_execution. - Acionadores: programe um agente em uma expressão cron com
client.triggers.create(). O ambiente persiste em todas as execuções.
ADK + Cloud Run x agentes gerenciados: a diferença em resumo
Capacidade | ADK + Cloud Run | Agentes gerenciados na API Gemini |
Provisionar uma sandbox |
|
|
Definir ferramentas | Funções Python registradas no agente | Integrados: navegação na Web, execução de código, sistema de arquivos |
Instalar pacotes |
| O agente executa |
Transmitir eventos | Infraestrutura de SSE personalizada |
|
Continuar uma sessão | Banco de dados da sessão + injeção de contexto |
|
Arquivos de configuração | Codificado no agente ou injetado na inicialização | Montado via |
Infraestrutura para gerenciar | Contêiner, Cloud Run, IAM, secrets | Nenhum |