Arquitectura de Agentes IA

AI Agents — Cloudflare Agents SDK, Persistent State, Multi-user WebSocket, EU Jurisdiction Arquitectura de Agentes IA Agents SDK Estado persistente WebSocket multiusuario Jurisdicción UE

Introducción

La plataforma proporciona servicios educativos impulsados por IA a través del Cloudflare Agents SDK, que extiende los Durable Objects con gestión de conversaciones, streaming por WebSocket, estado persistente y RPC tipado. Cada agente es una instancia de Durable Object con su propia base de datos SQLite, capaz de gestionar múltiples conexiones WebSocket simultáneas.

Los agentes IA cubren cuatro casos de uso educativos:

  • Asistente educativo — Preguntas y respuestas generales, navegación por el currículo, recomendaciones de itinerarios de aprendizaje.
  • Agente de tutoría — Tutoría específica por materia con método socrático, explicaciones paso a paso y dificultad adaptativa.
  • Agente de generación de contenido — Planes de clase, cuestionarios, rúbricas y materiales de estudio para profesores.
  • Agente de evaluación — Calificación automatizada, generación de retroalimentación y análisis de brechas de aprendizaje.

La infraestructura de agentes se apoya en las inversiones existentes de la plataforma en Cloudflare:

  • Durable Objects (07-real-time-communication.md) — Ya utilizados para funcionalidades en tiempo real con WebSocket. El Agents SDK extiende los DO con capacidades específicas para IA.
  • Service Bindings (03-cloudflare-infrastructure.md) — El API Gateway enruta las peticiones /api/agents al Worker agent-service.
  • Module Federation (02-module-federation.md) — El micro frontend mfe-agents expone la interfaz de chat, la gestión de agentes y el historial de conversaciones.
  • CustomEvents (11-inter-mfe-communication.md) — Otros MFEs pueden desencadenar interacciones con agentes a través de eventos tipados.

Arquitectura

Agent Architecture — Browser through Worker to Agent Durable Object to AI Model Provider Arquitectura de agentes Navegador mfe-agents useAgentChat hook Puente CustomEvents WebSocket Agent Service Worker • Route /api/agents/:type/:id • WebSocket upgrade • DO stub.fetch() stub Agent DO Subclase AIChatAgent Estado SQLite Conversaciones @callable() RPC Tool calling jurisdiction("eu") Proveedor de modelos IA OpenAI / Anthropic / Workers AI Endpoint UE preferido HTTPS API Gateway JWT · CORS · Rate limit → AGENT_SERVICE binding HTTP (REST API) Residencia de datos en la UE Estado del Agent DO (SQLite), historial de conversaciones y PII permanecen en la UE via jurisdiction("eu")

Flujo de datos:

  1. El navegador abre una conexión WebSocket al Agent Service Worker a través del API Gateway.
  2. El Agent Service Worker actualiza la conexión (upgrade), resuelve el agent DO por tipo e ID, y reenvía la petición al stub del DO.
  3. El Agent DO (una subclase de AIChatAgent) acepta el WebSocket, gestiona el estado de la conversación en su base de datos SQLite local y transmite las respuestas del proveedor de modelos IA en streaming.
  4. El proveedor de modelos IA (OpenAI, Anthropic o Cloudflare Workers AI) recibe el prompt y devuelve los tokens en streaming al agente, que los reenvía a todos los clientes conectados a través de WebSocket.

Por qué Cloudflare Agents SDK

CapacidadAgents SDKDOs personalizadosServicio IA externo
Estado de conversación persistenteIntegrado (SQLite local del agente)Implementación manualRequiere BD externa
WebSocket multiusuarioIntegrado con hibernaciónImplementación manualNo soportado
RPC tipado (@callable())IntegradoDisponible (WorkerEntrypoint)N/A
Hooks de ReactuseAgentChat incluidoImplementación manualEspecífico del proveedor
Tool calling / MCPSoporte nativoImplementación manualEspecífico del proveedor
Fijación de jurisdicción UEjurisdiction("eu") (basado en DO)jurisdiction("eu")Depende del proveedor
Respuestas en streamingIntegrado (streamText)SSE/WS manualEspecífico del proveedor
Programación (alarmas)IntegradoDisponible (alarmas DO)Planificador externo
InfraestructuraUsa la cuenta CF existenteUsa la cuenta CF existenteProveedor separado
Modelo de costesPrecios DO (pago por duración de estado)Precios DOPrecios SaaS por consulta

