Blog · Infraestrutura · 36 min de leitura

Claude Managed Agents integrado com Cloudflare Sandboxes

Por Fabio Douek

Ir para seção
Explica (TLDR) como se eu fosse...
O que é isso?

Imagine que seu robô ajudante tem um cérebro espertinho que mora na loja de brinquedos, e duas mãozinhas que você pode guardar na sua própria caixa de brinquedos. O cérebro fica onde mora e diz para as mãos o que fazer, mas as mãos brincam com os seus brinquedos, no seu quarto, onde valem as suas regras.

A novidade é que você pode escolher onde as mãos moram. Algumas crianças deixam as delas num galpão grande; eu testei as minhas numa rede rápida de casinhas chamada Cloudflare. As mãos conseguem pegar coisas das minhas próprias caixas (figurinhas, listas, anotações) sem precisar pedir para a loja de brinquedos primeiro, que é a parte que os adultos se importam.

Trate isso como uma divisão parcial da superfície de processamento. A Anthropic continua sendo controladora do agent loop, do session log e da distribuição de skills, enquanto o cliente vira operador do sandbox de execução de tools e do caminho de egress dele. Ganho material: dados de saída, secrets e artefatos intermediários podem ficar dentro do perímetro do cliente, com o cliente escolhendo a política de egress.

Ressalvas materiais: Memory não é suportado em self-hosted sandboxes, então o contexto durável do agent ainda vive apenas no caminho gerenciado pela Anthropic. A feature ainda não está disponível no Claude Platform on AWS, então clientes residentes na AWS não podem adotá-la hoje. A integridade do webhook depende de uma chave de assinatura compartilhada, e o template padrão da Cloudflare vem com dois relatos de segurança em aberto que vale acompanhar antes de um rollout de produção.

O sintoma que isso trata é o agent de produção parado em revisão jurídica porque a execução das tools toca dados que o time de segurança não deixa sair do perímetro. A intervenção move o sandbox para a infraestrutura do cliente mantendo o model harness na Anthropic, então o agent loop e as credenciais que ele toca não compartilham mais o mesmo processo.

A base de evidência tem um dia em público, então adote com cautela. Efeitos colaterais a monitorar: um caminho de auth do dashboard que falha aberto no template de referência, secrets em texto puro nos fingerprints de policy, e a ausência de Memory nesse code path. Bons candidatos são times que já hospedam dados na Cloudflare. Maus candidatos são lojas AWS-only, já que o Claude Platform on AWS ainda não suporta esse modo.

Repare no que muda quando o time finalmente consegue responder a pergunta de segurança com um diagrama de arquitetura real. A ansiedade lenta de fundo do tipo "para onde exatamente nossos dados estão indo enquanto o agent roda" alivia, porque a execução das tools agora vive dentro do perímetro em que o time já confia, com os controles de egress que já usa.

A fricção não some, ela só muda de lugar. O agent loop ainda fica hospedado em outro lugar, então as conversas sobre propriedade continuam. Times que torciam para self-hosted significar totalmente on-premises precisam conversar sobre o gap com honestidade. O trabalho é nomear o que se moveu, o que não se moveu, e o que muda sobre quem precisa assinar embaixo.

Imagine o cérebro da banda ficando no estúdio enquanto a base rítmica se muda para o seu palco. O model harness mantém o tempo pelo cabo, você decide em qual sala o baixo e a bateria se montam, e sua equipe local cuida da cabeação, do mix in-ear e de quem tem permissão de passar da porta.

O instrumento novo nesse kit é a custom tool, e na Cloudflare ela pluga direto no seu rig existente: storage, AI, serviços privados, todos alcançáveis como bindings, não como chamadas de rede. Quando a banda engata, o andamento dos dias de build acelera, porque menos horas vão para código de cola e mais vão para as partes que a plateia realmente escuta.

A história aqui é prontidão de enterprise para o time que estava quase-mas-não-totalmente pronto para lançar um agent. A execução das tools se move para dentro do perímetro do cliente, a revisão de segurança ganha um diagrama de arquitetura real, e o time até o primeiro agent útil medido pela parceira de launch Amplitude foi de dois dias do zero.

O posicionamento não é "substituir o time", é "desbloquear o projeto que estava parado em compliance há um mês". Esse enquadramento viaja bem com compradores que já confiam na Cloudflare para o data plane, e combina de forma limpa com uma demo prática em que o agent lê os inputs do bucket do próprio cliente e responde usando o binding de AI do próprio cliente.

Imagem de capa do post: Claude Managed Agents on Cloudflare self-hosted sandboxes

GitHub Veja o código-fonte aqui: claude-managed-agents-self-hosted-sandboxes

Visão Geral

Em 2026-05-19 a Anthropic anunciou duas adições ao Claude Managed Agents: self-hosted sandboxes (beta pública) e MCP tunnels (research preview). A primeira é a peça que sustenta tudo. Até esta semana, toda sessão de Managed Agents rodava dentro de um container cloud gerenciado pela Anthropic. A partir desta semana, você pode manter o agent loop na Anthropic e mover a execução das tools para uma infraestrutura que você controla. Quatro parceiros de launch são suportados de saída: Cloudflare, Daytona, Modal e Vercel.

O enquadramento da própria Anthropic é a frase que você vai ver em todo lugar: “o agent loop que cuida de orquestração, gerenciamento de contexto e error recovery permanece na infraestrutura da Anthropic, enquanto a execução das tools se move para o seu próprio ambiente configurado.” O jeito mais curto de dizer: o cérebro fica na Anthropic, as mãos se mudam para a sua conta. O resultado é um control plane dividido: a Anthropic detém o model harness e o session log, o cliente detém o sandbox e o egress.

Eu escolhi a Cloudflare para esse hands-on porque é o único dos quatro backends que expõe as primitivas da plataforma (R2, D1, KV, Vectorize, Workers AI, Email Routing) como inline bindings para o código da sua custom tool. Em Daytona, Modal e Vercel uma tool que lê de object storage faz uma chamada de rede. Na Cloudflare é um argumento de função. Eu faço o deploy do template cloudflare/claude-managed-agents, rodo uma sessão nos dois backends que ele traz de fábrica e depois adiciono um par de tools cf_list_pdfs / cf_read_pdf para conduzir um pequeno agent de PDF-summarizer contra o R2.

