pagina unica

This commit is contained in:
2026-06-10 18:21:33 +02:00
parent 7d0f10d21f
commit 37633920ce
108 changed files with 3874 additions and 1350 deletions
+20 -12
View File
@@ -332,12 +332,14 @@ Hashing/versionado: `registry/versioning.compute_hash(yaml_text)` = SHA-256 del
## 6. UI HTMX embebida en Core
Con la migración a Opción A (FastAPI + HTMX), la interfaz de usuario se sirve
directamente desde el mismo proceso `forja-core` (Jinja2 + HTMX vía CDN, sin
servicio separado). No hay `CoreClient` intermedio para las páginas; los
endpoints de UI llaman a los mismos servicios internos que la API REST.
La antigua sección de dashboard Streamlit ha sido eliminada.
La interfaz es **una sola página** (`GET /`) servida desde el mismo proceso
`forja-core` (Jinja2 + HTMX vía CDN, sin servicio separado), organizada como
las seis etapas del ciclo de vida: Define → Run → Approve → Train → Promote →
Audit. Cada etapa es un fragmento (`GET /fragments/*`) que se re-renderiza
cuando cualquier botón de acción dispara el evento global `forja-refresh`.
Los botones llaman directamente a los endpoints `/api/*` (con `json-enc`);
no hay cliente HTTP intermedio ni lógica duplicada. Las rutas multipágina
antiguas redirigen con 301 a las anclas de su etapa.
---
@@ -366,22 +368,28 @@ env `DATA_DIR=/app/data`, `AGENTS_DIR=/app/agents`, `POLICIES_DIR=/app/policies`
| Campo | Env var | Default | Lo consume |
|-------|---------|---------|------------|
| `llm_provider` | `LLM_PROVIDER` | `mock` | `build_llm_provider` |
| `llm_fallback_provider` | `LLM_FALLBACK_PROVIDER` | `""` | (declarado; la factory aún no lo usa) |
| `llm_fallback_provider` | `LLM_FALLBACK_PROVIDER` | `""` | `build_llm_provider``FallbackLLMProvider` (vacío = sin fallback) |
| `azure_openai_*` | `AZURE_OPENAI_*` | `""` / `2024-08-01-preview` | `AzureOpenAIProvider` |
| `openai_api_key` / `openai_model` | `OPENAI_API_KEY` / `OPENAI_MODEL` | `""` / `gpt-4o` | `OpenAIProvider` |
| `guardrails_nemo_enabled` | `GUARDRAILS_NEMO_ENABLED` | `False` | `build_guardrail_engine` |
| `guardrails_nemo_allowed_keywords` | `GUARDRAILS_NEMO_ALLOWED_KEYWORDS` | `""` | keywords CSV del stub topical-rails (vacío = sin chequeo) |
| `log_level` | `LOG_LEVEL` | `INFO` | `configure_logging` |
| `data_dir` | `DATA_DIR` | `./data` | `build_checkpointer`, `_record_execution`, `append_*`, `read_*` |
| `agents_dir` | `AGENTS_DIR` | `./agents` | `build_agent_registry` |
| `training_backend` | `TRAINING_BACKEND` | `mock` | `build_training_backend` |
| `azure_ml_*` | `AZURE_ML_*` | varios | `AzureMLTrainingBackend` (stub si falta workspace) |
| `data_dir` | `DATA_DIR` | `./data` | checkpointer, logs JSONL, training runs, evaluaciones, promociones |
| `agents_dir` | `AGENTS_DIR` | `./agents` | `build_agent_registry`, escenarios de evaluación |
| `policies_dir` | `POLICIES_DIR` | `./policies` | `build_policy_store` |
| `datasets_dir` | `DATASETS_DIR` | `./datasets` | `build_dataset_store` |
| `models_dir` | `MODELS_DIR` | `./models` | `build_model_store` |
---
## 8. Aristas y "gotchas"
- **`llm_fallback_provider`**: existe en `Settings` y en `.env.example`, pero
`build_llm_provider` aún no lo aplica (queda como punto de extensión).
- **`build_llm_provider`** usa `match` sin `case _:`; al ser el tipo un `Literal`
- **`llm_fallback_provider`**: si está configurado y difiere del primario,
`build_llm_provider` devuelve un `FallbackLLMProvider` que delega en el
secundario cuando el primario agota sus reintentos.
- **`_build_single`** usa `match` sin `case _:`; al ser el tipo un `Literal`
de tres valores es exhaustivo, pero un valor inesperado caería en "ninguna rama".
- **NeMo**: `NeMoGuardrailsEngine` solo entra al `CompositeGuardrailEngine` si
`GUARDRAILS_NEMO_ENABLED=true`; por defecto el composite tiene un solo engine.
+53 -53
View File
@@ -1,9 +1,9 @@
# 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.
> **Nota de estado (junio 2026):** Forja es un **único servicio FastAPI** con UI
> HTMX embebida. Además del runtime gobernado, incluye la capa MLOps: datasets y
> modelos versionados, training runs contra backends externos, evaluación
> post-training y promoción gobernada draft → active.
>
> Si solo quieres arrancarlo, ve al [`README.md`](../README.md).
@@ -49,37 +49,33 @@ Si entiendes estas seis ideas, entiendes el proyecto. Todo lo demás son detalle
---
## 3. Vista de pájaro: dos servicios
## 3. Vista de pájaro: un único servicio
Forja son **dos procesos** que se hablan por HTTP/JSON:
Forja es **un solo proceso** FastAPI (puerto 8000) con dos caras:
```
┌───────────────────────────── docker-compose ──────────────────────────────┐
│ ┌──────────────────────┐ HTTP/JSON ┌────────────────────────┐ │
│ │ forja-dashboard │ ───────────────► │ forja-core │ │
│ Streamlit :8501 │ ◄─────────────── │ FastAPI :8000
│ (la "consola") (el cerebro)
└──────────────────────┘ └───────────┬────────────┘
┌────────────────┬────────────────────┼───────────────┤
▼ ▼
capa LLM capa Guardrails runtime LangGraph
(Strategy+factory) (Strategy+factory) (grafo + checkpointer)
│ │ │ │ │
│ └────────────────┴──────────┬──────────┘ │
│ ▼ │
│ Persistencia: YAML · JSON · JSONL · SQLite │
forja-core :8000
│ ┌─────────────────────────────────────────────────────────────────┐
│ │ UI HTMX (humanos, Jinja2) │ API REST (/api/*)
└────────────────────────────┬────────────────────────────────────┘
capa LLM capa Guardrails runtime LangGraph
(Strategy+factory) (Strategy+factory) (grafo + checkpointer)
│ │ │
└──────────────────┴───────────┬──────────┘
Persistencia: YAML · JSON · JSONL · SQLite
└────────────────────────────────────────────────────────────────────────────┘
```
- **`forja-core`** (FastAPI, puerto 8000) — todo el dominio: registry de
agentes, motor de guardrails, runtime de ejecución, persistencia. No tiene UI.
- **`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
pipeline... el dashboard es solo una de las caras posibles.
- La **API REST** (bajo `/api`) es el contrato para máquinas: CLI, pipelines,
otros servicios.
- La **UI HTMX** es **una sola página** (`/`) organizada como las seis etapas
del ciclo de vida (Define → Run → Approve → Train → Promote → Audit). Se
sirve desde el mismo proceso y llama a los mismos servicios internos — no hay
un cliente HTTP intermedio ni lógica de negocio duplicada. Las rutas antiguas
(`/agents`, `/run`, ...) redirigen a su etapa.
### Las capas del core (de fuera hacia dentro)
@@ -285,25 +281,20 @@ inyectan). Dependen de él: la API (`api/executions.py` solo conoce el
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
HTTP) y los tests de integración (`tests/integration/`, vía `TestClient`).
Depende de: todo lo de arriba (vía `deps.py`). Dependen de él: la UI HTMX
(mismo proceso) y los tests (`TestClient`).
### 4.8 `dashboard/` — la consola Streamlit
### 4.8 `web/` — la UI HTMX embebida (una página)
| Fichero | Rol |
|---------|-----|
| `client.py` | `CoreClient`: un cliente HTTP síncrono (httpx, con reintentos) que envuelve **todos** los endpoints del core y mapea 404/409/422 a `{"error": ...}`. Es lo único que sabe hablar con el core. |
| `app.py` | La página raíz: sidebar de branding + un health-check del core. |
| `pages/1_🏛️_Registro.py` | Catálogo de agentes: detalle (prompt, esquema, LLM, guardrails), tabla de versiones, **diff coloreado v1↔v2**. |
| `pages/2_▶️_Ejecutar.py` | Lanza un agente: botones con los escenarios pregrabados, textarea, `invoke`, y render del status + output + violaciones + *timeline* del `decision_path`. Avisa si quedó en `awaiting_approval`. |
| `pages/3_🤝_Aprobaciones.py` | La cola de HITL: lista las ejecuciones `awaiting_approval`, muestra cada acción propuesta (risk_score coloreado, target, rollback_plan) con un checkbox, y aprueba el subconjunto elegido o rechaza con motivo. |
| `pages/4_📜_Historial.py` | Pestaña de ejecuciones (tabla + detalle por `trace_id`) y pestaña de violaciones (filtrable por severidad). |
| `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). |
| `ui.py` | `GET /` (la página completa), `POST /run`, `GET /fragments/{agents,approvals,training,promotions,history}` y redirects 301 de las rutas antiguas. Llama a los mismos singletons de `deps.py` que la API; no hay cliente HTTP intermedio. |
| `templates/index.html` | La página única: seis etapas (Define → Run → Approve → Train → Promote → Audit), cada una con su explicación y su panel. |
| `templates/_*.html` | Fragmentos por etapa. Cada panel lleva `hx-get="/fragments/X" hx-trigger="forja-refresh from:body"`: cualquier botón de acción dispara el evento `forja-refresh` al terminar y **todos los paneles se re-renderizan** — el ciclo entero se recorre con clicks, sin recargar la página. |
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).
Los botones de acción (approve/reject/train/evaluate/promote) hacen `hx-post`
con la extensión `json-enc` directamente contra los endpoints `/api/*`; el botón
**Run demo incident** lanza un incidente pregrabado que pausa en HITL.
### 4.9 Lo que no es código: `agents/`, `policies/`, `data/`
@@ -348,7 +339,7 @@ Esta es la sección que conviene leer despacio: aquí se ve cómo encaja todo. S
### Acto 1 — la petición entra y se ejecuta el grafo
```
Cliente (dashboard o curl)
Cliente (UI o curl)
│ POST /agents/incident_analyzer/invoke { "input": "<texto del incidente SIP>" }
TraceIdMiddleware ............ genera trace_id = UUID, lo bind-ea al log
@@ -391,10 +382,10 @@ router: status no es terminal → NO se escribe en executions.jsonl,
respuesta 200 { status: "awaiting_approval", trace_id, needs_human_for: [act-1], decision_path: [...], ... }
```
En el dashboard, la página **Ejecutar** muestra el timeline y un aviso "ve a
Aprobaciones". La página **Aprobaciones** hace `GET /executions`, encuentra esta
ejecución (el endpoint reconstruye las `awaiting_approval` desde el índice + el
checkpointer) y muestra `act-1` con su risk_score, target y rollback_plan.
En la UI, la página **Runs** muestra el timeline y un aviso "Open Approvals".
La página **Approvals** reconstruye las ejecuciones `awaiting_approval` desde el
índice + el checkpointer y muestra `act-1` con su risk_score, target y
rollback_plan.
### Acto 2 — el humano decide; el grafo se reanuda
@@ -431,7 +422,7 @@ respuesta 200 { status: "completed", final_output: { ..., approved_actions: [ac
```mermaid
sequenceDiagram
participant U as Cliente / Dashboard
participant U as Cliente / UI
participant MW as TraceIdMiddleware
participant API as router (api/executions.py)
participant ORCH as AgentOrchestrator
@@ -477,6 +468,7 @@ Forja no usa una sola base de datos; usa la herramienta adecuada para cada cosa.
| Mapa `trace_id → agente/versión` | **JSON** (`execution_index.json`) | Necesario para reanudar un HITL sabiendo qué configuración lo ejecutó; debe sobrevivir a reinicios. |
| Log de ejecuciones terminales y de violaciones | **JSONL append-only** | Inmutable, auditable, trivial de "shipear" a un sistema de logs. |
| Estado intermedio del grafo (incl. pausas HITL) | **SQLite** (`checkpoints.sqlite`, vía LangGraph) | Es lo que LangGraph espera; permite reanudar una ejecución pausada entre reinicios del proceso. |
| Training runs, evaluaciones y promociones | **JSON por entidad** bajo `data/` (+ JSONL para promociones aprobadas) | Estado mutable consultable por id; la auditoría de promociones es append-only. |
---
@@ -497,6 +489,9 @@ Forja no usa una sola base de datos; usa la herramienta adecuada para cada cosa.
| **Factory** | Función `build_X(settings)` que monta la implementación de `X` adecuada según la configuración. |
| **Status de ejecución** | `running`, `awaiting_approval` (pausada en HITL), `blocked_by_guardrail` (la frenó un validador), `completed`, `failed`. |
| **Estados de un agente** | `draft`, `active`, `deprecated` (metadato en su YAML). |
| **Training run** | Job de fine-tuning enviado a un backend externo (mock o Azure ML) con dataset, modelo base y versión candidata del agente. |
| **Evaluación post-training** | Ejecutar los escenarios canónicos del agente (`examples/*.txt`) por el runtime gobernado; pasa si no hay violaciones bloqueantes. Es el gate de promoción. |
| **Promoción** | Petición de gobernanza para pasar una versión `draft` a `active`; requiere run `succeeded` + evaluación `passed` de esa misma versión + aprobación humana. |
---
@@ -508,18 +503,22 @@ Forja no usa una sola base de datos; usa la herramienta adecuada para cada cosa.
│ ├── src/ forja_core/
│ │ ├── domain/ ← los modelos de datos (empieza por execution.py)
│ │ ├── config.py · observability/ ← cimientos transversales
│ │ ├── llm/ ← proveedores LLM (Protocol + impls + factory)
│ │ ├── registry/ ← catálogo y versionado (YAML/JSON, hash, diff)
│ │ ├── llm/ ← proveedores LLM (Protocol + impls + fallback + factory)
│ │ ├── registry/ ← catálogo y versionado (base yaml_store.py + 4 stores)
│ │ ├── guardrails/ ← motor de validación (Protocol + composite + validators)
│ │ ├── runtime/ ← el grafo LangGraph (state, nodes, graph, checkpointer, orchestrator)
│ │ ├── training/ ← backends de fine-tuning (mock, Azure ML)
│ │ ├── evaluation/ ← escenarios canónicos + evaluación post-training
│ │ ├── api/ ← FastAPI (middlewares, deps, routers, persistence)
│ │ ├── web/ ← UI HTMX (ui.py + templates/)
│ │ └── main.py ← create_app(): ensambla la app
│ ├── Dockerfile · requirements.txt
├── agents/incident_analyzer/ ← el agente de ejemplo (YAMLs + escenarios .txt)
├── policies/default/ ← la política de guardrails de ejemplo
├── datasets/ · models/ ← datasets y modelos base versionados (YAML)
├── data/ ← estado runtime (gitignored)
├── tests/ ← pytest: tests/unit/ y tests/integration/
├── docs/ ← este documento, manual_qa.md, futuro.md, superpowers/
├── docs/ ← este documento, componentes.md, futuro.md, product-voice.md
├── docker-compose.yml ← levanta core (API + UI HTMX en puerto 8000)
├── Makefile ← install / test / test-all / lint / smoke / up / down
├── README.md · ARCHITECTURE.md
@@ -531,7 +530,8 @@ Forja no usa una sola base de datos; usa la herramienta adecuada para cada cosa.
3. `runtime/nodes.py` y `runtime/graph.py` — el flujo de ejecución.
4. `api/executions.py` — cómo se expone (invoke / approve / reject).
5. `tests/integration/test_invoke_hitl.py` — el ciclo completo, en ~40 líneas.
6. Levanta `docker compose up` y haz clic por las cinco páginas del dashboard.
6. Levanta `docker compose up` y recorre la página única de arriba abajo:
los 6 clicks de la demo del README cubren el ciclo completo.
---
@@ -540,4 +540,4 @@ Forja no usa una sola base de datos; usa la herramienta adecuada para cada cosa.
Es un MVP. La detección de PII más fina (modelos grandes, español), autenticación,
OpenTelemetry, multi-tenant, persistencia en Postgres, evaluadores LLM-as-judge,
una cola de aprobaciones con SLA... están en el roadmap: [`docs/futuro.md`](futuro.md).
El estado actual y cómo verificarlo: [`README.md`](../README.md) y [`docs/manual_qa.md`](manual_qa.md).
El estado actual y cómo verificarlo: [`README.md`](../README.md).
-124
View File
@@ -1,124 +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 — Estado Actual</title>
<script src="https://cdn.tailwindcss.com"></script>
<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 {
--forge-bg: #1c1c1c;
--forge-surface: #151515;
--forge-surface2: #242424;
--forge-iron: #333333;
--forge-ember: #f97316;
--forge-gold: #fcd34d;
--forge-text: #e7e5e4;
--forge-muted: #78716c;
}
body {
font-family: 'Inter', system_ui, sans-serif;
background-color: var(--forge-bg);
color: var(--forge-text);
}
.font-display {
font-family: 'Space Grotesk', 'Inter', sans-serif;
font-weight: 600;
}
.station-card {
background-color: var(--forge-surface);
border: 2px solid var(--forge-iron);
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
.section-header {
font-family: 'Space Grotesk', 'Inter', sans-serif;
letter-spacing: -0.025em;
}
</style>
</head>
<body class="min-h-screen">
<div class="max-w-4xl mx-auto px-6 py-12">
<!-- Header -->
<div class="flex items-center gap-3 mb-8">
<span class="text-4xl">⚒︎</span>
<div>
<h1 class="text-4xl font-semibold tracking-tighter">Forja</h1>
<p class="text-forge-muted">Estado actual del proyecto (Gamificado)</p>
</div>
</div>
<div class="mb-10">
<div class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-forge-surface border border-forge-iron text-sm">
<span class="w-2 h-2 bg-forge-ember rounded-full animate-pulse"></span>
<span class="font-medium">Versión actual: 3 estaciones</span>
</div>
</div>
<!-- Resumen del estado actual -->
<div class="mb-12">
<h2 class="text-2xl font-semibold tracking-tight mb-4 section-header">Resumen del Estado Actual</h2>
<div class="prose prose-invert max-w-none text-forge-muted">
<p class="text-lg">
El proyecto ha sido simplificado a <strong>3 estaciones principales</strong>, siguiendo una estructura clara y lógica:
</p>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 my-6">
<!-- Station 1 -->
<div class="station-card rounded-2xl p-5">
<div class="text-forge-ember text-xs tracking-widest font-semibold mb-1">STATION I</div>
<div class="text-xl font-semibold mb-2">The Hearth</div>
<div class="text-sm">Definición de agentes (Arsenal)</div>
</div>
<!-- Station 2 -->
<div class="station-card rounded-2xl p-5">
<div class="text-forge-ember text-xs tracking-widest font-semibold mb-1">STATION II</div>
<div class="text-xl font-semibold mb-2">The Crucible</div>
<div class="text-sm">Pruebas, guardrails y Human-in-the-Loop (Agrupación de Anvil + Judgment + Armory)</div>
</div>
<!-- Station 3 -->
<div class="station-card rounded-2xl p-5">
<div class="text-forge-ember text-xs tracking-widest font-semibold mb-1">STATION III</div>
<div class="text-xl font-semibold mb-2">The Battle</div>
<div class="text-sm">Despliegue de agentes forjados (Azure, AWS, GCP, On-prem)</div>
</div>
</div>
<h3 class="text-xl font-semibold tracking-tight mt-8 mb-3">Cambios principales realizados:</h3>
<ul class="space-y-2 text-sm">
<li>✓ Reducción de 5 estaciones a <strong>3 estaciones</strong> bien definidas.</li>
<li>✓ Eliminación de etiquetas redundantes ("STATION I", "STATION II"...).</li>
<li>✓ Títulos de estaciones ahora en naranja (ember) y tamaño grande.</li>
<li>✓ Texto guía superior en blanco y tamaño grande (text-3xl).</li>
<li>✓ Recuperación del <strong>Forge Creed</strong> original como elemento inspirador.</li>
<li>✓ Eliminación de los enlaces "Go to the..." de las tarjetas para mayor limpieza.</li>
<li>✓ Botón flotante de Tutorial simplificado y centrado.</li>
<li>✓ Navbar con slogan: <strong>"Fire • Hammer • Judgment"</strong></li>
<li>✓ The Armory ya no es una estación principal (se integra conceptualmente dentro de The Crucible).</li>
</ul>
</div>
</div>
<div class="border-t border-forge-iron pt-8 text-sm text-forge-muted">
<p><strong>Próximos pasos sugeridos:</strong></p>
<ul class="mt-2 space-y-1 text-xs">
<li>• Mejorar la página de The Battle (/deploy) con más contenido real.</li>
<li>• Conectar visualmente The Crucible con las páginas de ejecución y aprobaciones.</li>
<li>• Añadir feedback visual de "éxito" cuando un agente se forja correctamente.</li>
<li>• Posiblemente añadir un estado de "agentes desplegados" en The Battle.</li>
</ul>
</div>
</div>
</body>
</html>
+20 -5
View File
@@ -1,15 +1,30 @@
# Roadmap (post-MVP)
- **NeMo Guardrails full**: configuración Colang con KB embedding + dialog rails.
- **Autenticación**: OAuth2/OIDC con MSAL para entornos Azure AD.
- **Training real**: conectar los jobs de Azure ML a scripts de fine-tuning
reales sobre los datasets registrados; regression LLM-as-judge en CI.
- **NeMo Guardrails full**: configuración Colang con KB embedding + dialog
rails (reintroducir `nemoguardrails` en requirements al integrarlo; hoy el
engine es un stub por keywords configurable vía
`GUARDRAILS_NEMO_ALLOWED_KEYWORDS`).
- **Autenticación**: OAuth2/OIDC con MSAL para entornos Azure AD. Hoy los
endpoints de approve/promote no exigen identidad (el "quién" es declarativo).
- **OpenTelemetry**: spans por nodo del grafo, métricas de violaciones por validador.
- **Multi-tenant**: `tenant_id` aislado por registry, política y datos.
- **Evaluadores LLM-as-judge**: regression suite que ejecuta cada agente
sobre escenarios canónicos y mide deriva.
- **Evaluadores LLM-as-judge**: complementar la evaluación por escenarios
(ya implementada como gate de promoción) con un juez LLM que mida deriva
de calidad, no solo violaciones de guardrails.
- **UI de aprobación con SLA**: cola Kanban, reasignación entre operadores,
alertas cuando un HITL rebasa SLA.
- **Persistencia migrable a Postgres + pgvector**: requerido para alta
disponibilidad multi-instance.
- **Esquema de prompts mejorado**: variables tipadas en system_prompt
(Jinja-like), valores por entorno.
- **Promoción explícita draft → active** vía PR/aprobación.
- **Deployments**: promoción de agentes validados a endpoints gestionados
(Azure/AWS/GCP/on-prem) conservando políticas y auditoría.
## Hecho (antes en este roadmap)
- ~~Promoción explícita draft → active vía aprobación~~ — implementada:
`/api/promotions` + página Promotions con gate de evaluación por versión.
- ~~Fallback LLM~~ — implementado (`LLM_FALLBACK_PROVIDER` +
`FallbackLLMProvider`).
+51
View File
@@ -0,0 +1,51 @@
# Product voice (Forja UI)
All **user-visible** strings in the web UI, API error messages shown in the UI, and **new code** (identifiers, comments, docstrings, logs) must be in **English**.
Agent YAML content (`purpose`, `system_prompt`, examples) may use any language required by the domain.
## Tone
- Operational and precise, like an internal governance console.
- Short labels; full sentences only where explanation helps.
- No role-play, lore, or game mechanics.
## Navigation: one page, six stages
The UI is a single page (`/`) organised as the lifecycle stages. The header nav
contains anchors to each stage; legacy multi-page routes 301-redirect to them.
| Stage | Anchor | Content |
|-------|--------|---------|
| 01 Define | `#define` | Agents (all versions + states) and guardrail policies |
| 02 Run | `#run` | Demo button + free-form governed execution |
| 03 Approve | `#approve` | HITL queue for paused runs |
| 04 Train & evaluate | `#train` | Training runs, evaluation, promotion request |
| 05 Promote | `#promote` | Governance promotion queue |
| 06 Audit | `#audit` | Completed runs |
## Execution status labels
| Internal status | UI label |
|-----------------|----------|
| `running` | Running |
| `awaiting_approval` | Pending approval |
| `completed` | Completed |
| `failed` | Failed |
| `blocked_by_guardrail` | Blocked by policy |
## Forbidden terms (UI copy)
Do not use these in templates, buttons, or dynamic HTML from `web/ui.py`:
- blade, steel, strike, anvil, forge (as verb), temper, tempered, forging
- judgment chamber, bless, shatter, arsenal (as place name)
- hearth, crucible, battle (as station names)
- FORGE MASTER, level, heat, XP, station I/II/III
- Fire • Hammer • Judgment and similar slogans
The product name **Forja** is allowed in the header and documentation.
## Development
Follow [`karpathy.md`](../karpathy.md): think before coding, simplicity first, surgical changes, verifiable success criteria (`make test`, `make lint`).
+220
View File
@@ -0,0 +1,220 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Forja — Project Progress</title>
<style>
:root {
--bg: #1c1c1c;
--surface: #151515;
--border: #333;
--text: #e7e5e4;
--muted: #78716c;
--accent: #f97316;
--ok: #34d399;
--warn: #fbbf24;
}
* { box-sizing: border-box; }
body {
font-family: ui-sans-serif, system-ui, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.6;
margin: 0;
padding: 2rem 1.5rem 4rem;
}
.wrap { max-width: 820px; margin: 0 auto; }
h1 { font-size: 2rem; font-weight: 600; margin: 0 0 0.25rem; letter-spacing: -0.02em; }
.subtitle { color: var(--muted); font-size: 0.95rem; margin-bottom: 2rem; }
.badge {
display: inline-block;
font-size: 0.7rem;
text-transform: uppercase;
letter-spacing: 0.08em;
padding: 0.2rem 0.5rem;
border-radius: 4px;
background: var(--surface);
border: 1px solid var(--border);
color: var(--accent);
margin-bottom: 1.5rem;
}
h2 {
font-size: 1.15rem;
margin: 2rem 0 0.75rem;
padding-bottom: 0.35rem;
border-bottom: 1px solid var(--border);
}
h3 { font-size: 0.95rem; color: var(--accent); margin: 1.25rem 0 0.5rem; }
p, li { color: var(--muted); font-size: 0.9rem; }
li { margin: 0.35rem 0; }
ul { padding-left: 1.25rem; }
.card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
padding: 1.25rem 1.5rem;
margin: 1rem 0;
}
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 0.75rem; margin: 1rem 0; }
.tile {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 10px;
padding: 1rem;
}
.tile strong { display: block; color: var(--text); font-size: 0.9rem; margin-bottom: 0.25rem; }
.tile span { font-size: 0.8rem; color: var(--muted); }
code, pre {
font-family: ui-monospace, monospace;
font-size: 0.8rem;
background: #0a0a0a;
border: 1px solid var(--border);
border-radius: 6px;
}
code { padding: 0.15rem 0.4rem; color: #fcd34d; }
pre {
padding: 1rem;
overflow-x: auto;
color: var(--text);
margin: 0.75rem 0;
}
a { color: var(--accent); }
.done { color: var(--ok); }
.todo { color: var(--warn); }
table { width: 100%; border-collapse: collapse; font-size: 0.85rem; margin: 0.75rem 0; }
th, td { text-align: left; padding: 0.5rem 0.6rem; border-bottom: 1px solid var(--border); }
th { color: var(--muted); font-weight: 500; }
td { color: var(--text); }
footer { margin-top: 3rem; font-size: 0.75rem; color: var(--muted); }
</style>
</head>
<body>
<div class="wrap">
<p class="badge">Status snapshot · 2 June 2026</p>
<h1>Forja</h1>
<p class="subtitle">Governed control plane for LLM agents and models — registry, guardrails, HITL, training orchestration, and promotion gates.</p>
<h2>Product direction</h2>
<p>
Forja is evolving from an <strong>agent governance runtime</strong> into a full <strong>model lifecycle forge</strong>:
define agents and policies (Studio), validate with guardrails and human review (Runs, Approvals),
orchestrate external fine-tuning (Azure ML), evaluate on canonical scenarios, and promote draft versions to production (Promotions).
</p>
<div class="grid">
<div class="tile"><strong>Studio</strong><span>Agents, policies, datasets, base models (YAML)</span></div>
<div class="tile"><strong>Validation</strong><span>Runs, guardrails, runtime HITL</span></div>
<div class="tile"><strong>Training</strong><span>Azure ML SDK or mock/stub backends</span></div>
<div class="tile"><strong>Governance</strong><span>Post-train eval gate + promotion HITL</span></div>
</div>
<h2>Completed in this iteration</h2>
<h3 class="done">Phase 0 — Professional UI (English)</h3>
<ul>
<li>Removed gamified copy (Hearth, Anvil, blades, levels, etc.)</li>
<li>Navigation: Agents, Runs, Approvals, Promotions, Run history, Deployments</li>
<li><code>docs/product-voice.md</code> and <code>karpathy.md</code> referenced from README</li>
<li>Terminal runs from the UI persist to <code>data/executions.jsonl</code></li>
</ul>
<h3 class="done">Phase A — MLOps orchestration (foundation)</h3>
<ul>
<li>Versioned <code>datasets/</code> and <code>models/</code> registries</li>
<li><code>TrainingBackend</code> strategy: <code>mock</code> and <code>azure_ml</code></li>
<li>REST: <code>/api/datasets</code>, <code>/api/models</code>, <code>/api/training/runs</code></li>
<li>Training runs stored under <code>data/training_runs/</code></li>
</ul>
<h3 class="done">Phase B — Eval gate and governance promotion</h3>
<ul>
<li>Post-train evaluation over <code>agents/&lt;name&gt;/examples/*.txt</code></li>
<li><code>POST /api/training/runs/{id}/evaluate</code> — blocks promotion if guardrails fail</li>
<li><code>/api/promotions</code> — request, approve, reject; activates agent version in registry</li>
<li>HTMX page <a href="http://localhost:8000/promotions">/promotions</a> for the governance queue</li>
</ul>
<h3 class="done">Azure ML SDK integration</h3>
<ul>
<li><code>azure-ai-ml</code> + <code>azure-identity</code> (<code>DefaultAzureCredential</code>)</li>
<li>Submits command jobs when <code>AZURE_ML_*</code> workspace settings are set</li>
<li>Falls back to <strong>stub mode</strong> locally when workspace is not configured</li>
<li>Placeholder script: <code>core/src/forja_core/training/_azure_job_bundle/train.py</code></li>
</ul>
<h2>Already in place (MVP core)</h2>
<ul>
<li>LangGraph runtime with SQLite checkpoints (HITL survives restarts)</li>
<li>Guardrails AI engine + policy YAML</li>
<li>Agent registry with Git-like versioning</li>
<li>REST API under <code>/api</code> and HTMX UI on port 8000</li>
</ul>
<h2>How to run the project</h2>
<h3>Option 1 — Docker (recommended)</h3>
<pre>cp .env.example .env
docker compose up</pre>
<p>Open <a href="http://localhost:8000">http://localhost:8000</a>. Default <code>LLM_PROVIDER=mock</code> works without API keys.</p>
<h3>Option 2 — Local Python</h3>
<pre>cp .env.example .env
python -m venv venv
source venv/bin/activate
pip install -r core/requirements.txt
pip install pytest pytest-asyncio ruff mypy freezegun
cd core/src
uvicorn forja_core.main:app --reload --host 0.0.0.0 --port 8000</pre>
<p>Run from repo root with <code>PYTHONPATH=core/src</code> if needed:</p>
<pre>PYTHONPATH=core/src uvicorn forja_core.main:app --reload --port 8000</pre>
<h3>Tests</h3>
<pre>source venv/bin/activate
./venv/bin/pytest tests/unit -q # unit
./venv/bin/pytest tests/integration -q # integration (slower)
make lint # ruff + mypy (if tools on PATH)</pre>
<h3>Quick demo (3 steps)</h3>
<ol>
<li><strong>Agents</strong><a href="http://localhost:8000/agents">/agents</a> → inspect <code>incident_analyzer</code></li>
<li><strong>Runs</strong><a href="http://localhost:8000/run">/run</a> → scenario <code>01_sip_registration_drop</code> (may trigger Approvals)</li>
<li><strong>Approvals / Promotions</strong><a href="http://localhost:8000/approvals">/approvals</a> and <a href="http://localhost:8000/promotions">/promotions</a></li>
</ol>
<h2>Key API endpoints</h2>
<table>
<thead><tr><th>Area</th><th>Endpoint</th></tr></thead>
<tbody>
<tr><td>Health</td><td><code>GET /health</code></td></tr>
<tr><td>Invoke agent</td><td><code>POST /api/agents/{name}/invoke</code></td></tr>
<tr><td>Training run</td><td><code>POST /api/training/runs</code></td></tr>
<tr><td>Post-train eval</td><td><code>POST /api/training/runs/{id}/evaluate</code></td></tr>
<tr><td>Promotion</td><td><code>POST /api/promotions</code><code>.../approve</code></td></tr>
</tbody>
</table>
<h2>Next steps (when you resume)</h2>
<ul>
<li class="todo">Wire Forja datasets to Azure ML data assets (not only env vars)</li>
<li class="todo">Replace placeholder <code>train.py</code> with real fine-tuning script</li>
<li class="todo">Multi-tenant, OIDC, Postgres (enterprise roadmap)</li>
<li class="todo">LLM-as-judge regression suite in CI</li>
</ul>
<h2>Documentation</h2>
<ul>
<li><a href="../README.md">README.md</a> — quickstart</li>
<li><a href="../ARCHITECTURE.md">ARCHITECTURE.md</a> — technical decisions</li>
<li><a href="product-voice.md">docs/product-voice.md</a> — UI language rules</li>
<li><a href="../karpathy.md">karpathy.md</a> — development guidelines</li>
<li><a href="futuro.md">docs/futuro.md</a> — roadmap</li>
</ul>
<footer>
Generated for end-of-session handoff. Open this file in a browser: <code>docs/project-progress.html</code>
</footer>
</div>
</body>
</html>