El Agents SDK elimina la necesidad de construir desde cero la gestión de conversaciones, el enrutamiento de WebSocket y la persistencia de estado. Dado que la plataforma ya utiliza Durable Objects para funcionalidades en tiempo real, el Agents SDK es una extensión natural sin nuevas dependencias de infraestructura.


Tipos de agente

Asistente educativo

El agente de propósito general para el soporte educativo del día a día. Se encarga de:

  • Navegación por el currículo y búsqueda de contenido
  • Recomendaciones de itinerarios de aprendizaje basadas en el progreso del estudiante
  • Preguntas y respuestas generales sobre el material del curso
  • Recordatorios de calendario y fechas límite (a través de alarmas DO)
// workers/agent-service/src/agents/assistant.ts
import { AIChatAgent } from "agents/ai-chat-agent";
import { type StreamTextOnFinishCallback, type ToolSet, streamText } from "ai";

interface AssistantState {
  studentId: string;
  courseIds: string[];
  preferredLanguage: "en" | "es";
}

export class EducationalAssistant extends AIChatAgent<Env, AssistantState> {
  async onChatMessage(onFinish: StreamTextOnFinishCallback<ToolSet>) {
    const result = streamText({
      model: this.getModel(),
      system: `You are an educational assistant for the A2R Workspaces platform.
        Student ID: ${this.state.studentId}
        Enrolled courses: ${this.state.courseIds.join(", ")}
        Respond in: ${this.state.preferredLanguage}
        Be helpful, encouraging, and age-appropriate.`,
      messages: this.messages,
      tools: this.getAssistantTools(),
      onFinish,
    });
    return result;
  }

  private getModel() {
    // Use a cost-effective model for general Q&A
    return this.env.AI_PROVIDER === "openai"
      ? createOpenAI({ apiKey: this.env.OPENAI_API_KEY })("gpt-4o-mini")
      : createAnthropic({ apiKey: this.env.ANTHROPIC_API_KEY })("claude-haiku-4-5-20251001");
  }

  private getAssistantTools(): ToolSet {
    return {
      searchCurriculum: {
        description: "Search course curriculum for relevant content",
        parameters: z.object({ query: z.string(), courseId: z.string() }),
        execute: async ({ query, courseId }) => {
          // Call curriculum BFF via service binding or HTTP
          return { results: [] };
        },
      },
      getStudentProgress: {
        description: "Get the student's progress in a course",
        parameters: z.object({ courseId: z.string() }),
        execute: async ({ courseId }) => {
          return { progress: 0.65, lastActivity: "2026-03-01" };
        },
      },
    };
  }
}

Agente de tutoría

El agente de tutoría proporciona tutoría individual y específica por materia con método socrático, resolución de problemas paso a paso y dificultad adaptativa:

// workers/agent-service/src/agents/tutoring.ts
import { AIChatAgent } from "agents/ai-chat-agent";
import { type StreamTextOnFinishCallback, type ToolSet, streamText } from "ai";

interface TutoringState {
  studentId: string;
  subject: string;
  difficultyLevel: "beginner" | "intermediate" | "advanced";
  sessionStartedAt: string;
}

export class TutoringAgent extends AIChatAgent<Env, TutoringState> {
  async onChatMessage(onFinish: StreamTextOnFinishCallback<ToolSet>) {
    const result = streamText({
      model: this.getModel(),
      system: `You are a ${this.state.subject} tutor for a ${this.state.difficultyLevel} student.
        Use the Socratic method: ask guiding questions rather than giving answers directly.
        Break complex problems into steps. Celebrate progress. Be patient and encouraging.
        If the student is stuck for more than 2 attempts, provide a hint.
        Never provide complete solutions without the student attempting first.`,
      messages: this.messages,
      tools: this.getTutoringTools(),
      onFinish,
    });
    return result;
  }

  private getModel() {
    // Use a capable model for tutoring (requires reasoning)
    return createOpenAI({ apiKey: this.env.OPENAI_API_KEY })("gpt-4o");
  }

