Introdução
Um agente de IA costuma ser descrito como um modelo capaz de raciocinar ou usar ferramentas. No entanto, antes de adicionar um modelo, uma aplicação precisa responder de forma confiável a uma pergunta mais simples: qual sessão em andamento deve receber esta solicitação? Uma aplicação de suporte deve enviar cada interação de planning de volta para a mesma sessão lógica, mantendo billing separado.
O Agents SDK do Cloudflare fornece uma classe Agent de nível mais alto para essa tarefa. Cada Agent nomeado é apoiado por uma instância de SQLite Durable Object. O SDK gerencia o estado salvo e o roteamento das solicitações, enquanto os Durable Objects fornecem a identidade estável e o armazenamento subjacentes. Você verá as duas camadas, em vez de tratar o SDK como uma caixa-preta.
Você criará uma pequena aplicação de suporte, deliberadamente sem LLM:
SupportAgentdefine o que uma sessão de suporte armazena e faz.- O binding
SupportAgentrepresenta o namespace da classe. /agents/support-agent/planningseleciona a instância chamadaplanning.initialState,this.stateesetState()permitem que o SDK persista o pequeno estado dessa instância.
Você escreverá duas notas em uma sessão nomeada, comprovará que outra sessão permanece isolada, interromperá e reiniciará todo o runtime local, implantará o mesmo código no Cloudflare, inspecionará o binding e o namespace reais no Dashboard e removerá todos os recursos descartáveis.
Antes de iniciar este curso, conclua Connect LabEx to Your Cloudflare Account. Esse laboratório ensina a usar o terminal da VM do LabEx, a autorização de dispositivo do Wrangler, a confirmação da conta e a configuração do ID da conta. Você já deve entender um Worker pequeno em TypeScript e o modelo de identidade dos Durable Objects apresentado em O01–O06. Não é necessário conhecimento prévio do Agents SDK, React ou de modelos.
Atualmente, a documentação oficial disponibiliza Durable Objects com backend SQLite no Workers Free. Este laboratório cria um namespace de classe descartável, algumas instâncias pequenas de Agent e apenas solicitações limitadas. Ele não chama um modelo e não exige o Workers Paid. A configuração instala Node.js 22.22.0, Agents SDK 0.23.0 e Wrangler 4.134.0 localmente no projeto /home/labex/project/named-support-agent; ela não faz login, não cria estado na nuvem, não implanta código nem conclui a implementação do aluno.
Autorizar a VM e configurar o Agent
Nesta etapa, você autorizará o Wrangler, confirmará a conta de aprendizagem pretendida e descreverá uma classe de Agent sem fazer nenhuma implantação ainda. Esta VM nova tem seu próprio sistema de arquivos, portanto estar conectado ao Cloudflare Dashboard não autoriza o terminal dela.
Entre no projeto preparado e confirme as versões fixadas:
cd /home/labex/project/named-support-agent
node --version
npx wrangler --version
npm list agents --depth=0
Espere Node.js v22.22.0, Wrangler 4.134.0 e agents@0.23.0. Fixar as versões é importante porque o Agents SDK muda mais rapidamente do que as APIs básicas de Worker.
Inicie o fluxo de autorização do dispositivo:
npx wrangler login --device --browser=false
O Wrangler exibirá uma URL do navegador e um código curto de dispositivo. Abra essa URL, informe o código, confirme que a conta selecionada é sua conta de aprendizagem dedicada e confira as permissões solicitadas antes de autorizar. Nunca digite uma senha do Cloudflare ou um token de API no terminal.
Depois que o navegador informar que a autorização foi concluída, volte ao terminal e aguarde o Wrangler terminar. Solicite informações estruturadas de identidade:
npx wrangler whoami --json
Confirme loggedIn: true. Em seguida, exiba somente os nomes das contas e selecione privadamente o ID pertencente a LabEx Learning:
WHOAMI="$(npx wrangler whoami --json)"
printf '%s\n' "$WHOAMI" | jq '{loggedIn, authType, accounts: [.accounts[] | {name}]}'
ACCOUNT_ID="$(printf '%s\n' "$WHOAMI" | jq -r '.accounts[] | select(.name == "LabEx Learning") | .id')"
test -n "$ACCOUNT_ID"
Se sua conta de aprendizagem dedicada tiver outro nome de exibição, substitua LabEx Learning somente depois de confirmar o nome correto. O ID da conta não é um segredo, mas este comando evita exibi-lo sem necessidade.
Crie um nome exclusivo para o Worker descartável:
RUN="labex-c11-s01-$(openssl rand -hex 6)"
printf '%s\n' "$RUN"
Crie wrangler.jsonc:
cat > wrangler.jsonc <<JSON
{
"\$schema": "./node_modules/wrangler/config-schema.json",
"name": "$RUN",
"account_id": "$ACCOUNT_ID",
"main": "src/index.ts",
"compatibility_date": "2026-09-18",
"compatibility_flags": ["nodejs_compat"],
"workers_dev": true,
"preview_urls": false,
"observability": {
"enabled": true,
"head_sampling_rate": 1
},
"durable_objects": {
"bindings": [
{ "name": "SupportAgent", "class_name": "SupportAgent" }
]
},
"migrations": [
{ "tag": "v1", "new_sqlite_classes": ["SupportAgent"] }
]
}
JSON
O binding SupportAgent é o identificador que o Worker usa para acessar o namespace da classe. A migração v1 instrui o Cloudflare a criar essa classe com armazenamento SQLite. Os Agents usam a base dos Durable Objects; o SDK não elimina essa camada de recursos. Atualmente, o SDK exige nodejs_compat. Nenhuma dessas declarações cria recursos na nuvem antes da implantação.
Implementar o Agent de suporte nomeado
Nesta etapa, você implementará o estado e o comportamento HTTP compartilhados por todas as sessões de suporte nomeadas. Uma classe de Agent é um comportamento reutilizável, enquanto uma instância de Agent é uma sessão nomeada, como planning. O Cloudflare pode executar várias instâncias da mesma classe, e cada instância possui estado independente.
Crie src/index.ts:
cat > src/index.ts <<'TS'
import { Agent, routeAgentRequest } from "agents";
export interface SupportState {
status: "new" | "active";
noteCount: number;
lastNote: string | null;
}
interface Env {
SupportAgent: DurableObjectNamespace<SupportAgent>;
}
function json(value: unknown, init: ResponseInit = {}): Response {
const headers = new Headers(init.headers);
headers.set("content-type", "application/json; charset=utf-8");
return new Response(JSON.stringify(value, null, 2), { ...init, headers });
}
export class SupportAgent extends Agent<Env, SupportState> {
initialState: SupportState = {
status: "new",
noteCount: 0,
lastNote: null
};
async onRequest(request: Request): Promise<Response> {
if (request.method === "GET") {
console.log(JSON.stringify({ event: "support_agent_read", instance: this.name, noteCount: this.state.noteCount }));
return json({ instance: this.name, ...this.state });
}
if (request.method === "POST") {
const body = await request.json<{ note?: unknown }>().catch(() => null);
const note = typeof body?.note === "string" ? body.note.trim() : "";
if (note.length < 1 || note.length > 120) {
return json({ error: "note must contain 1-120 characters" }, { status: 400 });
}
this.setState({
status: "active",
noteCount: this.state.noteCount + 1,
lastNote: note
});
console.log(JSON.stringify({ event: "support_agent_updated", instance: this.name, noteCount: this.state.noteCount }));
return json({ instance: this.name, ...this.state });
}
return json({ error: "method not allowed" }, { status: 405 });
}
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === "/health") {
return json({ status: "ok" });
}
const agentResponse = await routeAgentRequest(request, env, {
onBeforeRequest(incoming, { name }) {
if (!/^[a-z][a-z0-9-]{1,31}$/.test(name)) {
return json({ error: "invalid support session name" }, { status: 400 });
}
return incoming;
}
});
return agentResponse ?? json({ error: "not found" }, { status: 404 });
}
} satisfies ExportedHandler<Env>;
TS
Leia as partes importantes começando pelos elementos internos:
initialStateé o valor visto por uma instância nomeada recém-criada.this.statelê o estado atual, gerenciado pelo SDK, dessa instância.setState()valida e salva de forma síncrona o estado substituto no armazenamento SQLite da instância; laboratórios posteriores também o sincronizarão com clientes conectados.this.nameé o nome estável da instância selecionado pelo roteamento. Ele não é um nome de classe nem um ID aleatório de processo.routeAgentRequest()mapeia/agents/<binding>/<name>para o Agent correto. O bindingSupportAgenttorna-sesupport-agentna URL.onBeforeRequestrejeita nomes inválidos antes que uma instância de Durable Object seja selecionada, evitando identidades duráveis indesejadas.
O log registra somente um nome de instância sintético e a quantidade de notas. Ele exclui deliberadamente o texto das notas para que o exercício posterior do Dashboard não retenha conteúdo de suporte.
Gerar tipos e fazer o build antes de executar
Nesta etapa, você gerará tipos cientes da configuração e fará o build do Worker sem implantá-lo. Os tipos gerados do Worker conectam a configuração ao TypeScript, detectando um binding ou uma classe digitados incorretamente antes que um processo local ou uma implantação na nuvem consumam tempo.
Gere os tipos a partir de wrangler.jsonc:
npx wrangler types
O Wrangler escreverá worker-configuration.d.ts. Confirme que ele inclui o binding de Agent configurado sem exibir conteúdo gerado não relacionado:
grep -n "SupportAgent" worker-configuration.d.ts | head
Execute o compilador TypeScript:
npm run check
Nenhuma saída após o cabeçalho do script significa que o compilador não encontrou erros. Agora peça ao Wrangler para criar o bundle de implantação sem entrar em contato com o Cloudflare nem criar um recurso:
npx wrangler deploy --dry-run --outdir .labex/dry-run
Espere um resumo de tamanho do upload bem-sucedido e o binding do Durable Object SupportAgent. Uma execução de teste valida localmente o empacotamento e a configuração; ela não comprova autorização, armazenamento remoto nem o comportamento na edge.
Comprovar a identidade local e a persistência após reiniciar
Nesta etapa, você demonstrará três propriedades diferentes: o uso repetido de um nome chega ao mesmo estado, um nome diferente permanece isolado e o estado salvo sobrevive à reinicialização completa do processo de desenvolvimento.
Inicie o runtime local de Workers em segundo plano:
npm run dev > .labex/dev.log 2>&1 &
echo $! > .labex/dev.pid
for attempt in $(seq 1 30); do
if curl --silent --fail http://127.0.0.1:8787/health; then
break
fi
sleep 1
done
Espere {"status":"ok"}. Leia o novo Agent planning:
curl --silent http://127.0.0.1:8787/agents/support-agent/planning | jq
Ele começa com status: "new", noteCount: 0 e lastNote: null. Adicione duas notas sintéticas:
curl --silent --request POST --header 'content-type: application/json' \
--data '{"note":"Customer cannot open the invoice"}' \
http://127.0.0.1:8787/agents/support-agent/planning | jq
curl --silent --request POST --header 'content-type: application/json' \
--data '{"note":"Asked customer to retry"}' \
http://127.0.0.1:8787/agents/support-agent/planning | jq
A segunda resposta informa instance: "planning", status: "active", noteCount: 2 e a segunda nota. As duas solicitações usaram o mesmo nome na URL, portanto chegaram ao mesmo Agent lógico.
Leia uma instância diferente:
curl --silent http://127.0.0.1:8787/agents/support-agent/support | jq
support ainda tem seu próprio estado inicial, com contagem 0. Os dois nomes compartilham o comportamento da classe, mas não os valores armazenados.
Rejeite um nome inválido:
curl --silent --write-out '\nHTTP %{http_code}\n' \
http://127.0.0.1:8787/agents/support-agent/INVALID
Espere invalid support session name e HTTP 400.
Interrompa exatamente o processo que você iniciou e depois inicie um novo processo usando o mesmo diretório de persistência local:
kill "$(cat .labex/dev.pid)"
wait "$(cat .labex/dev.pid)" 2>/dev/null || true
npm run dev > .labex/dev-restart.log 2>&1 &
echo $! > .labex/dev.pid
for attempt in $(seq 1 30); do
if curl --silent --fail http://127.0.0.1:8787/health; then
break
fi
sleep 1
done
curl --silent http://127.0.0.1:8787/agents/support-agent/planning | jq
curl --silent http://127.0.0.1:8787/agents/support-agent/support | jq
Depois de uma reinicialização completa do Wrangler, planning continua em 2, enquanto support continua em 0. Isso é uma evidência mais forte do que ler duas vezes dentro de um único processo JavaScript: os dados voltaram do diretório local de persistência do Durable Object.
Implantar e testar instâncias do Agent na nuvem
Nesta etapa, você implantará a aplicação sem alterações e testará instâncias reais de Agent gerenciadas pela nuvem. As evidências locais não podem comprovar que a conta selecionada do Cloudflare possui o recurso nem que o runtime na edge fornece a mesma identidade nomeada.
Interrompa o processo local e faça a implantação:
kill "$(cat .labex/dev.pid)"
wait "$(cat .labex/dev.pid)" 2>/dev/null || true
npx wrangler deploy
O Wrangler aplica a migração v1, cria o namespace da classe SupportAgent com backend SQLite e exibe uma URL pública workers.dev. Salve essa URL exata substituindo o exemplo:
WORKER_URL="https://YOUR_WORKER_URL"
Aguarde a rota de health sem estado:
for attempt in $(seq 1 30); do
if curl --silent --fail "$WORKER_URL/health"; then
break
fi
sleep 2
done
Agora teste as instâncias gerenciadas pela nuvem com dados sintéticos:
curl --silent --request POST --header 'content-type: application/json' \
--data '{"note":"Cloud planning note one"}' \
"$WORKER_URL/agents/support-agent/planning" | jq
curl --silent --request POST --header 'content-type: application/json' \
--data '{"note":"Cloud planning note two"}' \
"$WORKER_URL/agents/support-agent/planning" | jq
curl --silent --request POST --header 'content-type: application/json' \
--data '{"note":"Independent support note"}' \
"$WORKER_URL/agents/support-agent/support" | jq
Leia as duas instâncias:
curl --silent "$WORKER_URL/agents/support-agent/planning" | jq
curl --silent "$WORKER_URL/agents/support-agent/support" | jq
O Agent planning na nuvem tem contagem 2; o Agent support, independente, tem contagem 1. O armazenamento local e o armazenamento na nuvem são intencionalmente separados, mas os dois ambientes implementam o mesmo contrato de nome para instância.
O verificador também cria dois nomes de Agent exclusivos para a execução e repete o estado inicial, a persistência do mesmo nome, o isolamento entre nomes e a rejeição de nomes inválidos. Ele nunca trata um arquivo local ou o histórico de comandos como prova de comportamento remoto.
Conectar as evidências do runtime ao Dashboard
Nesta etapa, você conectará o comportamento observado no terminal ao binding, ao namespace e aos logs visíveis no Dashboard. Os nomes, horários e totais nas capturas de tela são exemplos da execução testada; use o nome exclusivo labex-c11-s01-... mostrado em seu próprio terminal.
Abra Workers & Pages no Cloudflare Dashboard e selecione o Worker descartável. A visão geral identifica a aplicação implantada e o tráfego recente.

