pagina unica
This commit is contained in:
+20
-12
@@ -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
@@ -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).
|
||||
|
||||
@@ -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&family=Space+Grotesk:wght@500;600&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
@@ -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`).
|
||||
|
||||
@@ -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`).
|
||||
@@ -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/<name>/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>
|
||||
Reference in New Issue
Block a user