  private getTutoringTools(): ToolSet {
    return {
      generatePracticeExercise: {
        description: "Generate a practice exercise at the student's level",
        parameters: z.object({
          topic: z.string(),
          difficulty: z.enum(["easy", "medium", "hard"]),
        }),
        execute: async ({ topic, difficulty }) => {
          return { exercise: `Practice: ${topic} (${difficulty})`, hints: [] };
        },
      },
      recordProgress: {
        description: "Record that the student completed a concept",
        parameters: z.object({ concept: z.string(), mastery: z.number().min(0).max(1) }),
        execute: async ({ concept, mastery }) => {
          // Persist progress to agent state
          await this.setState({
            ...this.state,
            difficultyLevel: mastery > 0.8 ? "advanced" : mastery > 0.5 ? "intermediate" : "beginner",
          });
          return { recorded: true };
        },
      },
    };
  }
}

Agente de generación de contenido

Utilizado por profesores y administradores para generar materiales educativos:

// workers/agent-service/src/agents/content.ts
import { AIChatAgent } from "agents/ai-chat-agent";
import { type StreamTextOnFinishCallback, type ToolSet, streamText } from "ai";

interface ContentState {
  teacherId: string;
  subject: string;
  gradeLevel: string;
  generatedContent: Array<{ type: string; title: string; createdAt: string }>;
}

export class ContentGenerationAgent extends AIChatAgent<Env, ContentState> {
  async onChatMessage(onFinish: StreamTextOnFinishCallback<ToolSet>) {
    const result = streamText({
      model: createOpenAI({ apiKey: this.env.OPENAI_API_KEY })("gpt-4o"),
      system: `You are a curriculum content generator for ${this.state.subject}, grade level ${this.state.gradeLevel}.
        Generate high-quality, standards-aligned educational content.
        Always include learning objectives, assessment criteria, and differentiation suggestions.
        Format output in Markdown for easy editing by the teacher.`,
      messages: this.messages,
      tools: this.getContentTools(),
      onFinish,
    });
    return result;
  }

  private getContentTools(): ToolSet {
    return {
      generateLessonPlan: {
        description: "Generate a structured lesson plan",
        parameters: z.object({
          topic: z.string(),
          duration: z.number().describe("Duration in minutes"),
          standards: z.array(z.string()).optional(),
        }),
        execute: async ({ topic, duration, standards }) => {
          return { type: "lesson_plan", topic, duration };
        },
      },
      generateQuiz: {
        description: "Generate a quiz with answer key",
        parameters: z.object({
          topic: z.string(),
          questionCount: z.number(),
          questionTypes: z.array(z.enum(["multiple_choice", "short_answer", "true_false"])),
        }),
        execute: async ({ topic, questionCount, questionTypes }) => {
          return { type: "quiz", topic, questionCount };
        },
      },
    };
  }
}

Agente de evaluación

Proporciona calificación automatizada y retroalimentación. Requiere supervisión humana para evaluaciones de alto impacto:

// workers/agent-service/src/agents/assessment.ts
import { AIChatAgent } from "agents/ai-chat-agent";
import { type StreamTextOnFinishCallback, type ToolSet, streamText } from "ai";

interface AssessmentState {
  teacherId: string;
  rubricId: string;
  requiresReview: boolean; // true for grades that count toward final scores
}

export class AssessmentAgent extends AIChatAgent<Env, AssessmentState> {
  async onChatMessage(onFinish: StreamTextOnFinishCallback<ToolSet>) {
    const systemPrompt = this.state.requiresReview
      ? `You are an assessment assistant. Provide feedback and suggested grades.
         IMPORTANT: All grades are SUGGESTIONS that require teacher review before being recorded.
         Always explain your reasoning for each grade suggestion.`
      : `You are an assessment assistant for formative (non-graded) feedback.
         Provide detailed, constructive feedback to help students learn.`;

    const result = streamText({
      model: createOpenAI({ apiKey: this.env.OPENAI_API_KEY })("gpt-4o"),
      system: systemPrompt,
      messages: this.messages,
      tools: this.getAssessmentTools(),
      onFinish,
    });
    return result;
  }

