diff --git a/plan.html b/plan.html new file mode 100644 index 0000000..3ecc846 --- /dev/null +++ b/plan.html @@ -0,0 +1,1901 @@ + + +
+ + +tests/fixtures/agents/incident_analyzer/ que se actualizará en Fase 8.
+ No afecta a ningún import de código Python.
+ detect_pii, prompt_injection,
+ toxic_language. Cambia forbidden_topics a temas relevantes
+ para trading (["manipulación de mercado", "insider trading", "credenciales de broker"]).
+ El schema_match de output pasa a validar el output schema genérico de trading:
+ campos requeridos [proposed_actions] con subcampos [id, action, target,
+ risk_score, rollback_plan, requires_approval]. Cambia forbidden_action_keywords
+ a términos peligrosos de trading (["DROP TABLE", "rm -rf", "liquidate all",
+ "market order all"]). Reemplaza telco_safety_rules con
+ trading_safety_rules (se implementa en Fase 2).
+ build_guardrail_engine(), la lista allowed_keywords del
+ NeMoGuardrailsEngine contiene términos telecom (sip, ims, cscf, sbc,
+ mos, hss). Reemplazar con terminología de trading:
+ ["equity", "forex", "fx", "portfolio", "signal", "position", "order",
+ "market", "risk", "hedge", "rebalance", "execution", "volatility", "spread"].
+ NeMo actúa como guardrail de off-topic: si el input no menciona ningún keyword de
+ la lista, emite un warning (no bloquea). Esto evita que el agente procese peticiones
+ fuera de dominio.
+ LLMConfig: cambiar el Literal["mock", "azure", "openai"]
+ a Literal["mock", "azure", "openai", "anthropic"].AgentDefinition: añadir
+ hitl_timeout_minutes: int | None = Field(default=None, ge=1, le=1440).
+ Cuando es None, el HITL no expira. Cuando tiene valor, el background checker
+ de Fase 6 auto-rechaza la ejecución si el operador no decide en ese tiempo.
+ Valor recomendado para producción: 30 min equities, 60 min FX.dry_run: bool al TypedDict AgentState.
+ El orquestrador lo inyecta en el estado inicial al invocar. El nodo
+ finalize lo lee para omitir la emisión de approved_actions
+ y marcar el output con "dry_run": True.
+ El campo se inicializa a False en orchestrator.invoke()
+ para preservar retrocompatibilidad.
+ Settings:
+ anthropic_api_key: str = ""Literal de llm_provider y
+ llm_fallback_provider para incluir "anthropic".hitl_sla_check_interval_seconds: int = 60 — frecuencia del background
+ checker que auto-rechaza HITLs expirados.system_halted: bool = False — flag de kill switch (en memoria;
+ en producción se persistiría en Redis/DB, pero para MVP es suficiente).telco_safety_rules() completa (líneas 265–300).
+ Añadir trading_safety_rules(payload, config, trace_id, stage) con estas reglas
+ declarativas activables vía config:
+ no_order_without_stop_loss: para cada acción con
+ action in ["BUY","SELL","SHORT","COVER"], el campo rollback_plan
+ no puede ser vacío, "n/a" ni "none". Bloquea si incumple.no_leveraged_action_without_hedge: si la acción tiene
+ "leverage" o "margin" en su descripción, debe existir una acción
+ paralela con action="HEDGE" en el mismo output. Bloquea si incumple.no_mass_liquidation_without_tranche: si target contiene
+ "all", "*" o "portfolio" y la acción es
+ SELL/CLOSE, el rollback_plan debe mencionar "tranche"
+ o "tramo". Bloquea si incumple.require_notional_on_trade_actions: BUY/SELL deben tener el campo
+ "notional_usd" presente y ser un número positivo. Severity configurable.list[GuardrailViolation].
+ position_size_limit(payload, config, trace_id, stage).
+ Config keys:
+ max_notional_usd: float — si cualquier acción tiene
+ notional_usd mayor que este límite, bloquea.max_pct_adv: float (opcional, future) — % máximo del Average Daily
+ Volume. Si no hay dato de ADV en el payload, se ignora la validación.severity_on_breach: "block" | "warning" — default "block".payload.get("proposed_actions", []), extrae
+ notional_usd de cada acción (si presente), compara contra el límite.
+ El mensaje de violación incluye el instrumento, el notional real y el límite configurado.
+ notional_usd no está presente en la acción, el validador emite
+ un warning indicando que el campo es requerido (nunca bloquea por ausencia,
+ ese chequeo es de trading_safety_rules).
+ forbidden_instruments(payload, config, trace_id, stage).
+ Config keys:
+ instruments: list[str] — lista de tickers/símbolos bloqueados
+ (e.g. ["GME", "AMC", "BBBY"] para restricted list, o instrumentos
+ en embargo regulatorio).severity_on_match: "block" | "warning" — default "block".target de cada acción propuesta. La comparación es
+ case-insensitive. Si el ticker está en la lista, bloquea y reporta cuál instrumento
+ y qué acción lo referenciaba.
+ market_hours_check(payload, config, trace_id, stage).
+ Config keys:
+ asset_class: "equity" | "fx" | "crypto" — crypto nunca bloquea
+ (24/7); equity bloquea fuera de horario; FX emite warning (mercado casi 24/5).timezone: str — e.g. "America/New_York" para NYSE/NASDAQ,
+ "Europe/London" para LSE. Default "America/New_York".severity_outside_hours: "block" | "warning" | "info" — default
+ "warning".equity, horario NYSE es 09:30–16:00 ET de lunes a viernes,
+ excluidas holidays principales (usa datetime.now(tz)). Para fx,
+ solo bloquear fin de semana. Para crypto, nunca bloquear.
+ zoneinfo (Python 3.9+, ya en el env).
+ No añadir dependencias externas.
+ INPUT_VALIDATORS.
+ guardrails_ai.py:
+ INPUT_VALIDATORS: añadir "market_hours_check": market_hours_check.OUTPUT_VALIDATORS: quitar "telco_safety_rules": telco_safety_rules,
+ añadir "trading_safety_rules": trading_safety_rules,
+ "position_size_limit": position_size_limit,
+ "forbidden_instruments": forbidden_instruments.validators al inicio del archivo para
+ referenciar las funciones nuevas y quitar telco_safety_rules.
+ AnthropicProvider que implementa el LLMProvider Protocol.
+ Puntos clave:
+ anthropic con cliente AsyncAnthropic.cache_control: {"type": "ephemeral"} en el campo system
+ de la API de Messages. Esto activa el cache de 5 minutos de Anthropic.
+ Los system prompts de trading (instrucciones de análisis, reglas de riesgo) son
+ largos y no cambian entre invocaciones del mismo agente → máximo aprovechamiento.list[Message] al formato de la API:
+ el primer mensaje con role="system" va en el campo system
+ (con cache_control), el resto van en messages.claude-sonnet-4-6, claude-opus-4-7,
+ claude-haiku-4-5-20251001. Default en agentes: claude-sonnet-4-6.CompletionResult.tokens_in y tokens_out desde
+ response.usage. Incluir también cache_read_input_tokens y
+ cache_creation_input_tokens en el log de structlog para observabilidad
+ de costes.model en CompletionResult se toma de
+ response.model.api_key: str y model: str.
+ match settings.llm_provider:
+ case "anthropic": return AnthropicProvider(api_key=settings.anthropic_api_key, model=settings.anthropic_model).
+ anthropic_model: str = "claude-sonnet-4-6" a Settings
+ (en P1.T3 se puede añadir junto con la API key).
+ anthropic>=0.40,<1.0.
+ Verificar que no hay conflictos con las dependencias existentes (httpx, pydantic).
+ El SDK de Anthropic usa httpx internamente, que ya está en requirements;
+ asegurarse de que el rango de versiones es compatible.
+ # Anthropic / Claude (solo si LLM_PROVIDER=anthropic)ANTHROPIC_API_KEY=ANTHROPIC_MODEL=claude-sonnet-4-6HITL_SLA_CHECK_INTERVAL_SECONDS=60.
+ Actualizar el comentario de LLM_PROVIDER para incluir anthropic.
+ incident_analyzer/index.yaml. Versión activa: v1.
+ Campos: name: signal_analyzer, lista de versiones con id/hash/author/message/created_at,
+ active_version: v1.
+ purpose: analiza señales de mercado (precio, volumen, indicadores técnicos,
+ contexto macro) y propone entradas/salidas con análisis de riesgo.guardrails: [trading_equity]risk_threshold_for_hitl: 3 (más conservador: risk ≥ 3 requiere aprobación).hitl_timeout_minutes: 30llm.provider: mock, model: claude-sonnet-4-6,
+ temperature: 0.1 (determinismo en señales).system_prompt: Eres un analista cuantitativo senior. Recibes datos de mercado.
+ Devuelves JSON con: instrument, direction (BUY/SELL/HOLD),
+ rationale (string), confidence (0.0-1.0), timeframe
+ (intraday/swing/position), proposed_actions (lista con id, action, target,
+ quantity, notional_usd, risk_score 1-5, stop_loss, rollback_plan, requires_approval).
+ Reglas: nunca proponer sin stop_loss; acciones >$500k requieren requires_approval=true;
+ no proponer sobre instrumentos con spreads anómalos sin avisarlo en rationale.output_schema: JSON Schema completo de lo anterior.01: Acción tech supera estimaciones de earnings; RSI en 65; precio rompe
+ resistencia; pedir entrada larga.02: VIX spike al 28; flows defensivos; sesión risk-off; evaluar reducción
+ de exposición en renta variable.03: EUR/USD con divergencia RSI bearish en H4; momentum negativo tras
+ datos de inflación; candidato a entrada corta con gestión del riesgo.active_version: v1.
+ purpose: recibe snapshot de cartera (posiciones actuales + pesos objetivo)
+ y propone órdenes de rebalanceo optimizando coste de transacción.guardrails: [trading_equity]risk_threshold_for_hitl: 2 — casi todo requiere aprobación humana; las
+ operaciones de cartera afectan múltiples posiciones simultáneamente.hitl_timeout_minutes: 60temperature: 0.0 — rebalanceo es matemático, máximo determinismo.rebalance_summary (delta total en USD, coste
+ estimado de transacción), proposed_actions (una por instrumento afectado).01: rebalanceo trimestral de una cartera 60/40 que ha derivado a 72/28 por el
+ rally de renta variable. 02: tilt táctico defensivo incrementando bonos al 45%
+ por entorno macro adverso.
+ risk_threshold_for_hitl: 3,
+ hitl_timeout_minutes: 45.
+ Output schema: risk_summary (VaR 1d, max_drawdown_pct, concentration_top3),
+ alerts (lista de alertas activas con severity), proposed_actions
+ (hedges, reducciones, stops).
+ risk_threshold_for_hitl: 4 (la aprobación de la orden ya vino del agente
+ anterior; aquí solo se planifica la ejecución). Output schema: execution_strategy
+ (TWAP/VWAP/IS/POV), slices (lista de sub-órdenes con timestamp, qty, venue),
+ estimated_market_impact_bps, proposed_actions.
+ docker compose up core y hacer
+ GET /agents. Verificar que los 4 nuevos agentes aparecen en el listado
+ y que sus versiones se pueden leer correctamente. Comprobar que el hash de contenido
+ se calcula sin errores.
+ No requiere cambio de código — es validación funcional.
+ detect_pii (block EMAIL, IBAN),
+ prompt_injection (block), market_hours_check
+ (asset_class: equity, tz: America/New_York, severity: warning),
+ forbidden_topics (block: ["insider", "material non-public",
+ "manipulación de mercado"]).
+ schema_match (block, schema validando
+ proposed_actions con campos de trading), pii_leakage (block),
+ forbidden_action_keywords (block: ["liquidate all", "margin call ignore",
+ "DROP TABLE"]), trading_safety_rules (block: all rules),
+ position_size_limit (block, max_notional_usd: 1000000),
+ forbidden_instruments (block: restricted list de ejemplo),
+ on_validator_error: fail_closed
+ market_hours_check con asset_class: fx (solo bloquea
+ en weekend), sin límite de horario intradía. El límite de notional es mayor:
+ max_notional_usd: 5000000 (FX es mercado más líquido).
+ trading_safety_rules activa solo
+ no_order_without_stop_loss y no_leveraged_action_without_hedge.
+ max_notional_usd: 250000 (mayor volatilidad).
+ market_hours_check ausente o con asset_class: crypto
+ (nunca bloquea). forbidden_instruments incluye tokens con
+ restricciones regulatorias.
+ default se convierte en la política base de trading genérica:
+ usa todos los validadores de trading pero con límites permisivos. Es la fallback para
+ agentes que no especifiquen política de asset class concreta.
+ GET /policies y comprobar que aparecen
+ trading_equity, trading_fx, trading_crypto.
+ Hacer GET /policies/trading_equity y verificar que los validadores
+ se deserializan correctamente sin errores de Pydantic.
+ dry_run: bool = False a InvokeRequest.
+ En el handler invoke_agent(), añadir dry_run=body.dry_run
+ a la llamada orchestrator.invoke(). El orquestrador inyectará el valor
+ en el estado inicial del grafo (initial["dry_run"] = dry_run).
+ Si dry_run=True, la ejecución NO se escribe en el JSONL de historial
+ (solo en el checkpointer temporal). Añadir campo dry_run: bool
+ a AgentExecution en domain/execution.py para que el
+ response body refleje el modo.
+ build_node_finalize(), añadir al inicio:
+ if state.get("dry_run"): final_output = {**parsed, "approved_actions": final_actions, "dry_run": True}
+ El comportamiento es idéntico excepto que el output está marcado con
+ "dry_run": True. El orquestrador y la API usarán ese flag para
+ no escribir la ejecución en el log append-only de produción.
+ También se puede añadir un step en el decision_path indicando
+ dry_run=True para trazabilidad.
+ GET /control/status: devuelve {"halted": bool, "pending_hitl_count": int,
+ "message": str}. Lee el estado global del sistema.POST /control/halt: activa el kill switch. Comportamiento:
+ system_halted = True en el estado de la app
+ (variable de módulo o en Settings — MVP puede ser un módulo singleton).execution_index.json, encuentra todas las ejecuciones en
+ awaiting_approval y las rechaza automáticamente con reason
+ "kill_switch_activated" usando orchestrator.resume().{"halted": true, "auto_rejected": N}.POST /control/resume: desactiva el kill switch (restaura operación normal).invoke_agent() de executions.py, añadir una
+ comprobación al inicio: si system_halted, devolver 503.
+ control.router con prefix /control,
+ tag ["control"].lifespan con asyncio.create_task para
+ el background checker de SLA HITL. La tarea corre en bucle cada
+ settings.hitl_sla_check_interval_seconds segundos.@asynccontextmanager async def lifespan(app): task = asyncio.create_task(sla_checker()); yield; task.cancel().
+ runtime/sla.py con la función async
+ run_hitl_sla_checker(settings, registry, policies, orchestrator).
+ Algoritmo en cada tick:
+ execution_index.json.awaiting_approval:
+ agent_def del registry.agent_def.hitl_timeout_minutes no es None:
+ execution.started_at + timedelta(minutes=hitl_timeout_minutes)
+ con datetime.now(UTC).orchestrator.resume(decision={"approved_action_ids": [],
+ "rejected": True, "reason": "hitl_sla_expired"}) y escribir en JSONL.dry_run: bool = False a invoke().
+ Incluirlo en el dict initial: initial["dry_run"] = dry_run.
+ No se propaga a resume() — el dry_run se decide en la invocación inicial
+ y queda embebido en el estado del checkpointer.
+ docs/manual_qa.md con los pasos de prueba manual:
+ signal_analyzer con dry_run=true,
+ verificar que el output tiene "dry_run": true y que NO aparece en
+ el historial (GET /executions no lo lista).POST /control/halt,
+ verificar que la ejecución pasa a failed con error kill_switch_activated,
+ verificar que intentar invocar devuelve 503. Luego POST /control/resume y
+ verificar que vuelve a funcionar.hitl_timeout_minutes: 1 en un agente de test,
+ invocar, esperar 65 segundos, verificar auto-rechazo.st.title: "▶️ Ejecutar Agente" → sin cambio (genérico).st.text_area label: "Descripción del incidente"
+ → "Señal de mercado / Instrucción de trading".placeholder: actualizar con ejemplo de señal de trading.dry_run = st.checkbox("🔬 Modo simulación (dry-run)", value=False)
+ antes del botón de invocar.{"input": user_input, "dry_run": dry_run} al client.execution["final_output"]["dry_run"] es True, mostrar
+ badge amarillo "SIMULACIÓN — sin efecto real".notional_usd: si presente, mostrar en grande con color según tamaño
+ (<100K verde, 100K–1M amarillo, >1M rojo).stop_loss: mostrar si presente.quantity: mostrar si presente.asset_class: badge de tipo de activo si presente.st.set_page_config title a "AgentForge Trading"
+ y el icono a 📈. Actualizar la página principal (si existe contenido) para describir
+ la plataforma como fábrica de agentes de trading.
+ create_app(): title="AgentForge Trading",
+ description="Plataforma de gobernanza de agentes IA para trading — API REST.",
+ version="0.2.0". Esto actualiza la UI de /docs de Swagger.
+ (Este cambio puede hacerse junto con P6.T4 que también modifica main.py.)
+ trading_equity, trading_fx, trading_crypto
+ aparecen en el selector y que sus validadores se muestran correctamente.
+ Si hay algún problema de renderizado con los nuevos tipos de validador, ajustar
+ la visualización aquí.
+ tests/fixtures/agents/incident_analyzer/.tests/fixtures/agents/signal_analyzer/ con index y v1 (copia
+ simplificada de los YAML de producción, con provider: mock).test_agent/versions/v1.yaml: cambiar el output_schema
+ y system_prompt para que sean de trading (los tests que usan test_agent
+ deben funcionar con el nuevo schema de validación de políticas trading).test_policy puede simplificarse a solo schema_match
+ para mantener los tests unitarios simples. Añadir fixture trading_equity
+ para los tests de integración que usan signal_analyzer.
+ incident_analyzer y al output schema
+ telecom con signal_analyzer y el output schema de trading.
+ El MockProvider devuelve un JSON hardcodeado — actualizar el mock response
+ en conftest.py para que devuelva el formato de señal de trading:
+ {"instrument": "AAPL", "direction": "BUY", "rationale": "...", "confidence": 0.8,
+ "proposed_actions": [{"id": "a1", "action": "BUY", "target": "AAPL",
+ "quantity": 100, "notional_usd": 18500, "risk_score": 3,
+ "stop_loss": "18000", "rollback_plan": "Vender si cae -5%", "requires_approval": false}]}.
+ Los tests de PII, HITL y restart no necesitan cambios lógicos, solo las fixtures.
+ trading_safety_rules: test happy path (con stop_loss), test que bloquea
+ sin stop_loss, test que bloquea mass liquidation sin tranche, test leveraged sin hedge.position_size_limit: test que pasa si notional < max,
+ test que bloquea si notional > max, test que emite warning si notional ausente.forbidden_instruments: test que pasa con ticker permitido,
+ test que bloquea con ticker restringido (case-insensitive).market_hours_check: mockear datetime.now para simular
+ dentro/fuera de horario para equity, fx, crypto.AgentDefinition con hitl_timeout_minutes=None
+ (default), con valor válido (30), con valor fuera de rango (0 falla, 1441 falla).
+ Verificar que el Literal de LLMConfig.provider acepta "anthropic".
+ signal_analyzer con dry_run=True:
+ completed.final_output debe contener "dry_run": True.executions.jsonl) no debe contener la ejecución.TestClient:
+ GET /control/status devuelve {"halted": false, ...} por defecto.POST /control/halt cambia el estado a halted: true.POST /agents/test_agent/invoke devuelve 503.POST /control/resume restaura operación normal.telco_safety_rules en
+ test_guardrails_ai.py. Actualizar los mocks de AgentDefinition
+ en test_runtime_nodes.py y test_runtime_orchestrator.py
+ para usar el nuevo schema de trading (los tests de lógica de nodos son agnósticos
+ del dominio — solo necesitan que el fixture sea válido).
+ make test-all (unit + integration) y make lint
+ (ruff + mypy). Goal: 0 errores, 0 warnings de tipo. Si hay fallos residuales,
+ resolverlos antes de marcar la fase completa. Esta tarea actúa como gate de calidad
+ para todo el trabajo anterior.
+ signal_analyzer y los nuevos escenarios.control
+ (kill switch), el background SLA checker, y los 4 nuevos tipos de agentes.
+ Añadir sección "Flujo de una orden de trading" que muestre cómo los guardrails
+ de trading se aplican en el grafo LangGraph.
+ description de "Plataforma profesional de gobernanza de agentes IA"
+ a "Plataforma de gobernanza de agentes IA para trading — fábrica de agentes con guardrails, HITL y auditoría".
+ Sin cambios en dependencias ni en la configuración de herramientas.
+ make smoke (docker compose up + curl /health) con todos los
+ cambios del repurpose. Verificar:
+ GET /agents devuelve los 4 agentes de trading.GET /policies devuelve las 3 políticas de trading + default.GET /control/status devuelve {"halted": false, ...}.Las fases son mayoritariamente independientes pero tienen algunas dependencias duras:
++ Secuencia óptima por sesión: + Fase 0 → Fases 1+2 (juntas, sin dep entre sí) → Fases 3+4+5 (paralelas) → Fase 6 → Fase 7 → Fase 8 (gate de calidad) → Fases 9+10. +
+