Abra a aba Bindings do Worker. Encontre SupportAgent conectado à classe de Durable Object SupportAgent. O primeiro rótulo é o nome visível para o código do Worker e para o roteamento; o nome da classe identifica a implementação exportada de src/index.ts.

Abra Durable Objects na navegação Developer Platform e selecione o namespace pertencente ao Worker exato. Confirme a classe SupportAgent e Storage: SQL. O namespace é a coleção no nível da classe; planning, support e os nomes usados pelo verificador são instâncias individuais dentro dela. A imagem de exemplo omite o ID específico do namespace por motivos de privacidade.

Volte ao Worker e abra Observability → Logs. Encontre e expanda um evento de aplicação support_agent_read ou support_agent_updated. Compare seu instance sintético e noteCount com uma solicitação limitada. A aplicação não registra o texto das notas.

As métricas e os logs do Dashboard podem chegar com atraso, portanto um gráfico recente vazio não é conclusivo. A API autenticada, a propriedade do namespace e as verificações do runtime ativo continuam sendo as fontes de autoridade. As capturas de tela ensinam onde essas mesmas relações aparecem visualmente; elas não são entregas dos alunos.
Remover o namespace do Agent e o Worker
Nesta etapa, você removerá permanentemente o namespace exato do Agent e o Worker enquanto a VM ainda estiver autorizada. O estado do Agent pertence ao namespace da classe de Durable Object, portanto excluir apenas o script do Worker não é uma solicitação explícita para apagar esse estado armazenado. As migrações do Cloudflare são somente de acréscimo: mantenha v1 e depois adicione uma migração de exclusão v2 para a classe exata.
Crie um ponto de entrada pequeno para a limpeza, sem exportar um Agent:
cat > src/cleanup.ts <<'TS'
export default {
fetch() {
return Response.json({ status: "cleanup" }, { status: 410 });
}
};
TS
Leia o nome exato e a conta da configuração original e depois crie wrangler.cleanup.jsonc:
RUN="$(node -e 'console.log(JSON.parse(require("fs").readFileSync("wrangler.jsonc", "utf8")).name)')"
ACCOUNT_ID="$(node -e 'console.log(JSON.parse(require("fs").readFileSync("wrangler.jsonc", "utf8")).account_id)')"
cat > wrangler.cleanup.jsonc <<JSON
{
"\$schema": "./node_modules/wrangler/config-schema.json",
"name": "$RUN",
"account_id": "$ACCOUNT_ID",
"main": "src/cleanup.ts",
"compatibility_date": "2026-09-18",
"compatibility_flags": ["nodejs_compat"],
"workers_dev": true,
"preview_urls": false,
"migrations": [
{ "tag": "v1", "new_sqlite_classes": ["SupportAgent"] },
{ "tag": "v2", "deleted_classes": ["SupportAgent"] }
]
}
JSON
Manter v1 é importante: o histórico de migrações é uma sequência, não uma descrição que deve ser reescrita. v2 remove permanentemente o namespace da classe e todas as instâncias nomeadas descartáveis que estão nele.
Implante a migração de exclusão:
npx wrangler deploy --config wrangler.cleanup.jsonc
Leia a saída da migração e confirme que somente SupportAgent do seu Worker exclusivo foi excluído. Depois, exclua o Worker de limpeza sem estado:
npx wrangler delete --config wrangler.cleanup.jsonc --force
Confirme a aplicação exata labex-c11-s01-... se for solicitado. Em Workers & Pages, confirme que o Worker exato não está mais presente. Esta conta de teste não contém outras aplicações, portanto a execução aceita mostra a lista inteira vazia. Uma conta com outros projetos deve manter essas linhas não relacionadas.

