Files
forja/docs/superpowers/specs/2026-05-09-agentforge-design.md
T
JuanandClaude Opus 4.7 b471fa454c docs: add AgentForge initial design spec
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>
2026-05-09 20:08:59 +02:00

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_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

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)

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:

  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:

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/<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, 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 draftactive cambia index.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: 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