proyecto generalizado

This commit is contained in:
2026-05-23 16:44:45 +02:00
parent 2f990ea636
commit b4964261ec
94 changed files with 1032 additions and 320 deletions
+11 -11
View File
@@ -1,4 +1,4 @@
# Componentes de AgentForge y su interrelación (bajo nivel)
# 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
@@ -9,7 +9,7 @@
> - ¿Las decisiones técnicas resumidas? → [`ARCHITECTURE.md`](../ARCHITECTURE.md).
> - ¿Cómo arrancarlo? → [`README.md`](../README.md).
>
> Rutas relativas a `core/src/agentforge_core/` salvo que se diga otra cosa.
> Rutas relativas a `core/src/ forja_core/` salvo que se diga otra cosa.
> Refleja el estado del repo en `v0.1.0`.
---
@@ -67,7 +67,7 @@ NIVEL 6
main ⇐ api (routers), api.middlewares, config, observability.logging → create_app(), app
(separado, sin imports del paquete core)
dashboard/src/agentforge_dashboard/* ← habla con `main` por HTTP, no por import
dashboard/src/ forja_dashboard/* ← habla con `main` por HTTP, no por import
```
Reglas que se cumplen y conviene mantener:
@@ -333,11 +333,11 @@ Hashing/versionado: `registry/versioning.compute_hash(yaml_text)` = SHA-256 del
---
## 6. Dashboard ↔ Core (`agentforge_dashboard`)
## 6. Dashboard ↔ Core (` forja_dashboard`)
El dashboard no comparte código con el core: solo lo llama por HTTP a través de
`CoreClient` (`dashboard/src/agentforge_dashboard/client.py`, httpx síncrono con 2
retries, `base_url = AGENTFORGE_CORE_URL`; mapea 404/409/422 → `{"error": <json>}`).
`CoreClient` (`dashboard/src/ forja_dashboard/client.py`, httpx síncrono con 2
retries, `base_url = FORJA_CORE_URL`; mapea 404/409/422 → `{"error": <json>}`).
| `CoreClient.<método>` | Endpoint del core | Página(s) que lo usan |
|-----------------------|-------------------|-----------------------|
@@ -365,11 +365,11 @@ Componentes reutilizables: `components/diff_view.render_unified_diff(diff_text)`
## 7. Arranque y ciclo de vida
**Proceso core** (`uvicorn agentforge_core.main:app`):
1. Import de `agentforge_core.main` ⇒ se ejecuta `app = create_app()`:
**Proceso core** (`uvicorn forja_core.main:app`):
1. Import de ` forja_core.main` ⇒ se ejecuta `app = create_app()`:
`Settings()``configure_logging(level=settings.log_level)``FastAPI(...)`
`app.add_middleware(TraceIdMiddleware)` → registra `GET /health`
`from agentforge_core.api import agents, executions, policies, violations`
`from forja_core.api import agents, executions, policies, violations`
`include_router` ×5.
2. Las dependencias (`deps.get_registry`, `get_policy_store`, `get_llm_provider`,
`get_guardrail_engine`, `get_orchestrator`) **no** se construyen aún; se
@@ -378,12 +378,12 @@ Componentes reutilizables: `components/diff_view.render_unified_diff(diff_text)`
pueden disparar la construcción perezosa) → handler → respuesta con `X-Trace-Id`.
**Contenedores** (`docker-compose.yml`): servicio `core` (`core/Dockerfile`,
`uvicorn agentforge_core.main:app --host 0.0.0.0 --port 8000`, `HEALTHCHECK`
`uvicorn forja_core.main:app --host 0.0.0.0 --port 8000`, `HEALTHCHECK`
`curl /health`, monta `./agents:ro`, `./policies:ro`, `./data:rw`, env
`DATA_DIR=/app/data`, `AGENTS_DIR=/app/agents`, `POLICIES_DIR=/app/policies`,
`env_file: .env`); servicio `dashboard` (`dashboard/Dockerfile`, `streamlit run
app.py`, `HEALTHCHECK``/_stcore/health`, `depends_on: core: service_healthy`,
env `AGENTFORGE_CORE_URL=http://core:8000`, monta `./agents:ro` para leer los
env `FORJA_CORE_URL=http://core:8000`, monta `./agents:ro` para leer los
`examples/*.txt`).
**`Settings` (env vars)** — `config.py`:
+13 -13
View File
@@ -1,4 +1,4 @@
# AgentForge explicado de principio a fin
# Forja explicado de principio a fin
> **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í*
@@ -18,7 +18,7 @@
> opacos: prompts que cambian sin historial, validaciones inconsistentes, acciones
> de alto impacto sin supervisión y ninguna auditoría de lo que decidió el agente.
**AgentForge es el "plano de control" que pones *delante* de tus agentes** antes de
**Forja es el "plano de control" que pones *delante* de tus agentes** antes de
dejarlos tocar nada importante. No es un framework para *construir* agentes; es la
capa que los **cataloga, versiona, valida, ejecuta de forma supervisada y audita**.
@@ -47,13 +47,13 @@ Si entiendes estas seis ideas, entiendes el proyecto. Todo lo demás son detalle
## 3. Vista de pájaro: dos servicios
AgentForge son **dos procesos** que se hablan por HTTP/JSON:
Forja son **dos procesos** que se hablan por HTTP/JSON:
```
┌───────────────────────────── docker-compose ──────────────────────────────┐
│ │
│ ┌──────────────────────┐ HTTP/JSON ┌────────────────────────┐ │
│ │ agentforge-dashboard │ ───────────────► │ agentforge-core │ │
│ │ forja-dashboard │ ───────────────► │ forja-core │ │
│ │ Streamlit :8501 │ ◄─────────────── │ FastAPI :8000 │ │
│ │ (la "consola") │ │ (el cerebro) │ │
│ └──────────────────────┘ └───────────┬────────────┘ │
@@ -69,9 +69,9 @@ AgentForge son **dos procesos** que se hablan por HTTP/JSON:
└────────────────────────────────────────────────────────────────────────────┘
```
- **`agentforge-core`** (FastAPI, puerto 8000) — todo el dominio: registry de
- **`forja-core`** (FastAPI, puerto 8000) — todo el dominio: registry de
agentes, motor de guardrails, runtime de ejecución, persistencia. No tiene UI.
- **`agentforge-dashboard`** (Streamlit, puerto 8501) — una consola visual. **No
- **`forja-dashboard`** (Streamlit, puerto 8501) — una consola visual. **No
contiene lógica de negocio**: es un cliente HTTP del core con cinco páginas.
La separación importa: el core podría servir a una CLI, a otro servicio, a un
@@ -97,7 +97,7 @@ pipeline... el dashboard es solo una de las caras posibles.
## 4. Recorrido por los módulos (y quién depende de quién)
El código del core vive bajo `core/src/agentforge_core/`. Lo agrupo por capas, de
El código del core vive bajo `core/src/ forja_core/`. Lo agrupo por capas, de
las más internas (sin dependencias) a las más externas.
### 4.1 `domain/` — el vocabulario del sistema
@@ -278,7 +278,7 @@ inyectan). Dependen de él: la API (`api/executions.py` solo conoce el
| `executions.py` | El más cargado: `POST /agents/{n}/invoke`, `GET /executions`, `GET /executions/{trace_id}`, `POST /executions/{trace_id}/approve`, `POST /executions/{trace_id}/reject`. Mantiene además `execution_index.json` (mapa `trace_id → agente/versión`) para poder reanudar tras un reinicio. |
| `policies.py`, `violations.py` | Routers `/policies` y `/violations` (este con filtros por severidad, stage, etc.). |
Y la **raíz de la app**: `core/src/agentforge_core/main.py``create_app()` instancia
Y la **raíz de la app**: `core/src/ forja_core/main.py``create_app()` instancia
`FastAPI`, añade el `TraceIdMiddleware`, monta los routers, y expone `/health`.
Depende de: todo lo de arriba (vía `deps.py`). Dependen de él: el dashboard (por
@@ -297,7 +297,7 @@ HTTP) y los tests de integración (`tests/integration/`, vía `TestClient`).
| `pages/5_📐_Politicas.py` | Inventario de políticas: validadores de entrada/salida (cada uno expandible con su config) y versiones. |
| `components/` | Trozos reutilizables de UI: `diff_view` (pinta `+`/`-`/`@@`), `trace_view` (el timeline del `decision_path`), `violation_view` (badges de severidad). |
Depende de: el `agentforge-core` por HTTP (vía `AGENTFORGE_CORE_URL`). **Nadie del
Depende de: el `forja-core` por HTTP (vía `FORJA_CORE_URL`). **Nadie del
core depende del dashboard.** No tiene tests unitarios (mal coste/beneficio para
Streamlit); su verificación es la checklist manual de [`docs/manual_qa.md`](manual_qa.md).
@@ -464,7 +464,7 @@ sequenceDiagram
## 7. Persistencia: cuatro formas, cuatro razones
AgentForge no usa una sola base de datos; usa la herramienta adecuada para cada cosa.
Forja no usa una sola base de datos; usa la herramienta adecuada para cada cosa.
| Qué | Cómo | Por qué así |
|-----|------|-------------|
@@ -499,9 +499,9 @@ AgentForge no usa una sola base de datos; usa la herramienta adecuada para cada
## 9. Mapa del repositorio y por dónde empezar a leer
```
agentforge/
forja/
├── core/
│ ├── src/agentforge_core/
│ ├── src/ forja_core/
│ │ ├── domain/ ← los modelos de datos (empieza por execution.py)
│ │ ├── config.py · observability/ ← cimientos transversales
│ │ ├── llm/ ← proveedores LLM (Protocol + impls + factory)
@@ -512,7 +512,7 @@ agentforge/
│ │ └── main.py ← create_app(): ensambla la app
│ ├── Dockerfile · requirements.txt
├── dashboard/
│ ├── src/agentforge_dashboard/ ← Streamlit (client + app + pages + components)
│ ├── src/ forja_dashboard/ ← Streamlit (client + app + pages + components)
│ ├── Dockerfile · requirements.txt
├── agents/incident_analyzer/ ← el agente de ejemplo (YAMLs + escenarios .txt)
├── policies/default/ ← la política de guardrails de ejemplo
+419
View File
@@ -0,0 +1,419 @@
<!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&amp;family=Space+Grotesk:wght@500;600&amp;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">78</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: 78%"></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">95%</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-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 &amp;&amp; make lint &amp;&amp; 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/&lt;proyecto&gt;/&lt;agente&gt;</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_dashboard</span></div>
<div><strong>Python:</strong> 3.11+</div>
<div><strong>UI:</strong> Streamlit 1.38 + Tailwind (editores)</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">Este documento fue generado automáticamente tras la sesión de refactorización del 23 de mayo de 2026.</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>
+21 -21
View File
@@ -3,8 +3,8 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Walkthrough completo de AgentForge — de alto a bajo nivel, con diagramas.">
<title>AgentForge · Walkthrough</title>
<meta name="description" content="Walkthrough completo de Forja — de alto a bajo nivel, con diagramas.">
<title>Forja · Walkthrough</title>
<script>
(function () {
try {
@@ -255,7 +255,7 @@ details.dd .body{padding:4px 16px 14px}
<div class="topbar">
<button class="icon-btn" id="menuBtn" aria-label="Abrir índice"></button>
<span class="tt">🛡️ AgentForge · Walkthrough</span>
<span class="tt">🛡️ Forja · Walkthrough</span>
<button class="icon-btn" id="themeBtnM" aria-label="Cambiar tema" style="margin-left:auto"></button>
</div>
<div class="scrim" id="scrim"></div>
@@ -266,7 +266,7 @@ details.dd .body{padding:4px 16px 14px}
<div class="brand">
<span class="logo">🛡️</span>
<span>
<span class="t1">AgentForge</span>
<span class="t1">Forja</span>
<span class="t2">Walkthrough · de alto a bajo nivel</span>
</span>
</div>
@@ -274,7 +274,7 @@ details.dd .body{padding:4px 16px 14px}
<button class="btn" id="themeBtn" style="flex:1; justify-content:center">◐ Tema</button>
<button class="btn" onclick="window.print()" style="flex:1; justify-content:center">⎙ Imprimir</button>
</div>
<div class="meta">HTML autocontenido · <code>docs/walkthrough.html</code> · AgentForge v0.1.0</div>
<div class="meta">HTML autocontenido · <code>docs/walkthrough.html</code> · Forja v0.1.0</div>
<nav>
<ul class="toc" id="toc">
<li class="group">Panorama</li>
@@ -314,7 +314,7 @@ details.dd .body{padding:4px 16px 14px}
<header class="hero">
<div class="crumbs">Plataforma de gobernanza de agentes IA · documento generado para entender el proyecto completo</div>
<h1>🛡️ AgentForge — Walkthrough</h1>
<h1>🛡️ Forja — Walkthrough</h1>
<p class="tagline">Catalogación y versionado de agentes y políticas, guardrails en runtime, ejecución <em>stateful</em> con Human-in-the-Loop, observabilidad y trazabilidad de extremo a extremo. Aquí está todo: <strong>de la vista de pájaro al cableado de cada módulo</strong>, con diagramas.</p>
<div class="badges">
<span class="badge k">🐍 <b>Python 3.11+</b></span>
@@ -338,7 +338,7 @@ details.dd .body{padding:4px 16px 14px}
<section id="intro">
<h2><span class="kicker">00</span> Qué es y por qué</h2>
<p class="lead">Poner agentes de IA en producción <strong>sin una capa de gobierno</strong> produce sistemas opacos: prompts que cambian sin historial, validaciones inconsistentes, acciones de alto impacto sin supervisión y ninguna auditoría de lo que decidió el agente.</p>
<p><strong>AgentForge es el "plano de control" que pones <em>delante</em> de tus agentes</strong> antes de dejarlos tocar nada importante. No es un framework para <em>construir</em> agentes; es la capa que los <strong>cataloga, versiona, valida, ejecuta de forma supervisada y audita</strong>.</p>
<p><strong>Forja es el "plano de control" que pones <em>delante</em> de tus agentes</strong> antes de dejarlos tocar nada importante. No es un framework para <em>construir</em> agentes; es la capa que los <strong>cataloga, versiona, valida, ejecuta de forma supervisada y audita</strong>.</p>
<div class="callout key">
<div class="ct">🧭 El caso de ejemplo del repo</div>
<p>Un agente de operaciones de telco — <code>incident_analyzer</code>: recibe la descripción de un incidente de plataforma de voz (caída de registros SIP, degradación de MOS, saturación de HSS…) y propone acciones con <strong>análisis de riesgo</strong> y <strong>plan de rollback</strong>. Las acciones de riesgo alto quedan <strong>pausadas esperando aprobación humana</strong>. Todo queda registrado con un <code>trace_id</code>.</p>
@@ -393,10 +393,10 @@ details.dd .body{padding:4px 16px 14px}
<!-- ============================================================= -->
<section id="vista">
<h2><span class="kicker">02</span> Vista de pájaro: dos servicios</h2>
<p>AgentForge son <strong>dos procesos</strong> que se hablan por HTTP/JSON, levantados por <code>docker-compose</code>:</p>
<p>Forja son <strong>dos procesos</strong> que se hablan por HTTP/JSON, levantados por <code>docker-compose</code>:</p>
<ul>
<li><strong><code>agentforge-core</code></strong> (FastAPI, puerto <strong>8000</strong>) — todo el dominio: registry de agentes, motor de guardrails, runtime de ejecución, persistencia. <em>No tiene UI.</em></li>
<li><strong><code>agentforge-dashboard</code></strong> (Streamlit, puerto <strong>8501</strong>) — una consola visual con cinco páginas. <strong>No contiene lógica de negocio</strong>: es un cliente HTTP del core.</li>
<li><strong><code>forja-core</code></strong> (FastAPI, puerto <strong>8000</strong>) — todo el dominio: registry de agentes, motor de guardrails, runtime de ejecución, persistencia. <em>No tiene UI.</em></li>
<li><strong><code>forja-dashboard</code></strong> (Streamlit, puerto <strong>8501</strong>) — una consola visual con cinco páginas. <strong>No contiene lógica de negocio</strong>: es un cliente HTTP del core.</li>
</ul>
<p>La separación importa: el core podría servir a una CLI, a otro servicio, a un pipeline… el dashboard es solo una de las caras posibles. (Y este documento es otra cara más: el HTML que estás leyendo.)</p>
@@ -416,14 +416,14 @@ details.dd .body{padding:4px 16px 14px}
<!-- dashboard -->
<rect class="dg-box accent" x="56" y="60" width="300" height="106" rx="13"/>
<text class="dg-t" x="206" y="92" text-anchor="middle">agentforge-dashboard</text>
<text class="dg-t" x="206" y="92" text-anchor="middle">forja-dashboard</text>
<text class="dg-s" x="206" y="112" text-anchor="middle">Streamlit · :8501 · "la consola"</text>
<text class="dg-m dim" x="206" y="132" text-anchor="middle">5 páginas · sin lógica de negocio</text>
<text class="dg-m dim" x="206" y="150" text-anchor="middle">CoreClient (httpx) → habla solo HTTP</text>
<!-- core -->
<rect class="dg-box violet" x="600" y="60" width="344" height="106" rx="13"/>
<text class="dg-t" x="772" y="92" text-anchor="middle">agentforge-core</text>
<text class="dg-t" x="772" y="92" text-anchor="middle">forja-core</text>
<text class="dg-s" x="772" y="112" text-anchor="middle">FastAPI · :8000 · "el cerebro"</text>
<text class="dg-m dim" x="772" y="132" text-anchor="middle">dominio · runtime · guardrails · persistencia</text>
<text class="dg-m dim" x="772" y="150" text-anchor="middle">/health · /agents · /executions · /policies · /violations</text>
@@ -513,7 +513,7 @@ details.dd .body{padding:4px 16px 14px}
<!-- ============================================================= -->
<section id="modulos">
<h2><span class="kicker">04</span> El grafo de módulos (quién depende de quién)</h2>
<p>El código del core vive bajo <code>core/src/agentforge_core/</code>. Es un <strong>DAG</strong>: las capas de abajo no importan nada de las de arriba. El "nivel" es la profundidad topológica. Lee de abajo hacia arriba: el vocabulario primero, la composición de la app al final.</p>
<p>El código del core vive bajo <code>core/src/forja_core/</code>. Es un <strong>DAG</strong>: las capas de abajo no importan nada de las de arriba. El "nivel" es la profundidad topológica. Lee de abajo hacia arriba: el vocabulario primero, la composición de la app al final.</p>
<div class="bands">
<div class="band">
<div class="lvl"><b>6</b><span>app</span></div>
@@ -980,7 +980,7 @@ details.dd .body{padding:4px 16px 14px}
<p>El estado que fluye por el grafo es un <code>TypedDict</code>. <code>decision_path</code> usa un <em>reducer</em> (<code>Annotated[list, operator.add]</code>) para que cada nodo <strong>añada</strong> pasos en vez de sobrescribir:</p>
<div class="code">
<div class="hd"><span class="dot"></span><span class="fn">core/src/agentforge_core/runtime/state.py</span><span class="lang">python</span></div>
<div class="hd"><span class="dot"></span><span class="fn">core/src/forja_core/runtime/state.py</span><span class="lang">python</span></div>
<pre><span class="k">class</span> <span class="y">AgentState</span>(TypedDict, total=<span class="k">False</span>):
trace_id: <span class="y">str</span>; agent_name: <span class="y">str</span>; agent_version: <span class="y">str</span>; user_input: <span class="y">str</span>
messages: <span class="y">list</span>[<span class="y">dict</span>]; raw_llm_output: <span class="y">str</span> | <span class="k">None</span>; parsed_output: <span class="y">dict</span> | <span class="k">None</span>
@@ -1097,7 +1097,7 @@ g.add_edge(<span class="s">"finalize"</span>, END)
<!-- divider -->
<rect class="dg-band" x="14" y="460" width="1092" height="34" rx="6"/>
<line class="dg-divider" x1="14" y1="460" x2="1106" y2="460"/><line class="dg-divider" x1="14" y1="494" x2="1106" y2="494"/>
<text class="dg-s" x="560" y="481" text-anchor="middle" style="font-style:italic">· · · más tarde — incluso tras reiniciar agentforge-core: el estado pausado sigue en checkpoints.sqlite · · ·</text>
<text class="dg-s" x="560" y="481" text-anchor="middle" style="font-style:italic">· · · más tarde — incluso tras reiniciar forja-core: el estado pausado sigue en checkpoints.sqlite · · ·</text>
<!-- ACT 2 label -->
<rect class="dg-box ok" x="18" y="504" width="232" height="22" rx="6"/><text class="dg-t sm" x="28" y="520" style="font-size:11.5px;fill:var(--ok)">ACTO 2 · el humano decide → completed</text>
@@ -1222,7 +1222,7 @@ curl -s localhost:8000/executions/$TRACE/approve \
<!-- ============================================================= -->
<section id="persistencia">
<h2><span class="kicker">11</span> Persistencia: cuatro formas, cuatro razones</h2>
<p>AgentForge no usa una sola base de datos; usa la herramienta adecuada para cada cosa.</p>
<p>Forja no usa una sola base de datos; usa la herramienta adecuada para cada cosa.</p>
<figure class="diagram">
<svg viewBox="0 0 1020 420" role="img" aria-label="Mapa de persistencia: quién escribe y lee qué">
@@ -1230,7 +1230,7 @@ curl -s localhost:8000/executions/$TRACE/approve \
<!-- core in the middle -->
<rect class="dg-box violet" x="396" y="170" width="228" height="80" rx="12"/>
<text class="dg-t sm" x="510" y="196" text-anchor="middle">agentforge-core</text>
<text class="dg-t sm" x="510" y="196" text-anchor="middle">forja-core</text>
<text class="dg-s" x="510" y="214" text-anchor="middle">registry · orchestrator</text>
<text class="dg-s" x="510" y="230" text-anchor="middle">api/persistence · runtime/checkpointer</text>
@@ -1489,11 +1489,11 @@ curl -s localhost:8000/executions/$TRACE/approve \
<div>
<h4>El ciclo de vida del proceso core</h4>
<ol>
<li>Importar <code>agentforge_core.main</code> ejecuta <code>app = create_app()</code>: <code>Settings()</code><code>configure_logging(level)</code><code>FastAPI(...)</code><code>add_middleware(TraceIdMiddleware)</code> → registra <code>GET /health</code> → importa los routers → <code>include_router</code> (agents, executions ×2, policies, violations).</li>
<li>Importar <code>forja_core.main</code> ejecuta <code>app = create_app()</code>: <code>Settings()</code><code>configure_logging(level)</code><code>FastAPI(...)</code><code>add_middleware(TraceIdMiddleware)</code> → registra <code>GET /health</code> → importa los routers → <code>include_router</code> (agents, executions ×2, policies, violations).</li>
<li>Las dependencias (<code>get_registry</code>, <code>get_policy_store</code>, <code>get_llm_provider</code>, <code>get_guardrail_engine</code>, <code>get_orchestrator</code>) <strong>no</strong> se construyen aún; se construyen y cachean en la <strong>primera request</strong> que las inyecta.</li>
<li>Cada request: <code>TraceIdMiddleware.dispatch</code> → router → resuelve <code>Depends(...)</code> (que puede disparar la construcción perezosa) → handler → respuesta con <code>X-Trace-Id</code>.</li>
</ol>
<p class="muted">En contenedores: <code>uvicorn agentforge_core.main:app --host 0.0.0.0 --port 8000</code>; <code>HEALTHCHECK</code><code>curl /health</code>; monta <code>./agents:ro</code>, <code>./policies:ro</code>, <code>./data:rw</code>; <code>DATA_DIR=/app/data</code>, etc. El dashboard depende de <code>core: service_healthy</code> y usa <code>AGENTFORGE_CORE_URL=http://core:8000</code>. La imagen del core instala <code>en_core_web_sm</code> de spaCy para Presidio.</p>
<p class="muted">En contenedores: <code>uvicorn forja_core.main:app --host 0.0.0.0 --port 8000</code>; <code>HEALTHCHECK</code><code>curl /health</code>; monta <code>./agents:ro</code>, <code>./policies:ro</code>, <code>./data:rw</code>; <code>DATA_DIR=/app/data</code>, etc. El dashboard depende de <code>core: service_healthy</code> y usa <code>FORJA_CORE_URL=http://core:8000</code>. La imagen del core instala <code>en_core_web_sm</code> de spaCy para Presidio.</p>
</div>
<div>
<h4>Variables de entorno (<code>config.py</code> · <code>.env</code>)</h4>
@@ -1510,7 +1510,7 @@ curl -s localhost:8000/executions/$TRACE/approve \
<tr><td><code>DATA_DIR</code></td><td><code>./data</code></td><td>checkpointer · índice · JSONL</td></tr>
<tr><td><code>AGENTS_DIR</code></td><td><code>./agents</code></td><td><code>FileSystemAgentRegistry</code></td></tr>
<tr><td><code>POLICIES_DIR</code></td><td><code>./policies</code></td><td><code>FileSystemPolicyStore</code></td></tr>
<tr><td><code>AGENTFORGE_CORE_URL</code></td><td><code>http://core:8000</code></td><td>el dashboard (<code>CoreClient</code>)</td></tr>
<tr><td><code>FORJA_CORE_URL</code></td><td><code>http://core:8000</code></td><td>el dashboard (<code>CoreClient</code>)</td></tr>
</tbody>
</table>
</div>
@@ -1721,7 +1721,7 @@ open docs/walkthrough.html <span class="c"># macOS</span>
<div class="footer">
<p><strong>AgentForge · Walkthrough.</strong> Documento autocontenido (sin recursos externos). Acompaña a <code>README.md</code>, <code>ARCHITECTURE.md</code>, <code>docs/explicacion.md</code> (narrativa) y <code>docs/componentes.md</code> (referencia de cableado). Refleja el repo en <code>v0.1.0</code>.</p>
<p><strong>Forja · Walkthrough.</strong> Documento autocontenido (sin recursos externos). Acompaña a <code>README.md</code>, <code>ARCHITECTURE.md</code>, <code>docs/explicacion.md</code> (narrativa) y <code>docs/componentes.md</code> (referencia de cableado). Refleja el repo en <code>v0.1.0</code>.</p>
<p class="muted">Documento HTML autocontenido (<code>docs/walkthrough.html</code>) — sin recursos externos: ábrelo en cualquier navegador.</p>
</div>
</main>