Algumas ressalvas antes que te peguem de surpresa no Setup. Memory não é suportado em self-hosted sandboxes no launch; se o seu agent precisa de contexto durável entre sessões, você fica no sandbox gerenciado pela Anthropic por enquanto. O template da Cloudflare exige um Workers Paid plan porque os bindings de Containers e Worker Loader estão gateados nele.

Arquitetura

Veja como as peças se encaixam:

Control plane Anthropic Claude harness Work queue Conta Cloudflare do cliente Worker control plane Sandbox (MicroVM) — ou — IsolateRunner Bindings Cloudflare (inline, sem HTTP) R2 KV D1 Vectorize Workers AI VPC / Mesh Email webhook 1 drain 2 dispatch 3 callback 4 bindings 5

As cinco setas numeradas traçam uma sessão pelo sistema:

  1. Webhook. A Anthropic faz um POST de um evento session.status_run_started para /webhooks no Worker, assinado com WEBHOOK_SECRET. É só a campainha; nenhuma chamada de tool trafega nesse hop.
  2. Drain. O Worker (claude-managed-agents-control-plane) responde chamando work.poller({ drain: true }) contra /v1/environments/<id>/work para puxar os work items enfileirados. A queue é o contrato; o webhook é só o gatilho.
  3. Dispatch. O Worker lê o backend escolhido de cada agent no D1 e roteia a sessão para um dos dois backends. Qualquer um deles roda o toolset padrão (bash, read, write, edit, glob, grep) dentro do sandbox.
    • Sandbox (MicroVM): um Container Durable Object standard-1 rodando ant beta:worker run --workdir /workspace, com /workspace snapshotado para R2 quando ocioso.
    • IsolateRunner: um isolate V8 inicializado via Worker Loader e apoiado no filesystem SQLite do @cloudflare/shell (o estado persiste pelo storage SQLite do Durable Object automaticamente).
  4. Callback. Quando uma custom tool dispara, o sandbox a despacha de volta ao tool runtime do Worker, não para nada dentro do sandbox. O código da custom tool, portanto, executa no isolate V8 do Worker. O sandbox é para código que o próprio agent escreve e roda; custom tools são cola de primeira classe entre o agent e a sua plataforma.
  5. Bindings. Com a tool agora executando no Worker, env.PDFS.get(key), env.AI.run(...), env.DB.prepare(...) e companhia são argumentos de função de primeira classe, não endpoints HTTPS com credenciais e round-trips. R2, KV, D1, Vectorize, Workers AI, VPC/Mesh e Email todos chegam ao agent pelo mesmo caminho.

Setup

Pré-requisitos

  • Uma conta Cloudflare no Workers Paid plan ($5/mês no mínimo). Bindings de Containers e Worker Loader estão gateados no plano pago; o plano free não vai conseguir deploy desse template.
  • Um workspace Anthropic com Managed Agents habilitado (Step 2 cunha a API key que o Worker vai usar).
  • Docker rodando localmente para o build inicial da container image. (Deploys subsequentes usam cache.)
  • wrangler 4.76 ou mais recente, Node 22 ou mais recente. O template instala o resto.

Step 1: Pré-requisitos Cloudflare

Um pouco de preparo do lado da Cloudflare antes de tocar em qualquer código. O objetivo desse step é cunhar um API token escopado para a conta que o template vai usar para chamadas REST do Browser Rendering e outras ações conduzidas pelo dashboard. Access keys do R2 vêm de uma página diferente mais tarde no Step 6.

  1. Abra a página de API tokens do dashboard da Cloudflare: https://dash.cloudflare.com/?to=/:account/api-tokens. O segmento :account é um placeholder de deeplink da Cloudflare que resolve para a conta que você tem selecionada.
  2. Clique em Create Token. Dê a ele um nome reconhecível (eu usei claude-agents-poc).
  3. Em Permission policies, escolha o template Developer Services. Esse preset junta as permissões de Workers, KV, R2, D1 e as escopadas para a conta que o Worker de control plane precisa, sem você ter que montá-las à mão.

Página Cloudflare Create a token com o nome do token claude-agents-poc e o template de permission policy Developer Services selecionado

  1. Clique em Create Token, depois copie o valor e guarde em um lugar seguro. Você vai empurrar ele como secret CLOUDFLARE_API_TOKEN mais tarde se habilitar tools REST de Browser Rendering. A Cloudflare só mostra o token uma vez.

  2. Pegue seu Account ID enquanto está no dashboard. Ele fica na home da conta (ou na sidebar direita de qualquer zone) com o label Account ID. O template usa ele mais tarde no Step 6 para o path do snapshot no R2.

  3. Exporte os dois valores no terminal que você vai usar pelo resto do Setup, para que os comandos wrangler mais adiante consigam capturá-los automaticamente:

    export CLOUDFLARE_ACCOUNT_ID=<your-account-id>
    export CLOUDFLARE_API_TOKEN=<the-token-you-just-created>

Step 2: Pré-requisitos Anthropic

Um pequeno desvio para o lado da Anthropic antes do deploy. O Worker de control plane precisa de uma API key da Claude Platform escopada à organização (o token sk-ant-..., não a environment key do Step 4) para chamar os endpoints de monitoramento da Anthropic. Cunhe agora para tê-la pronta quando os secrets forem empurrados no Step 5.

  1. Abra o Claude Platform Console e navegue para Organization Settings → API Keys.

  2. Clique em Create Key. Escolha o workspace (eu usei Default), dê um nome reconhecível (eu usei cloudflare-worker) e clique em Add.

    Modal Anthropic 'Create API key' com o workspace setado para Default e o nome da key setado para cloudflare-worker

  3. Copie o valor da key quando ele aparecer. A Anthropic exibe o valor completo só uma vez.

Você vai empurrar ele como secret ANTHROPIC_API_KEY no Step 5. Mantenha do lado do Worker apenas; é uma credencial escopada à organização e não pode ser exposta ao código do sandbox.

Step 3: Deploy do template

Há dois caminhos: o botão Deploy to Cloudflare (faz fork do repo na sua conta e provisiona recursos via dashboard da Cloudflare) ou o terminal. Eu usei o terminal porque queria ler o que estava acontecendo.

