Diseño completo de la plataforma de gobernanza de agentes IA: arquitectura two-tier (FastAPI core + Streamlit dashboard), modelos de dominio, contratos REST, interfaces Strategy (LLM, Guardrails, Registry), flujo LangGraph con HITL dinámico vía interrupt(), versionado tipo Git en sistema de ficheros, política y validadores, manejo de errores y plan de testing. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
31 KiB
AgentForge — Documento de diseño
Fecha: 2026-05-09 Estado: aprobado — listo para plan de implementación Autor: Juan Revisión: v1.0
1. Resumen ejecutivo
AgentForge es una plataforma profesional de gobernanza de agentes IA orientada a entornos de producción. Cubre el ciclo de vida completo: catalogación, versionado de prompts y políticas, validación con guardrails en runtime, ejecución stateful con checkpointing, Human-in-the-Loop nativo, y trazabilidad end-to-end.
La plataforma se compone de dos servicios:
agentforge-core— API REST (FastAPI) que materializa el dominio de gobierno: registry, versionado, ejecución de agentes vía LangGraph, validación de guardrails y persistencia.agentforge-dashboard— UI ejecutiva (Streamlit) cliente del core; nunca habla con LLMs ni guardrails directamente.
Una imagen Docker por servicio, orquestadas con docker-compose. Arranque sin claves API (proveedor LLM mock por defecto).
2. Problema y motivación
Poner agentes IA en producción sin una capa de gobernanza produce sistemas opacos:
- Prompts que cambian en caliente sin historial.
- Validaciones inconsistentes según quién despliega.
- Acciones de alto impacto (rollbacks, cambios de configuración, ejecución de comandos) propuestas sin supervisión humana ni rollback plan.
- Decisiones del agente no auditables — imposible reconstruir por qué se tomó una acción concreta.
AgentForge define el plano de control mínimo que un equipo de plataforma necesita antes de operar agentes con impacto real. El proyecto no resuelve la inteligencia del agente; resuelve su operabilidad y gobierno.
3. Decisiones de diseño (resumen)
| Eje | Decisión | Justificación |
|---|---|---|
| Topología | Two-tier: FastAPI core + Streamlit dashboard | Separación de responsabilidades; el core es API-first y consumible por terceros (RPA, n8n, otros agentes). Patrón de plataformas reales. |
| LLM | Interfaz LLMProvider con mock (default), azure, openai |
Demo arranca sin secrets. Demuestra portabilidad de proveedor. |
| Guardrails | Interfaz GuardrailEngine con GuardrailsAI (default) y NeMo (opt-in vía flag) |
Lo mejor de ambos mundos: arquitectura extensible visible + imagen ligera por defecto. |
| Persistencia | Mezcla por capa: JSON (registry, definiciones), JSONL (logs append-only), YAML (versiones), SQLite (checkpoints LangGraph) | Cada capa con la herramienta correcta. Definiciones humanas en YAML; estado runtime en formatos legibles por máquina. |
| Versionado | Simulación tipo Git en sistema de ficheros: agents/<n>/versions/v1.yaml + index.yaml con hashes y mensajes |
No requiere git real; reproduce la disciplina de versionado y diff. |
| Estado | LangGraph con SqliteSaver + interrupt() dinámico para HITL |
Persistencia entre reinicios crítica para esperas humanas. |
| Auth | Ninguna en MVP; placeholder hook Depends(get_current_user) |
Pluggable a OAuth2/OIDC/MSAL en futuro. |
| Comentarios | Español | Coherente con el autor. |
4. Arquitectura
┌───────────────────────── docker-compose ──────────────────────────┐
│ │
│ ┌─────────────────────┐ HTTP/JSON ┌────────────────────┐ │
│ │ agentforge-dashboard│ ────────────────► │ agentforge-core │ │
│ │ Streamlit :8501 │ ◄──────────────── │ FastAPI :8000 │ │
│ └─────────────────────┘ └─────────┬──────────┘ │
│ │ │
│ ┌────────────────────────────────────────┼──────────┐ │
│ ▼ ▼ ▼ ▼ │
│ LLM layer Guardrail layer Runtime State│
│ ┌──────────────────┐ ┌────────────────────┐ ┌─────────┐ ┌───┐│
│ │ LLMProvider (P) │ │ GuardrailEngine(P) │ │LangGraph│ │JSN││
│ │ ├ MockProvider │ │ ├ GuardrailsAIEng │ │ + HITL │ │SQL││
│ │ ├ AzureOpenAI │ │ ├ NeMoEngine (opt) │ │ + ckpt │ │JNL││
│ │ └ OpenAI │ │ └ CompositeEngine │ └─────────┘ └───┘│
│ └──────────────────┘ └────────────────────┘ │
│ │
│ Strategy pattern con Pydantic v2 + Protocol typing │
└────────────────────────────────────────────────────────────────────┘
Principios:
- API-first: el core es invocable por cualquier cliente HTTP; el dashboard es un consumidor sustituible.
- Configuration over code: definiciones de agentes y políticas en YAML editables sin redeploy.
- Strategy/Plugin: todas las dependencias externas (LLM, guardrails, registry) detrás de
Protocol. - Fail-closed por defecto: errores en validadores se traducen a violación que bloquea la ejecución.
- Trazabilidad obligatoria: cada ejecución tiene un
trace_idUUID 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 enapi/. Los routers solo serializan. - Las factories (
llm/factory.py,guardrails/factory.py) son la frontera donde el.envdecide la implementación. - YAML para definiciones humanas (legibles, comentables, mejor diff). JSON/JSONL/SQLite para estado runtime.
__init__.pymínimos, sin re-exports masivos.
6. Modelos de dominio
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:
404agente o ejecución no existe409/approveo/rejectsobre ejecución no enawaiting_approval422body inválido (Pydantic)500fallo interno (checkpoint corrupto, configuración inválida)
Todas las respuestas incluyen header X-Trace-Id (eco del request o generado).
8. Interfaces clave (Strategy)
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):
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:
- API genera
trace_id(UUID4); resuelveAgentDefinitionvíaAgentRegistry; resuelvePolicyDefinitionreferenciada enagent_def.guardrails; logaexecution.startedcontrace_id. runtime.graph.build_graph(agent_def, policy)compila el grafo:- Inyecta
LLMProvidervía factory (override porLLM_PROVIDERenv). - Inyecta
GuardrailEnginevía factory (Composite[GuardrailsAI], +NeMosi flag). - Checkpointer =
SqliteSaver. El nodoapprove_gateinvoca dinámicamenteinterrupt(payload)(API de LangGraph ≥0.2) solo cuando hay acciones que requieren aprobación; si todas son seguras, transiciona afinalizesin pausa. Esto evita elinterrupt_beforeestático y mantiene el flujo declarativo.
- Inyecta
graph.invoke({...}, config={"configurable":{"thread_id": trace_id}})ejecuta hasta:- (a) END por guardrail block en
validate_inputovalidate_output. - (b) END por LLM unrecoverable failure.
- (c) INTERRUPT en
approve_gate(HITL). - (d) END normal (
finalize) si no hay acciones de alto riesgo.
- (a) END por guardrail block en
- API serializa
AgentExecutiony devuelve 200. Append adata/executions.jsonly violaciones adata/violations.jsonl. - (caso HITL) Dashboard pinta cards con cada
ProposedAction(action, target, badgerisk_score,rollback_plan, botones[Approve]/[Reject]). Operador pulsa Approve. - Dashboard
POST /executions/{trace_id}/approvecon body{"approved_action_ids": ["..."], "comment": "..."}. - API:
graph.invoke(Command(resume={"human_decision": {...}}), config={"configurable":{"thread_id": trace_id}}). Nodofinalizeleestate.human_decision, componefinal_output.status="completed",finished_at=now. Append final aexecutions.jsonl.
10. Guardrails (capa runtime)
Estructura policies/default/versions/v1.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— usapresidio_analyzer+ entidades configuradas;ES_NIFcomo recognizer custom (regex documentada).PromptInjection— heurística de patrones (lista mantenible) + clasificador ligero. En modomockLLM, lista de strings exactos para reproducibilidad de tests.ToxicLanguage— validator del Guardrails Hub.ForbiddenTopics— substring matcher en MVP.docs/futuro.mddocumenta upgrade a similarity con embeddings.SchemaMatch— Pydantic parse contraagent.output_schema.PIILeakage— Presidio sobre output stringificado.ForbiddenActionKeywords— substring matcher sobre acciones propuestas.TelcoSafetyRules— validador custom; reglas declarativas evaluadas sobreproposed_actions[].
Comportamiento del engine:
- Toda violación se persiste a
violations.jsonl(inclusoinfo/warning). - Solo
severity=blockconblocked=Truedetiene el grafo. on_validator_errordecide qué pasa cuando un validador lanza excepción (fail_closedpor defecto).- Composite engine ejecuta sub-engines en paralelo y agrega resultados.
NeMo Guardrails (opt-in): activado con GUARDRAILS_NEMO_ENABLED=true. Aporta topical rails declarados en Colang (policies/default/nemo/rails.co). MVP entrega configuración mínima (off-topic refusal); el flag por defecto está en false para mantener la imagen ligera.
11. Versionado de prompts y políticas (simulación tipo Git)
Estructura agents/<name>/index.yaml:
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, escribeversions/vN.yaml, actualizaindex.yaml.Registry.diff_versions(name, v1, v2)—difflib.unified_diffsobre los YAMLs.Registry.get_agent(name)— devuelve la versiónactive_version.- Promoción de
draft→activecambiaindex.yaml.active_version.
Estructura idéntica para policies/<name>/.
El dashboard ofrece:
- Vista de "Versiones" del agente con autor, mensaje, hash, fecha.
- Botón "Comparar con anterior" → render del diff con sintaxis coloreada.
12. Observabilidad
- Logging:
structlogcon renderer JSON. Cada log incluyetrace_id,agent_name,agent_version,step,duration_mscuando aplica. - Trace-id propagation: middleware FastAPI inyecta
X-Trace-Id(genera UUID4 si no viene).structloglo bind-ea en context (structlog.contextvars). Dashboard incluyeX-Trace-Iden 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.ymlcon dos servicios:core(uvicorn,:8000) ydashboard(Streamlit,:8501).Dockerfile.coreyDockerfile.dashboardseparados; cada uno con sus deps mínimas.- Healthchecks:
coreGET /health;dashboardGET /(Streamlit responde 200 cuando el server arranca). - Volume mount
./data:/app/datay./agents:/app/agents:ro,./policies:/app/policies:ro. .env.examplecon todas las variables documentadas y comentadas.LLM_PROVIDER=mockpor 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
- 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.
- Problema que resuelve — Sin gobernanza, los agentes en producción son cajas negras (ver §2).
- Diagrama de arquitectura — Mermaid en línea + PNG renderizado en
docs/. - Quickstart:
cp .env.example .env && docker compose up. Funciona out-of-the-box (LLM_PROVIDER=mock). - Demo guiada en 3 pasos: 1) registro; 2) ejecuta
incident_analyzercon02_mos_degradation_pool_sbc.txt; 3) aprueba el rollback en HITL. - Capacidades implementadas — tabla
feature → ubicación en código. - 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 — patrón checkpointer + interrupt.
- Guardrails AI — validators hub.
- NVIDIA NeMo Guardrails — Colang DSL.
- Microsoft Presidio — PII detection.
- Pydantic v2 — modelos y settings.
- FastAPI — API REST.
- Streamlit — dashboard.
- structlog — logging estructurado.