  private getAssessmentTools(): ToolSet {
    return {
      gradeSubmission: {
        description: "Grade a student submission against a rubric",
        parameters: z.object({
          submissionText: z.string(),
          rubricCriteria: z.array(z.object({
            criterion: z.string(),
            maxPoints: z.number(),
          })),
        }),
        execute: async ({ submissionText, rubricCriteria }) => {
          return {
            status: this.state.requiresReview ? "pending_review" : "formative",
            feedback: [],
          };
        },
      },
    };
  }
}

Implementación de agentes

Clase base AIChatAgent

Todos los agentes extienden AIChatAgent del Cloudflare Agents SDK. Esta clase base proporciona:

  • Historial de conversaciones almacenado automáticamente en SQLite local del agente
  • Conexiones WebSocket multiusuario con soporte de hibernación
  • RPC tipado con @callable() para interacciones no relacionadas con el chat
  • Gestión de estado mediante this.state y this.setState()
import { AIChatAgent } from "agents/ai-chat-agent";
import { type Connection, type WSMessage } from "agents";

// AIChatAgent<Env, State> extends the base Agent class
// Env: Worker environment bindings (secrets, service bindings, etc.)
// State: Agent-specific state persisted in SQLite
export class MyAgent extends AIChatAgent<Env, MyState> {
  // Called when a chat message arrives via WebSocket
  async onChatMessage(onFinish: StreamTextOnFinishCallback<ToolSet>) {
    // this.messages — full conversation history (auto-persisted)
    // this.state — agent-specific state
    // this.env — Worker environment bindings
    const result = streamText({ model, system, messages: this.messages, onFinish });
    return result;
  }

  // Called when a new WebSocket client connects
  async onConnect(connection: Connection) {
    console.log(`Client connected: ${connection.id}`);
  }

  // Called when a client disconnects
  async onDisconnect(connection: Connection) {
    console.log(`Client disconnected: ${connection.id}`);
  }
}

Gestión de estado y persistencia

Cada instancia de agente tiene su propia base de datos SQLite (parte del Durable Object). El estado se gestiona mediante this.state (lectura) y this.setState() (escritura):

// Reading state
const studentId = this.state.studentId;

// Updating state (persisted to SQLite automatically)
await this.setState({
  ...this.state,
  difficultyLevel: "advanced",
  lastInteractionAt: new Date().toISOString(),
});

El historial de conversaciones es gestionado por la clase base AIChatAgent y se almacena en la base de datos SQLite del agente. El SDK se encarga automáticamente de la serialización, la paginación y la recuperación.

RPC tipado con @callable()

El decorador @callable() expone métodos del agente como endpoints RPC tipados sobre WebSocket. Se utiliza para interacciones no relacionadas con el chat, como recuperar el estado del agente, actualizar la configuración o desencadenar acciones:

import { AIChatAgent } from "agents/ai-chat-agent";
import { callable } from "agents";

export class TutoringAgent extends AIChatAgent<Env, TutoringState> {
  @callable()
  async getSessionSummary(): Promise<SessionSummary> {
    return {
      messageCount: this.messages.length,
      duration: Date.now() - new Date(this.state.sessionStartedAt).getTime(),
      topicsCovered: this.state.topicsCovered,
    };
  }

  @callable()
  async updateDifficulty(level: "beginner" | "intermediate" | "advanced"): Promise<void> {
    await this.setState({ ...this.state, difficultyLevel: level });
  }

  // Chat messages are handled separately via onChatMessage
  async onChatMessage(onFinish: StreamTextOnFinishCallback<ToolSet>) {
    // ...
  }
}

En el lado del cliente, los métodos @callable() se invocan a través del hook useAgent:

import { useAgent } from "agents/react";

function AgentControls({ agentId }: { agentId: string }) {
  const agent = useAgent({ agent: "tutoring-agent", name: agentId });

  const handleGetSummary = async () => {
    const summary = await agent.call("getSessionSummary");
    console.log(summary);
  };

  return <button onClick={handleGetSummary}>Get Session Summary</button>;
}

Conexiones WebSocket multiusuario

Múltiples usuarios pueden conectarse a la misma instancia de agente de forma simultánea. El AIChatAgent difunde las respuestas a todos los clientes conectados:

