# AgentForge — Documento de diseño **Fecha:** 2026-05-09 **Estado:** aprobado — listo para plan de implementación **Autor:** Juan **Revisión:** v1.0 --- ## 1. Resumen ejecutivo **AgentForge** es una plataforma profesional de **gobernanza de agentes IA** orientada a entornos de producción. Cubre el ciclo de vida completo: catalogación, versionado de prompts y políticas, validación con guardrails en runtime, ejecución stateful con checkpointing, Human-in-the-Loop nativo, y trazabilidad end-to-end. La plataforma se compone de dos servicios: - **`agentforge-core`** — API REST (FastAPI) que materializa el dominio de gobierno: registry, versionado, ejecución de agentes vía LangGraph, validación de guardrails y persistencia. - **`agentforge-dashboard`** — UI ejecutiva (Streamlit) cliente del core; nunca habla con LLMs ni guardrails directamente. Una imagen Docker por servicio, orquestadas con `docker-compose`. Arranque sin claves API (proveedor LLM mock por defecto). ## 2. Problema y motivación Poner agentes IA en producción sin una capa de gobernanza produce sistemas opacos: - Prompts que cambian en caliente sin historial. - Validaciones inconsistentes según quién despliega. - Acciones de alto impacto (rollbacks, cambios de configuración, ejecución de comandos) propuestas sin supervisión humana ni rollback plan. - Decisiones del agente no auditables — imposible reconstruir por qué se tomó una acción concreta. AgentForge define el plano de control mínimo que un equipo de plataforma necesita antes de operar agentes con impacto real. El proyecto no resuelve la inteligencia del agente; resuelve su **operabilidad y gobierno**. ## 3. Decisiones de diseño (resumen) | Eje | Decisión | Justificación | |---|---|---| | Topología | Two-tier: FastAPI core + Streamlit dashboard | Separación de responsabilidades; el core es API-first y consumible por terceros (RPA, n8n, otros agentes). Patrón de plataformas reales. | | LLM | Interfaz `LLMProvider` con `mock` (default), `azure`, `openai` | Demo arranca sin secrets. Demuestra portabilidad de proveedor. | | Guardrails | Interfaz `GuardrailEngine` con `GuardrailsAI` (default) y `NeMo` (opt-in vía flag) | Lo mejor de ambos mundos: arquitectura extensible visible + imagen ligera por defecto. | | Persistencia | Mezcla por capa: JSON (registry, definiciones), JSONL (logs append-only), YAML (versiones), SQLite (checkpoints LangGraph) | Cada capa con la herramienta correcta. Definiciones humanas en YAML; estado runtime en formatos legibles por máquina. | | Versionado | Simulación tipo Git en sistema de ficheros: `agents//versions/v1.yaml` + `index.yaml` con hashes y mensajes | No requiere git real; reproduce la disciplina de versionado y diff. | | Estado | LangGraph con `SqliteSaver` + `interrupt()` dinámico para HITL | Persistencia entre reinicios crítica para esperas humanas. | | Auth | Ninguna en MVP; placeholder hook `Depends(get_current_user)` | Pluggable a OAuth2/OIDC/MSAL en futuro. | | Comentarios | Español | Coherente con el autor. | ## 4. Arquitectura ``` ┌───────────────────────── docker-compose ──────────────────────────┐ │ │ │ ┌─────────────────────┐ HTTP/JSON ┌────────────────────┐ │ │ │ agentforge-dashboard│ ────────────────► │ agentforge-core │ │ │ │ Streamlit :8501 │ ◄──────────────── │ FastAPI :8000 │ │ │ └─────────────────────┘ └─────────┬──────────┘ │ │ │ │ │ ┌────────────────────────────────────────┼──────────┐ │ │ ▼ ▼ ▼ ▼ │ │ LLM layer Guardrail layer Runtime State│ │ ┌──────────────────┐ ┌────────────────────┐ ┌─────────┐ ┌───┐│ │ │ LLMProvider (P) │ │ GuardrailEngine(P) │ │LangGraph│ │JSN││ │ │ ├ MockProvider │ │ ├ GuardrailsAIEng │ │ + HITL │ │SQL││ │ │ ├ AzureOpenAI │ │ ├ NeMoEngine (opt) │ │ + ckpt │ │JNL││ │ │ └ OpenAI │ │ └ CompositeEngine │ └─────────┘ └───┘│ │ └──────────────────┘ └────────────────────┘ │ │ │ │ Strategy pattern con Pydantic v2 + Protocol typing │ └────────────────────────────────────────────────────────────────────┘ ``` **Principios:** - **API-first:** el core es invocable por cualquier cliente HTTP; el dashboard es un consumidor sustituible. - **Configuration over code:** definiciones de agentes y políticas en YAML editables sin redeploy. - **Strategy/Plugin:** todas las dependencias externas (LLM, guardrails, registry) detrás de `Protocol`. - **Fail-closed por defecto:** errores en validadores se traducen a violación que bloquea la ejecución. - **Trazabilidad obligatoria:** cada ejecución tiene un `trace_id` UUID propagado por logs, API, dashboard y persistencia. ## 5. Estructura de repositorio ``` agentforge/ ├── README.md ├── ARCHITECTURE.md ├── docker-compose.yml ├── .env.example ├── .gitignore ├── Makefile # up / down / test / lint / format / smoke ├── pyproject.toml # ruff + mypy + pytest config ├── core/ │ ├── Dockerfile │ ├── requirements.txt │ └── src/agentforge_core/ │ ├── main.py # FastAPI app + middlewares │ ├── config.py # Pydantic Settings, .env │ ├── api/ │ │ ├── agents.py │ │ ├── executions.py │ │ ├── policies.py │ │ └── violations.py │ ├── domain/ # modelos Pydantic v2 │ │ ├── agent.py │ │ ├── execution.py │ │ ├── guardrail.py │ │ └── policy.py │ ├── registry/ │ │ ├── repository.py │ │ └── versioning.py │ ├── llm/ │ │ ├── base.py # Protocol LLMProvider │ │ ├── mock.py │ │ ├── azure.py │ │ ├── openai.py │ │ └── factory.py │ ├── guardrails/ │ │ ├── base.py # Protocol GuardrailEngine │ │ ├── guardrails_ai.py │ │ ├── nemo.py │ │ ├── composite.py │ │ └── factory.py │ ├── runtime/ │ │ ├── graph.py # build_graph(agent_def, policy) │ │ ├── state.py # AgentState (TypedDict) │ │ ├── nodes.py # cada nodo del grafo │ │ └── checkpointer.py # SqliteSaver wrapper │ └── observability/ │ └── logging.py # structlog + trace_id propagation ├── dashboard/ │ ├── Dockerfile │ ├── requirements.txt │ └── src/agentforge_dashboard/ │ ├── app.py │ ├── client.py # httpx client tipado al core │ ├── pages/ │ │ ├── 1_🏛️_Registro.py │ │ ├── 2_▶️_Ejecutar.py │ │ ├── 3_🤝_Aprobaciones.py │ │ ├── 4_📜_Historial.py │ │ └── 5_📐_Politicas.py │ └── components/ │ ├── trace_view.py │ ├── violation_view.py │ └── diff_view.py ├── agents/ │ └── incident_analyzer/ │ ├── index.yaml # versiones disponibles + metadata │ ├── versions/ │ │ ├── v1.yaml │ │ └── v2.yaml │ └── examples/ │ ├── 01_sip_registration_drop.txt │ ├── 02_mos_degradation_pool_sbc.txt │ └── 03_hss_capacity_active_active.txt ├── policies/ │ └── default/ │ ├── index.yaml │ └── versions/ │ └── v1.yaml ├── data/ # gitignored │ ├── registry.json │ ├── checkpoints.sqlite │ ├── violations.jsonl │ └── executions.jsonl ├── docs/ │ ├── architecture.md │ ├── futuro.md │ ├── manual_qa.md │ └── superpowers/specs/ │ └── 2026-05-09-agentforge-design.md └── tests/ ├── unit/ └── integration/ ``` **Notas:** - Pydantic models residen en `domain/`, no en `api/`. Los routers solo serializan. - Las factories (`llm/factory.py`, `guardrails/factory.py`) son la frontera donde el `.env` decide la implementación. - YAML para definiciones humanas (legibles, comentables, mejor diff). JSON/JSONL/SQLite para estado runtime. - `__init__.py` mínimos, sin re-exports masivos. ## 6. Modelos de dominio ```python class LLMConfig(BaseModel): provider: Literal["mock", "azure", "openai"] = "mock" model: str = "gpt-4o" temperature: float = 0.2 max_tokens: int = 2000 class AgentVersionMeta(BaseModel): id: str # ej. "v2" hash: str # SHA-256 del YAML normalizado author: str message: str created_at: datetime class AgentDefinition(BaseModel): name: str version: str # semver o vN owner: str purpose: str state: Literal["draft", "active", "deprecated"] guardrails: list[str] # nombres de policies llm: LLMConfig system_prompt: str output_schema: dict # JSON Schema del output esperado risk_threshold_for_hitl: int = 4 # risk_score ≥ X exige aprobación humana updated_at: datetime class GuardrailViolation(BaseModel): trace_id: UUID timestamp: datetime stage: Literal["input", "output"] validator: str # ej. "DetectPII" severity: Literal["info", "warning", "block"] message: str blocked: bool # True si detuvo la ejecución class ProposedAction(BaseModel): id: str # generado, único dentro de la ejecución action: str # ej. "rollback_image" target: str # ej. "cscf-cluster-aravaca-01" risk_score: int = Field(ge=1, le=5) rollback_plan: str requires_approval: bool class DecisionStep(BaseModel): step: str # validate_input | llm_reason | ... timestamp: datetime duration_ms: int detail: dict # libre por nodo class AgentExecution(BaseModel): trace_id: UUID agent_name: str agent_version: str status: Literal[ "running", "awaiting_approval", "blocked_by_guardrail", "completed", "failed", ] started_at: datetime finished_at: datetime | None decision_path: list[DecisionStep] violations: list[GuardrailViolation] proposed_actions: list[ProposedAction] needs_human_for: list[ProposedAction] | None final_output: dict | None error: str | None class AgentExecutionSummary(BaseModel): """Versión ligera para listados (sin decision_path ni violations completas).""" trace_id: UUID agent_name: str agent_version: str status: str started_at: datetime finished_at: datetime | None n_violations: int n_proposed_actions: int class PolicyValidator(BaseModel): type: str # "detect_pii", "prompt_injection", ... config: dict # parámetros del validador class PolicyDefinition(BaseModel): name: str version: str description: str input_validators: list[PolicyValidator] output_validators: list[PolicyValidator] on_validator_error: Literal["fail_open", "fail_closed"] = "fail_closed" ``` ## 7. API REST ``` GET /health GET /agents → list[AgentDefinition] GET /agents/{name} → AgentDefinition (versión activa) GET /agents/{name}/versions → list[AgentVersionMeta] GET /agents/{name}/versions/{v} → AgentDefinition (versión concreta) GET /agents/{name}/versions/{v}/diff/{w} → DiffResult (unified diff) POST /agents/{name}/invoke → AgentExecution body: {"input": str, "version": str?} GET /executions → list[AgentExecutionSummary] GET /executions/{trace_id} → AgentExecution POST /executions/{trace_id}/approve → AgentExecution body: {"approved_action_ids": list[str], "comment": str?} POST /executions/{trace_id}/reject → AgentExecution body: {"reason": str} GET /violations → list[GuardrailViolation] query: trace_id?, severity?, since? GET /policies → list[PolicyDefinition] GET /policies/{name}/versions → list[PolicyVersionMeta] ``` Códigos de error relevantes: - `404` agente o ejecución no existe - `409` `/approve` o `/reject` sobre ejecución no en `awaiting_approval` - `422` body inválido (Pydantic) - `500` fallo interno (checkpoint corrupto, configuración inválida) Todas las respuestas incluyen header `X-Trace-Id` (eco del request o generado). ## 8. Interfaces clave (Strategy) ```python class Message(BaseModel): role: Literal["system", "user", "assistant"] content: str class CompletionResult(BaseModel): content: str model: str tokens_in: int tokens_out: int latency_ms: int class LLMProvider(Protocol): name: str async def complete( self, messages: list[Message], schema: dict | None = None, temperature: float = 0.2, max_tokens: int = 2000, ) -> CompletionResult: ... class GuardrailEngine(Protocol): name: str async def validate_input( self, payload: str, policy: PolicyDefinition, trace_id: UUID ) -> list[GuardrailViolation]: ... async def validate_output( self, payload: dict, policy: PolicyDefinition, trace_id: UUID ) -> list[GuardrailViolation]: ... class AgentRegistry(Protocol): def list_agents(self) -> list[AgentDefinition]: ... def get_agent(self, name: str, version: str | None = None) -> AgentDefinition: ... def list_versions(self, name: str) -> list[AgentVersionMeta]: ... def get_version(self, name: str, version: str) -> AgentDefinition: ... def diff_versions(self, name: str, v1: str, v2: str) -> DiffResult: ... def upsert_version( self, name: str, body: AgentDefinition, message: str, author: str ) -> AgentVersionMeta: ... class PolicyStore(Protocol): def list_policies(self) -> list[PolicyDefinition]: ... def get_policy(self, name: str, version: str | None = None) -> PolicyDefinition: ... def list_versions(self, name: str) -> list[PolicyVersionMeta]: ... ``` ## 9. Flujo de ejecución (LangGraph) **Topología del grafo (común para todos los agentes; varía la `AgentDefinition` y `Policy`):** ``` ┌──────────────┐ │ validate_in │── violations.severity=block ──► [STATUS: blocked_by_guardrail] ─► END └──────┬───────┘ │ pass ▼ ┌──────────────┐ │ llm_reason │── 3xretry → fallback → giveup ─► [STATUS: failed] ─► END └──────┬───────┘ │ ok ▼ ┌──────────────┐ │ validate_out │── violations.severity=block ──► [STATUS: blocked_by_guardrail] ─► END └──────┬───────┘ │ pass ▼ ┌──────────────────┐ │ propose_actions │ └──────┬───────────┘ ▼ ┌──────────────────┐ ─ any(risk≥threshold or requires_approval) ─┐ │ approve_gate │ │ │ (dyn. interrupt)│ ─ todas seguras ──┐ │ └──────────────────┘ │ │ ▼ ▼ ┌──────────────┐ [interrupt(payload)] │ finalize │ status=awaiting_approval └──────┬───────┘ │ ▼ │ POST /approve [STATUS: completed] │ POST /reject ▼ [Command(resume={...}) → finalize → completed o status=failed, error="rejected_by_human"] ``` **Estado del grafo (TypedDict de LangGraph):** ```python class AgentState(TypedDict): trace_id: str agent_name: str agent_version: str user_input: str messages: list[dict] raw_llm_output: str | None parsed_output: dict | None proposed_actions: list[dict] violations: list[dict] decision_path: Annotated[list[dict], operator.add] # acumulativo status: str error: str | None human_decision: dict | None # rellena en /approve|/reject ``` `Annotated[..., operator.add]` permite que cada nodo añada pasos sin pisar lo previo (semántica nativa de LangGraph para reducers). **Checkpointing:** `SqliteSaver("data/checkpoints.sqlite")` con `thread_id = trace_id`. El estado se persiste tras cada nodo. Si el dashboard cierra durante un `awaiting_approval`, `GET /executions/{trace_id}` reconstruye el snapshot exacto. **Recorrido típico — `POST /agents/incident_analyzer/invoke`:** 1. API genera `trace_id` (UUID4); resuelve `AgentDefinition` vía `AgentRegistry`; resuelve `PolicyDefinition` referenciada en `agent_def.guardrails`; loga `execution.started` con `trace_id`. 2. `runtime.graph.build_graph(agent_def, policy)` compila el grafo: - Inyecta `LLMProvider` vía factory (override por `LLM_PROVIDER` env). - Inyecta `GuardrailEngine` vía factory (`Composite[GuardrailsAI]`, +`NeMo` si flag). - Checkpointer = `SqliteSaver`. El nodo `approve_gate` invoca dinámicamente `interrupt(payload)` (API de LangGraph ≥0.2) solo cuando hay acciones que requieren aprobación; si todas son seguras, transiciona a `finalize` sin pausa. Esto evita el `interrupt_before` estático y mantiene el flujo declarativo. 3. `graph.invoke({...}, config={"configurable":{"thread_id": trace_id}})` ejecuta hasta: - (a) END por guardrail block en `validate_input` o `validate_output`. - (b) END por LLM unrecoverable failure. - (c) INTERRUPT en `approve_gate` (HITL). - (d) END normal (`finalize`) si no hay acciones de alto riesgo. 4. API serializa `AgentExecution` y devuelve 200. Append a `data/executions.jsonl` y violaciones a `data/violations.jsonl`. 5. (caso HITL) Dashboard pinta cards con cada `ProposedAction` (action, target, badge `risk_score`, `rollback_plan`, botones `[Approve]`/`[Reject]`). Operador pulsa Approve. 6. Dashboard `POST /executions/{trace_id}/approve` con body `{"approved_action_ids": ["..."], "comment": "..."}`. 7. API: `graph.invoke(Command(resume={"human_decision": {...}}), config={"configurable":{"thread_id": trace_id}})`. Nodo `finalize` lee `state.human_decision`, compone `final_output`. `status="completed"`, `finished_at=now`. Append final a `executions.jsonl`. ## 10. Guardrails (capa runtime) **Estructura `policies/default/versions/v1.yaml`:** ```yaml name: default version: v1 description: Política base para agentes de operación de plataforma de voz input_validators: - type: detect_pii entities: [PERSON, EMAIL, PHONE_NUMBER, ES_NIF, IP_ADDRESS, IBAN_CODE] severity_on_match: block - type: prompt_injection severity_on_match: block - type: toxic_language threshold: 0.7 severity_on_match: warning - type: forbidden_topics topics: ["instrucciones de explotación", "credenciales", "código malicioso"] severity_on_match: block output_validators: - type: schema_match schema_ref: agent.output_schema # del agent_def en runtime severity_on_mismatch: block - type: pii_leakage severity_on_match: block - type: forbidden_action_keywords keywords: ["DROP TABLE", "rm -rf", "shutdown -h now", "delete production"] severity_on_match: block - type: telco_safety_rules # validador custom rules: - never_propose_action_targeting_production_without_rollback - never_propose_mass_action_without_canary on_validator_error: fail_closed ``` **Validadores (Guardrails AI engine):** - `DetectPII` — usa `presidio_analyzer` + entidades configuradas; `ES_NIF` como recognizer custom (regex documentada). - `PromptInjection` — heurística de patrones (lista mantenible) + clasificador ligero. En modo `mock` LLM, lista de strings exactos para reproducibilidad de tests. - `ToxicLanguage` — validator del Guardrails Hub. - `ForbiddenTopics` — substring matcher en MVP. `docs/futuro.md` documenta upgrade a similarity con embeddings. - `SchemaMatch` — Pydantic parse contra `agent.output_schema`. - `PIILeakage` — Presidio sobre output stringificado. - `ForbiddenActionKeywords` — substring matcher sobre acciones propuestas. - `TelcoSafetyRules` — validador custom; reglas declarativas evaluadas sobre `proposed_actions[]`. **Comportamiento del engine:** - Toda violación se persiste a `violations.jsonl` (incluso `info`/`warning`). - Solo `severity=block` con `blocked=True` detiene el grafo. - `on_validator_error` decide qué pasa cuando un validador lanza excepción (`fail_closed` por defecto). - Composite engine ejecuta sub-engines en paralelo y agrega resultados. **NeMo Guardrails (opt-in):** activado con `GUARDRAILS_NEMO_ENABLED=true`. Aporta topical rails declarados en Colang (`policies/default/nemo/rails.co`). MVP entrega configuración mínima (off-topic refusal); el flag por defecto está en `false` para mantener la imagen ligera. ## 11. Versionado de prompts y políticas (simulación tipo Git) **Estructura `agents//index.yaml`:** ```yaml name: incident_analyzer versions: - id: v1 hash: 7c9a4f... author: Juan message: "Versión inicial; cobertura básica de SIP/IMS" created_at: 2026-04-12T10:00:00Z - id: v2 hash: a1b2c3... author: Juan message: "Añade detección de codec mismatch; sube risk_threshold a 4" created_at: 2026-05-01T12:00:00Z active_version: v2 ``` **Operaciones:** - `Registry.upsert_version(name, body, message, author)` — calcula SHA-256 del YAML normalizado, valida formato, escribe `versions/vN.yaml`, actualiza `index.yaml`. - `Registry.diff_versions(name, v1, v2)` — `difflib.unified_diff` sobre los YAMLs. - `Registry.get_agent(name)` — devuelve la versión `active_version`. - Promoción de `draft` → `active` cambia `index.yaml.active_version`. **Estructura idéntica para `policies//`.** El dashboard ofrece: - Vista de "Versiones" del agente con autor, mensaje, hash, fecha. - Botón "Comparar con anterior" → render del diff con sintaxis coloreada. ## 12. Observabilidad - **Logging**: `structlog` con renderer JSON. Cada log incluye `trace_id`, `agent_name`, `agent_version`, `step`, `duration_ms` cuando aplica. - **Trace-id propagation**: middleware FastAPI inyecta `X-Trace-Id` (genera UUID4 si no viene). `structlog` lo bind-ea en context (`structlog.contextvars`). Dashboard incluye `X-Trace-Id` en approve/reject para correlar logs. - **Eventos persistidos**: - `data/executions.jsonl` — un append por ejecución completada/fallida (no por step). - `data/violations.jsonl` — un append por violación (cualquier severidad). - **Métricas/Tracing OTel**: fuera de MVP. Documentado en `docs/futuro.md`. ## 13. Manejo de errores | Tipo de fallo | Respuesta | |---|---| | Azure OpenAI 5xx / timeout | Retry exponencial (1s, 2s, 4s; 3 intentos). Si agota, fallback a `LLM_FALLBACK_PROVIDER` si está configurado. Si falla, `status=failed`, `error="llm_unavailable"`. La API devuelve `200` con la ejecución fallida (el dominio sí ha respondido). | | Guardrail validator excepción interna | Por defecto `fail_closed` → cuenta como `severity=block`, `blocked=True`. Configurable por policy. | | Pydantic validation falla en LLM output | 1 retry con prompt extendido `"Respond strictly with JSON matching this schema: ..."`. Si falla, `status=failed`, `error="output_schema_mismatch"`. | | Checkpoint SQLite corrupto | Log `error.checkpoint_corrupt`, devolver `500`, `error_class="checkpoint_unreadable"`. Sin auto-recovery (mejor falla explícita). | | Agente solicitado no existe | `404` con `{"detail":"agent 'X' not found"}`. | | `/approve` o `/reject` sobre estado no válido | `409` con `{"detail":"execution status 'X' does not allow approve"}`. | | Dashboard pierde conexión con core | `httpx` retry simple (2 intentos, 1s). Si sigue fallando, banner `st.error("Core API unreachable")`. | | Variable `.env` requerida ausente | `Pydantic Settings` falla en arranque con mensaje claro (no en runtime: el contenedor no debe estar "vivo y roto"). | ## 14. Testing ``` tests/ ├── unit/ │ ├── test_llm_mock.py │ ├── test_guardrails_ai.py │ ├── test_registry.py │ ├── test_runtime_state.py │ └── test_policy_loader.py ├── integration/ │ ├── test_invoke_happy_path.py │ ├── test_invoke_hitl.py │ ├── test_invoke_pii_block.py │ └── test_invoke_resume_after_restart.py └── fixtures/ ├── agents/incident_analyzer_test.yaml ├── policies/test_policy.yaml └── inputs/ ``` - **Stack**: `pytest` + `pytest-asyncio` + `httpx.AsyncClient` (FastAPI TestClient) + `freezegun`. - **MockProvider** con lookup determinista (`hash(input) → respuesta`). - **Sin tests Streamlit** en MVP. Smoke manual en `docs/manual_qa.md`. - **CI** (`.github/workflows/ci.yml`): `ruff check` + `ruff format --check` + `mypy` + `pytest`. - **Cobertura objetivo** ~70% sobre `core/src/agentforge_core`. ## 15. Despliegue - `docker-compose.yml` con dos servicios: `core` (uvicorn, `:8000`) y `dashboard` (Streamlit, `:8501`). - `Dockerfile.core` y `Dockerfile.dashboard` separados; cada uno con sus deps mínimas. - Healthchecks: `core` `GET /health`; `dashboard` `GET /` (Streamlit responde 200 cuando el server arranca). - Volume mount `./data:/app/data` y `./agents:/app/agents:ro`, `./policies:/app/policies:ro`. - `.env.example` con todas las variables documentadas y comentadas. - `LLM_PROVIDER=mock` por defecto → arranque sin claves. - Variables relevantes: `LLM_PROVIDER`, `LLM_FALLBACK_PROVIDER`, `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY`, `AZURE_OPENAI_DEPLOYMENT`, `OPENAI_API_KEY`, `GUARDRAILS_NEMO_ENABLED`, `LOG_LEVEL`, `DATA_DIR`. ## 16. README — estructura 1. **Qué es AgentForge** — Plataforma de gobernanza de agentes IA: catalogación, versionado de prompts y políticas, guardrails runtime, ejecución stateful con HITL, observabilidad. 2. **Problema que resuelve** — Sin gobernanza, los agentes en producción son cajas negras (ver §2). 3. **Diagrama de arquitectura** — Mermaid en línea + PNG renderizado en `docs/`. 4. **Quickstart**: `cp .env.example .env && docker compose up`. Funciona out-of-the-box (`LLM_PROVIDER=mock`). 5. **Demo guiada en 3 pasos**: 1) registro; 2) ejecuta `incident_analyzer` con `02_mos_degradation_pool_sbc.txt`; 3) aprueba el rollback en HITL. 6. **Capacidades implementadas** — tabla `feature → ubicación en código`. 7. **Roadmap** — link a `docs/futuro.md`. ## 17. Roadmap (`docs/futuro.md`) - NeMo Guardrails con configuración rica (Colang KB). - Autenticación: OAuth2/OIDC con MSAL para entornos Azure AD. - OpenTelemetry: spans por nodo, métricas de violaciones por validador. - Multi-tenant: aislamiento por `tenant_id`, registry por tenant. - Evaluadores LLM-as-judge automatizados: regression suite que ejecuta cada agente sobre escenarios canónicos y mide deriva. - UI de aprobación con SLA, reasignación entre operadores, cola Kanban. - Persistencia migrable a Postgres + pgvector. ## 18. No-objetivos (out of scope MVP) - Aprendizaje/fine-tuning de modelos. - Integración con sistemas reales de orquestación (RPA, Ansible, Kubernetes). - Inteligencia avanzada del agente (RAG, multi-step planning con tool use complejo). - Tests automatizados de UI Streamlit. - Soporte multi-idioma del dashboard. - High-availability del core (1 réplica suficiente para MVP). ## 19. Decisiones pospuestas - **Postgres vs SQLite para checkpoints en multi-instance**: SQLite suficiente para MVP single-node; cambio a Postgres documentado en `docs/futuro.md`. - **NeMo full integration**: feature flag preparado, pero la configuración Colang completa es post-MVP. - **Métricas**: estructura de logging permite extracción posterior; no se entregan dashboards Grafana. ## 20. Referencias - [LangGraph documentation](https://langchain-ai.github.io/langgraph/) — patrón checkpointer + interrupt. - [Guardrails AI](https://www.guardrailsai.com/) — validators hub. - [NVIDIA NeMo Guardrails](https://github.com/NVIDIA/NeMo-Guardrails) — Colang DSL. - [Microsoft Presidio](https://microsoft.github.io/presidio/) — PII detection. - [Pydantic v2](https://docs.pydantic.dev/latest/) — modelos y settings. - [FastAPI](https://fastapi.tiangolo.com/) — API REST. - [Streamlit](https://streamlit.io/) — dashboard. - [structlog](https://www.structlog.org/) — logging estructurado.