Introducción
A menudo se describe un agente de IA como un modelo capaz de razonar o utilizar herramientas. Sin embargo, antes de añadir un modelo, una aplicación necesita responder de forma fiable a una pregunta más sencilla: ¿qué sesión activa debe recibir esta solicitud? Una aplicación de soporte debe enviar cada interacción de planning a la misma sesión lógica y mantener billing separada.
El Agents SDK de Cloudflare proporciona una clase Agent de nivel superior para resolver esta tarea. Cada Agent con nombre está respaldado por una instancia de SQLite Durable Object. El SDK gestiona el estado guardado y el enrutamiento de las solicitudes, mientras que Durable Objects proporcionan la identidad estable y el almacenamiento subyacentes. Podrá observar ambas capas en lugar de tratar el SDK como una caja negra.
Construirá una pequeña aplicación de soporte que deliberadamente no utiliza un LLM:
SupportAgentdefine qué almacena y qué hace una sesión de soporte.- El binding
SupportAgentrepresenta el espacio de nombres de la clase. /agents/support-agent/planningselecciona la instancia llamadaplanning.initialState,this.stateysetState()permiten que el SDK conserve el pequeño estado de esa instancia.
Escribirá dos notas en una sesión con nombre, demostrará que otra sesión permanece aislada, detendrá y reiniciará todo el entorno local, desplegará el mismo código en Cloudflare, inspeccionará su binding y espacio de nombres reales en el Dashboard y eliminará todos los recursos desechables.
Antes de comenzar este curso, complete Conectar LabEx con su cuenta de Cloudflare. Allí aprenderá a utilizar el terminal de la máquina virtual de LabEx, la autorización del dispositivo de Wrangler, la confirmación de la cuenta y la configuración del ID de cuenta. Ya debe comprender un Worker pequeño de TypeScript y el modelo de identidad de Durable Objects de O01–O06. No se presupone ningún conocimiento del Agents SDK, React ni de modelos.
Actualmente, la documentación oficial permite utilizar Durable Objects respaldados por SQLite en Workers Free. Este laboratorio crea un único espacio de nombres de clase desechable, unas pocas instancias pequeñas de Agent y solo solicitudes acotadas. No llama a ningún modelo ni requiere Workers Paid. La configuración instala Node.js 22.22.0, Agents SDK 0.23.0 y Wrangler 4.134.0 local del proyecto en /home/labex/project/named-support-agent; no inicia sesión, crea estado en la nube, despliega código ni completa la implementación del estudiante.
Autorizar la máquina virtual y configurar el Agent
En este paso autorizará Wrangler, confirmará la cuenta de aprendizaje prevista y describirá una clase de Agent sin desplegar nada todavía. Esta máquina virtual nueva tiene su propio sistema de archivos, por lo que haber iniciado sesión en el Dashboard de Cloudflare no autoriza el terminal.
Entre en el proyecto preparado y confirme las versiones fijadas:
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 y agents@0.23.0. Fijar las versiones es importante porque el Agents SDK cambia más rápidamente que las API básicas de Worker.
Inicie el flujo de autorización del dispositivo:
npx wrangler login --device --browser=false
Wrangler mostrará una URL del navegador y un código de dispositivo breve. Abra esa URL, introduzca el código, confirme que la cuenta seleccionada es su cuenta de aprendizaje dedicada y revise los permisos solicitados antes de autorizar. Nunca escriba una contraseña de Cloudflare ni un token de API en el terminal.
Cuando el navegador indique que la autorización se completó, vuelva al terminal y espere a que Wrangler termine. Solicite la información estructurada de identidad:
npx wrangler whoami --json
Confirme loggedIn: true. Después, muestre únicamente los nombres de las cuentas y seleccione de forma privada el ID correspondiente 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"
Si su cuenta de aprendizaje dedicada tiene otro nombre visible, sustituya LabEx Learning únicamente después de confirmar el nombre correcto. El ID de cuenta es un dato de configuración, no un secreto, pero este comando evita mostrarlo innecesariamente.
Cree un nombre único para el Worker desechable:
RUN="labex-c11-s01-$(openssl rand -hex 6)"
printf '%s\n' "$RUN"
Cree 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
El binding SupportAgent es el identificador que permite al Worker acceder al espacio de nombres de la clase. La migración v1 indica a Cloudflare que cree esa clase con almacenamiento SQLite. Agents utiliza la base de Durable Objects; el SDK no elimina esta capa de recursos. Actualmente, el SDK requiere nodejs_compat. Ninguna de estas declaraciones crea recursos en la nube hasta que se realiza el despliegue.
Implementar el Agent de soporte con nombre
En este paso implementará el estado y el comportamiento HTTP que comparten todas las sesiones de soporte con nombre. Una clase de Agent define un comportamiento reutilizable, mientras que una instancia de Agent es una sesión con nombre, como planning. Cloudflare puede ejecutar muchas instancias de la misma clase y cada instancia posee un estado independiente.
Cree 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
Lea las partes importantes desde el interior hacia el exterior:
initialStatees el valor que recibe una instancia con nombre recién creada.this.statelee el estado actual de esa instancia gestionado por el SDK.setState()valida sincrónicamente y guarda el estado de reemplazo en el almacenamiento SQLite de la instancia; en laboratorios posteriores también lo sincronizará con los clientes conectados.this.namees el nombre estable de la instancia seleccionado por el enrutamiento. No es el nombre de una clase ni un ID aleatorio del proceso.routeAgentRequest()asigna/agents/<binding>/<name>al Agent correcto. El bindingSupportAgentse convierte ensupport-agenten la URL.onBeforeRequestrechaza los nombres con formato incorrecto antes de seleccionar una instancia de Durable Object, evitando crear identidades persistentes no deseadas.
El registro solo contiene un nombre de instancia sintético y el contador. Excluye deliberadamente el texto de las notas para que el ejercicio posterior del Dashboard no conserve contenido de soporte.
Generar tipos y compilar antes de ejecutar
En este paso generará tipos conscientes de la configuración y compilará el Worker sin desplegarlo. Los tipos generados del Worker conectan la configuración con TypeScript y detectan un binding o una clase escritos incorrectamente antes de que un proceso local o un despliegue en la nube consuman tiempo.
Genere los tipos a partir de wrangler.jsonc:
npx wrangler types
Wrangler escribirá worker-configuration.d.ts. Confirme que incluye el binding de Agent configurado sin mostrar otro contenido generado no relacionado:
grep -n "SupportAgent" worker-configuration.d.ts | head
Ejecute el compilador de TypeScript:
npm run check
Si después del encabezado del script no aparece ninguna salida, significa que el compilador no encontró errores. Ahora pida a Wrangler que compile el paquete de despliegue sin contactar con Cloudflare ni crear recursos:
npx wrangler deploy --dry-run --outdir .labex/dry-run
Espere un resumen correcto del tamaño de la carga y el binding de Durable Object SupportAgent. Una ejecución en seco valida localmente el empaquetado y la configuración; no demuestra la autorización, el almacenamiento remoto ni el comportamiento en el edge.
Demostrar la identidad local y la persistencia tras reiniciar
En este paso demostrará tres propiedades distintas: utilizar repetidamente un nombre llega al mismo estado, un nombre diferente permanece aislado y el estado guardado sobrevive al reinicio completo del proceso de desarrollo.
Inicie en segundo plano el entorno local de Workers:
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"}. Lea el Agent planning recién creado:
curl --silent http://127.0.0.1:8787/agents/support-agent/planning | jq
Comienza con status: "new", noteCount: 0 y lastNote: null. Añada dos 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
La segunda respuesta informa instance: "planning", status: "active", noteCount: 2 y la segunda nota. Ambas solicitudes utilizaron el mismo nombre en la URL, por lo que llegaron al mismo Agent lógico.
Lea una instancia diferente:
curl --silent http://127.0.0.1:8787/agents/support-agent/support | jq
support todavía tiene su propio estado inicial con el contador en 0. Ambos nombres comparten el comportamiento de la clase, pero no los valores almacenados.
Rechace un nombre con formato incorrecto:
curl --silent --write-out '\nHTTP %{http_code}\n' \
http://127.0.0.1:8787/agents/support-agent/INVALID
Espere invalid support session name y HTTP 400.
Detenga exactamente el proceso que inició y, después, ejecute un proceso nuevo con el mismo directorio de persistencia 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
Después de reiniciar Wrangler por completo, planning permanece en 2, mientras que support permanece en 0. Esto constituye una evidencia más sólida que leer dos veces dentro de un único proceso de JavaScript: los datos volvieron del directorio de persistencia local de Durable Object.
Desplegar y probar las instancias de Agent en la nube
En este paso desplegará la aplicación sin cambios y probará instancias de Agent reales administradas en la nube. La evidencia local no puede demostrar que la cuenta de Cloudflare seleccionada sea propietaria del recurso ni que el entorno edge proporcione la misma identidad con nombre.
Detenga el proceso local y realice el despliegue:
kill "$(cat .labex/dev.pid)"
wait "$(cat .labex/dev.pid)" 2>/dev/null || true
npx wrangler deploy
Wrangler aplica la migración v1, crea el espacio de nombres de clase SupportAgent respaldado por SQLite y muestra una URL pública de workers.dev. Guarde esa URL exacta sustituyendo el ejemplo:
WORKER_URL="https://YOUR_WORKER_URL"
Espere a que responda la ruta de comprobación de estado sin estado:
for attempt in $(seq 1 30); do
if curl --silent --fail "$WORKER_URL/health"; then
break
fi
sleep 2
done
Ahora pruebe las instancias administradas en la nube con datos 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
Lea ambas instancias:
curl --silent "$WORKER_URL/agents/support-agent/planning" | jq
curl --silent "$WORKER_URL/agents/support-agent/support" | jq
El Agent planning de la nube tiene un contador de 2; el Agent independiente support tiene un contador de 1. El almacenamiento local y el de la nube están separados deliberadamente, pero ambos entornos implementan el mismo contrato entre nombre e instancia.
El verificador también crea dos nombres de Agent únicos para esta ejecución y repite las comprobaciones del estado inicial, la persistencia con el mismo nombre, el aislamiento con nombres diferentes y el rechazo de nombres no válidos. Nunca considera un archivo local ni el historial de comandos como prueba del comportamiento remoto.
Conectar la evidencia del entorno con el Dashboard
En este paso relacionará el comportamiento observado en el terminal con el binding, el espacio de nombres y los registros visibles en el Dashboard. Los nombres, las marcas de tiempo y los totales de las capturas son ejemplos de la ejecución probada; utilice el nombre único labex-c11-s01-... de su propio terminal.
Abra Workers & Pages en el Dashboard de Cloudflare y seleccione su Worker desechable. Su vista general identifica la aplicación desplegada y el tráfico reciente.