export class ClassroomAgent extends AIChatAgent<Env, ClassroomState> {
  async onConnect(connection: Connection) {
    // Track connected users
    const users = this.state.connectedUsers || [];
    await this.setState({
      ...this.state,
      connectedUsers: [...users, connection.id],
    });

    // Notify other connected clients
    this.broadcast(JSON.stringify({
      type: "user_joined",
      userId: connection.id,
    }));
  }

  async onDisconnect(connection: Connection) {
    const users = this.state.connectedUsers || [];
    await this.setState({
      ...this.state,
      connectedUsers: users.filter((id) => id !== connection.id),
    });

    this.broadcast(JSON.stringify({
      type: "user_left",
      userId: connection.id,
    }));
  }

  // broadcast() sends to all connected WebSocket clients
  // This is inherited from the Agent base class
}

Integración frontend

Hook useAgentChat de React

El hook useAgentChat del Agents SDK proporciona una integración lista para usar con la arquitectura de MFEs basada en React:

// mfe-agents/src/components/ChatInterface.tsx
import { useAgentChat } from "agents/ai-react";

interface ChatInterfaceProps {
  agentType: "tutoring" | "content" | "assessment" | "assistant";
  agentId: string;
}

export function ChatInterface({ agentType, agentId }: ChatInterfaceProps) {
  const {
    messages,
    input,
    handleInputChange,
    handleSubmit,
    isLoading,
    error,
  } = useAgentChat({
    agent: `${agentType}-agent`,
    name: agentId,
  });

  return (
    <div className="chat-container">
      <div className="messages">
        {messages.map((message) => (
          <div key={message.id} className={`message message--${message.role}`}>
            <div className="message__content">{message.content}</div>
          </div>
        ))}
        {isLoading && <div className="message message--loading">Thinking...</div>}
      </div>
      <form onSubmit={handleSubmit} className="chat-input">
        <input
          value={input}
          onChange={handleInputChange}
          placeholder="Type your message..."
          disabled={isLoading}
        />
        <button type="submit" disabled={isLoading || !input.trim()}>
          Send
        </button>
      </form>
      {error && <div className="chat-error">{error.message}</div>}
    </div>
  );
}

Patrón de integración MFE

El micro frontend mfe-agents es un MFE dedicado que es propietario de toda la interfaz de usuario relacionada con agentes. Se carga a través de Module Federation y se monta en la shell app cuando los usuarios navegan a funcionalidades de agentes.

Exposición de la interfaz de agentes como remoto:

// mfe-agents/rsbuild.config.ts
import { pluginModuleFederation } from "@module-federation/rsbuild-plugin";

export default {
  plugins: [
    pluginModuleFederation({
      name: "mfe_agents",
      exposes: {
        "./ChatInterface": "./src/components/ChatInterface",
        "./AgentDashboard": "./src/pages/AgentDashboard",
      },
      shared: {
        react: { singleton: true },
        "react-dom": { singleton: true },
      },
    }),
  ],
};

Carga del MFE de agentes desde la shell:

// shell-app/src/routes/agents.tsx
import { lazy, Suspense } from "react";
import { loadRemote } from "@module-federation/enhanced/runtime";

const AgentDashboard = lazy(() => loadRemote("mfe_agents/AgentDashboard"));

export function AgentsRoute() {
  return (
    <Suspense fallback={<div>Loading agents...</div>}>
      <AgentDashboard />
    </Suspense>
  );
}

Contratos de eventos para interacciones con agentes

Otros MFEs pueden desencadenar interacciones con agentes a través de CustomEvents, siguiendo el patrón existente de comunicación inter-MFE de la plataforma (11-inter-mfe-communication.md):

// packages/event-contracts/src/agent-events.ts

/** Dispatched by any MFE to request an agent chat session */
export interface AgentRequestEvent {
  type: "agent:request";
  payload: {
    agentType: "tutoring" | "content" | "assessment" | "assistant";
    context?: {
      subject?: string;
      courseId?: string;
      documentId?: string;
    };
  };
}

/** Dispatched by mfe-agents when a session is established */
export interface AgentSessionEvent {
  type: "agent:session:started";
  payload: {
    agentId: string;
    agentType: string;
  };
}

/** Dispatched by mfe-agents when a session ends */
export interface AgentSessionEndEvent {
  type: "agent:session:ended";
  payload: {
    agentId: string;
    summary?: string;
  };
}

