diff --git a/docs/walkthrough.html b/docs/walkthrough.html index 45e0027..8937fe0 100644 --- a/docs/walkthrough.html +++ b/docs/walkthrough.html @@ -304,6 +304,7 @@ details.dd .body{padding:4px 16px 14px}
Si quieres entender el proyecto escribiéndolo, este es el orden que maximiza la comprensión. La lógica: sigue el grafo de dependencias hacia afuera desde el centro — empieza por lo que no depende de nada, termina por lo que depende de todo.
+ +domain/ no importa nada del proyecto. El grafo no sabe que existe una API. La API no sabe que existe el dashboard. Escribe en esa dirección: adentro → afuera, abstracto → concreto, sin dependencias → con dependencias.
domain/ (~30 min)Estos cuatro ficheros no importan nada del proyecto — solo Pydantic. Son el vocabulario de todo lo demás. Escribirlos primero te obliga a responder "¿qué maneja este sistema?" antes de pensar en cómo lo procesa.
+domain/agent.py — AgentDefinition, LLMConfig, AgentVersionMeta. Al terminar sabrás exactamente qué es "un agente" en este sistema: una declaración Pydantic, no código Python.domain/guardrail.py — GuardrailViolation. Qué devuelve un guardrail cuando detecta un problema: una violación tipada, no una excepción.domain/policy.py — PolicyDefinition. La lista de reglas que rigen un agente concreto.domain/execution.py — el modelo más rico: AgentExecution, ProposedAction, ExecutionStatus, DecisionStep. Contiene el estado completo de una ejecución. Fíjate en que AgentExecution tiene definición del agente separada de su instancia en runtime.Al escribir AgentExecution te das cuenta de que el sistema separa definir un agente (YAML → Pydantic) de ejecutarlo (runtime → JSONL). Son dos ciclos de vida distintos.
Protocols (~15 min)Los Protocols son las interfaces estructurales del sistema — el contrato que separa "qué hace" de "cómo lo hace". El grafo solo importa estos dos ficheros, nunca OpenAI ni guardrails-ai directamente.
llm/base.py — LLMProvider (un método async complete()), Message, CompletionResult. Dos clases Pydantic y un Protocol de cuatro líneas.guardrails/base.py — GuardrailEngine: dos métodos (validate_input y validate_output), ambos devuelven list[GuardrailViolation].Escribir estos dos Protocols antes que cualquier implementación te revela por qué cambiar el proveedor LLM de mock a azure no requiere tocar el grafo: el grafo nunca ve la implementación concreta.
Con dominios e interfaces ya puedes ejecutar el sistema sin ninguna clave de API. Los mocks son tu andamio.
+llm/mock.py — implementa LLMProvider devolviendo un JSON hardcodeado. ~30 líneas. Permite correr el grafo completo sin tocar OpenAI ni Azure.guardrails/validators.py — validadores keyword-based simples. Implementan GuardrailEngine sin dependencias externas.agents/incident_analyzer/versions/v1.yaml. Escribe la definición declarativa (nombre, owner, purpose, guardrails, config LLM, system_prompt, output_schema).Al escribir el YAML del agente entiendes que un agente es pura configuración: no tiene código Python. Cambiar su comportamiento — modelo, temperatura, guardrails, threshold de HITL — es editar texto, no redeployar.
+Esta es la parte más densa e importante. El grafo LangGraph es la máquina de estados que orquesta todo. El orden dentro de la fase también importa.
+runtime/state.py primero — AgentState: un TypedDict con todos los campos del estado mutable que fluye por el grafo.
+ El detalle a entender: decision_path: Annotated[list, operator.add] — no se sobreescribe, se acumula; LangGraph llama al reducer automáticamente cada vez que un nodo devuelve un decision_path.
+ runtime/nodes.py — los seis nodos, en el orden en que aparecerán en el grafo:
+ build_node_validate_input — llama al engine, acumula violations, decide si poner blocked_by_guardrail.build_node_llm_reason — llama al provider, captura el fallo y lo transforma en status="failed".build_node_validate_output — parsea el JSON del LLM y valida el output contra la política.build_node_propose_actions — extrae proposed_actions del output parseado.build_node_approve_gate — el nodo HITL: llama a interrupt({"awaiting_actions": risky}) si hay acciones con riesgo ≥ threshold. La ejecución se pausa aquí hasta que llegue un Command(resume=...).build_node_finalize — filtra acciones por las aprobadas, construye final_output.Nota el patrón: cada builder captura sus dependencias por clausura y devuelve una función async (state) → dict. Las dependencias no viajan en el estado — están fijas en el cierre.
runtime/graph.py — conecta los nodos. Los tres routers condicionales (_after_validate_input, _after_llm, _after_validate_output) hacen que cualquier error cortocircuite hacia END sin pasar por nodos posteriores.
+ Al implementar approve_gate entiendes cómo funciona HITL en LangGraph: interrupt() no lanza excepción — serializa el estado en el checkpoint SQLite y devuelve el control. La ejecución se reanuda en el mismo nodo cuando llega Command(resume=...). El estado nunca se pierde entre las dos llamadas HTTP.
runtime/checkpointer.py — configura SQLite como backend de checkpoints LangGraph. Sin esto, interrupt() no puede persistir el estado entre la llamada a invoke y la llamada a approve.runtime/orchestrator.py — el objeto que la API usará. Recibe AgentDefinition + PolicyDefinition + providers ya construidos, compila el grafo y expone dos métodos: invoke() (nueva ejecución) y approve() (reanudar tras HITL).Punto de control: en este momento tienes un sistema funcionando de extremo a extremo. Puedes llamar directamente al orquestador en un test y observar el decision_path completo, sin API ni dashboard.
Dentro de api/, el orden también importa.
api/deps.py — el armario de cableado: los @lru_cache que construyen el registry, el policy store, el LLM provider y el guardrail engine. Escríbelo después de tener todo lo anterior — solo entonces sabrás exactamente qué estás conectando.api/executions.py — los tres endpoints críticos: POST /executions/invoke, POST /executions/{trace_id}/approve, GET /executions/{trace_id}.api/agents.py — CRUD del registry (listar agentes, leer versión concreta).api/middlewares.py — inyecta X-Trace-Id y bind-ea el contexto de structlog.main.py — monta los routers, configura CORS, arranca la app FastAPI.Si has estado usando el mock registry, ahora añades la persistencia real basada en YAML.
+registry/repository.py — carga definiciones de agentes desde YAML, cachea en memoria.registry/versioning.py — hash SHA-256 + index.yaml: el sistema de versionado tipo-Git.registry/policy_store.py — análogo al repository, pero para políticas.registry/factory.py — el factory que elige qué implementación de registry usar según Settings.El dashboard es un cliente HTTP del core — puedes empezarlo en cualquier momento después de tener los endpoints.
+dashboard/client.py — primero. Te fuerza a pensar en qué necesita la UI antes de diseñar las páginas. Es el contrato del core visto desde el consumidor.pages/1_Registro.py — solo lectura: lista agentes y versiones.pages/5_Politicas.py — solo lectura: muestra políticas activas.pages/4_Historial.py — lectura + filtros: lista ejecuciones pasadas.pages/2_Ejecutar.py — escritura: invoke + polling de estado + visualización del decision_path.pages/3_Aprobaciones.py — la más compleja: muestra acciones pendientes, permite aprobar o rechazar individual o masivamente.Con las fases 1–5 completas y el mock LLM tienes un sistema funcionando sin Docker ni credenciales externas. Llama directamente al orquestador desde un test de integración, observa el decision_path completo, y pausa-reanuda en el nodo HITL. No necesitas la API ni el dashboard para validar el núcleo.