git clone https://github.com/cloudflare/claude-managed-agents
cd claude-managed-agents
npm install

Dois patches que o template upstream precisa atualmente

Em 2026-05-26 o template upstream cloudflare/claude-managed-agents vem com dois bugs que impedem o agent de demo de completar uma única tool round-trip end-to-end. Eu bati em ambos no meu primeiro deploy e quero sinalizar com honestidade. Aplique os patches abaixo no diretório claude-managed-agents recém-clonado antes de rodar npm run deploy.

Patch A: subir a base image do sandbox para uma variante com Python. O Dockerfile padrão fixa em cloudflare/sandbox:0.10.1, que não traz Python. O prompt do Test 1 pede para o agent rodar um script Python; a bash tool falha com python3: command not found e o agent queima turnos tentando apt-get install python3 em vez de só rodar o arquivo. A correção é um bump de uma linha para a variante -python:

# Antes (Dockerfile:4)
FROM docker.io/cloudflare/sandbox:0.10.1

# Depois
FROM docker.io/cloudflare/sandbox:0.10.2-python

Patch B: confiar no CA do egress-proxy em cada dispatch. A base image monta o CA do egress-proxy da Cloudflare em /etc/cloudflare/certs/cloudflare-containers-ca.crt mas nunca o adiciona ao trust store do sistema. HTTPS de saída de dentro do container falha com self-signed certificate in certificate chain, o ant não consegue alcançar api.anthropic.com, e toda sessão fica parada em requires_action depois da primeira tool call. Um wrapper ENTRYPOINT não ajuda: o runtime de container da Cloudflare sobrescreve ENTRYPOINT no boot. Rode o passo de trust pelo Worker em vez disso: abra src/microvm/sandbox.ts e insira o bloco abaixo logo após o try/catch de setEnvVars (por volta da linha 416):

// Trust the Cloudflare egress-proxy CA the platform mounts at
// /etc/cloudflare/certs/cloudflare-containers-ca.crt. The base image
// docs claim the runtime auto-trusts it on startup; in 0.10.2-python
// it does not, and outbound HTTPS from the container fails with
// `self-signed certificate in certificate chain` until the cert is
// added to the system trust store. Without this every session stalls
// at `requires_action` after the first tool_use.
try {
  const trustResult = await this.exec(
    "if [ -f /etc/cloudflare/certs/cloudflare-containers-ca.crt ] && [ ! -f /usr/local/share/ca-certificates/cloudflare-containers-ca.crt ]; then cp /etc/cloudflare/certs/cloudflare-containers-ca.crt /usr/local/share/ca-certificates/ && update-ca-certificates >/dev/null 2>&1; fi",
    { timeout: 15000 },
  );
  if (trustResult.exitCode !== 0) {
    console.warn(
      `[sandbox] CA trust step exited ${trustResult.exitCode} for ${opts.sessionId}: ${trustResult.stderr || trustResult.stdout}`,
    );
  }
} catch (error) {
  console.warn(
    `[sandbox] CA trust step threw for ${opts.sessionId}: ${toErrorMessage(error)}`,
  );
}

Com os dois patches no lugar, faça deploy:

npm run deploy
⚠️ Pegadinha do keychain do macOS. Se o npm run deploy falhar bem no final com error saving credentials ... User interaction is not allowed. (-25308) (depois do upload do Worker e do build da Container image terem completado com sucesso), isso é o login keychain do macOS recusando salvar a credencial do container-registry da Cloudflare sem um prompt de GUI. Destrave ele na mesma shell com security unlock-keychain ~/Library/Keychains/login.keychain-db e rode npm run deploy de novo. O step do Worker é idempotente e o build da image bate no cache de layer do Docker, então o retry é barato.

O primeiro npm run deploy faz bastante coisa:

  • Roda o step prebuild (scripts/ensure-kv.mjs, ensure-d1.mjs, sync-vpc-bindings.mjs, copy-redoc.mjs), que provisiona ou consulta seus namespaces KV e database D1, depois patcha os IDs deles numa cópia de trabalho do wrangler.jsonc.
  • Builda o frontend Vite.
  • Builda a Container image a partir do Dockerfile local. Esse é o step lento, 2-3 minutos na primeira vez.
  • Faz deploy via Wrangler.
Saída de deploy bem-sucedido (push, criação do container app, deploy do worker, migração do D1)
Login Succeeded
no such manifest: registry.cloudflare.com/<account-id>/claude-managed-agents-sandbox@sha256:<digest>
Image does not exist remotely, pushing: registry.cloudflare.com/<account-id>/claude-managed-agents-sandbox:<image-tag>
The push refers to repository [registry.cloudflare.com/<account-id>/claude-managed-agents-sandbox]
<layer-id>: Pushed
<layer-id>: Pushed
... (17 layers pushed total)
<image-tag>: digest: sha256:<digest> size: 4035
╭ Deploy a container application deploy changes to your application

│ Container application changes

├ NEW claude-managed-agents-sandbox

│   {
│     "containers": [
│       {
│         "name": "claude-managed-agents-sandbox",
│         "scheduling_policy": "default",
│         "configuration": {
│           "image": "registry.cloudflare.com/<account-id>/claude-managed-agents-sandbox:<image-tag>",
│           "instance_type": "standard-1",
│           "observability": { "logs": { "enabled": true } }
│         },
│         "instances": 0,
│         "max_instances": 100,
│         "constraints": { "tiers": [1, 2] },
│         "durable_objects": { "namespace_id": "<do-namespace-id>" },
│         "rollout_active_grace_period": 1800
│       }
│     ]
│   }

│  SUCCESS  Created application claude-managed-agents-sandbox (Application ID: <application-id>)

╰ Applied changes

Deployed claude-managed-agents-control-plane triggers (2.66 sec)
  https://claude-managed-agents-control-plane.<account>.workers.dev
  schedule: 0 4 * * *
Current Version ID: <version-id>

> npm run db:migrate:remote
> wrangler d1 migrations apply DB --remote

Migrations to be applied:
┌───────────────┐
│ name          │
├───────────────┤
│ 0001_init.sql │
└───────────────┘
🌀 Executing on remote database DB (<d1-db-id>)
🚣 Executed 16 commands in 1.97ms
┌───────────────┬────────┐
│ name          │ status │
├───────────────┼────────┤
│ 0001_init.sql │ ✅     │
└───────────────┴────────┘