Ejemplo: Inicio de una sesión de tutoría desde el MFE del dashboard:

// mfe-dashboard/src/components/HelpButton.tsx
function HelpButton({ subject, courseId }: { subject: string; courseId: string }) {
  const handleClick = () => {
    window.dispatchEvent(
      new CustomEvent("agent:request", {
        detail: {
          agentType: "tutoring",
          context: { subject, courseId },
        },
      }),
    );
  };

  return <button onClick={handleClick}>Get Help with {subject}</button>;
}

Integración con servidor MCP

Los agentes pueden exponer sus herramientas a través del Model Context Protocol (MCP), permitiendo la integración con herramientas externas impulsadas por IA e IDEs:

import { AIChatAgent } from "agents/ai-chat-agent";
import { MCPClientManager } from "agents/mcp/client";

export class EducationalAssistant extends AIChatAgent<Env, AssistantState> {
  #mcpClientManager: MCPClientManager | null = null;

  get mcpClientManager(): MCPClientManager {
    if (!this.#mcpClientManager) {
      this.#mcpClientManager = new MCPClientManager("educational-assistant", "1.0.0");
    }
    return this.#mcpClientManager;
  }

  async onChatMessage(onFinish: StreamTextOnFinishCallback<ToolSet>) {
    // Get tools from connected MCP servers
    const mcpTools = await this.mcpClientManager.getTools();

    const result = streamText({
      model: this.getModel(),
      system: this.getSystemPrompt(),
      messages: this.messages,
      tools: {
        ...this.getBuiltInTools(),
        ...mcpTools,
      },
      onFinish,
    });
    return result;
  }
}

Esto permite a los agentes descubrir y utilizar dinámicamente herramientas proporcionadas por servidores MCP -- por ejemplo, una herramienta de base de datos curricular, una herramienta de expedientes de estudiantes o una herramienta del sistema de calificación.


Cumplimiento GDPR para agentes IA

Toda la infraestructura de agentes cumple con el GDPR a través de los mecanismos descritos en 03-cloudflare-infrastructure.md. Esta sección cubre las consideraciones GDPR específicas de la IA.

Residencia de datos

Todos los Durable Objects de agentes utilizan jurisdiction("eu"), lo que garantiza:

  • El historial de conversaciones (almacenado en SQLite local del agente) reside en centros de datos de la UE.
  • El estado del agente (perfiles de estudiantes, datos de sesión, resultados de evaluación) nunca sale de la UE.
  • Las conexiones WebSocket se terminan en instancias de Durable Object ubicadas en la UE.
// All agent DO bindings include jurisdiction: "eu"
{
  "durable_objects": {
    "bindings": [{
      "name": "TUTORING_AGENT",
      "class_name": "TutoringAgent",
      "jurisdiction": "eu"
    }]
  }
}

Políticas de retención de datos

Tipo de datoPeríodo de retenciónJustificación
Historial de conversaciones activasIndefinido (mientras el estudiante esté matriculado)Necesario para la continuidad de la tutoría
Conversaciones archivadas2 años tras la última interacciónRetención de registros educativos
Resultados de evaluación5 añosCumplimiento de normativas educativas
Estado del agente (no conversacional)1 año tras la última interacciónDatos operacionales
Contenido generadoHasta que el profesor lo elimineContenido propiedad del profesor

Derecho de supresión (Artículo 17 del GDPR)

Los estudiantes y profesores pueden solicitar la eliminación de sus datos. La plataforma implementa esto a través de la limpieza a nivel de agente:

// workers/agent-service/src/gdpr/deletion.ts

export async function deleteUserAgentData(
  env: Env,
  userId: string,
  agentBindings: DurableObjectNamespace[],
): Promise<DeletionReport> {
  const deletedAgents: string[] = [];

  for (const binding of agentBindings) {
    // List all agent instances associated with this user
    // Agent IDs follow the pattern: {userId}:{sessionId}
    const id = binding.idFromName(userId);
    const stub = binding.get(id);

    // Request the agent to delete all user data
    const response = await stub.fetch(new Request("http://internal/gdpr/delete", {
      method: "DELETE",
      headers: { "X-User-Id": userId },
    }));

    if (response.ok) {
      deletedAgents.push(userId);
    }
  }

  return {
    userId,
    deletedAgents,
    timestamp: new Date().toISOString(),
  };
}

