cambios profundos
This commit is contained in:
+4
-6
@@ -1,16 +1,14 @@
|
||||
# Componentes de Forja y su interrelación (bajo nivel)
|
||||
|
||||
> **Alcance.** Este documento es la **referencia de cableado**: módulos exactos,
|
||||
> firmas, el grafo de dependencias de imports, el grafo de inyección de
|
||||
> dependencias, los contratos entre capas y las cadenas de llamada de cada
|
||||
> endpoint. Es preciso, no narrativo.
|
||||
> **Nota (post-simplificación 2026):** Este documento describe el cableado del
|
||||
> núcleo. La UI ahora es HTMX embebida en el propio core (no hay dashboard
|
||||
> separado). La API REST está bajo el prefijo `/api`.
|
||||
>
|
||||
> - ¿Quieres la historia y el "por qué"? → [`docs/explicacion.md`](explicacion.md).
|
||||
> - ¿Las decisiones técnicas resumidas? → [`ARCHITECTURE.md`](../ARCHITECTURE.md).
|
||||
> - ¿Cómo arrancarlo? → [`README.md`](../README.md).
|
||||
>
|
||||
> Rutas relativas a `core/src/ forja_core/` salvo que se diga otra cosa.
|
||||
> Refleja el estado del repo en `v0.1.0`.
|
||||
> Rutas relativas a `core/src/forja_core/` salvo que se diga otra cosa.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+9
-5
@@ -1,14 +1,18 @@
|
||||
# Forja explicado de principio a fin
|
||||
|
||||
> **Nota de estado (mayo 2026):** Este documento fue escrito para la arquitectura
|
||||
> anterior (dos servicios + Streamlit/NiceGUI). Forja ahora es un **único servicio
|
||||
> FastAPI** con UI HTMX embebida. El núcleo (runtime, guardrails, HITL, versionado)
|
||||
> sigue siendo el mismo. Algunas secciones de UI y despliegue están desactualizadas.
|
||||
>
|
||||
> Si solo quieres arrancarlo, ve al [`README.md`](../README.md).
|
||||
|
||||
> **Para quién es esto.** Una guía didáctica para alguien que llega nuevo al
|
||||
> proyecto —técnico o no— y quiere entender *qué hace*, *por qué está hecho así*
|
||||
> y *cómo encajan las piezas* sin tener que leer todo el código primero.
|
||||
>
|
||||
> Si solo quieres arrancarlo, ve al [`README.md`](../README.md). Si quieres las
|
||||
> decisiones técnicas en bruto, ve a [`ARCHITECTURE.md`](../ARCHITECTURE.md). Si
|
||||
> quieres la referencia de cableado a bajo nivel (módulos, firmas, grafos de
|
||||
> dependencias, cadenas de llamada), ve a [`docs/componentes.md`](componentes.md).
|
||||
> Este documento está en medio: cuenta la historia.
|
||||
> Decisiones técnicas: [`ARCHITECTURE.md`](../ARCHITECTURE.md).
|
||||
> Cableado de bajo nivel: [`docs/componentes.md`](componentes.md).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,430 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Forja — Progreso y Próximos Pasos</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Space+Grotesk:wght@500;600&display=swap');
|
||||
|
||||
:root {
|
||||
--primary: #0ea5e9;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', system_ui, sans-serif;
|
||||
}
|
||||
|
||||
.font-display {
|
||||
font-family: 'Space Grotesk', 'Inter', sans-serif;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
font-family: 'Space Grotesk', 'Inter', sans-serif;
|
||||
letter-spacing: -0.025em;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
font-size: 0.75rem;
|
||||
padding: 0.125rem 0.625rem;
|
||||
border-radius: 9999px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.forja-gradient {
|
||||
background: linear-gradient(135deg, #0ea5e9, #3b82f6);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.card {
|
||||
transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
box-shadow 0.2s cubic-bezier(0.4, 0.0, 0.2, 1);
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.05), 0 8px 10px -6px rgb(0 0 0 / 0.05);
|
||||
}
|
||||
|
||||
.metric {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.nav-active {
|
||||
border-bottom: 3px solid #0ea5e9;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
transition: width 1s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
}
|
||||
|
||||
.mono {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
width: 2.25rem;
|
||||
height: 2.25rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 9999px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-zinc-950 text-zinc-200">
|
||||
<!-- Header -->
|
||||
<div class="border-b border-zinc-800 bg-zinc-900/70 backdrop-blur-lg sticky top-0 z-50">
|
||||
<div class="max-w-6xl mx-auto px-6 py-5 flex items-center justify-between">
|
||||
<div class="flex items-center gap-x-3">
|
||||
<div class="w-10 h-10 rounded-2xl bg-sky-500 flex items-center justify-center shadow-inner">
|
||||
<i class="fa-solid fa-hammer text-white text-3xl"></i>
|
||||
</div>
|
||||
<div>
|
||||
<span class="font-display text-3xl font-semibold tracking-tighter">Forja</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-x-2 text-sm">
|
||||
<div class="px-3 py-1.5 bg-zinc-900 border border-zinc-800 rounded-2xl flex items-center gap-x-2">
|
||||
<div class="w-2 h-2 bg-emerald-400 rounded-full animate-pulse"></div>
|
||||
<span class="font-medium text-emerald-400 text-xs tracking-wider">v0.2 — EN PRODUCCIÓN</span>
|
||||
</div>
|
||||
<div class="text-zinc-500 text-xs px-2">23 mayo 2026</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="max-w-6xl mx-auto px-6 pt-10 pb-24">
|
||||
|
||||
<!-- Hero -->
|
||||
<div class="flex flex-col lg:flex-row gap-8 items-start">
|
||||
<div class="flex-1">
|
||||
<div class="inline-flex items-center gap-x-2 px-3 py-1 rounded-3xl bg-zinc-900 border border-zinc-800 text-xs mb-4">
|
||||
<i class="fa-solid fa-sync fa-spin-pulse text-sky-400"></i>
|
||||
<span class="font-semibold tracking-widest">GRAN REFACTORIZACIÓN COMPLETADA</span>
|
||||
</div>
|
||||
|
||||
<h1 class="font-display text-6xl lg:text-7xl font-semibold tracking-tighter leading-none">
|
||||
Forja<br>
|
||||
<span class="forja-gradient">está lista</span>
|
||||
</h1>
|
||||
|
||||
<p class="mt-4 max-w-lg text-xl text-zinc-400">
|
||||
Plataforma de gobernanza genérica para agentes IA de cualquier tipo.
|
||||
Renombrada, generalizada y con editores gráficos completos.
|
||||
</p>
|
||||
|
||||
<div class="flex items-center gap-x-3 mt-8">
|
||||
<a href="https://github.com/anomalyco/opencode"
|
||||
class="inline-flex items-center gap-x-2 px-5 py-3 rounded-3xl bg-white text-zinc-950 font-semibold text-sm hover:bg-zinc-100 transition-colors">
|
||||
<i class="fa-brands fa-github"></i>
|
||||
<span>Ver en GitHub</span>
|
||||
</a>
|
||||
<button onclick="document.getElementById('proximos-pasos').scrollIntoView({behavior:'smooth'})"
|
||||
class="inline-flex items-center gap-x-2 px-5 py-3 rounded-3xl border border-zinc-700 hover:bg-zinc-900 text-sm font-medium transition-colors">
|
||||
<span>Ver próximos pasos</span>
|
||||
<i class="fa-solid fa-arrow-down"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="lg:w-80 w-full">
|
||||
<div class="bg-zinc-900 border border-zinc-800 rounded-3xl p-5">
|
||||
<div class="text-xs uppercase tracking-[1px] text-zinc-500 mb-3 font-medium">Progreso general</div>
|
||||
|
||||
<div class="flex items-baseline gap-x-2">
|
||||
<div class="text-6xl font-semibold tabular-nums tracking-tighter">82</div>
|
||||
<div class="text-2xl font-medium text-zinc-400">/100</div>
|
||||
</div>
|
||||
|
||||
<div class="h-2.5 bg-zinc-800 rounded-full mt-3 overflow-hidden">
|
||||
<div class="h-2.5 bg-gradient-to-r from-sky-400 to-blue-500 rounded-full progress-bar" style="width: 82%"></div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 gap-4 mt-6 text-center">
|
||||
<div>
|
||||
<div class="text-xs text-zinc-500">Core</div>
|
||||
<div class="font-semibold text-xl">100%</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-xs text-zinc-500">UI Gráfica</div>
|
||||
<div class="font-semibold text-xl">68%</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-xs text-zinc-500">Docs</div>
|
||||
<div class="font-semibold text-xl">65%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Métricas rápidas -->
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-3 mt-12">
|
||||
<div class="bg-zinc-900 border border-zinc-800 rounded-3xl p-4">
|
||||
<div class="flex items-center gap-x-3">
|
||||
<div class="feature-icon bg-emerald-500/10 text-emerald-400"><i class="fa-solid fa-check-double"></i></div>
|
||||
<div>
|
||||
<div class="text-xs text-zinc-400">Renombrado global</div>
|
||||
<div class="font-semibold">483 → 0 referencias antiguas</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-zinc-900 border border-zinc-800 rounded-3xl p-4">
|
||||
<div class="flex items-center gap-x-3">
|
||||
<div class="feature-icon bg-amber-500/10 text-amber-400"><i class="fa-solid fa-edit"></i></div>
|
||||
<div>
|
||||
<div class="text-xs text-zinc-400">Editores gráficos</div>
|
||||
<div class="font-semibold">2 páginas nuevas funcionales</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-zinc-900 border border-zinc-800 rounded-3xl p-4">
|
||||
<div class="flex items-center gap-x-3">
|
||||
<div class="feature-icon bg-violet-500/10 text-violet-400"><i class="fa-solid fa-globe"></i></div>
|
||||
<div>
|
||||
<div class="text-xs text-zinc-400">Generalización</div>
|
||||
<div class="font-semibold">Cualquier tipo de agente</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-zinc-900 border border-zinc-800 rounded-3xl p-4">
|
||||
<div class="flex items-center gap-x-3">
|
||||
<div class="feature-icon bg-emerald-500/10 text-emerald-400"><i class="fa-solid fa-palette"></i></div>
|
||||
<div>
|
||||
<div class="text-xs text-zinc-400">Modernización de interfaz</div>
|
||||
<div class="font-semibold">Migración Streamlit → NiceGUI</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-zinc-900 border border-zinc-800 rounded-3xl p-4">
|
||||
<div class="flex items-center gap-x-3">
|
||||
<div class="feature-icon bg-sky-500/10 text-sky-400"><i class="fa-solid fa-database"></i></div>
|
||||
<div>
|
||||
<div class="text-xs text-zinc-400">Write APIs</div>
|
||||
<div class="font-semibold">POST /versions + upsert</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Qué se hizo -->
|
||||
<div class="mt-16">
|
||||
<h2 class="section-header text-3xl font-semibold tracking-tight mb-6 flex items-center gap-x-3">
|
||||
<span>Lo que se completó</span>
|
||||
<span class="text-xs px-3 py-1 bg-emerald-400/10 text-emerald-400 rounded-2xl font-mono tracking-wider">FASE 1 + 2 + 3</span>
|
||||
</h2>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-4">
|
||||
<!-- Columna 1 -->
|
||||
<div class="space-y-4">
|
||||
<div class="card bg-zinc-900 border border-zinc-800 rounded-3xl p-6">
|
||||
<div class="font-semibold flex items-center gap-x-2 text-lg">
|
||||
<i class="fa-solid fa-sync text-sky-400"></i>
|
||||
<span>Renombrado completo a "Forja"</span>
|
||||
</div>
|
||||
<ul class="mt-4 text-sm space-y-2 text-zinc-300">
|
||||
<li class="flex gap-x-2"><span class="text-sky-400 mt-0.5">•</span> Paquetes Python: <span class="mono font-medium">forja_core</span> y <span class="mono font-medium">forja_dashboard</span></li>
|
||||
<li class="flex gap-x-2"><span class="text-sky-400 mt-0.5">•</span> Imágenes Docker, contenedores y URLs (<code>forja-core:dev</code>)</li>
|
||||
<li class="flex gap-x-2"><span class="text-sky-400 mt-0.5">•</span> Variables de entorno (<code>FORJA_CORE_URL</code>)</li>
|
||||
<li class="flex gap-x-2"><span class="text-sky-400 mt-0.5">•</span> pyproject, Dockerfiles, docker-compose, Makefile</li>
|
||||
<li class="flex gap-x-2"><span class="text-sky-400 mt-0.5">•</span> 300+ archivos actualizados (código + docs activos)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card bg-zinc-900 border border-zinc-800 rounded-3xl p-6">
|
||||
<div class="font-semibold flex items-center gap-x-2 text-lg">
|
||||
<i class="fa-solid fa-universal-access text-violet-400"></i>
|
||||
<span>Generalización para cualquier tipo de agente</span>
|
||||
</div>
|
||||
<div class="mt-4 text-sm text-zinc-300">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<div class="px-3 py-1 bg-zinc-800 rounded-2xl text-xs">Campos UI opcionales en AgentDefinition</div>
|
||||
<div class="px-3 py-1 bg-zinc-800 rounded-2xl text-xs">input_label / placeholder / category / tags / icon</div>
|
||||
<div class="px-3 py-1 bg-zinc-800 rounded-2xl text-xs">Execute page 100% genérico</div>
|
||||
<div class="px-3 py-1 bg-zinc-800 rounded-2xl text-xs">Posicionamiento como plano de control reutilizable</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Columna 2 -->
|
||||
<div class="space-y-4">
|
||||
<div class="card bg-zinc-900 border border-zinc-800 rounded-3xl p-6">
|
||||
<div class="font-semibold flex items-center gap-x-2 text-lg">
|
||||
<i class="fa-solid fa-pencil-ruler text-amber-400"></i>
|
||||
<span>Editores gráficos completos (la gran novedad)</span>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 grid grid-cols-1 gap-3">
|
||||
<div class="bg-zinc-950 border border-zinc-800 p-4 rounded-2xl">
|
||||
<div class="font-medium text-amber-300 flex items-center gap-x-2">
|
||||
<i class="fa-solid fa-hammer"></i>
|
||||
<span>6_🔨_Forjar_Agente.py</span>
|
||||
</div>
|
||||
<div class="text-xs text-zinc-400 mt-1">Form completo + LLM config + schema JSON + multiselect de guardrails + fork + versionado</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-zinc-950 border border-zinc-800 p-4 rounded-2xl">
|
||||
<div class="font-medium text-amber-300 flex items-center gap-x-2">
|
||||
<i class="fa-solid fa-shield-halved"></i>
|
||||
<span>7_🛡️_Forjar_Politica.py</span>
|
||||
</div>
|
||||
<div class="text-xs text-zinc-400 mt-1">Constructor dinámico de validadores (input/output) + /validators endpoint + configs JSON</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card bg-zinc-900 border border-zinc-800 rounded-3xl p-6">
|
||||
<div class="font-semibold text-lg">Backend de escritura</div>
|
||||
<div class="text-sm mt-3 space-y-1.5 text-zinc-300">
|
||||
<div>✓ <code class="mono text-xs">POST /agents/{name}/versions</code> + upsert</div>
|
||||
<div>✓ <code class="mono text-xs">POST /policies/{name}/versions</code> + upsert simétrico</div>
|
||||
<div>✓ <code class="mono text-xs">GET /policies/validators</code> (metadata para UI)</div>
|
||||
<div>✓ <code class="mono text-xs">FileSystemPolicyStore.upsert_version()</code></div>
|
||||
<div>✓ Docker mounts cambiados a <strong>:rw</strong></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Estado actual -->
|
||||
<div class="mt-16">
|
||||
<h2 class="section-header text-3xl font-semibold tracking-tight mb-6">Estado actual (23 mayo 2026)</h2>
|
||||
|
||||
<div class="bg-zinc-900 border border-zinc-800 rounded-3xl p-7">
|
||||
<div class="grid md:grid-cols-3 gap-8">
|
||||
<div>
|
||||
<div class="uppercase text-xs font-semibold tracking-wider text-emerald-400">Compila y funciona</div>
|
||||
<ul class="mt-3 space-y-2 text-sm">
|
||||
<li class="flex items-start gap-x-2"><i class="fa-solid fa-check text-emerald-400 mt-0.5"></i> <span>Todos los imports y paquetes renombrados</span></li>
|
||||
<li class="flex items-start gap-x-2"><i class="fa-solid fa-check text-emerald-400 mt-0.5"></i> <span>py_compile 100% limpio</span></li>
|
||||
<li class="flex items-start gap-x-2"><i class="fa-solid fa-check text-emerald-400 mt-0.5"></i> <span>Editores guardan YAMLs reales</span></li>
|
||||
<li class="flex items-start gap-x-2"><i class="fa-solid fa-check text-emerald-400 mt-0.5"></i> <span>Execute page usa metadata del agente</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="uppercase text-xs font-semibold tracking-wider text-amber-400">Listo para usar</div>
|
||||
<ul class="mt-3 space-y-2 text-sm">
|
||||
<li class="flex items-start gap-x-2"><i class="fa-solid fa-check text-amber-400 mt-0.5"></i> <span><code>docker compose up</code> (con :rw)</span></li>
|
||||
<li class="flex items-start gap-x-2"><i class="fa-solid fa-check text-amber-400 mt-0.5"></i> <span>Registro + Ejecución + Aprobaciones</span></li>
|
||||
<li class="flex items-start gap-x-2"><i class="fa-solid fa-check text-amber-400 mt-0.5"></i> <span>Editores en barra lateral (páginas 6 y 7)</span></li>
|
||||
<li class="flex items-start gap-x-2"><i class="fa-solid fa-check text-amber-400 mt-0.5"></i> <span>README y docs principales actualizados</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="uppercase text-xs font-semibold tracking-wider text-zinc-400">Pendiente de verificación completa</div>
|
||||
<ul class="mt-3 space-y-2 text-sm text-zinc-400">
|
||||
<li class="flex items-start gap-x-2"><i class="fa-solid fa-clock mt-0.5"></i> <span>make test-all (requiere deps pesadas)</span></li>
|
||||
<li class="flex items-start gap-x-2"><i class="fa-solid fa-clock mt-0.5"></i> <span>Smoke test con Docker real (guardrails-ai)</span></li>
|
||||
<li class="flex items-start gap-x-2"><i class="fa-solid fa-clock mt-0.5"></i> <span>Actualización de walkthrough.html</span></li>
|
||||
<li class="flex items-start gap-x-2"><i class="fa-solid fa-clock mt-0.5"></i> <span>Tests específicos de los nuevos endpoints</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Próximos pasos -->
|
||||
<div id="proximos-pasos" class="mt-16">
|
||||
<h2 class="section-header text-3xl font-semibold tracking-tight mb-6">Próximos pasos recomendados</h2>
|
||||
|
||||
<div class="space-y-3">
|
||||
<!-- Paso 1 -->
|
||||
<div class="card bg-zinc-900 border border-zinc-800 rounded-3xl p-5 flex gap-5 items-start">
|
||||
<div class="w-7 h-7 flex-shrink-0 rounded-2xl bg-blue-500 flex items-center justify-center text-xs font-bold text-white mt-0.5">1</div>
|
||||
<div class="flex-1">
|
||||
<div class="font-semibold">Verificación completa de calidad</div>
|
||||
<div class="text-sm text-zinc-400 mt-1">Ejecutar <span class="mono bg-zinc-950 px-1.5 py-px rounded">make install && make lint && make test-all</span> + smoke con docker compose en entorno limpio.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Paso 2 -->
|
||||
<div class="card bg-zinc-900 border border-zinc-800 rounded-3xl p-5 flex gap-5 items-start">
|
||||
<div class="w-7 h-7 flex-shrink-0 rounded-2xl bg-blue-500 flex items-center justify-center text-xs font-bold text-white mt-0.5">2</div>
|
||||
<div class="flex-1">
|
||||
<div class="font-semibold">Mejora de los editores (UX)</div>
|
||||
<div class="text-sm text-zinc-400 mt-1">Añadir editor JSON más amigable (ace o monaco), validación en tiempo real de schemas, preview de agente antes de guardar, y botón “Probar inmediatamente”.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Paso 3 -->
|
||||
<div class="card bg-zinc-900 border border-zinc-800 rounded-3xl p-5 flex gap-5 items-start">
|
||||
<div class="w-7 h-7 flex-shrink-0 rounded-2xl bg-blue-500 flex items-center justify-center text-xs font-bold text-white mt-0.5">3</div>
|
||||
<div class="flex-1">
|
||||
<div class="font-semibold">Añadir más plantillas de agentes</div>
|
||||
<div class="text-sm text-zinc-400 mt-1">Crear 2-3 agentes genéricos de ejemplo (resumidor, revisor de código, chatbot con guardrails) + sus políticas base.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card bg-zinc-900 border border-zinc-800 rounded-3xl p-5 flex gap-5 items-start">
|
||||
<div class="w-7 h-7 flex-shrink-0 rounded-2xl bg-blue-500 flex items-center justify-center text-xs font-bold text-white mt-0.5">4</div>
|
||||
<div class="flex-1">
|
||||
<div class="font-semibold">Soporte de namespaces / multi-proyecto</div>
|
||||
<div class="text-sm text-zinc-400 mt-1">Estructura opcional <span class="mono text-xs">agents/<proyecto>/<agente></span> para que una sola instancia de Forja sirva a múltiples equipos.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card bg-zinc-900 border border-zinc-800 rounded-3xl p-5 flex gap-5 items-start">
|
||||
<div class="w-7 h-7 flex-shrink-0 rounded-2xl bg-blue-500 flex items-center justify-center text-xs font-bold text-white mt-0.5">5</div>
|
||||
<div class="flex-1">
|
||||
<div class="font-semibold">Documentación y walkthrough actualizado</div>
|
||||
<div class="text-sm text-zinc-400 mt-1">Regenerar o actualizar <span class="mono">docs/walkthrough.html</span> y <span class="mono">docs/explicacion.md</span> con los editores y el nuevo nombre.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notas técnicas -->
|
||||
<div class="mt-16 text-xs text-zinc-500 border-t border-zinc-800 pt-8">
|
||||
<div class="flex flex-wrap gap-x-8 gap-y-2">
|
||||
<div><strong>Paquetes:</strong> <span class="mono">forja_core</span> / <span class="mono">forja_web</span> (NiceGUI) + <span class="mono">forja_common</span></div>
|
||||
<div><strong>Python:</strong> 3.11+</div>
|
||||
<div><strong>UI:</strong> NiceGUI (reemplazo moderno de Streamlit) + Tailwind</div>
|
||||
<div><strong>Runtime:</strong> LangGraph + FastAPI</div>
|
||||
<div><strong>Persistencia:</strong> YAML versionado + SQLite checkpoints</div>
|
||||
</div>
|
||||
<div class="mt-3 text-[10px] text-zinc-600">Actualizado el 23 de mayo de 2026 — Migración a NiceGUI iniciada, CLI removido por decisión de producto.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Tailwind script
|
||||
function initializeTailwind() {
|
||||
document.documentElement.style.setProperty('--accent', '#0ea5e9');
|
||||
}
|
||||
|
||||
// Simple confetti on load for celebration
|
||||
function celebrate() {
|
||||
if (Math.random() > 0.7) {
|
||||
console.log('%c[Forja] ¡Refactorización completada con éxito!', 'color:#64748b;font-size:9px');
|
||||
}
|
||||
}
|
||||
|
||||
window.onload = function() {
|
||||
initializeTailwind();
|
||||
celebrate();
|
||||
};
|
||||
|
||||
// Keyboard hint
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === '/' && document.activeElement.tagName === 'BODY') {
|
||||
e.preventDefault();
|
||||
const next = document.getElementById('proximos-pasos');
|
||||
if (next) next.scrollIntoView({behavior: 'smooth'});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,242 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Forja — Seguimiento de Progreso</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Space+Grotesk:wght@500;600&display=swap');
|
||||
|
||||
:root {
|
||||
--tokyo-bg: #1a1b26;
|
||||
--tokyo-surface: #24283b;
|
||||
--tokyo-border: #414868;
|
||||
--tokyo-purple: #bb9af7;
|
||||
--tokyo-blue: #7aa2f7;
|
||||
--tokyo-yellow: #e0af68;
|
||||
--tokyo-text: #c0caf5;
|
||||
--tokyo-muted: #565f89;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', system_ui, sans-serif;
|
||||
background-color: var(--tokyo-bg);
|
||||
color: var(--tokyo-text);
|
||||
}
|
||||
|
||||
.font-display {
|
||||
font-family: 'Space Grotesk', 'Inter', sans-serif;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.tokyo-card {
|
||||
background-color: var(--tokyo-surface);
|
||||
border: 1px solid var(--tokyo-border);
|
||||
}
|
||||
|
||||
.section-header {
|
||||
font-family: 'Space Grotesk', 'Inter', sans-serif;
|
||||
letter-spacing: -0.025em;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
transition: width 1s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
}
|
||||
|
||||
.station {
|
||||
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.milestone {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.milestone::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 15px;
|
||||
top: 0;
|
||||
bottom: -24px;
|
||||
width: 2px;
|
||||
background: var(--tokyo-border);
|
||||
}
|
||||
|
||||
.milestone:last-child::before {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="min-h-screen">
|
||||
<div class="max-w-5xl mx-auto px-6 py-12">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="flex items-center gap-4 mb-10">
|
||||
<div class="w-14 h-14 rounded-2xl bg-gradient-to-br from-[#bb9af7] to-[#7aa2f7] flex items-center justify-center">
|
||||
<span class="text-3xl">🔨</span>
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="font-display text-5xl font-semibold tracking-tighter">Forja</h1>
|
||||
<p class="text-[#565f89] text-xl">Seguimiento de Progreso</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Current Vision -->
|
||||
<div class="tokyo-card rounded-3xl p-8 mb-10">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="px-3 py-1 bg-[#bb9af7]/10 text-[#bb9af7] rounded-full text-sm font-medium">Visión Actual</div>
|
||||
</div>
|
||||
<h2 class="text-3xl font-semibold tracking-tight mb-3">Cadena de Montaje (Assembly Line)</h2>
|
||||
<p class="text-[#c0caf5]/80 text-lg max-w-3xl">
|
||||
La interfaz se ha transformado en una <strong>vista única de fábrica</strong>.
|
||||
El objetivo es que el usuario entienda de un vistazo cómo se "forja" un agente,
|
||||
mostrando solo los bloques que generan <strong>inputs configurables</strong> o
|
||||
<strong>outputs significativos</strong>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Timeline -->
|
||||
<div class="mb-12">
|
||||
<h3 class="font-display text-2xl font-semibold tracking-tight mb-6">Evolución Principal</h3>
|
||||
|
||||
<div class="space-y-6">
|
||||
|
||||
<!-- Milestone 1 -->
|
||||
<div class="milestone flex gap-4">
|
||||
<div class="w-8 h-8 rounded-full bg-[#bb9af7] flex-shrink-0 flex items-center justify-center text-[#1a1b26] font-bold text-sm">1</div>
|
||||
<div class="tokyo-card rounded-2xl p-5 flex-1">
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<div class="font-semibold">Eliminación de la Navbar</div>
|
||||
<div class="text-sm text-[#565f89] mt-1">Se eliminó completamente la navegación superior tradicional.</div>
|
||||
</div>
|
||||
<div class="text-xs text-[#565f89] font-mono">Paso 1</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Milestone 2 -->
|
||||
<div class="milestone flex gap-4">
|
||||
<div class="w-8 h-8 rounded-full bg-[#bb9af7] flex-shrink-0 flex items-center justify-center text-[#1a1b26] font-bold text-sm">2</div>
|
||||
<div class="tokyo-card rounded-2xl p-5 flex-1">
|
||||
<div class="font-semibold">Paso a Vista Única de Fábrica</div>
|
||||
<div class="text-sm text-[#565f89] mt-1">Toda la experiencia se concentra en una "cadena de montaje" visual en lugar de páginas separadas.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Milestone 3 -->
|
||||
<div class="milestone flex gap-4">
|
||||
<div class="w-8 h-8 rounded-full bg-[#bb9af7] flex-shrink-0 flex items-center justify-center text-[#1a1b26] font-bold text-sm">3</div>
|
||||
<div class="tokyo-card rounded-2xl p-5 flex-1">
|
||||
<div class="font-semibold">Eliminación de las Cajas de Fases</div>
|
||||
<div class="text-sm text-[#565f89] mt-1">Se quitaron los contenedores grandes que englobaban cada fase. Ahora solo quedan los encabezados de fase + las estaciones sueltas.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Milestone 4 -->
|
||||
<div class="milestone flex gap-4">
|
||||
<div class="w-8 h-8 rounded-full bg-[#bb9af7] flex-shrink-0 flex items-center justify-center text-[#1a1b26] font-bold text-sm">4</div>
|
||||
<div class="tokyo-card rounded-2xl p-5 flex-1">
|
||||
<div class="font-semibold">Unificación de Fondo + Colores Llamativos en Texto</div>
|
||||
<div class="text-sm text-[#565f89] mt-1">Todas las cajas de la línea de ensamblado usan el mismo gris. Los títulos tienen colores muy saturados y distintos (cyan, púrpura, amarillo, verde, rojo).</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Milestone 5 -->
|
||||
<div class="milestone flex gap-4">
|
||||
<div class="w-8 h-8 rounded-full bg-[#bb9af7] flex-shrink-0 flex items-center justify-center text-[#1a1b26] font-bold text-sm">5</div>
|
||||
<div class="tokyo-card rounded-2xl p-5 flex-1">
|
||||
<div class="font-semibold">Limpieza de Estaciones sin Valor de I/O</div>
|
||||
<div class="text-sm text-[#565f89] mt-1">
|
||||
Se eliminaron los bloques que no generan inputs configurables ni outputs significativos:
|
||||
<span class="font-medium text-[#e0af68]">Input Intake</span> y
|
||||
<span class="font-medium text-[#e0af68]">Audit & Logging</span>.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Current Assembly Line -->
|
||||
<div class="mb-12">
|
||||
<h3 class="font-display text-2xl font-semibold tracking-tight mb-6">Estado Actual de la Cadena de Montaje</h3>
|
||||
|
||||
<div class="tokyo-card rounded-3xl p-8">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
|
||||
<!-- Phase 1 -->
|
||||
<div>
|
||||
<div class="text-[#bb9af7] text-xs tracking-[1.5px] font-semibold mb-2">FASE 1 — PREPARACIÓN</div>
|
||||
<div class="space-y-2">
|
||||
<div class="bg-[#24283b] border border-[#bb9af7]/40 rounded-xl px-4 py-3 text-sm">1. Agent Definition</div>
|
||||
<div class="bg-[#24283b] border border-[#bb9af7]/40 rounded-xl px-4 py-3 text-sm">2. Policy Application</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Phase 2 -->
|
||||
<div>
|
||||
<div class="text-[#7aa2f7] text-xs tracking-[1.5px] font-semibold mb-2">FASE 2 — LÍNEA DE ENSAMBLADO</div>
|
||||
<div class="space-y-2">
|
||||
<div class="bg-[#24283b] border border-[#7aa2f7]/40 rounded-xl px-4 py-3 text-sm">
|
||||
<span class="text-[#bb9af7]">3.</span> Input Guardrails
|
||||
</div>
|
||||
<div class="bg-[#24283b] border border-[#7aa2f7]/40 rounded-xl px-4 py-3 text-sm">
|
||||
<span class="text-[#e0af68]">4.</span> LLM Reasoning
|
||||
</div>
|
||||
<div class="bg-[#24283b] border border-[#7aa2f7]/40 rounded-xl px-4 py-3 text-sm">
|
||||
<span class="text-[#9ece6a]">5.</span> Output Guardrails
|
||||
</div>
|
||||
<div class="bg-[#24283b] border border-[#7aa2f7]/40 rounded-xl px-4 py-3 text-sm">
|
||||
<span class="text-[#f7768e]">6.</span> Action Proposal
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Phase 3 -->
|
||||
<div>
|
||||
<div class="text-[#e0af68] text-xs tracking-[1.5px] font-semibold mb-2">FASE 3 — SUPERVISIÓN HUMANA</div>
|
||||
<div class="space-y-2">
|
||||
<div class="bg-[#24283b] border border-[#e0af68]/40 rounded-xl px-4 py-3 text-sm">7. Human Approval Gate</div>
|
||||
<div class="bg-[#24283b] border border-[#9ece6a]/40 rounded-xl px-4 py-3 text-sm">8. Finalization</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="mt-6 pt-6 border-t border-[#414868] text-xs text-[#565f89]">
|
||||
Total de estaciones activas: <span class="font-semibold text-[#c0caf5]">8</span>
|
||||
(todas producen inputs configurables o outputs significativos)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Principles -->
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<div class="tokyo-card rounded-3xl p-6">
|
||||
<div class="text-sm font-medium text-[#bb9af7] mb-2">PRINCIPIOS APLICADOS</div>
|
||||
<ul class="space-y-2 text-sm">
|
||||
<li class="flex gap-2">• <span>Strict adherence to <strong>karpathy.md</strong> guidelines</span></li>
|
||||
<li class="flex gap-2">• <span>Simplicity First</span></li>
|
||||
<li class="flex gap-2">• <span>Surgical changes (solo tocar lo necesario)</span></li>
|
||||
<li class="flex gap-2">• <span>Eliminar lo que no aporta valor al flujo visual</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="tokyo-card rounded-3xl p-6">
|
||||
<div class="text-sm font-medium text-[#bb9af7] mb-2">PRÓXIMOS PASOS (SUGERIDOS)</div>
|
||||
<div class="text-sm text-[#565f89]">
|
||||
<p class="mb-3">Este documento puede servir como registro vivo del proyecto.</p>
|
||||
<p>Añade aquí nuevas decisiones, experimentos visuales o cambios de rumbo cuando ocurran.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-12 text-center text-xs text-[#565f89]">
|
||||
Documento generado para seguimiento del proyecto Forja •
|
||||
<span class="font-mono">Tokyo Night Theme</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,58 +0,0 @@
|
||||
# Smoke manual del dashboard
|
||||
|
||||
> Esta lista cubre los flujos no automatizados (Streamlit). Ejecutar tras
|
||||
> cambios visuales o estructurales del dashboard.
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
docker compose up -d
|
||||
sleep 15
|
||||
```
|
||||
|
||||
Abrir [http://localhost:8501](http://localhost:8501).
|
||||
|
||||
## 1) Registro
|
||||
|
||||
- [ ] Aparece `incident_analyzer` en el desplegable.
|
||||
- [ ] Detalle muestra version, owner, propósito, guardrails, system_prompt.
|
||||
- [ ] Tabla de versiones lista `v1` y `v2`.
|
||||
- [ ] Diff `v1 → v2` muestra cambios coloreados.
|
||||
|
||||
## 2) Ejecutar
|
||||
|
||||
- [ ] Botones de escenarios cargan texto en el textarea.
|
||||
- [ ] Invocar `02_mos_degradation_pool_sbc` → status=`completed`, sin HITL,
|
||||
decision_path con 6 steps.
|
||||
- [ ] Invocar `01_sip_registration_drop` → status=`awaiting_approval`,
|
||||
banner amarillo redirige a Aprobaciones.
|
||||
|
||||
## 3) Aprobaciones
|
||||
|
||||
- [ ] La ejecución pendiente aparece en el desplegable.
|
||||
- [ ] Cada acción muestra risk_score con color, target y rollback_plan.
|
||||
- [ ] Aprobar acciones seleccionadas → status=`completed`.
|
||||
- [ ] Rechazo con razón → status=`failed`, error=`rejected_by_human`.
|
||||
|
||||
## 4) Historial
|
||||
|
||||
- [ ] Tab "Ejecuciones" lista todas las ejecuciones con summary.
|
||||
- [ ] Detalle muestra trace + violations.
|
||||
- [ ] Tab "Violaciones" filtrable por severity.
|
||||
|
||||
## 5) Politicas
|
||||
|
||||
- [ ] `default` aparece con sus validadores de input/output.
|
||||
- [ ] Cada validador expandible con su config.
|
||||
|
||||
## Bloqueo por PII (input)
|
||||
|
||||
- [ ] Pegar `El cliente con NIF 12345678Z reporta caída`.
|
||||
- [ ] Invocar → status=`blocked_by_guardrail`, violación `DetectPII`.
|
||||
|
||||
## Persistencia
|
||||
|
||||
- [ ] `docker compose down && docker compose up` → ejecuciones previas
|
||||
siguen accesibles vía Historial; aprobaciones pendientes siguen
|
||||
pendientes.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,631 +0,0 @@
|
||||
# AgentForge — Documento de diseño
|
||||
|
||||
**Fecha:** 2026-05-09
|
||||
**Estado:** aprobado — listo para plan de implementación
|
||||
**Autor:** Juan
|
||||
**Revisión:** v1.0
|
||||
|
||||
---
|
||||
|
||||
## 1. Resumen ejecutivo
|
||||
|
||||
**AgentForge** es una plataforma profesional de **gobernanza de agentes IA** orientada a entornos de producción. Cubre el ciclo de vida completo: catalogación, versionado de prompts y políticas, validación con guardrails en runtime, ejecución stateful con checkpointing, Human-in-the-Loop nativo, y trazabilidad end-to-end.
|
||||
|
||||
La plataforma se compone de dos servicios:
|
||||
|
||||
- **`agentforge-core`** — API REST (FastAPI) que materializa el dominio de gobierno: registry, versionado, ejecución de agentes vía LangGraph, validación de guardrails y persistencia.
|
||||
- **`agentforge-dashboard`** — UI ejecutiva (Streamlit) cliente del core; nunca habla con LLMs ni guardrails directamente.
|
||||
|
||||
Una imagen Docker por servicio, orquestadas con `docker-compose`. Arranque sin claves API (proveedor LLM mock por defecto).
|
||||
|
||||
## 2. Problema y motivación
|
||||
|
||||
Poner agentes IA en producción sin una capa de gobernanza produce sistemas opacos:
|
||||
|
||||
- Prompts que cambian en caliente sin historial.
|
||||
- Validaciones inconsistentes según quién despliega.
|
||||
- Acciones de alto impacto (rollbacks, cambios de configuración, ejecución de comandos) propuestas sin supervisión humana ni rollback plan.
|
||||
- Decisiones del agente no auditables — imposible reconstruir por qué se tomó una acción concreta.
|
||||
|
||||
AgentForge define el plano de control mínimo que un equipo de plataforma necesita antes de operar agentes con impacto real. El proyecto no resuelve la inteligencia del agente; resuelve su **operabilidad y gobierno**.
|
||||
|
||||
## 3. Decisiones de diseño (resumen)
|
||||
|
||||
| Eje | Decisión | Justificación |
|
||||
|---|---|---|
|
||||
| Topología | Two-tier: FastAPI core + Streamlit dashboard | Separación de responsabilidades; el core es API-first y consumible por terceros (RPA, n8n, otros agentes). Patrón de plataformas reales. |
|
||||
| LLM | Interfaz `LLMProvider` con `mock` (default), `azure`, `openai` | Demo arranca sin secrets. Demuestra portabilidad de proveedor. |
|
||||
| Guardrails | Interfaz `GuardrailEngine` con `GuardrailsAI` (default) y `NeMo` (opt-in vía flag) | Lo mejor de ambos mundos: arquitectura extensible visible + imagen ligera por defecto. |
|
||||
| Persistencia | Mezcla por capa: JSON (registry, definiciones), JSONL (logs append-only), YAML (versiones), SQLite (checkpoints LangGraph) | Cada capa con la herramienta correcta. Definiciones humanas en YAML; estado runtime en formatos legibles por máquina. |
|
||||
| Versionado | Simulación tipo Git en sistema de ficheros: `agents/<n>/versions/v1.yaml` + `index.yaml` con hashes y mensajes | No requiere git real; reproduce la disciplina de versionado y diff. |
|
||||
| Estado | LangGraph con `SqliteSaver` + `interrupt()` dinámico para HITL | Persistencia entre reinicios crítica para esperas humanas. |
|
||||
| Auth | Ninguna en MVP; placeholder hook `Depends(get_current_user)` | Pluggable a OAuth2/OIDC/MSAL en futuro. |
|
||||
| Comentarios | Español | Coherente con el autor. |
|
||||
|
||||
## 4. Arquitectura
|
||||
|
||||
```
|
||||
┌───────────────────────── docker-compose ──────────────────────────┐
|
||||
│ │
|
||||
│ ┌─────────────────────┐ HTTP/JSON ┌────────────────────┐ │
|
||||
│ │ agentforge-dashboard│ ────────────────► │ agentforge-core │ │
|
||||
│ │ Streamlit :8501 │ ◄──────────────── │ FastAPI :8000 │ │
|
||||
│ └─────────────────────┘ └─────────┬──────────┘ │
|
||||
│ │ │
|
||||
│ ┌────────────────────────────────────────┼──────────┐ │
|
||||
│ ▼ ▼ ▼ ▼ │
|
||||
│ LLM layer Guardrail layer Runtime State│
|
||||
│ ┌──────────────────┐ ┌────────────────────┐ ┌─────────┐ ┌───┐│
|
||||
│ │ LLMProvider (P) │ │ GuardrailEngine(P) │ │LangGraph│ │JSN││
|
||||
│ │ ├ MockProvider │ │ ├ GuardrailsAIEng │ │ + HITL │ │SQL││
|
||||
│ │ ├ AzureOpenAI │ │ ├ NeMoEngine (opt) │ │ + ckpt │ │JNL││
|
||||
│ │ └ OpenAI │ │ └ CompositeEngine │ └─────────┘ └───┘│
|
||||
│ └──────────────────┘ └────────────────────┘ │
|
||||
│ │
|
||||
│ Strategy pattern con Pydantic v2 + Protocol typing │
|
||||
└────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Principios:**
|
||||
|
||||
- **API-first:** el core es invocable por cualquier cliente HTTP; el dashboard es un consumidor sustituible.
|
||||
- **Configuration over code:** definiciones de agentes y políticas en YAML editables sin redeploy.
|
||||
- **Strategy/Plugin:** todas las dependencias externas (LLM, guardrails, registry) detrás de `Protocol`.
|
||||
- **Fail-closed por defecto:** errores en validadores se traducen a violación que bloquea la ejecución.
|
||||
- **Trazabilidad obligatoria:** cada ejecución tiene un `trace_id` UUID propagado por logs, API, dashboard y persistencia.
|
||||
|
||||
## 5. Estructura de repositorio
|
||||
|
||||
```
|
||||
agentforge/
|
||||
├── README.md
|
||||
├── ARCHITECTURE.md
|
||||
├── docker-compose.yml
|
||||
├── .env.example
|
||||
├── .gitignore
|
||||
├── Makefile # up / down / test / lint / format / smoke
|
||||
├── pyproject.toml # ruff + mypy + pytest config
|
||||
├── core/
|
||||
│ ├── Dockerfile
|
||||
│ ├── requirements.txt
|
||||
│ └── src/agentforge_core/
|
||||
│ ├── main.py # FastAPI app + middlewares
|
||||
│ ├── config.py # Pydantic Settings, .env
|
||||
│ ├── api/
|
||||
│ │ ├── agents.py
|
||||
│ │ ├── executions.py
|
||||
│ │ ├── policies.py
|
||||
│ │ └── violations.py
|
||||
│ ├── domain/ # modelos Pydantic v2
|
||||
│ │ ├── agent.py
|
||||
│ │ ├── execution.py
|
||||
│ │ ├── guardrail.py
|
||||
│ │ └── policy.py
|
||||
│ ├── registry/
|
||||
│ │ ├── repository.py
|
||||
│ │ └── versioning.py
|
||||
│ ├── llm/
|
||||
│ │ ├── base.py # Protocol LLMProvider
|
||||
│ │ ├── mock.py
|
||||
│ │ ├── azure.py
|
||||
│ │ ├── openai.py
|
||||
│ │ └── factory.py
|
||||
│ ├── guardrails/
|
||||
│ │ ├── base.py # Protocol GuardrailEngine
|
||||
│ │ ├── guardrails_ai.py
|
||||
│ │ ├── nemo.py
|
||||
│ │ ├── composite.py
|
||||
│ │ └── factory.py
|
||||
│ ├── runtime/
|
||||
│ │ ├── graph.py # build_graph(agent_def, policy)
|
||||
│ │ ├── state.py # AgentState (TypedDict)
|
||||
│ │ ├── nodes.py # cada nodo del grafo
|
||||
│ │ └── checkpointer.py # SqliteSaver wrapper
|
||||
│ └── observability/
|
||||
│ └── logging.py # structlog + trace_id propagation
|
||||
├── dashboard/
|
||||
│ ├── Dockerfile
|
||||
│ ├── requirements.txt
|
||||
│ └── src/agentforge_dashboard/
|
||||
│ ├── app.py
|
||||
│ ├── client.py # httpx client tipado al core
|
||||
│ ├── pages/
|
||||
│ │ ├── 1_🏛️_Registro.py
|
||||
│ │ ├── 2_▶️_Ejecutar.py
|
||||
│ │ ├── 3_🤝_Aprobaciones.py
|
||||
│ │ ├── 4_📜_Historial.py
|
||||
│ │ └── 5_📐_Politicas.py
|
||||
│ └── components/
|
||||
│ ├── trace_view.py
|
||||
│ ├── violation_view.py
|
||||
│ └── diff_view.py
|
||||
├── agents/
|
||||
│ └── incident_analyzer/
|
||||
│ ├── index.yaml # versiones disponibles + metadata
|
||||
│ ├── versions/
|
||||
│ │ ├── v1.yaml
|
||||
│ │ └── v2.yaml
|
||||
│ └── examples/
|
||||
│ ├── 01_sip_registration_drop.txt
|
||||
│ ├── 02_mos_degradation_pool_sbc.txt
|
||||
│ └── 03_hss_capacity_active_active.txt
|
||||
├── policies/
|
||||
│ └── default/
|
||||
│ ├── index.yaml
|
||||
│ └── versions/
|
||||
│ └── v1.yaml
|
||||
├── data/ # gitignored
|
||||
│ ├── registry.json
|
||||
│ ├── checkpoints.sqlite
|
||||
│ ├── violations.jsonl
|
||||
│ └── executions.jsonl
|
||||
├── docs/
|
||||
│ ├── architecture.md
|
||||
│ ├── futuro.md
|
||||
│ ├── manual_qa.md
|
||||
│ └── superpowers/specs/
|
||||
│ └── 2026-05-09-agentforge-design.md
|
||||
└── tests/
|
||||
├── unit/
|
||||
└── integration/
|
||||
```
|
||||
|
||||
**Notas:**
|
||||
|
||||
- Pydantic models residen en `domain/`, no en `api/`. Los routers solo serializan.
|
||||
- Las factories (`llm/factory.py`, `guardrails/factory.py`) son la frontera donde el `.env` decide la implementación.
|
||||
- YAML para definiciones humanas (legibles, comentables, mejor diff). JSON/JSONL/SQLite para estado runtime.
|
||||
- `__init__.py` mínimos, sin re-exports masivos.
|
||||
|
||||
## 6. Modelos de dominio
|
||||
|
||||
```python
|
||||
class LLMConfig(BaseModel):
|
||||
provider: Literal["mock", "azure", "openai"] = "mock"
|
||||
model: str = "gpt-4o"
|
||||
temperature: float = 0.2
|
||||
max_tokens: int = 2000
|
||||
|
||||
class AgentVersionMeta(BaseModel):
|
||||
id: str # ej. "v2"
|
||||
hash: str # SHA-256 del YAML normalizado
|
||||
author: str
|
||||
message: str
|
||||
created_at: datetime
|
||||
|
||||
class AgentDefinition(BaseModel):
|
||||
name: str
|
||||
version: str # semver o vN
|
||||
owner: str
|
||||
purpose: str
|
||||
state: Literal["draft", "active", "deprecated"]
|
||||
guardrails: list[str] # nombres de policies
|
||||
llm: LLMConfig
|
||||
system_prompt: str
|
||||
output_schema: dict # JSON Schema del output esperado
|
||||
risk_threshold_for_hitl: int = 4 # risk_score ≥ X exige aprobación humana
|
||||
updated_at: datetime
|
||||
|
||||
class GuardrailViolation(BaseModel):
|
||||
trace_id: UUID
|
||||
timestamp: datetime
|
||||
stage: Literal["input", "output"]
|
||||
validator: str # ej. "DetectPII"
|
||||
severity: Literal["info", "warning", "block"]
|
||||
message: str
|
||||
blocked: bool # True si detuvo la ejecución
|
||||
|
||||
class ProposedAction(BaseModel):
|
||||
id: str # generado, único dentro de la ejecución
|
||||
action: str # ej. "rollback_image"
|
||||
target: str # ej. "cscf-cluster-aravaca-01"
|
||||
risk_score: int = Field(ge=1, le=5)
|
||||
rollback_plan: str
|
||||
requires_approval: bool
|
||||
|
||||
class DecisionStep(BaseModel):
|
||||
step: str # validate_input | llm_reason | ...
|
||||
timestamp: datetime
|
||||
duration_ms: int
|
||||
detail: dict # libre por nodo
|
||||
|
||||
class AgentExecution(BaseModel):
|
||||
trace_id: UUID
|
||||
agent_name: str
|
||||
agent_version: str
|
||||
status: Literal[
|
||||
"running",
|
||||
"awaiting_approval",
|
||||
"blocked_by_guardrail",
|
||||
"completed",
|
||||
"failed",
|
||||
]
|
||||
started_at: datetime
|
||||
finished_at: datetime | None
|
||||
decision_path: list[DecisionStep]
|
||||
violations: list[GuardrailViolation]
|
||||
proposed_actions: list[ProposedAction]
|
||||
needs_human_for: list[ProposedAction] | None
|
||||
final_output: dict | None
|
||||
error: str | None
|
||||
|
||||
class AgentExecutionSummary(BaseModel):
|
||||
"""Versión ligera para listados (sin decision_path ni violations completas)."""
|
||||
trace_id: UUID
|
||||
agent_name: str
|
||||
agent_version: str
|
||||
status: str
|
||||
started_at: datetime
|
||||
finished_at: datetime | None
|
||||
n_violations: int
|
||||
n_proposed_actions: int
|
||||
|
||||
class PolicyValidator(BaseModel):
|
||||
type: str # "detect_pii", "prompt_injection", ...
|
||||
config: dict # parámetros del validador
|
||||
|
||||
class PolicyDefinition(BaseModel):
|
||||
name: str
|
||||
version: str
|
||||
description: str
|
||||
input_validators: list[PolicyValidator]
|
||||
output_validators: list[PolicyValidator]
|
||||
on_validator_error: Literal["fail_open", "fail_closed"] = "fail_closed"
|
||||
```
|
||||
|
||||
## 7. API REST
|
||||
|
||||
```
|
||||
GET /health
|
||||
GET /agents → list[AgentDefinition]
|
||||
GET /agents/{name} → AgentDefinition (versión activa)
|
||||
GET /agents/{name}/versions → list[AgentVersionMeta]
|
||||
GET /agents/{name}/versions/{v} → AgentDefinition (versión concreta)
|
||||
GET /agents/{name}/versions/{v}/diff/{w} → DiffResult (unified diff)
|
||||
POST /agents/{name}/invoke → AgentExecution
|
||||
body: {"input": str, "version": str?}
|
||||
GET /executions → list[AgentExecutionSummary]
|
||||
GET /executions/{trace_id} → AgentExecution
|
||||
POST /executions/{trace_id}/approve → AgentExecution
|
||||
body: {"approved_action_ids": list[str],
|
||||
"comment": str?}
|
||||
POST /executions/{trace_id}/reject → AgentExecution
|
||||
body: {"reason": str}
|
||||
GET /violations → list[GuardrailViolation]
|
||||
query: trace_id?, severity?, since?
|
||||
GET /policies → list[PolicyDefinition]
|
||||
GET /policies/{name}/versions → list[PolicyVersionMeta]
|
||||
```
|
||||
|
||||
Códigos de error relevantes:
|
||||
|
||||
- `404` agente o ejecución no existe
|
||||
- `409` `/approve` o `/reject` sobre ejecución no en `awaiting_approval`
|
||||
- `422` body inválido (Pydantic)
|
||||
- `500` fallo interno (checkpoint corrupto, configuración inválida)
|
||||
|
||||
Todas las respuestas incluyen header `X-Trace-Id` (eco del request o generado).
|
||||
|
||||
## 8. Interfaces clave (Strategy)
|
||||
|
||||
```python
|
||||
class Message(BaseModel):
|
||||
role: Literal["system", "user", "assistant"]
|
||||
content: str
|
||||
|
||||
class CompletionResult(BaseModel):
|
||||
content: str
|
||||
model: str
|
||||
tokens_in: int
|
||||
tokens_out: int
|
||||
latency_ms: int
|
||||
|
||||
class LLMProvider(Protocol):
|
||||
name: str
|
||||
async def complete(
|
||||
self,
|
||||
messages: list[Message],
|
||||
schema: dict | None = None,
|
||||
temperature: float = 0.2,
|
||||
max_tokens: int = 2000,
|
||||
) -> CompletionResult: ...
|
||||
|
||||
class GuardrailEngine(Protocol):
|
||||
name: str
|
||||
async def validate_input(
|
||||
self, payload: str, policy: PolicyDefinition, trace_id: UUID
|
||||
) -> list[GuardrailViolation]: ...
|
||||
async def validate_output(
|
||||
self, payload: dict, policy: PolicyDefinition, trace_id: UUID
|
||||
) -> list[GuardrailViolation]: ...
|
||||
|
||||
class AgentRegistry(Protocol):
|
||||
def list_agents(self) -> list[AgentDefinition]: ...
|
||||
def get_agent(self, name: str, version: str | None = None) -> AgentDefinition: ...
|
||||
def list_versions(self, name: str) -> list[AgentVersionMeta]: ...
|
||||
def get_version(self, name: str, version: str) -> AgentDefinition: ...
|
||||
def diff_versions(self, name: str, v1: str, v2: str) -> DiffResult: ...
|
||||
def upsert_version(
|
||||
self, name: str, body: AgentDefinition, message: str, author: str
|
||||
) -> AgentVersionMeta: ...
|
||||
|
||||
class PolicyStore(Protocol):
|
||||
def list_policies(self) -> list[PolicyDefinition]: ...
|
||||
def get_policy(self, name: str, version: str | None = None) -> PolicyDefinition: ...
|
||||
def list_versions(self, name: str) -> list[PolicyVersionMeta]: ...
|
||||
```
|
||||
|
||||
## 9. Flujo de ejecución (LangGraph)
|
||||
|
||||
**Topología del grafo (común para todos los agentes; varía la `AgentDefinition` y `Policy`):**
|
||||
|
||||
```
|
||||
┌──────────────┐
|
||||
│ validate_in │── violations.severity=block ──► [STATUS: blocked_by_guardrail] ─► END
|
||||
└──────┬───────┘
|
||||
│ pass
|
||||
▼
|
||||
┌──────────────┐
|
||||
│ llm_reason │── 3xretry → fallback → giveup ─► [STATUS: failed] ─► END
|
||||
└──────┬───────┘
|
||||
│ ok
|
||||
▼
|
||||
┌──────────────┐
|
||||
│ validate_out │── violations.severity=block ──► [STATUS: blocked_by_guardrail] ─► END
|
||||
└──────┬───────┘
|
||||
│ pass
|
||||
▼
|
||||
┌──────────────────┐
|
||||
│ propose_actions │
|
||||
└──────┬───────────┘
|
||||
▼
|
||||
┌──────────────────┐ ─ any(risk≥threshold or requires_approval) ─┐
|
||||
│ approve_gate │ │
|
||||
│ (dyn. interrupt)│ ─ todas seguras ──┐ │
|
||||
└──────────────────┘ │ │
|
||||
▼ ▼
|
||||
┌──────────────┐ [interrupt(payload)]
|
||||
│ finalize │ status=awaiting_approval
|
||||
└──────┬───────┘ │
|
||||
▼ │ POST /approve
|
||||
[STATUS: completed] │ POST /reject
|
||||
▼
|
||||
[Command(resume={...}) → finalize → completed
|
||||
o status=failed, error="rejected_by_human"]
|
||||
```
|
||||
|
||||
**Estado del grafo (TypedDict de LangGraph):**
|
||||
|
||||
```python
|
||||
class AgentState(TypedDict):
|
||||
trace_id: str
|
||||
agent_name: str
|
||||
agent_version: str
|
||||
user_input: str
|
||||
messages: list[dict]
|
||||
raw_llm_output: str | None
|
||||
parsed_output: dict | None
|
||||
proposed_actions: list[dict]
|
||||
violations: list[dict]
|
||||
decision_path: Annotated[list[dict], operator.add] # acumulativo
|
||||
status: str
|
||||
error: str | None
|
||||
human_decision: dict | None # rellena en /approve|/reject
|
||||
```
|
||||
|
||||
`Annotated[..., operator.add]` permite que cada nodo añada pasos sin pisar lo previo (semántica nativa de LangGraph para reducers).
|
||||
|
||||
**Checkpointing:** `SqliteSaver("data/checkpoints.sqlite")` con `thread_id = trace_id`. El estado se persiste tras cada nodo. Si el dashboard cierra durante un `awaiting_approval`, `GET /executions/{trace_id}` reconstruye el snapshot exacto.
|
||||
|
||||
**Recorrido típico — `POST /agents/incident_analyzer/invoke`:**
|
||||
|
||||
1. API genera `trace_id` (UUID4); resuelve `AgentDefinition` vía `AgentRegistry`; resuelve `PolicyDefinition` referenciada en `agent_def.guardrails`; loga `execution.started` con `trace_id`.
|
||||
2. `runtime.graph.build_graph(agent_def, policy)` compila el grafo:
|
||||
- Inyecta `LLMProvider` vía factory (override por `LLM_PROVIDER` env).
|
||||
- Inyecta `GuardrailEngine` vía factory (`Composite[GuardrailsAI]`, +`NeMo` si flag).
|
||||
- Checkpointer = `SqliteSaver`. El nodo `approve_gate` invoca dinámicamente `interrupt(payload)` (API de LangGraph ≥0.2) solo cuando hay acciones que requieren aprobación; si todas son seguras, transiciona a `finalize` sin pausa. Esto evita el `interrupt_before` estático y mantiene el flujo declarativo.
|
||||
3. `graph.invoke({...}, config={"configurable":{"thread_id": trace_id}})` ejecuta hasta:
|
||||
- (a) END por guardrail block en `validate_input` o `validate_output`.
|
||||
- (b) END por LLM unrecoverable failure.
|
||||
- (c) INTERRUPT en `approve_gate` (HITL).
|
||||
- (d) END normal (`finalize`) si no hay acciones de alto riesgo.
|
||||
4. API serializa `AgentExecution` y devuelve 200. Append a `data/executions.jsonl` y violaciones a `data/violations.jsonl`.
|
||||
5. (caso HITL) Dashboard pinta cards con cada `ProposedAction` (action, target, badge `risk_score`, `rollback_plan`, botones `[Approve]`/`[Reject]`). Operador pulsa Approve.
|
||||
6. Dashboard `POST /executions/{trace_id}/approve` con body `{"approved_action_ids": ["..."], "comment": "..."}`.
|
||||
7. API: `graph.invoke(Command(resume={"human_decision": {...}}), config={"configurable":{"thread_id": trace_id}})`. Nodo `finalize` lee `state.human_decision`, compone `final_output`. `status="completed"`, `finished_at=now`. Append final a `executions.jsonl`.
|
||||
|
||||
## 10. Guardrails (capa runtime)
|
||||
|
||||
**Estructura `policies/default/versions/v1.yaml`:**
|
||||
|
||||
```yaml
|
||||
name: default
|
||||
version: v1
|
||||
description: Política base para agentes de operación de plataforma de voz
|
||||
input_validators:
|
||||
- type: detect_pii
|
||||
entities: [PERSON, EMAIL, PHONE_NUMBER, ES_NIF, IP_ADDRESS, IBAN_CODE]
|
||||
severity_on_match: block
|
||||
- type: prompt_injection
|
||||
severity_on_match: block
|
||||
- type: toxic_language
|
||||
threshold: 0.7
|
||||
severity_on_match: warning
|
||||
- type: forbidden_topics
|
||||
topics: ["instrucciones de explotación", "credenciales", "código malicioso"]
|
||||
severity_on_match: block
|
||||
|
||||
output_validators:
|
||||
- type: schema_match
|
||||
schema_ref: agent.output_schema # del agent_def en runtime
|
||||
severity_on_mismatch: block
|
||||
- type: pii_leakage
|
||||
severity_on_match: block
|
||||
- type: forbidden_action_keywords
|
||||
keywords: ["DROP TABLE", "rm -rf", "shutdown -h now", "delete production"]
|
||||
severity_on_match: block
|
||||
- type: telco_safety_rules # validador custom
|
||||
rules:
|
||||
- never_propose_action_targeting_production_without_rollback
|
||||
- never_propose_mass_action_without_canary
|
||||
|
||||
on_validator_error: fail_closed
|
||||
```
|
||||
|
||||
**Validadores (Guardrails AI engine):**
|
||||
|
||||
- `DetectPII` — usa `presidio_analyzer` + entidades configuradas; `ES_NIF` como recognizer custom (regex documentada).
|
||||
- `PromptInjection` — heurística de patrones (lista mantenible) + clasificador ligero. En modo `mock` LLM, lista de strings exactos para reproducibilidad de tests.
|
||||
- `ToxicLanguage` — validator del Guardrails Hub.
|
||||
- `ForbiddenTopics` — substring matcher en MVP. `docs/futuro.md` documenta upgrade a similarity con embeddings.
|
||||
- `SchemaMatch` — Pydantic parse contra `agent.output_schema`.
|
||||
- `PIILeakage` — Presidio sobre output stringificado.
|
||||
- `ForbiddenActionKeywords` — substring matcher sobre acciones propuestas.
|
||||
- `TelcoSafetyRules` — validador custom; reglas declarativas evaluadas sobre `proposed_actions[]`.
|
||||
|
||||
**Comportamiento del engine:**
|
||||
|
||||
- Toda violación se persiste a `violations.jsonl` (incluso `info`/`warning`).
|
||||
- Solo `severity=block` con `blocked=True` detiene el grafo.
|
||||
- `on_validator_error` decide qué pasa cuando un validador lanza excepción (`fail_closed` por defecto).
|
||||
- Composite engine ejecuta sub-engines en paralelo y agrega resultados.
|
||||
|
||||
**NeMo Guardrails (opt-in):** activado con `GUARDRAILS_NEMO_ENABLED=true`. Aporta topical rails declarados en Colang (`policies/default/nemo/rails.co`). MVP entrega configuración mínima (off-topic refusal); el flag por defecto está en `false` para mantener la imagen ligera.
|
||||
|
||||
## 11. Versionado de prompts y políticas (simulación tipo Git)
|
||||
|
||||
**Estructura `agents/<name>/index.yaml`:**
|
||||
|
||||
```yaml
|
||||
name: incident_analyzer
|
||||
versions:
|
||||
- id: v1
|
||||
hash: 7c9a4f...
|
||||
author: Juan
|
||||
message: "Versión inicial; cobertura básica de SIP/IMS"
|
||||
created_at: 2026-04-12T10:00:00Z
|
||||
- id: v2
|
||||
hash: a1b2c3...
|
||||
author: Juan
|
||||
message: "Añade detección de codec mismatch; sube risk_threshold a 4"
|
||||
created_at: 2026-05-01T12:00:00Z
|
||||
active_version: v2
|
||||
```
|
||||
|
||||
**Operaciones:**
|
||||
|
||||
- `Registry.upsert_version(name, body, message, author)` — calcula SHA-256 del YAML normalizado, valida formato, escribe `versions/vN.yaml`, actualiza `index.yaml`.
|
||||
- `Registry.diff_versions(name, v1, v2)` — `difflib.unified_diff` sobre los YAMLs.
|
||||
- `Registry.get_agent(name)` — devuelve la versión `active_version`.
|
||||
- Promoción de `draft` → `active` cambia `index.yaml.active_version`.
|
||||
|
||||
**Estructura idéntica para `policies/<name>/`.**
|
||||
|
||||
El dashboard ofrece:
|
||||
- Vista de "Versiones" del agente con autor, mensaje, hash, fecha.
|
||||
- Botón "Comparar con anterior" → render del diff con sintaxis coloreada.
|
||||
|
||||
## 12. Observabilidad
|
||||
|
||||
- **Logging**: `structlog` con renderer JSON. Cada log incluye `trace_id`, `agent_name`, `agent_version`, `step`, `duration_ms` cuando aplica.
|
||||
- **Trace-id propagation**: middleware FastAPI inyecta `X-Trace-Id` (genera UUID4 si no viene). `structlog` lo bind-ea en context (`structlog.contextvars`). Dashboard incluye `X-Trace-Id` en approve/reject para correlar logs.
|
||||
- **Eventos persistidos**:
|
||||
- `data/executions.jsonl` — un append por ejecución completada/fallida (no por step).
|
||||
- `data/violations.jsonl` — un append por violación (cualquier severidad).
|
||||
- **Métricas/Tracing OTel**: fuera de MVP. Documentado en `docs/futuro.md`.
|
||||
|
||||
## 13. Manejo de errores
|
||||
|
||||
| Tipo de fallo | Respuesta |
|
||||
|---|---|
|
||||
| Azure OpenAI 5xx / timeout | Retry exponencial (1s, 2s, 4s; 3 intentos). Si agota, fallback a `LLM_FALLBACK_PROVIDER` si está configurado. Si falla, `status=failed`, `error="llm_unavailable"`. La API devuelve `200` con la ejecución fallida (el dominio sí ha respondido). |
|
||||
| Guardrail validator excepción interna | Por defecto `fail_closed` → cuenta como `severity=block`, `blocked=True`. Configurable por policy. |
|
||||
| Pydantic validation falla en LLM output | 1 retry con prompt extendido `"Respond strictly with JSON matching this schema: ..."`. Si falla, `status=failed`, `error="output_schema_mismatch"`. |
|
||||
| Checkpoint SQLite corrupto | Log `error.checkpoint_corrupt`, devolver `500`, `error_class="checkpoint_unreadable"`. Sin auto-recovery (mejor falla explícita). |
|
||||
| Agente solicitado no existe | `404` con `{"detail":"agent 'X' not found"}`. |
|
||||
| `/approve` o `/reject` sobre estado no válido | `409` con `{"detail":"execution status 'X' does not allow approve"}`. |
|
||||
| Dashboard pierde conexión con core | `httpx` retry simple (2 intentos, 1s). Si sigue fallando, banner `st.error("Core API unreachable")`. |
|
||||
| Variable `.env` requerida ausente | `Pydantic Settings` falla en arranque con mensaje claro (no en runtime: el contenedor no debe estar "vivo y roto"). |
|
||||
|
||||
## 14. Testing
|
||||
|
||||
```
|
||||
tests/
|
||||
├── unit/
|
||||
│ ├── test_llm_mock.py
|
||||
│ ├── test_guardrails_ai.py
|
||||
│ ├── test_registry.py
|
||||
│ ├── test_runtime_state.py
|
||||
│ └── test_policy_loader.py
|
||||
├── integration/
|
||||
│ ├── test_invoke_happy_path.py
|
||||
│ ├── test_invoke_hitl.py
|
||||
│ ├── test_invoke_pii_block.py
|
||||
│ └── test_invoke_resume_after_restart.py
|
||||
└── fixtures/
|
||||
├── agents/incident_analyzer_test.yaml
|
||||
├── policies/test_policy.yaml
|
||||
└── inputs/
|
||||
```
|
||||
|
||||
- **Stack**: `pytest` + `pytest-asyncio` + `httpx.AsyncClient` (FastAPI TestClient) + `freezegun`.
|
||||
- **MockProvider** con lookup determinista (`hash(input) → respuesta`).
|
||||
- **Sin tests Streamlit** en MVP. Smoke manual en `docs/manual_qa.md`.
|
||||
- **CI** (`.github/workflows/ci.yml`): `ruff check` + `ruff format --check` + `mypy` + `pytest`.
|
||||
- **Cobertura objetivo** ~70% sobre `core/src/agentforge_core`.
|
||||
|
||||
## 15. Despliegue
|
||||
|
||||
- `docker-compose.yml` con dos servicios: `core` (uvicorn, `:8000`) y `dashboard` (Streamlit, `:8501`).
|
||||
- `Dockerfile.core` y `Dockerfile.dashboard` separados; cada uno con sus deps mínimas.
|
||||
- Healthchecks: `core` `GET /health`; `dashboard` `GET /` (Streamlit responde 200 cuando el server arranca).
|
||||
- Volume mount `./data:/app/data` y `./agents:/app/agents:ro`, `./policies:/app/policies:ro`.
|
||||
- `.env.example` con todas las variables documentadas y comentadas.
|
||||
- `LLM_PROVIDER=mock` por defecto → arranque sin claves.
|
||||
- Variables relevantes: `LLM_PROVIDER`, `LLM_FALLBACK_PROVIDER`, `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY`, `AZURE_OPENAI_DEPLOYMENT`, `OPENAI_API_KEY`, `GUARDRAILS_NEMO_ENABLED`, `LOG_LEVEL`, `DATA_DIR`.
|
||||
|
||||
## 16. README — estructura
|
||||
|
||||
1. **Qué es AgentForge** — Plataforma de gobernanza de agentes IA: catalogación, versionado de prompts y políticas, guardrails runtime, ejecución stateful con HITL, observabilidad.
|
||||
2. **Problema que resuelve** — Sin gobernanza, los agentes en producción son cajas negras (ver §2).
|
||||
3. **Diagrama de arquitectura** — Mermaid en línea + PNG renderizado en `docs/`.
|
||||
4. **Quickstart**: `cp .env.example .env && docker compose up`. Funciona out-of-the-box (`LLM_PROVIDER=mock`).
|
||||
5. **Demo guiada en 3 pasos**: 1) registro; 2) ejecuta `incident_analyzer` con `02_mos_degradation_pool_sbc.txt`; 3) aprueba el rollback en HITL.
|
||||
6. **Capacidades implementadas** — tabla `feature → ubicación en código`.
|
||||
7. **Roadmap** — link a `docs/futuro.md`.
|
||||
|
||||
## 17. Roadmap (`docs/futuro.md`)
|
||||
|
||||
- NeMo Guardrails con configuración rica (Colang KB).
|
||||
- Autenticación: OAuth2/OIDC con MSAL para entornos Azure AD.
|
||||
- OpenTelemetry: spans por nodo, métricas de violaciones por validador.
|
||||
- Multi-tenant: aislamiento por `tenant_id`, registry por tenant.
|
||||
- Evaluadores LLM-as-judge automatizados: regression suite que ejecuta cada agente sobre escenarios canónicos y mide deriva.
|
||||
- UI de aprobación con SLA, reasignación entre operadores, cola Kanban.
|
||||
- Persistencia migrable a Postgres + pgvector.
|
||||
|
||||
## 18. No-objetivos (out of scope MVP)
|
||||
|
||||
- Aprendizaje/fine-tuning de modelos.
|
||||
- Integración con sistemas reales de orquestación (RPA, Ansible, Kubernetes).
|
||||
- Inteligencia avanzada del agente (RAG, multi-step planning con tool use complejo).
|
||||
- Tests automatizados de UI Streamlit.
|
||||
- Soporte multi-idioma del dashboard.
|
||||
- High-availability del core (1 réplica suficiente para MVP).
|
||||
|
||||
## 19. Decisiones pospuestas
|
||||
|
||||
- **Postgres vs SQLite para checkpoints en multi-instance**: SQLite suficiente para MVP single-node; cambio a Postgres documentado en `docs/futuro.md`.
|
||||
- **NeMo full integration**: feature flag preparado, pero la configuración Colang completa es post-MVP.
|
||||
- **Métricas**: estructura de logging permite extracción posterior; no se entregan dashboards Grafana.
|
||||
|
||||
## 20. Referencias
|
||||
|
||||
- [LangGraph documentation](https://langchain-ai.github.io/langgraph/) — patrón checkpointer + interrupt.
|
||||
- [Guardrails AI](https://www.guardrailsai.com/) — validators hub.
|
||||
- [NVIDIA NeMo Guardrails](https://github.com/NVIDIA/NeMo-Guardrails) — Colang DSL.
|
||||
- [Microsoft Presidio](https://microsoft.github.io/presidio/) — PII detection.
|
||||
- [Pydantic v2](https://docs.pydantic.dev/latest/) — modelos y settings.
|
||||
- [FastAPI](https://fastapi.tiangolo.com/) — API REST.
|
||||
- [Streamlit](https://streamlit.io/) — dashboard.
|
||||
- [structlog](https://www.structlog.org/) — logging estructurado.
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user