Algumas coisas que vale destacar dessa saída:

  • Defaults do container application: instances: 0 com max_instances: 100. O control plane sobe sandboxes sob demanda, nada roda ocioso.
  • Grace period de rollout: rollout_active_grace_period: 1800, que são 30 minutos para sessões em curso completarem durante um rollout antes de serem cortadas.
  • Migração D1 roda automaticamente: o hook postdeploy aplica wrangler d1 migrations apply DB --remote contra o seu database de produção sem prompt. Tudo bem para o primeiro deploy; vale saber para os próximos.
  • Dois warnings do Wrangler: workers_dev e preview_urls não estão explicitamente setados em wrangler.jsonc, então ambos vão para o default habilitado. Tudo bem para o dashboard, mas configure preview_urls = false no config do Wrangler antes de apontar tráfego real para esse Worker.

Visite a URL do Worker deployado agora e você deve ver o dashboard do control plane com um banner amarelo “Finish setup before creating sandboxes”. Isso é esperado: o Worker está saudável, mas os quatro secrets da Anthropic (ANTHROPIC_ENVIRONMENT_KEY, ANTHROPIC_API_KEY, ENVIRONMENT_ID, WEBHOOK_SECRET) ainda não foram empurrados. Os Steps 4 e 5 fecham essa lacuna.

Dashboard Cloudflare Claude Agents deployado mostrando um banner 'Finish setup before creating sandboxes' listando os quatro secrets Anthropic faltantes, e uma lista de Sandboxes vazia com a mensagem 'No sandboxes yet. Webhook events from Anthropic will populate this list as they arrive.'

Step 4: Conectar o lado Anthropic

No Claude Platform Console eu criei um self-hosted environment e um webhook:

  1. Workspaces → Environments → Add environment. Dê um nome (eu usei cloudflare-sandbox) e configure Hosting type para Self-hosted. O hosting type não pode ser mudado depois da criação, então escolha agora. Submeta. Isso te dá o ENVIRONMENT_ID.

    Modal 'Add environment' da Anthropic Claude Platform com o Name setado para cloudflare-sandbox e o dropdown Hosting type setado para Self-hosted

  2. Dentro do novo environment, clique em Generate Environment Key. Dê um nome para a key (eu usei cloudflare-dev) e clique em Create. A Anthropic então revela o valor ANTHROPIC_ENVIRONMENT_KEY no formato sk-ant-oat01-.... Copie agora; essa é a única vez que o valor completo é mostrado.

    Modal 'Create environment key' da Anthropic Claude Platform com o campo nome setado para cloudflare-dev

  3. Manage → Webhooks, aponte para https://<your-worker>.<account>.workers.dev/webhooks. O Console retorna um WEBHOOK_SECRET (HMAC Standard Webhooks).

⚠️ Pegadinha de entrega de webhook. Se as suas primeiras sessões ficarem em requires_action para sempre, o dashboard nunca avança e o wrangler tail não mostra POST /webhooks chegando, é provável que a sua subscription de webhook esteja no estado preso "ativo mas não entregando" da Anthropic. Eu bati nisso e confirmei via Cloudflare edge analytics que zero tentativas chegaram ao Worker. O toggle disable + re-enable do Console não limpa o estado. Só um delete e recreate completo da subscription força a Anthropic a provisionar um novo estado de entrega. A Anthropic não expõe API para inspecionar ou diagnosticar isso de fora do Console; deletar e recriar é o único fix conhecido.

Step 5: Empurrar os secrets da Anthropic para o Worker

Os Steps 2 e 4 produziram quatro valores (ANTHROPIC_API_KEY, ENVIRONMENT_ID, ANTHROPIC_ENVIRONMENT_KEY, WEBHOOK_SECRET) que precisam aterrissar no Worker como secrets antes que ele consiga drenar work ou verificar webhooks. Mantenha ANTHROPIC_API_KEY só no Worker; é escopada à organização e não pode vazar para o env do sandbox.

npx wrangler secret put ENVIRONMENT_ID            --name claude-managed-agents-control-plane
npx wrangler secret put ANTHROPIC_ENVIRONMENT_KEY --name claude-managed-agents-control-plane
npx wrangler secret put ANTHROPIC_API_KEY         --name claude-managed-agents-control-plane
npx wrangler secret put WEBHOOK_SECRET            --name claude-managed-agents-control-plane

A flag --name claude-managed-agents-control-plane fixa cada secret put ao Worker deployado por nome. Ela torna o comando independente de diretório (você pode rodar de qualquer lugar) e evita o modo de falha em que o Wrangler pega o wrangler.jsonc errado do cwd da sua shell.

Depois que os quatro secrets forem empurrados, recarregue o dashboard. O banner amarelo “Finish setup” deve ter sumido e você pode começar a criar agents. Você também pode verificar que os secrets aterrissaram pelo lado do dashboard da Cloudflare: navegue para Workers & Pages → claude-managed-agents-control-plane → Settings → Variables and Secrets, onde as quatro entradas devem aparecer listadas com os valores como Value encrypted.

Aba Settings do dashboard Workers da Cloudflare mostrando o painel Variables and Secrets para o Worker claude-managed-agents-control-plane, com quatro secrets criptografados listados: ANTHROPIC_API_KEY, ANTHROPIC_ENVIRONMENT_KEY, ENVIRONMENT_ID e WEBHOOK_SECRET

Step 6: Credenciais de snapshot do R2

O backend MicroVM faz snapshot de /workspace para R2 quando uma sessão fica ociosa. Cunhe uma access key do R2 escopada ao bucket claude-managed-agents-snapshots (o template cria o bucket no primeiro deploy).

  1. Abra a página de API tokens do R2 da Cloudflare e clique em Create Account API Token.
  2. Dê um nome reconhecível ao token (eu usei r2-token-claude-managed).
  3. Em Permissions, escolha Object Read & Write.
  4. Em Specify bucket(s), escolha Apply to specific buckets only e selecione claude-managed-agents-snapshots.
  5. Submeta. A Cloudflare vai revelar o access key ID e o secret access key uma vez; copie ambos imediatamente.