Abra la pestaña Bindings del Worker. Busque SupportAgent conectado a la clase Durable Object SupportAgent. La primera etiqueta es el nombre visible para el código del Worker y el enrutamiento; el nombre de la clase identifica la implementación exportada desde src/index.ts.

Abra Durable Objects desde la navegación de Developer Platform y seleccione el espacio de nombres propiedad de su Worker exacto. Confirme la clase SupportAgent y Storage: SQL. El espacio de nombres es la colección de nivel de clase; planning, support y los nombres del verificador son instancias individuales dentro de ella. La imagen de ejemplo omite por privacidad el ID específico del espacio de nombres de la ejecución.

Vuelva al Worker y abra Observability → Logs. Busque y expanda un evento de aplicación support_agent_read o support_agent_updated. Compare sus valores sintéticos instance y noteCount con una solicitud acotada. La aplicación no registra deliberadamente el texto de las notas.

Las métricas y los registros del Dashboard pueden tardar en aparecer, por lo que un gráfico reciente vacío no es concluyente. La API autenticada, la propiedad del espacio de nombres y las comprobaciones del entorno en ejecución siguen siendo la autoridad. Las capturas enseñan dónde aparecen visualmente esas mismas relaciones; no son entregas del estudiante.
Eliminar el espacio de nombres del Agent y el Worker
En este paso eliminará permanentemente el espacio de nombres exacto del Agent y el Worker mientras la máquina virtual siga autorizada. El estado del Agent pertenece al espacio de nombres de la clase Durable Object, por lo que eliminar únicamente el script del Worker no constituye una solicitud explícita para borrar ese estado almacenado. Las migraciones de Cloudflare son acumulativas: conserve v1 y añada una migración de eliminación v2 para la clase exacta.
Cree un punto de entrada de limpieza pequeño que no exporte ningún Agent:
cat > src/cleanup.ts <<'TS'
export default {
fetch() {
return Response.json({ status: "cleanup" }, { status: 410 });
}
};
TS
Lea el nombre exacto y la cuenta de la configuración original y, después, cree 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
Conservar v1 es importante: el historial de migraciones es una secuencia, no una descripción que deba reescribirse. v2 elimina permanentemente el espacio de nombres de la clase y todas las instancias con nombre desechables que contiene.
Despliegue la migración de eliminación:
npx wrangler deploy --config wrangler.cleanup.jsonc
Lea la salida de la migración y confirme que se elimina únicamente SupportAgent de su Worker único. Después, elimine el Worker de limpieza sin estado restante:
npx wrangler delete --config wrangler.cleanup.jsonc --force
Confirme la aplicación exacta labex-c11-s01-... si se le solicita. En Workers & Pages, confirme que el Worker exacto ya no aparece. Esta cuenta de prueba no contiene aplicaciones no relacionadas, por lo que, en una ejecución aceptada, toda la lista queda vacía. Una cuenta con otros proyectos debe conservar las filas no relacionadas.