Derecho a la portabilidad de datos (Artículo 20 del GDPR)

Los usuarios pueden exportar su historial de conversaciones y los datos del agente en un formato portable:

@callable()
async exportUserData(): Promise<ExportData> {
  return {
    conversations: this.messages,
    state: this.state,
    exportedAt: new Date().toISOString(),
    format: "json",
  };
}

Requisitos de transparencia de la Ley de IA de la UE

La plataforma cumple con los requisitos de transparencia de la Ley de IA de la UE para sistemas de IA utilizados en educación:

  1. Divulgación: Todo el contenido generado por IA está claramente etiquetado como producido por IA.
  2. Supervisión humana: Los agentes de evaluación marcan los resultados como "sugerencias" que requieren revisión del profesor para las calificaciones de alto impacto.
  3. Explicabilidad: Los agentes proporcionan el razonamiento detrás de las puntuaciones de evaluación y las recomendaciones.
  4. Clasificación de riesgo: La IA educativa se clasifica como "alto riesgo" según la Ley de IA de la UE. La plataforma mantiene documentación de las fuentes de datos de entrenamiento, versiones de modelos y monitorización de resultados.

Supervisión humana para evaluaciones

Los agentes de evaluación que influyen en las calificaciones requieren la revisión del profesor:

interface AssessmentResult {
  studentId: string;
  score: number;
  feedback: string;
  requiresReview: boolean;  // true for summative assessments
  reviewedBy?: string;      // teacherId after review
  reviewedAt?: string;
  status: "draft" | "pending_review" | "approved" | "rejected";
}

La plataforma garantiza que ninguna calificación generada por IA se registre en el expediente oficial del estudiante sin la aprobación explícita del profesor.


Ciclo de vida de los agentes

Creación

Las instancias de agente se crean bajo demanda cuando un usuario inicia una sesión. El ID del agente se deriva del ID de usuario y el contexto de la sesión:

// Agent ID pattern: {userId}:{agentType}:{contextId}
const agentId = `${userId}:tutoring:${courseId}`;
const id = env.TUTORING_AGENT.idFromName(agentId);
const stub = env.TUTORING_AGENT.get(id);

Programación con alarmas

Los agentes utilizan alarmas de Durable Object para tareas programadas:

export class TutoringAgent extends AIChatAgent<Env, TutoringState> {
  async onStart() {
    // Schedule a daily check for inactive sessions
    const tomorrow = new Date();
    tomorrow.setDate(tomorrow.getDate() + 1);
    tomorrow.setHours(2, 0, 0, 0); // 2 AM UTC
    await this.ctx.storage.setAlarm(tomorrow.getTime());
  }

  async alarm() {
    // Check if the session has been inactive for more than 30 days
    const lastInteraction = new Date(this.state.lastInteractionAt);
    const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);

    if (lastInteraction < thirtyDaysAgo) {
      // Archive the conversation and clean up
      await this.archiveAndCleanup();
    } else {
      // Reschedule for tomorrow
      const tomorrow = new Date();
      tomorrow.setDate(tomorrow.getDate() + 1);
      tomorrow.setHours(2, 0, 0, 0);
      await this.ctx.storage.setAlarm(tomorrow.getTime());
    }
  }
}

Hibernación

Los Durable Objects de agentes utilizan hibernación de WebSocket. Cuando no se están intercambiando mensajes, el DO hiberna -- las conexiones WebSocket permanecen abiertas pero el DO no consume CPU ni memoria. Es el mismo patrón utilizado en la capa de comunicación en tiempo real de la plataforma (07-real-time-communication.md).

  • Durante la hibernación: Las conexiones WebSocket permanecen abiertas, pero el DO es desalojado de la memoria. La facturación es de $0 durante el tiempo inactivo.
  • Al recibir un mensaje: El DO se despierta, restaura el estado desde SQLite, procesa el mensaje y puede volver a hibernar.

Limpieza

Cuando un agente ya no es necesario (por ejemplo, tras la eliminación de datos o el archivado de una sesión):