Página Cloudflare 'Create Account API Token' com o nome do token setado para r2-token-claude-managed, permissão Object Read & Write selecionada, e o bucket claude-managed-agents-snapshots escopado em 'Apply to specific buckets only'

Em seguida empurre os quatro secrets relacionados ao R2:

npx wrangler secret put R2_ACCESS_KEY_ID     --name claude-managed-agents-control-plane
npx wrangler secret put R2_SECRET_ACCESS_KEY --name claude-managed-agents-control-plane
npx wrangler secret put BACKUP_BUCKET_NAME   --name claude-managed-agents-control-plane   # value: claude-managed-agents-snapshots
npx wrangler secret put CLOUDFLARE_ACCOUNT_ID --name claude-managed-agents-control-plane

Se você está rodando só Isolate, pode pular esse step; o SQLite do Durable Object cuida do estado. Eu mantive porque queria testar ambos os backends.

Step 7: Construir as custom tools do PDF-summarizer

Duas custom tools, cf_list_pdfs e cf_read_pdf, que leem PDFs direto de um bucket R2. O agent usa elas no Test 3. Todo o trabalho acontece dentro do clone claude-managed-agents do Step 3; o repo complementar traz os arquivos prontos se você quiser copiar em vez de digitar.

Custom tools vivem em src/tools/custom-tools.ts. Cada uma é uma função tipada que o agent pode chamar pelo nome; o corpo roda dentro do Worker com acesso direto a todo binding que você declarou em wrangler.jsonc (R2, KV, D1, Workers AI, serviços VPC, Email Routing). Os dois exemplos abaixo leem objetos PDF do R2 e retornam o texto extraído.

7.1 Adicionar o binding PDFS do R2

Abra wrangler.jsonc e adicione um binding PDFS ao array r2_buckets existente (o array já contém BACKUP_BUCKET do path de snapshot; adicione PDFS junto):

"r2_buckets": [
  {
    "binding": "BACKUP_BUCKET",
    "bucket_name": "claude-managed-agents-snapshots"
    // ... (existing fields)
  },
  {
    "binding": "PDFS",
    "bucket_name": "claude-managed-agents-pdfs",
    "preview_bucket_name": "claude-managed-agents-pdfs-preview"
  }
]

7.2 Criar o bucket

npx wrangler r2 bucket create claude-managed-agents-pdfs

(O preview_bucket_name acima é opcional e só é necessário se você rodar wrangler dev localmente; você pode criá-lo com npx wrangler r2 bucket create claude-managed-agents-pdfs-preview se quiser.)

7.3 Escrever as duas custom tools

Substitua o conteúdo de src/tools/custom-tools.ts por:

import { z } from "zod";
import { defineTool, type CustomTool } from "./custom-tools-runtime";
import { extractPdfText } from "./pdf-text";

export const CUSTOM_TOOLS: CustomTool[] = [
  defineTool({
    name: "cf_list_pdfs",
    description:
      "List PDF objects in the configured R2 bucket. Returns key and size in bytes (tab-separated) for each object.",
    inputSchema: z.object({
      prefix: z.string().optional().describe("Optional R2 key prefix to filter by, e.g. 'pdfs/'"),
    }),
    requires: (env) => Boolean((env as unknown as { PDFS?: unknown }).PDFS),
    run: async ({ prefix }, { env }) => {
      const pdfs = (env as unknown as { PDFS: R2Bucket }).PDFS;
      const listed = await pdfs.list({ prefix });
      const rows = listed.objects.map((o) => `${o.key}\t${o.size}`);
      return rows.length ? rows.join("\n") : "(no objects found)";
    },
  }),

  defineTool({
    name: "cf_read_pdf",
    description:
      "Fetch a PDF from R2 by key and return its extracted text. Call cf_list_pdfs first to discover keys.",
    inputSchema: z.object({
      key: z.string().describe("R2 object key, e.g. pdfs/q1-2026.pdf"),
    }),
    requires: (env) => Boolean((env as unknown as { PDFS?: unknown }).PDFS),
    run: async ({ key }, { env }) => {
      const pdfs = (env as unknown as { PDFS: R2Bucket }).PDFS;
      const obj = await pdfs.get(key);
      if (!obj) return `error: no object at key ${key}`;
      const buffer = await obj.arrayBuffer();
      const text = await extractPdfText(buffer);
      return text || "(no extractable text)";
    },
  }),
];

Por que isso importa: pdfs.list() e pdfs.get() são bindings no Worker, não chamadas de rede. Sem HTTP, sem handshake de auth, sem política de egress para aplicar. O agent chama a tool, o Worker chama o binding, e o R2 responde numa única chamada de função.

7.4 Colocar o extrator de texto de PDF

Copie src/tools/pdf-text.ts do repo complementar para o mesmo path no seu clone. É um extrator pure-JS de cerca de 140 linhas que lida com PDFs baseados em texto (relatórios, artigos, exports de slides). PDFs só de imagem ou escaneados retornam (no extractable text).

7.5 Upload de PDFs de exemplo

O repo complementar traz três relatórios Highlights do US Government Accountability Office (GAO), de domínio público, em samples/. Eles têm exatamente a estrutura “escopo / número de manchete / riscos” que o prompt da demo pede. Pegue eles do diretório samples/ do repo complementar (ou substitua por quaisquer PDFs baseados em texto que você tiver) e suba para o prefix pdfs/:

npx wrangler r2 object put claude-managed-agents-pdfs/pdfs/gao-cybersecurity.pdf --file ./samples/gao-cybersecurity.pdf
npx wrangler r2 object put claude-managed-agents-pdfs/pdfs/gao-dod-readiness.pdf --file ./samples/gao-dod-readiness.pdf
npx wrangler r2 object put claude-managed-agents-pdfs/pdfs/gao-supply-chain.pdf  --file ./samples/gao-supply-chain.pdf

7.6 Redeploy

Do clone claude-managed-agents que você criou no Step 3 (não o repo complementar; o complementar é só referência para os arquivos-fonte que você copiou):

cd ~/path/to/claude-managed-agents   # the cloudflare/claude-managed-agents clone
npm run deploy