Abra Durable Objects y confirme que también ha desaparecido el espacio de nombres propiedad del Worker eliminado. La cuenta de prueba aceptada no contiene espacios de nombres no relacionados, por lo que su lista indica que no hay Durable Objects. No elimine un espacio de nombres perteneciente a otro proyecto solo para reproducir este ejemplo.

Los registros históricos pueden permanecer temporalmente y no son recursos activos.
Ejecute la comprobación autenticada de ausencia antes de cerrar la sesión:
python3 .labex/verify.py deleted
Solo PASS: deleted demuestra que la cuenta seleccionada ya no contiene ninguno de los dos recursos propios. Un 404 causado por la pérdida de autorización o un error de red no se acepta como evidencia de eliminación.
Revocar la autorización de esta máquina virtual
En este paso eliminará la autorización de OAuth almacenada en esta máquina virtual desechable. La limpieza de recursos en la nube y la limpieza de credenciales locales resuelven problemas distintos; el Worker y el espacio de nombres ya se han eliminado.
Cierre la sesión:
npx wrangler logout
Pida a Wrangler el estado estructurado:
npx wrangler whoami --json
El resultado debe contener explícitamente "loggedIn": false. Ese valor estructurado es más fiable que un mensaje amistoso, porque la versión probada de Wrangler puede producir una salida normal en varios estados de autenticación. Un error de red no es concluyente; vuelva a intentarlo en lugar de interpretarlo como un cierre de sesión correcto.
Ha eliminado los dos tipos de estado creados por este laboratorio: el espacio de nombres y el Worker del Agent de soporte remotos, y la autorización local de la máquina virtual.
Resumen
Construyó el primer Agent de Cloudflare del curso sin ocultar sus fundamentos detrás de la terminología de IA. Aprendió que una clase Agent define el comportamiento, que su binding expone un espacio de nombres de SQLite Durable Object, que un nombre estable en la URL selecciona una instancia lógica y que el Agents SDK conserva las actualizaciones de initialState mediante this.state y setState().
Demostró localmente la persistencia con el mismo nombre, el aislamiento con nombres diferentes y la durabilidad tras reiniciar el proceso; repitió el contrato en su cuenta de aprendizaje de Cloudflare; relacionó la evidencia del entorno con el binding, el espacio de nombres y los registros con privacidad acotada del Dashboard; y eliminó tanto los recursos de la nube como la autorización de la máquina virtual. En el siguiente laboratorio conectará clientes del navegador a este estado e introducirá una sincronización en tiempo real controlada.



