Criar um agente de resumo diário de tecnologia com agentes gerenciados na API Gemini

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 + pip install

O agente instala pacotes no sandbox

Infraestrutura de streaming de SSE

stream=True retorna um iterável de eventos

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

Clique no botão abaixo para abrir este codelab no Google Cloud Shell. Todas as dependências são pré-instaladas.

Abrir no Cloud Shell

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 usando agent_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_search e url_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âmetro environment. 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 como interaction.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():

step.type

O que é

O que run_stream() imprime

"url_context_call"

agente buscando um URL

[tool] url_context (https://...)

"code_execution_call"

agente executando código no sandbox

[tool] run_code

"google_search_call"

agente pesquisando na Web

[tool] google_search

"function_call"

ferramentas de arquivos e outros

[tool] read_file (/workspace/...)

step.delta onde delta.type == "text"

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 target

Variável

O que o arnês faz com ele

.agents/AGENTS.md

AGENTS_MD

Carregadas automaticamente como instruções permanentes: estilo editorial, fluxo de trabalho, regras de execução

.agents/skills/digest-pdf/SKILL.md

SKILL_MD

Descoberta e registrada automaticamente como uma capacidade nomeada. O agente a invoca pelo nome.

.agents/skills/digest-pdf/scripts/generate_pdf.py

GENERATE_PDF_PY

Renderizador de PDF pré-criado. O agente escreve summaries.json e executa este script.

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

environment=environment_id

Arquivos, pacotes instalados, estado do sistema: tudo no sistema de arquivos do Linux

Manter a mesma mesa de escritório entre as reuniões

previous_interaction_id=interaction_id

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_id apenas: reutilize arquivos e pacotes, mas inicie uma nova conversa. Útil para uma nova tarefa no mesmo espaço de trabalho.
  • previous_interaction_id apenas: 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:

  1. Acesse console.cloud.google.com/apis/library/gmail.googleapis.com e ative a API Gmail.
  2. Acesse APIs e serviços > Credenciais > Criar credenciais > ID do cliente OAuth 2.0.
  3. Tipo de aplicativo: App para computador. Faça o download do JSON e salve-o como credentials.json na 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

interactions.create(agent, input, environment, stream=True), event.event_type

Personalizar o agente

Montar arquivos de configuração; manter IDs em .env na mesma execução

environment.sources, save_env

Adicionar um gancho de segurança

Interceptar chamadas de ferramentas antes da execução e negar comandos destrutivos

hooks.json, pre_tool_execution, gate.py

Faça download do PDF

Baixar o PDF sem executar o agente novamente

API Gemini Files :download em download_pdf.py

Continuar a conversa

Continuar a conversa sem buscar novamente na Web

environment=environment_id, previous_interaction_id=interaction_id

Persistir configuração do agente

Persistir a configuração do agente; invocar por ID, sem necessidade de fontes

agents.create(), agents.list()

Enviar pelo Gmail

Receba um token OAuth localmente, transmita-o ao agente, que chama a API REST do Gmail via code_execution.

OAuth 2.0, client.interactions.create(input=...)

Programar execuções diárias

Executar o agente automaticamente em uma programação cron

client.triggers.create(schedule, time_zone, interaction)

Padrões principais

  1. Uma chamada, um sandbox: interactions.create() processa toda a infraestrutura (sem contêineres para implantação, sem pacotes para instalação local).
  2. Streaming progressivo: o stream=True transforma uma caixa preta de 90 segundos em um feed publicado de chamadas de ferramentas e partes de texto.
  3. Fontes in-line: monte AGENTS.md, SKILL.md e scripts pré-criados no sandbox sem etapas de upload ou implantação.
  4. Descoberta automática de arneses: os arquivos colocados em .agents/ são coletados automaticamente (não é necessário configurar o SDK).
  5. Estado bidimensional: environment_id rastreia arquivos e pacotes; previous_interaction_id rastreia o contexto da conversa. Qualquer um pode ser transmitido de forma independente.
  6. Download de snapshots: o ambiente é um tarball de sistema de arquivos completo, acessível pela API Gemini Files.
  7. Agentes nomeados: o agents.create() incorpora a configuração de forma permanente. As chamadas futuras transmitem apenas o ID do agente e environment="remote", sem fontes.
  8. Hooks: hooks.json + uma ferramenta de interceptação de script de gate chama antes da execução; uma resposta deny cancela a chamada e o agente se corrige
  9. Chamadas de API externa: transmita uma credencial no comando input. O agente escreve e executa o código de integração no sandbox usando code_execution.
  10. 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

docker build + gcloud run deploy

interactions.create()

Definir ferramentas

Funções Python registradas no agente

Integrados: navegação na Web, execução de código, sistema de arquivos

Instalar pacotes

pip install no Dockerfile

O agente executa pip install no sandbox

Transmitir eventos

Infraestrutura de SSE personalizada

stream=True

Continuar uma sessão

Banco de dados da sessão + injeção de contexto

environment_id + previous_interaction_id

Arquivos de configuração

Codificado no agente ou injetado na inicialização

Montado via environment.sources

Infraestrutura para gerenciar

Contêiner, Cloud Run, IAM, secrets

Nenhum

Próximas etapas