O script prebuild preserva os patches existentes de IDs de KV / D1, e o Wrangler vai diferenciar o novo binding PDFS no Worker deployado. Secrets que foram empurrados nos Steps 5 e 6 persistem; você não precisa empurrá-los de novo.

Esse é todo o build da demo. A seção de Testing abaixo passa por habilitar as novas tools em um agent específico e rodar o prompt.

Step 8: Proteger o dashboard

O dashboard do template não é autenticado no nível do Worker. Há uma issue aberta (#7) sinalizando o comportamento fail-open e um PR correspondente (#8) que adiciona validação de JWT dentro do Worker; ambos estavam abertos e não merged em 2026-05-26. Até esse PR aterrissar, embrulhe o Worker por trás do Cloudflare Access. O Cloudflare Access consegue proteger um Worker direto pelo nome, então toda rota ganha o mesmo portão de login sem cabeamento por rota.

8.1 Pré-requisitos

Se você nunca usou a suíte Zero Trust da Cloudflare, configure duas coisas primeiro (a introdução da Cloudflare passa pelas duas):

  • Uma organização Zero Trust na sua conta (escolha um nome de team quando pedido; ele vira parte da sua URL de login https://<team-name>.cloudflareaccess.com).
  • Pelo menos um identity provider configurado. One-Time PIN é a opção mais rápida (sem IdP externo necessário; o Access envia um código por email para o usuário). Para uso em time você provavelmente vai querer Google, GitHub, Okta, Entra ID, ou qualquer SSO que você já rode.

O plano Zero Trust tem um tier free que cobre até 50 usuários, suficiente para quase qualquer dashboard interno de time.

8.2 Criar uma aplicação Access self-hosted protegendo o Worker

  1. Abra o dashboard da Cloudflare e vá em Zero Trust → Access controls → Applications.

  2. Clique em Add an application.

  3. Selecione Self-hosted and private.

    Página Cloudflare Zero Trust 'Add an application' com a opção 'Self-hosted and private' selecionada

  4. Para o destino, escolha Add a Cloudflare Worker (em vez de “Add public hostname”). Escolha claude-managed-agents-control-plane do dropdown. Esse é o path que a Cloudflare recomenda para Workers porque toda request ao Worker (em qualquer rota, incluindo preview deployments) passa pelo Access primeiro, sem mudanças por rota no seu código.

  5. Dê um nome reconhecível à aplicação, por exemplo claude-managed-agents-dashboard. Uma duração de sessão de 24 horas é um default razoável; encurte se o dashboard fica dentro de um workflow regulado.

    Página de configuração 'Add an application' do Cloudflare Zero Trust com a aplicação nomeada claude-managed-agents-dashboard, destino setado para o Worker claude-managed-agents-control-plane, e duração de sessão de 24 horas

8.3 Adicionar uma policy do Access

Ainda no fluxo de criação da aplicação:

  1. Selecione Create new policy.

  2. Action: Allow.

  3. Include: escolha a checagem de identidade que combine com o seu time. Padrões comuns:

    • Emails ending in@yourcompany.com (todos com email da empresa).
    • Emails → lista explícita de endereços (pequeno time de operadores).
    • Identity provider group → seu grupo do SSO (melhor para orgs maiores).
  4. Salve a policy e anexe à aplicação.

    Formulário de policy do Cloudflare Zero Trust Access com a action setada para Allow e uma regra Include escopando acesso por email

Se você quer um segundo fator dentro do Access, adicione um bloco Require (Action: Require) para MFA, país ou checagens de posture do Cloudflare One Client. Para uma demo prática, uma policy Allow é suficiente.

8.4 Escolher identity providers

No step Authentication, marque os IdPs que você configurou em 8.1. Se você escolheu apenas um e quer pular a página landing de IdP-picker da Cloudflare, ative Apply instant authentication para que os usuários vão direto para o fluxo de login do SSO.

Step de authentication do 'Add an application' do Cloudflare Zero Trust com um identity provider marcado e o toggle 'Apply instant authentication' habilitado

Clique em Save and finish no final da página para publicar a aplicação.

8.5 Bypass de /webhooks para a Anthropic

A aplicação de 8.2 cobre toda rota no Worker, incluindo /webhooks. As entregas de webhook da Anthropic não carregam cookie do Access nem cabeçalho CF-Access-*, então sem uma exceção todas dariam 302 para a página de login e o loop de dispatch quebraria silenciosamente. A correção é uma segunda aplicação Access, escopada só para o path /webhooks, com uma policy Bypass. A Cloudflare avalia a aplicação mais específica (escopada por path) primeiro, então o bypass ganha nessa rota enquanto a aplicação por nome do Worker continua gatando o resto. A verificação HMAC do próprio Worker contra WEBHOOK_SECRET mantém a rota gatada por assinatura mesmo bypassando o Access.

  1. De volta em Zero Trust → Access controls → Applications, clique em Add an application novamente.
  2. Selecione Self-hosted and private (mesmo tipo da app do dashboard).
  3. Para o destino, escolha Add public hostname desta vez, não “Add a Cloudflare Worker”:
    • Subdomain: <your-worker> (e.g. claude-managed-agents-control-plane)
    • Domain: <account>.workers.dev
    • Path: webhooks
  4. Nomeie a app claude-managed-agents-webhook-bypass.
  5. No step de policy, crie uma policy com Action: Bypass e Include: Everyone.
  6. Pule o step Authentication (Bypass não precisa de IdPs) e clique em Save and finish.

Policy do Cloudflare Zero Trust Access configurada com Action setada para Bypass e Include setado para Everyone, escopando o path /webhooks do Worker para que entregas da Anthropic alcancem a rota sem login do Access

8.6 Verificar

Abra https://<your-worker>.<account>.workers.dev/ em uma nova janela privada/incógnita. Você deve ser redirecionado para a página de login do Cloudflare Access (ou, com instant authentication ligado, direto para o seu SSO).

Página de login do Cloudflare Access pedindo o sign-in antes do dashboard protegido do Worker carregar

Se você escolheu One-Time PIN como identity provider, a Cloudflare envia um código de seis dígitos por email e mostra a tela de entrada de código:

Tela de entrada de código de seis dígitos do One-Time PIN do Cloudflare Access pedindo o código que foi enviado por email para o usuário

Depois de autenticar, o dashboard carrega. A Cloudflare seta um cookie CF_Authorization para continuidade de sessão e encaminha um cabeçalho Cf-Access-Jwt-Assertion em cada request. Esse é o JWT que o futuro PR #8 vai validar dentro do Worker quando ele for merged.

Para confirmar que o bypass de 8.5 está na frente do /webhooks, rode um curl rápido:

curl -i -X POST https://<your-worker>.<account>.workers.dev/webhooks

Uma resposta 401 do Worker (rejeitando a assinatura Standard Webhooks ausente) significa que o Access se afastou e a rota está gatada por assinatura, como pretendido. Um redirect 302 para a página de login do Access significa que o bypass ainda não está na frente da rota; cheque de novo o path na app de bypass.

Com o Access ativo, o deploy não é mais fail-open. O fix do PR #8 vira defesa em profundidade adicional em vez da única proteção, mas até ele ser merged, não exponha esse Worker publicamente sem o wrap do Access.

Testing

Eu rodei três cenários. Os dois primeiros testam os dois backends com um agent out-of-the-box. O terceiro exercita o diferencial dos inline bindings com as custom tools de PDF construídas no Step 7. Todos os três partem do dashboard deployado em https://<your-worker>.<account>.workers.dev/.

Test 1: Round-trip na MicroVM

Setup

  1. Abra o dashboard, navegue para Agents e clique em New Agent.
  2. Nomeie o agent como microvm-demo.
  3. Backend: escolha microvm. O texto de ajuda do formulário confirma o que você ganha: “full Linux container per session with bash + filesystem. Outbound HTTP passes through your egress policies.”
  4. Model: claude-sonnet-4-6.
  5. System Prompt: deixe o prompt default de MicroVM Sandbox que o dashboard pré-preenche (ele instrui o agent sobre semântica de /workspace e comportamento de snapshot do BACKUP_BUCKET). Mantenha Include MicroVM Sandbox defaults marcado.
  6. Tools: deixe o toolset Anthropic default habilitado (bash, read, write, edit, glob, grep). Não habilite nenhuma custom tool; esse teste é o caminho vanilla.
  7. Salve. O dashboard registra o agent com a Anthropic; você vai vê-lo na lista de agents.

Formulário 'New Agent' do dashboard Cloudflare Claude Agents preenchido para o Test 1: nome microvm-demo, model claude-sonnet-4-6, backend microvm, o system prompt default do MicroVM pré-preenchido, 'Include MicroVM Sandbox defaults' marcado, e as seis tools default da Anthropic (bash, edit, read, write, glob, grep) selecionadas.

  1. Clique no agent e depois em + New Session.

Página de detalhe do agent microvm-demo mostrando o Backend como MicroVM e o botão '+ New Session' destacado no topo direito

Prompt

Create a small Python script that lists the squares of the integers 1 to 10, run it, and tell me the result.

View de sessão do dashboard Cloudflare Claude Agents para o agent microvm-demo mostrando os detalhes da sessão e o prompt dos quadrados digitado no campo 'Send Message', pronto para enviar

Resultado

O agent escreveu squares.py em /workspace, rodou python3 squares.py e retornou a resposta. Do dashboard eu pude entrar via SSH no MicroVM vivo pelo painel de terminal e confirmar que /workspace/squares.py estava onde o agent deixou. A primeira sessão pagou um cold start de Container, uns poucos segundos na minha medição, o que bate com a linha de marketing da Cloudflare “boot em alguns milissegundos” ser para isolates, não MicroVMs.

Painel Session Events no dashboard Cloudflare Claude Agents mostrando o loop completo do agent para o Test 1: session.status_running, user.message com o prompt dos quadrados, agent.thinking, agent.tool_use (write), user.tool_result confirmando '54 bytes to /workspace/squares.py', depois agent.tool_use (bash) e user.tool_result com a saída dos quadrados '1² = 1, 2² = 4, … 10² = 100'

Sessões subsequentes no mesmo agent ficaram quentes e rápidas. O /workspace do agent foi snapshotado para R2 no momento em que ele ficou ocioso, e a sessão seguinte restaurou.

Test 2: Cold start do Isolate

Setup

Idêntico ao Test 1 mas no backend Isolate.

  1. No dashboard, vá em Agents e clique em New Agent.
  2. Nomeie o agent como isolate-demo.
  3. Backend: escolha Isolate (essa é a única diferença significativa do Test 1).
  4. Model: claude-sonnet-4-6.
  5. Tools: mesmo toolset Anthropic default, sem custom tools.
  6. Salve e Start session.

Prompt

Mesmo do Test 1, para manter a comparação maçã com maçã:

Create a small Python script that lists the squares of the integers 1 to 10, run it, and tell me the result.

Resultado

O caminho Isolate ficou visivelmente mais ágil na primeira run. A história do boot é estrutural: um isolate Workers é um contexto V8 que o binding Worker Loader sobe no momento da request, não um microVM Linux esquentando um kernel. O blog da Cloudflare chama eles de “alguns milissegundos” qualitativamente, e isso bate com o que eu vi.

Depois apareceu a assimetria do runtime. O backend Isolate não traz nenhum runtime Python: o ambiente @cloudflare/shell apoiado em SQLite não é um container Linux real, então o mesmo python3 squares.py que funcionou literal no MicroVM falha aqui. A reação do agent é interessante: ele reconheceu a restrição e reescreveu o script em JavaScript na hora para executá-lo via runtime codemode em vez disso. O log de eventos captura a virada. Um agent.message diz “Now let’s run it (simulated via JavaScript, since this sandbox has no Python runtime).”, seguido por um agent.custom_tool_use invocando o executor JS in-isolate, que retorna o mesmo resultado 1, 4, 9, … 100. O model termina com um resumo breve.

Painel Session Events para o agent isolate-demo mostrando o agent escrevendo /squares.py, uma agent.message anotando 'this sandbox has no Python runtime' e pivotando para simular via JavaScript, a execução de custom tool retornando os quadrados, e a mensagem final do assistant

Essa virada é uma feature do backend Isolate, não um bug: o model é instruído em seu system prompt sobre qual toolset está disponível (as custom tools do lado Isolate incluem um caminho de execução de código; o caminho do lado MicroVM usa bash + filesystem). No Isolate, “compile esse crate Rust” ou “instale pandas e rode um notebook” não são opções, mas qualquer coisa que o agent consiga expressar em JavaScript (ou via os bindings Workers empacotados) roda bem. O trade-off é estrutural: Isolate = cold start de milissegundos + runtime V8/JS-only; MicroVM = Linux completo com processos arbitrários, cold start mais lento.

Test 3: A custom tool de PDF-summarizer

É aqui que o trabalho do Step 7 compensa. Pré-requisitos: você completou o Step 7 (adicionou o binding PDFS, copiou as duas custom tools e pdf-text.ts para o seu clone, subiu os três GAO PDFs e redeployou). Se você pulou o Step 7, os toggles cf_list_pdfs e cf_read_pdf não vão aparecer no step Tools abaixo.

Setup

  1. Abra o dashboard, navegue para Agents e clique em New Agent.
  2. Nomeie o agent como pdf-summarizer.
  3. Backend: MicroVM ou Isolate. Ambos funcionam, já que custom tools rodam no Worker, não no sandbox. Eu testei no Isolate pelo cold start mais ágil.
  4. Model: claude-sonnet-4-6.
  5. Tools: role a lista de toggle de tool por agent e marque cf_list_pdfs e cf_read_pdf. Eles aparecem em “Custom tools” assim que o redeploy do Step 7 aterrissa. O toolset Anthropic padrão é opcional para esse prompt; deixe ligado se quiser que o agent consiga escrever notas de volta no workspace.
  6. Salve. O dashboard re-registra o agent com a Anthropic, então a próxima sessão vai ver as duas custom tools no seu catálogo.
  7. Clique no agent e Start session.

Prompt

Summarize the three PDFs in the pdfs/ prefix of the R2 bucket. For each one, give me three bullet points covering scope, headline number, and risks.

Resultado

O agent chamou cf_list_pdfs uma vez, depois chamou cf_read_pdf três vezes (uma por objeto), e produziu um resumo de três seções limpo. Olhando o log de sessão do dashboard, as tool calls round-trippearam em menos de um segundo cada porque, e esse é todo o ponto, pdfs.list() e pdfs.get() não são chamadas de rede do ponto de vista do agent. São bindings no Worker que o dispatcher de custom tool roda dentro. Sem HTTP, sem auth, sem política de egress para aplicar.

Eu rodei a mesma demo contra uma API externa como controle: uma custom tool que buscava os PDFs de uma URL HTTPS em vez do R2. O resultado funcional foi idêntico, mas cada fetch adicionou latência de round-trip e o agent agora precisava de credenciais para autenticar. O modelo de inline binding colapsa as duas coisas.

Saída completa da sessão (truncada)
[session-start] sandbox=isolate agent=pdf-summarizer
[tool_use] cf_list_pdfs(prefix="pdfs/")
[tool_result] pdfs/gao-cybersecurity.pdf  3040706
              pdfs/gao-dod-readiness.pdf  302538
              pdfs/gao-supply-chain.pdf   174946
[tool_use] cf_read_pdf(key="pdfs/gao-cybersecurity.pdf")
[tool_result] (... extracted text, ~7200 chars ...)
[tool_use] cf_read_pdf(key="pdfs/gao-dod-readiness.pdf")
[tool_result] (... extracted text, ~2400 chars ...)
[tool_use] cf_read_pdf(key="pdfs/gao-supply-chain.pdf")
[tool_result] (... extracted text, ~2100 chars ...)
[assistant] Here is a summary of the three documents:

Q1 report:
- Scope: ...
- Headline number: ...
- Risks: ...

(... etc ...)
[session-end] duration=42s tools_called=4

Cleanup

O template provisiona recursos cobrados em quantidade suficiente para você querer ser explícito ao derrubá-los. A ordem importa por causa de dependências:

# 1. Revoke the Anthropic webhook (Claude Platform Console → Settings → Webhooks → delete).
# 2. Delete the self-managed environment (Console → Environments → delete).

# 3. Tear down the Cloudflare Worker.
npx wrangler delete

# 4. Drop the R2 snapshot bucket (this also removes any unsnapshot data).
npx wrangler r2 bucket delete claude-managed-agents-snapshots

# 5. Drop the D1 database.
npx wrangler d1 delete claude-managed-agents-db

# 6. Drop the KV namespaces (look up IDs with `wrangler kv namespace list`).
npx wrangler kv namespace delete --namespace-id <SECRETS_ID>
npx wrangler kv namespace delete --namespace-id <EGRESS_POLICIES_ID>

Se você provisionou serviços VPC ou quaisquer bindings opcionais, derrube esses também. A Cloudflare cobra Containers e storage R2 continuamente, então deixar o deploy ocioso ainda custa.

Por que esse padrão importa

A jogada arquitetural por trás de tudo isso é pequena mas consequente: o loop do model fica gerenciado pela Anthropic, e a execução das tools se move para uma infraestrutura que o cliente já controla. Essa divisão é o que torna o Claude Managed Agents usável para workloads que antes ficavam presos em revisão de segurança. O sandbox fica do lado dos dados que ele precisa (object stores, databases internos, APIs privadas, serviços de identidade), usa os controles de egress existentes do cliente, e produz um audit trail que ops já sabe ler.

Na Cloudflare a história da integração é a mais apertada dos quatro parceiros de launch: custom tools viram bindings do Worker, então uma leitura de R2 ou uma chamada de inferência ao Workers AI é um argumento de função em vez de um round trip de rede. O mesmo padrão cabe em outros formatos também. A Daytona vai rodar o mesmo template em infraestrutura on-premises operada pelo cliente para times cujo regulador quer um data plane OSS; Vercel e Modal cobrem workloads BYO-cloud e pesados em GPU. A cloud é intercambiável; a propriedade arquitetural é o que conta.

Os times que mais aproveitam isso são aqueles cujas tools do agent já têm o formato da plataforma existente: documentos no object store, busca no vector database, lógica de negócio atrás dos serviços HTTP internos. Para eles, o padrão colapsa duas das perguntas mais difíceis em deploy de agent (para onde os dados vão e quem detém as credenciais) num passo de configuração. O cérebro continua sendo o da Anthropic; as mãos finalmente são suas.

Comments