Abra Durable Objects e confirme que o namespace pertencente ao Worker excluído também não está presente. A conta de teste aceita não possui outros namespaces, portanto a lista informa que não há Durable Objects. Não exclua um namespace pertencente a outro projeto apenas para reproduzir este exemplo.

Os logs históricos podem permanecer temporariamente e não são recursos ativos.
Execute a verificação autenticada de ausência antes de sair da conta:
python3 .labex/verify.py deleted
Somente PASS: deleted comprova que a conta selecionada não contém mais nenhum dos dois recursos pertencentes a ela. Um 404 causado por perda de autorização ou um erro de rede não é aceito como evidência de exclusão.
Revogar a autorização desta VM
Nesta etapa, você removerá a autorização OAuth armazenada nesta VM descartável. A limpeza dos recursos da nuvem e a limpeza das credenciais locais resolvem problemas diferentes; o Worker e o namespace já foram removidos.
Saia da conta:
npx wrangler logout
Peça ao Wrangler um status estruturado:
npx wrangler whoami --json
O resultado deve conter explicitamente "loggedIn": false. Esse valor estruturado é mais confiável do que uma mensagem amigável, porque a versão testada do Wrangler pode produzir saídas comuns em vários estados de autenticação. Uma falha de rede é inconclusiva; tente novamente em vez de interpretá-la como logout.
Você removeu os dois tipos de estado criados por este laboratório: o namespace e o Worker do Agent de suporte remoto, além da autorização local da VM.
Resumo
Você criou o primeiro Agent do curso no Cloudflare sem esconder a base por trás da terminologia de IA. Aprendeu que uma classe Agent define o comportamento, que seu binding expõe um namespace de Durable Object com backend SQLite, que um nome estável na URL seleciona uma instância lógica e que o Agents SDK persiste as atualizações de initialState por meio de this.state e setState().
Você comprovou localmente a persistência do mesmo nome, o isolamento entre nomes e a durabilidade após a reinicialização do processo, repetiu o contrato na sua conta de aprendizagem do Cloudflare, conectou as evidências do runtime ao binding, ao namespace e aos logs com privacidade limitada no Dashboard e removeu os recursos da nuvem e a autorização da VM. No próximo laboratório, você conectará clientes do navegador a esse estado e introduzirá uma sincronização em tempo real controlada.