async archiveAndCleanup() {
  // Export conversation to long-term storage (R2) if retention policy requires
  const exportData = await this.exportUserData();
  // ... store in R2 with jurisdiction("eu")

  // Clear agent-local state
  await this.ctx.storage.deleteAll();
}

Configuración de Wrangler

Configuración completa de wrangler.jsonc para el Worker agent-service:

// workers/agent-service/wrangler.jsonc
{
  "name": "agent-service",
  "main": "src/index.ts",
  "compatibility_date": "2026-02-25",
  "compatibility_flags": ["nodejs_compat"],

  // ── Durable Object bindings (all EU-pinned) ──────────────────────
  "durable_objects": {
    "bindings": [
      {
        "name": "TUTORING_AGENT",
        "class_name": "TutoringAgent",
        "jurisdiction": "eu"
      },
      {
        "name": "CONTENT_AGENT",
        "class_name": "ContentGenerationAgent",
        "jurisdiction": "eu"
      },
      {
        "name": "ASSESSMENT_AGENT",
        "class_name": "AssessmentAgent",
        "jurisdiction": "eu"
      },
      {
        "name": "EDUCATIONAL_ASSISTANT",
        "class_name": "EducationalAssistant",
        "jurisdiction": "eu"
      }
    ]
  },

  // ── DO SQLite migrations ─────────────────────────────────────────
  "migrations": [
    {
      "tag": "v1",
      "new_sqlite_classes": [
        "TutoringAgent",
        "ContentGenerationAgent",
        "AssessmentAgent",
        "EducationalAssistant"
      ]
    }
  ],

  // ── Environment variables ────────────────────────────────────────
  "vars": {
    "AI_PROVIDER": "openai",
    "ENVIRONMENT": "production"
  }

  // Secrets set via: wrangler secret put OPENAI_API_KEY
  // Secrets set via: wrangler secret put ANTHROPIC_API_KEY
}

Todos los campos jurisdiction están configurados como "eu" para garantizar que el estado del agente, el historial de conversaciones y todos los datos personales permanezcan en centros de datos de la UE.


Consideraciones de costes

Precios de Durable Objects (infraestructura de agentes)

RecursoPrecioUso mensual estimadoCoste estimado
Peticiones DO$0.15/millón~2M peticiones (interacciones con agentes)~$0.30
Duración DO$12.50/millón GB-segundos~500K GB-segundos~$6.25
Almacenamiento DO (lecturas)$0.20/millón~1M lecturas (historial de conversaciones)~$0.20
Almacenamiento DO (escrituras)$1.00/millón~200K escrituras (actualizaciones de estado)~$0.20
Datos almacenados DO$0.20/GB-mes~2 GB (conversaciones + estado)~$0.40

Costes de API de modelos IA

ProveedorModeloCoste por 1M tokens de entradaCoste por 1M tokens de salida
OpenAIgpt-4o$2.50$10.00
OpenAIgpt-4o-mini$0.15$0.60
AnthropicClaude Haiku 4.5$0.80$4.00
CloudflareWorkers AI (modelos seleccionados)Incluido (beta)Incluido (beta)

Estrategias de control de costes:

  • Usar modelos más pequeños (gpt-4o-mini, Haiku) para tareas simples (asistente educativo, plantillas de generación de contenido).
  • Usar modelos más capaces (gpt-4o) solo para tareas que requieren razonamiento intensivo (tutoría, evaluación).
  • Implementar presupuestos de tokens por tenant para prevenir el desbordamiento de costes.
  • Cachear respuestas comunes (por ejemplo, respuestas a preguntas frecuentes) en el estado del agente para evitar llamadas repetidas al modelo.
  • Utilizar la hibernación de DO para evitar pagar por instancias de agentes inactivas.

Documentos relacionados

DocumentoDescripción
Visión general de la arquitecturaArquitectura del sistema y principios de diseño
ADR-012: Cloudflare Agents SDKRegistro de decisión para agentes IA
Infraestructura CloudflareWorkers, almacenamiento, service bindings, cumplimiento GDPR
Desarrollo localDesarrollo local del servicio de agentes
Comunicación en tiempo realDurable Objects y patrones WebSocket
Comunicación inter-MFECustomEvents para interacciones con agentes

Referencias