Files
larry/plan.html
T
2026-05-21 20:19:18 +02:00

1902 lines
89 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>AgentForge Trading — Plan de Ejecución</title>
<style>
:root {
--bg: #0f1117;
--surface: #1a1d27;
--border: #2a2d3e;
--text: #e2e8f0;
--muted: #8892a4;
--accent: #6366f1;
--green: #22c55e;
--yellow: #f59e0b;
--red: #ef4444;
--blue: #3b82f6;
--gray: #475569;
--todo-bg: #1e2536;
--done-bg: #0f2318;
--prog-bg: #162035;
--code-bg: #0d1117;
--font: 'Segoe UI', system-ui, -apple-system, sans-serif;
--mono: 'Fira Code', 'Cascadia Code', 'Consolas', monospace;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: var(--bg);
color: var(--text);
font-family: var(--font);
font-size: 14px;
line-height: 1.6;
padding: 2rem;
max-width: 1200px;
margin: 0 auto;
}
a { color: var(--accent); text-decoration: none; }
/* ── Header ──────────────────────────────────────────── */
.header { margin-bottom: 2rem; }
.header h1 {
font-size: 1.8rem;
font-weight: 700;
color: #fff;
margin-bottom: .25rem;
}
.header .subtitle { color: var(--muted); font-size: .9rem; }
.header .meta {
display: flex; gap: 1.5rem; margin-top: .75rem;
color: var(--muted); font-size: .85rem;
}
.header .meta span { display: flex; align-items: center; gap: .3rem; }
/* ── Summary grid ────────────────────────────────────── */
.summary-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
gap: .75rem;
margin-bottom: 2.5rem;
}
.summary-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 8px;
padding: .85rem 1rem;
}
.summary-card .label { font-size: .75rem; color: var(--muted); text-transform: uppercase; letter-spacing: .05em; }
.summary-card .value { font-size: 1.6rem; font-weight: 700; margin-top: .15rem; }
.summary-card.total .value { color: #fff; }
.summary-card.done .value { color: var(--green); }
.summary-card.prog .value { color: var(--blue); }
.summary-card.todo .value { color: var(--muted); }
/* ── Progress bar ────────────────────────────────────── */
.progress-bar-wrap { background: var(--border); border-radius: 4px; height: 6px; margin-bottom: 2.5rem; overflow: hidden; }
.progress-bar { height: 100%; background: var(--green); border-radius: 4px; transition: width .3s; }
/* ── Phase section ───────────────────────────────────── */
.phase {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 10px;
margin-bottom: 1.5rem;
overflow: hidden;
}
.phase-header {
display: flex;
align-items: center;
gap: 1rem;
padding: 1rem 1.25rem;
background: #1e2133;
border-bottom: 1px solid var(--border);
flex-wrap: wrap;
}
.phase-num {
background: var(--accent);
color: #fff;
font-weight: 700;
font-size: .75rem;
padding: .2rem .55rem;
border-radius: 5px;
white-space: nowrap;
}
.phase-title { font-size: 1rem; font-weight: 600; color: #fff; flex: 1; }
.phase-desc { color: var(--muted); font-size: .82rem; width: 100%; margin-top: -.25rem; }
.phase-stats { display: flex; gap: .5rem; flex-shrink: 0; }
.phase-stats .badge {
font-size: .72rem;
font-weight: 600;
padding: .15rem .45rem;
border-radius: 4px;
color: #fff;
}
.badge-done { background: #166534; color: #86efac; }
.badge-prog { background: #1e3a5f; color: #93c5fd; }
.badge-todo { background: #374151; color: #9ca3af; }
/* ── Task list ───────────────────────────────────────── */
.task-list { padding: .5rem 0; }
.task {
display: grid;
grid-template-columns: 140px 1fr;
gap: 0;
border-bottom: 1px solid var(--border);
transition: background .15s;
}
.task:last-child { border-bottom: none; }
.task:hover { background: rgba(255,255,255,.02); }
.task-left {
padding: .85rem 1rem;
border-right: 1px solid var(--border);
display: flex;
flex-direction: column;
align-items: flex-start;
gap: .4rem;
}
.task-id {
font-family: var(--mono);
font-size: .72rem;
color: var(--muted);
font-weight: 600;
}
.status-badge {
font-size: .68rem;
font-weight: 700;
padding: .2rem .5rem;
border-radius: 4px;
text-transform: uppercase;
letter-spacing: .05em;
}
[data-status="todo"] .status-badge { background: #1f2937; color: #9ca3af; }
[data-status="in-progress"] .status-badge { background: #1e3a5f; color: #60a5fa; }
[data-status="done"] .status-badge { background: #14532d; color: #4ade80; }
[data-status="done"] { background: var(--done-bg); }
[data-status="in-progress"] { background: var(--prog-bg); }
.task-right { padding: .85rem 1.25rem; }
.task-title {
font-weight: 600;
color: #d1d5db;
margin-bottom: .4rem;
font-size: .9rem;
}
[data-status="done"] .task-title {
color: var(--muted);
text-decoration: line-through;
text-decoration-color: #374151;
}
.task-files {
display: flex;
flex-wrap: wrap;
gap: .3rem;
margin-bottom: .45rem;
}
.file-tag {
font-family: var(--mono);
font-size: .7rem;
background: var(--code-bg);
border: 1px solid var(--border);
border-radius: 3px;
padding: .1rem .4rem;
color: #a5b4fc;
}
.file-tag.new { border-color: #166534; color: #4ade80; }
.file-tag.del { border-color: #7f1d1d; color: #f87171; }
.task-notes {
color: #94a3b8;
font-size: .82rem;
line-height: 1.55;
}
.task-notes code {
font-family: var(--mono);
background: var(--code-bg);
border: 1px solid var(--border);
border-radius: 3px;
padding: .05rem .3rem;
font-size: .78rem;
color: #fcd34d;
}
.task-notes ul { padding-left: 1.2rem; margin-top: .3rem; }
.task-notes li { margin-bottom: .15rem; }
.task-dep {
margin-top: .4rem;
font-size: .75rem;
color: var(--muted);
}
.task-dep span {
font-family: var(--mono);
background: #1f2937;
border-radius: 3px;
padding: .05rem .3rem;
margin-left: .2rem;
}
/* ── Horizontal rule between sections ───────────────── */
hr { border: none; border-top: 1px solid var(--border); margin: 2rem 0; }
/* ── Legend ──────────────────────────────────────────── */
.legend {
display: flex; gap: 1rem; flex-wrap: wrap;
margin-bottom: 1.5rem; font-size: .8rem; color: var(--muted);
}
.legend-item { display: flex; align-items: center; gap: .35rem; }
</style>
</head>
<body>
<!-- ═══════════════════════════════════════════════════════════
HEADER
══════════════════════════════════════════════════════════════ -->
<div class="header">
<h1>AgentForge Trading — Plan de Ejecución</h1>
<div class="subtitle">Repurpose completo: plataforma de gobernanza de incidentes telecom → fábrica de agentes de trading</div>
<div class="meta">
<span>Creado: 2026-05-21</span>
<span>Última actualización: 2026-05-21</span>
<span>Rama: main</span>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════
TOTALES
══════════════════════════════════════════════════════════════ -->
<div class="summary-grid">
<div class="summary-card total"><div class="label">Tareas totales</div><div class="value">52</div></div>
<div class="summary-card done"><div class="label">Completadas</div><div class="value">0</div></div>
<div class="summary-card prog"><div class="label">En progreso</div><div class="value">0</div></div>
<div class="summary-card todo"><div class="label">Pendientes</div><div class="value">52</div></div>
</div>
<div class="progress-bar-wrap">
<div class="progress-bar" style="width: 0%"></div>
</div>
<!-- ─── Leyenda ─────────────────────────────────────────────── -->
<div class="legend">
<div class="legend-item"><span class="status-badge" style="background:#1f2937;color:#9ca3af;font-size:.7rem;padding:.15rem .4rem;border-radius:3px">TODO</span> Pendiente</div>
<div class="legend-item"><span class="status-badge" style="background:#1e3a5f;color:#60a5fa;font-size:.7rem;padding:.15rem .4rem;border-radius:3px">IN-PROGRESS</span> En progreso</div>
<div class="legend-item"><span class="status-badge" style="background:#14532d;color:#4ade80;font-size:.7rem;padding:.15rem .4rem;border-radius:3px">DONE</span> Completada</div>
<div class="legend-item"><span class="file-tag">archivo.py</span> Modificar</div>
<div class="legend-item"><span class="file-tag new">archivo.py</span> Crear nuevo</div>
<div class="legend-item"><span class="file-tag del">archivo.py</span> Eliminar</div>
</div>
<!-- ═══════════════════════════════════════════════════════════
FASE 0 · Eliminación de artefactos telecom
══════════════════════════════════════════════════════════════ -->
<div class="phase">
<div class="phase-header">
<div class="phase-num">FASE 0</div>
<div class="phase-title">Eliminación de artefactos telecom</div>
<div class="phase-stats">
<span class="badge badge-done">0 done</span>
<span class="badge badge-todo">3 todo</span>
</div>
<div class="phase-desc">Limpieza quirúrgica: solo los 3 puntos donde vive el dominio telecom. El resto de la plataforma es agnóstico. Sin dependencias de fase previa.</div>
</div>
<div class="task-list">
<!-- P0.T1 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P0.T1</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">Eliminar directorio agents/incident_analyzer/ completo</div>
<div class="task-files">
<span class="file-tag del">agents/incident_analyzer/index.yaml</span>
<span class="file-tag del">agents/incident_analyzer/versions/v1.yaml</span>
<span class="file-tag del">agents/incident_analyzer/versions/v2.yaml</span>
<span class="file-tag del">agents/incident_analyzer/examples/01_sip_registration_drop.txt</span>
<span class="file-tag del">agents/incident_analyzer/examples/02_mos_degradation_pool_sbc.txt</span>
<span class="file-tag del">agents/incident_analyzer/examples/03_hss_capacity_active_active.txt</span>
</div>
<div class="task-notes">
Borrar el directorio completo. Los tests de fixtures tienen su propia copia en
<code>tests/fixtures/agents/incident_analyzer/</code> que se actualizará en Fase 8.
No afecta a ningún import de código Python.
</div>
</div>
</div>
<!-- P0.T2 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P0.T2</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">Reemplazar contenido de policies/default/versions/v1.yaml</div>
<div class="task-files">
<span class="file-tag">policies/default/versions/v1.yaml</span>
</div>
<div class="task-notes">
Reescribir como política base genérica de trading (sustituye la telecom). Mantiene
los validadores genéricos: <code>detect_pii</code>, <code>prompt_injection</code>,
<code>toxic_language</code>. Cambia <code>forbidden_topics</code> a temas relevantes
para trading (<code>["manipulación de mercado", "insider trading", "credenciales de broker"]</code>).
El <code>schema_match</code> de output pasa a validar el output schema genérico de trading:
campos requeridos <code>[proposed_actions]</code> con subcampos <code>[id, action, target,
risk_score, rollback_plan, requires_approval]</code>. Cambia <code>forbidden_action_keywords</code>
a términos peligrosos de trading (<code>["DROP TABLE", "rm -rf", "liquidate all",
"market order all"]</code>). Reemplaza <code>telco_safety_rules</code> con
<code>trading_safety_rules</code> (se implementa en Fase 2).
<div class="task-dep">Depende de: <span>P2.T1</span> (trading_safety_rules debe existir antes de que la política use el validador)</div>
</div>
</div>
</div>
<!-- P0.T3 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P0.T3</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">Actualizar NeMo Guardrails allowed_keywords a terminología trading</div>
<div class="task-files">
<span class="file-tag">core/src/agentforge_core/guardrails/factory.py</span>
</div>
<div class="task-notes">
En <code>build_guardrail_engine()</code>, la lista <code>allowed_keywords</code> del
<code>NeMoGuardrailsEngine</code> contiene términos telecom (<code>sip, ims, cscf, sbc,
mos, hss</code>). Reemplazar con terminología de trading:
<code>["equity", "forex", "fx", "portfolio", "signal", "position", "order",
"market", "risk", "hedge", "rebalance", "execution", "volatility", "spread"]</code>.
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.
</div>
</div>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════
FASE 1 · Extensiones del modelo de dominio
══════════════════════════════════════════════════════════════ -->
<div class="phase">
<div class="phase-header">
<div class="phase-num">FASE 1</div>
<div class="phase-title">Extensiones del modelo de dominio</div>
<div class="phase-stats">
<span class="badge badge-done">0 done</span>
<span class="badge badge-todo">3 todo</span>
</div>
<div class="phase-desc">Cambios mínimos a los Pydantic models y Settings. Las fases 3, 4 y 6 dependen de estas extensiones.</div>
</div>
<div class="task-list">
<!-- P1.T1 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P1.T1</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">domain/agent.py — añadir hitl_timeout_minutes y soporte "anthropic" en LLMConfig</div>
<div class="task-files">
<span class="file-tag">core/src/agentforge_core/domain/agent.py</span>
</div>
<div class="task-notes">
Dos cambios:
<ul>
<li>En <code>LLMConfig</code>: cambiar el <code>Literal["mock", "azure", "openai"]</code>
a <code>Literal["mock", "azure", "openai", "anthropic"]</code>.</li>
<li>En <code>AgentDefinition</code>: añadir
<code>hitl_timeout_minutes: int | None = Field(default=None, ge=1, le=1440)</code>.
Cuando es <code>None</code>, 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.</li>
</ul>
</div>
</div>
</div>
<!-- P1.T2 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P1.T2</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">runtime/state.py — añadir campo dry_run al AgentState</div>
<div class="task-files">
<span class="file-tag">core/src/agentforge_core/runtime/state.py</span>
</div>
<div class="task-notes">
Añadir <code>dry_run: bool</code> al <code>TypedDict AgentState</code>.
El orquestrador lo inyecta en el estado inicial al invocar. El nodo
<code>finalize</code> lo lee para omitir la emisión de <code>approved_actions</code>
y marcar el output con <code>"dry_run": True</code>.
El campo se inicializa a <code>False</code> en <code>orchestrator.invoke()</code>
para preservar retrocompatibilidad.
</div>
</div>
</div>
<!-- P1.T3 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P1.T3</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">config.py — añadir ANTHROPIC_API_KEY y parámetros SLA/control</div>
<div class="task-files">
<span class="file-tag">core/src/agentforge_core/config.py</span>
</div>
<div class="task-notes">
Añadir a <code>Settings</code>:
<ul>
<li><code>anthropic_api_key: str = ""</code></li>
<li>Actualizar el <code>Literal</code> de <code>llm_provider</code> y
<code>llm_fallback_provider</code> para incluir <code>"anthropic"</code>.</li>
<li><code>hitl_sla_check_interval_seconds: int = 60</code> — frecuencia del background
checker que auto-rechaza HITLs expirados.</li>
<li><code>system_halted: bool = False</code> — flag de kill switch (en memoria;
en producción se persistiría en Redis/DB, pero para MVP es suficiente).</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════
FASE 2 · Nuevos validadores de trading
══════════════════════════════════════════════════════════════ -->
<div class="phase">
<div class="phase-header">
<div class="phase-num">FASE 2</div>
<div class="phase-title">Nuevos validadores de trading</div>
<div class="phase-stats">
<span class="badge badge-done">0 done</span>
<span class="badge badge-todo">5 todo</span>
</div>
<div class="phase-desc">Núcleo de las reglas de seguridad específicas de trading. Sustituye telco_safety_rules. Sin dependencias de otras fases.</div>
</div>
<div class="task-list">
<!-- P2.T1 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P2.T1</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">validators.py — reemplazar telco_safety_rules con trading_safety_rules</div>
<div class="task-files">
<span class="file-tag">core/src/agentforge_core/guardrails/validators.py</span>
</div>
<div class="task-notes">
Eliminar la función <code>telco_safety_rules()</code> completa (líneas 265300).
Añadir <code>trading_safety_rules(payload, config, trace_id, stage)</code> con estas reglas
declarativas activables vía config:
<ul>
<li><code>no_order_without_stop_loss</code>: para cada acción con
<code>action in ["BUY","SELL","SHORT","COVER"]</code>, el campo <code>rollback_plan</code>
no puede ser vacío, <code>"n/a"</code> ni <code>"none"</code>. Bloquea si incumple.</li>
<li><code>no_leveraged_action_without_hedge</code>: si la acción tiene
<code>"leverage"</code> o <code>"margin"</code> en su descripción, debe existir una acción
paralela con <code>action="HEDGE"</code> en el mismo output. Bloquea si incumple.</li>
<li><code>no_mass_liquidation_without_tranche</code>: si <code>target</code> contiene
<code>"all"</code>, <code>"*"</code> o <code>"portfolio"</code> y la acción es
<code>SELL/CLOSE</code>, el <code>rollback_plan</code> debe mencionar <code>"tranche"</code>
o <code>"tramo"</code>. Bloquea si incumple.</li>
<li><code>require_notional_on_trade_actions</code>: BUY/SELL deben tener el campo
<code>"notional_usd"</code> presente y ser un número positivo. Severity configurable.</li>
</ul>
Misma firma que el resto: devuelve <code>list[GuardrailViolation]</code>.
</div>
</div>
</div>
<!-- P2.T2 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P2.T2</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">validators.py — añadir position_size_limit</div>
<div class="task-files">
<span class="file-tag">core/src/agentforge_core/guardrails/validators.py</span>
</div>
<div class="task-notes">
Nueva función <code>position_size_limit(payload, config, trace_id, stage)</code>.
Config keys:
<ul>
<li><code>max_notional_usd: float</code> — si cualquier acción tiene
<code>notional_usd</code> mayor que este límite, bloquea.</li>
<li><code>max_pct_adv: float</code> (opcional, future) — % máximo del Average Daily
Volume. Si no hay dato de ADV en el payload, se ignora la validación.</li>
<li><code>severity_on_breach: "block" | "warning"</code> — default <code>"block"</code>.</li>
</ul>
Itera <code>payload.get("proposed_actions", [])</code>, extrae
<code>notional_usd</code> 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.
<br>Nota: Si <code>notional_usd</code> no está presente en la acción, el validador emite
un <code>warning</code> indicando que el campo es requerido (nunca bloquea por ausencia,
ese chequeo es de <code>trading_safety_rules</code>).
</div>
</div>
</div>
<!-- P2.T3 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P2.T3</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">validators.py — añadir forbidden_instruments</div>
<div class="task-files">
<span class="file-tag">core/src/agentforge_core/guardrails/validators.py</span>
</div>
<div class="task-notes">
Nueva función <code>forbidden_instruments(payload, config, trace_id, stage)</code>.
Config keys:
<ul>
<li><code>instruments: list[str]</code> — lista de tickers/símbolos bloqueados
(e.g. <code>["GME", "AMC", "BBBY"]</code> para restricted list, o instrumentos
en embargo regulatorio).</li>
<li><code>severity_on_match: "block" | "warning"</code> — default <code>"block"</code>.</li>
</ul>
Busca en el campo <code>target</code> 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.
</div>
</div>
</div>
<!-- P2.T4 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P2.T4</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">validators.py — añadir market_hours_check</div>
<div class="task-files">
<span class="file-tag">core/src/agentforge_core/guardrails/validators.py</span>
</div>
<div class="task-notes">
Nueva función <code>market_hours_check(payload, config, trace_id, stage)</code>.
Config keys:
<ul>
<li><code>asset_class: "equity" | "fx" | "crypto"</code> — crypto nunca bloquea
(24/7); equity bloquea fuera de horario; FX emite warning (mercado casi 24/5).</li>
<li><code>timezone: str</code> — e.g. <code>"America/New_York"</code> para NYSE/NASDAQ,
<code>"Europe/London"</code> para LSE. Default <code>"America/New_York"</code>.</li>
<li><code>severity_outside_hours: "block" | "warning" | "info"</code> — default
<code>"warning"</code>.</li>
</ul>
Lógica: para <code>equity</code>, horario NYSE es 09:3016:00 ET de lunes a viernes,
excluidas holidays principales (usa <code>datetime.now(tz)</code>). Para <code>fx</code>,
solo bloquear fin de semana. Para <code>crypto</code>, nunca bloquear.
<br>Nota: Usa la librería estándar <code>zoneinfo</code> (Python 3.9+, ya en el env).
No añadir dependencias externas.
<br>Este validador actúa sobre el <em>input</em> (momento de la invocación), no sobre
el output. Se registra en <code>INPUT_VALIDATORS</code>.
</div>
</div>
</div>
<!-- P2.T5 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P2.T5</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">guardrails_ai.py — actualizar registries INPUT/OUTPUT con nuevos validadores</div>
<div class="task-files">
<span class="file-tag">core/src/agentforge_core/guardrails/guardrails_ai.py</span>
</div>
<div class="task-notes">
Actualizar los dos diccionarios en <code>guardrails_ai.py</code>:
<ul>
<li>En <code>INPUT_VALIDATORS</code>: añadir <code>"market_hours_check": market_hours_check</code>.</li>
<li>En <code>OUTPUT_VALIDATORS</code>: quitar <code>"telco_safety_rules": telco_safety_rules</code>,
añadir <code>"trading_safety_rules": trading_safety_rules</code>,
<code>"position_size_limit": position_size_limit</code>,
<code>"forbidden_instruments": forbidden_instruments</code>.</li>
</ul>
Actualizar el import de <code>validators</code> al inicio del archivo para
referenciar las funciones nuevas y quitar <code>telco_safety_rules</code>.
</div>
</div>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════
FASE 3 · Proveedor LLM Anthropic / Claude
══════════════════════════════════════════════════════════════ -->
<div class="phase">
<div class="phase-header">
<div class="phase-num">FASE 3</div>
<div class="phase-title">Proveedor LLM Anthropic / Claude</div>
<div class="phase-stats">
<span class="badge badge-done">0 done</span>
<span class="badge badge-todo">4 todo</span>
</div>
<div class="phase-desc">Añadir Claude como proveedor LLM con prompt caching. Los system prompts de trading son largos y estables → ahorro ~90% en cache hits. Depende de P1.T1 (Literal) y P1.T3 (ANTHROPIC_API_KEY en Settings).</div>
</div>
<div class="task-list">
<!-- P3.T1 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P3.T1</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">llm/anthropic.py — nuevo proveedor con prompt caching</div>
<div class="task-files">
<span class="file-tag new">core/src/agentforge_core/llm/anthropic.py</span>
</div>
<div class="task-notes">
Crear clase <code>AnthropicProvider</code> que implementa el <code>LLMProvider</code> Protocol.
Puntos clave:
<ul>
<li>Usar SDK oficial <code>anthropic</code> con cliente <code>AsyncAnthropic</code>.</li>
<li><strong>Prompt caching</strong>: el system prompt se envía con
<code>cache_control: {"type": "ephemeral"}</code> en el campo <code>system</code>
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.</li>
<li>Mapear <code>list[Message]</code> al formato de la API:
el primer mensaje con <code>role="system"</code> va en el campo <code>system</code>
(con cache_control), el resto van en <code>messages</code>.</li>
<li>Modelos soportados: <code>claude-sonnet-4-6</code>, <code>claude-opus-4-7</code>,
<code>claude-haiku-4-5-20251001</code>. Default en agentes: <code>claude-sonnet-4-6</code>.</li>
<li>Rellenar <code>CompletionResult.tokens_in</code> y <code>tokens_out</code> desde
<code>response.usage</code>. Incluir también <code>cache_read_input_tokens</code> y
<code>cache_creation_input_tokens</code> en el log de structlog para observabilidad
de costes.</li>
<li>El campo <code>model</code> en <code>CompletionResult</code> se toma de
<code>response.model</code>.</li>
</ul>
El constructor recibe <code>api_key: str</code> y <code>model: str</code>.
</div>
</div>
</div>
<!-- P3.T2 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P3.T2</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">llm/factory.py — registrar proveedor anthropic</div>
<div class="task-files">
<span class="file-tag">core/src/agentforge_core/llm/factory.py</span>
</div>
<div class="task-notes">
Añadir al <code>match settings.llm_provider</code>:
<code>case "anthropic": return AnthropicProvider(api_key=settings.anthropic_api_key, model=settings.anthropic_model)</code>.
<br>Añadir <code>anthropic_model: str = "claude-sonnet-4-6"</code> a <code>Settings</code>
(en P1.T3 se puede añadir junto con la API key).
</div>
</div>
</div>
<!-- P3.T3 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P3.T3</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">core/requirements.txt — añadir dependencia anthropic</div>
<div class="task-files">
<span class="file-tag">core/requirements.txt</span>
</div>
<div class="task-notes">
Añadir <code>anthropic&gt;=0.40,&lt;1.0</code>.
Verificar que no hay conflictos con las dependencias existentes (httpx, pydantic).
El SDK de Anthropic usa <code>httpx</code> internamente, que ya está en requirements;
asegurarse de que el rango de versiones es compatible.
</div>
</div>
</div>
<!-- P3.T4 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P3.T4</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">.env.example — añadir ANTHROPIC_API_KEY y ANTHROPIC_MODEL</div>
<div class="task-files">
<span class="file-tag">.env.example</span>
</div>
<div class="task-notes">
Añadir sección:
<code># Anthropic / Claude (solo si LLM_PROVIDER=anthropic)</code><br>
<code>ANTHROPIC_API_KEY=</code><br>
<code>ANTHROPIC_MODEL=claude-sonnet-4-6</code><br>
También añadir <code>HITL_SLA_CHECK_INTERVAL_SECONDS=60</code>.
Actualizar el comentario de <code>LLM_PROVIDER</code> para incluir <code>anthropic</code>.
</div>
</div>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════
FASE 4 · Agentes de trading (YAML declarativos)
══════════════════════════════════════════════════════════════ -->
<div class="phase">
<div class="phase-header">
<div class="phase-num">FASE 4</div>
<div class="phase-title">Agentes de trading — definiciones YAML</div>
<div class="phase-stats">
<span class="badge badge-done">0 done</span>
<span class="badge badge-todo">12 todo</span>
</div>
<div class="phase-desc">Cuatro agentes con sus index, versión v1 y ejemplos de escenarios. Solo ficheros YAML y .txt, sin código Python. Depende de Fase 2 (los validadores de las políticas deben existir) y Fase 5 (las políticas deben existir para referenciarlas).</div>
</div>
<div class="task-list">
<!-- P4.T1 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P4.T1</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">signal_analyzer — index.yaml</div>
<div class="task-files">
<span class="file-tag new">agents/signal_analyzer/index.yaml</span>
</div>
<div class="task-notes">
Estructura idéntica a <code>incident_analyzer/index.yaml</code>. Versión activa: <code>v1</code>.
Campos: <code>name: signal_analyzer</code>, lista de versiones con id/hash/author/message/created_at,
<code>active_version: v1</code>.
</div>
</div>
</div>
<!-- P4.T2 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P4.T2</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">signal_analyzer — versions/v1.yaml</div>
<div class="task-files">
<span class="file-tag new">agents/signal_analyzer/versions/v1.yaml</span>
</div>
<div class="task-notes">
Campos clave:
<ul>
<li><code>purpose</code>: analiza señales de mercado (precio, volumen, indicadores técnicos,
contexto macro) y propone entradas/salidas con análisis de riesgo.</li>
<li><code>guardrails: [trading_equity]</code></li>
<li><code>risk_threshold_for_hitl: 3</code> (más conservador: risk ≥ 3 requiere aprobación).</li>
<li><code>hitl_timeout_minutes: 30</code></li>
<li><code>llm.provider: mock</code>, <code>model: claude-sonnet-4-6</code>,
<code>temperature: 0.1</code> (determinismo en señales).</li>
<li><code>system_prompt</code>: Eres un analista cuantitativo senior. Recibes datos de mercado.
Devuelves JSON con: <code>instrument</code>, <code>direction</code> (BUY/SELL/HOLD),
<code>rationale</code> (string), <code>confidence</code> (0.0-1.0), <code>timeframe</code>
(intraday/swing/position), <code>proposed_actions</code> (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 &gt;$500k requieren requires_approval=true;
no proponer sobre instrumentos con spreads anómalos sin avisarlo en rationale.</li>
<li><code>output_schema</code>: JSON Schema completo de lo anterior.</li>
</ul>
</div>
</div>
</div>
<!-- P4.T3 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P4.T3</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">signal_analyzer — 3 escenarios de ejemplo</div>
<div class="task-files">
<span class="file-tag new">agents/signal_analyzer/examples/01_tech_earnings_breakout.txt</span>
<span class="file-tag new">agents/signal_analyzer/examples/02_macro_risk_off_session.txt</span>
<span class="file-tag new">agents/signal_analyzer/examples/03_fx_momentum_divergence.txt</span>
</div>
<div class="task-notes">
Escenarios realistas de trading para usar en la demo del dashboard.
<ul>
<li><code>01</code>: Acción tech supera estimaciones de earnings; RSI en 65; precio rompe
resistencia; pedir entrada larga.</li>
<li><code>02</code>: VIX spike al 28; flows defensivos; sesión risk-off; evaluar reducción
de exposición en renta variable.</li>
<li><code>03</code>: EUR/USD con divergencia RSI bearish en H4; momentum negativo tras
datos de inflación; candidato a entrada corta con gestión del riesgo.</li>
</ul>
</div>
</div>
</div>
<!-- P4.T4 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P4.T4</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">portfolio_rebalancer — index.yaml</div>
<div class="task-files">
<span class="file-tag new">agents/portfolio_rebalancer/index.yaml</span>
</div>
<div class="task-notes">
Misma estructura. <code>active_version: v1</code>.
</div>
</div>
</div>
<!-- P4.T5 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P4.T5</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">portfolio_rebalancer — versions/v1.yaml</div>
<div class="task-files">
<span class="file-tag new">agents/portfolio_rebalancer/versions/v1.yaml</span>
</div>
<div class="task-notes">
<ul>
<li><code>purpose</code>: recibe snapshot de cartera (posiciones actuales + pesos objetivo)
y propone órdenes de rebalanceo optimizando coste de transacción.</li>
<li><code>guardrails: [trading_equity]</code></li>
<li><code>risk_threshold_for_hitl: 2</code> — casi todo requiere aprobación humana; las
operaciones de cartera afectan múltiples posiciones simultáneamente.</li>
<li><code>hitl_timeout_minutes: 60</code></li>
<li><code>temperature: 0.0</code> — rebalanceo es matemático, máximo determinismo.</li>
<li>Output schema incluye: <code>rebalance_summary</code> (delta total en USD, coste
estimado de transacción), <code>proposed_actions</code> (una por instrumento afectado).</li>
</ul>
</div>
</div>
</div>
<!-- P4.T6 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P4.T6</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">portfolio_rebalancer — 2 escenarios de ejemplo</div>
<div class="task-files">
<span class="file-tag new">agents/portfolio_rebalancer/examples/01_quarterly_rebalance.txt</span>
<span class="file-tag new">agents/portfolio_rebalancer/examples/02_tactical_tilt_bonds.txt</span>
</div>
<div class="task-notes">
<code>01</code>: rebalanceo trimestral de una cartera 60/40 que ha derivado a 72/28 por el
rally de renta variable. <code>02</code>: tilt táctico defensivo incrementando bonos al 45%
por entorno macro adverso.
</div>
</div>
</div>
<!-- P4.T7 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P4.T7</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">risk_monitor — index.yaml + versions/v1.yaml + ejemplos</div>
<div class="task-files">
<span class="file-tag new">agents/risk_monitor/index.yaml</span>
<span class="file-tag new">agents/risk_monitor/versions/v1.yaml</span>
<span class="file-tag new">agents/risk_monitor/examples/01_drawdown_alert.txt</span>
<span class="file-tag new">agents/risk_monitor/examples/02_concentration_risk.txt</span>
</div>
<div class="task-notes">
Monitoriza métricas de riesgo (VaR, drawdown, concentración, beta) y propone coberturas o
reducciones de posición. <code>risk_threshold_for_hitl: 3</code>,
<code>hitl_timeout_minutes: 45</code>.
Output schema: <code>risk_summary</code> (VaR 1d, max_drawdown_pct, concentration_top3),
<code>alerts</code> (lista de alertas activas con severity), <code>proposed_actions</code>
(hedges, reducciones, stops).
</div>
</div>
</div>
<!-- P4.T8 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P4.T8</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">execution_planner — index.yaml + versions/v1.yaml + ejemplos</div>
<div class="task-files">
<span class="file-tag new">agents/execution_planner/index.yaml</span>
<span class="file-tag new">agents/execution_planner/versions/v1.yaml</span>
<span class="file-tag new">agents/execution_planner/examples/01_large_block_twap.txt</span>
<span class="file-tag new">agents/execution_planner/examples/02_cross_venue_split.txt</span>
</div>
<div class="task-notes">
Descompone órdenes grandes en planes de ejecución que minimizan market impact.
<code>risk_threshold_for_hitl: 4</code> (la aprobación de la orden ya vino del agente
anterior; aquí solo se planifica la ejecución). Output schema: <code>execution_strategy</code>
(TWAP/VWAP/IS/POV), <code>slices</code> (lista de sub-órdenes con timestamp, qty, venue),
<code>estimated_market_impact_bps</code>, <code>proposed_actions</code>.
</div>
</div>
</div>
<!-- P4.T9 (collapsed 4.T9 to P4.T8 above for compactness, but they're separate tasks) -->
<!-- keeping as P4.T9 for index count accuracy -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P4.T9</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">Verificar que todos los agentes cargan correctamente en el registry</div>
<div class="task-files">
<span class="file-tag">core/src/agentforge_core/registry/repository.py</span>
</div>
<div class="task-notes">
Arrancar el core con <code>docker compose up core</code> y hacer
<code>GET /agents</code>. 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.
</div>
</div>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════
FASE 5 · Políticas de trading (YAML)
══════════════════════════════════════════════════════════════ -->
<div class="phase">
<div class="phase-header">
<div class="phase-num">FASE 5</div>
<div class="phase-title">Políticas de trading — definiciones YAML</div>
<div class="phase-stats">
<span class="badge badge-done">0 done</span>
<span class="badge badge-todo">7 todo</span>
</div>
<div class="phase-desc">Una política por asset class más la actualización de la default. Depende de Fase 2 (los validadores deben estar registrados).</div>
</div>
<div class="task-list">
<!-- P5.T1 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P5.T1</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">policies/trading_equity/ — index.yaml + versions/v1.yaml</div>
<div class="task-files">
<span class="file-tag new">policies/trading_equity/index.yaml</span>
<span class="file-tag new">policies/trading_equity/versions/v1.yaml</span>
</div>
<div class="task-notes">
Política para renta variable (NYSE, NASDAQ, IBEX…). Validators:
<br><strong>Input:</strong> <code>detect_pii</code> (block EMAIL, IBAN),
<code>prompt_injection</code> (block), <code>market_hours_check</code>
(asset_class: equity, tz: America/New_York, severity: warning),
<code>forbidden_topics</code> (block: ["insider", "material non-public",
"manipulación de mercado"]).
<br><strong>Output:</strong> <code>schema_match</code> (block, schema validando
proposed_actions con campos de trading), <code>pii_leakage</code> (block),
<code>forbidden_action_keywords</code> (block: ["liquidate all", "margin call ignore",
"DROP TABLE"]), <code>trading_safety_rules</code> (block: all rules),
<code>position_size_limit</code> (block, max_notional_usd: 1000000),
<code>forbidden_instruments</code> (block: restricted list de ejemplo),
<br><code>on_validator_error: fail_closed</code>
</div>
</div>
</div>
<!-- P5.T2 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P5.T2</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">policies/trading_fx/ — index.yaml + versions/v1.yaml</div>
<div class="task-files">
<span class="file-tag new">policies/trading_fx/index.yaml</span>
<span class="file-tag new">policies/trading_fx/versions/v1.yaml</span>
</div>
<div class="task-notes">
Política para FX (EURUSD, GBPUSD, USDJPY…). Diferencias respecto a equity:
<code>market_hours_check</code> con <code>asset_class: fx</code> (solo bloquea
en weekend), sin límite de horario intradía. El límite de notional es mayor:
<code>max_notional_usd: 5000000</code> (FX es mercado más líquido).
<code>trading_safety_rules</code> activa solo
<code>no_order_without_stop_loss</code> y <code>no_leveraged_action_without_hedge</code>.
</div>
</div>
</div>
<!-- P5.T3 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P5.T3</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">policies/trading_crypto/ — index.yaml + versions/v1.yaml</div>
<div class="task-files">
<span class="file-tag new">policies/trading_crypto/index.yaml</span>
<span class="file-tag new">policies/trading_crypto/versions/v1.yaml</span>
</div>
<div class="task-notes">
Política para crypto (BTC, ETH, SOL…). Sin restricción de horario (24/7).
Límites más conservadores: <code>max_notional_usd: 250000</code> (mayor volatilidad).
<code>market_hours_check</code> ausente o con <code>asset_class: crypto</code>
(nunca bloquea). <code>forbidden_instruments</code> incluye tokens con
restricciones regulatorias.
</div>
</div>
</div>
<!-- P5.T4 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P5.T4</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">policies/default/ — actualizar v1.yaml (ya referenciado en P0.T2)</div>
<div class="task-files">
<span class="file-tag">policies/default/versions/v1.yaml</span>
</div>
<div class="task-notes">
Completar la tarea P0.T2 una vez que P2.T1 esté done (trading_safety_rules existe).
La política <code>default</code> 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.
<br><strong>Nota</strong>: Esta tarea es la finalización formal de P0.T2.
</div>
</div>
</div>
<!-- P5.T5 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P5.T5</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">Verificar que las 3 políticas nuevas cargan en el PolicyStore</div>
<div class="task-files">
<span class="file-tag">core/src/agentforge_core/registry/policy_store.py</span>
</div>
<div class="task-notes">
Hacer <code>GET /policies</code> y comprobar que aparecen
<code>trading_equity</code>, <code>trading_fx</code>, <code>trading_crypto</code>.
Hacer <code>GET /policies/trading_equity</code> y verificar que los validadores
se deserializan correctamente sin errores de Pydantic.
</div>
</div>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════
FASE 6 · Extensiones de API y runtime
══════════════════════════════════════════════════════════════ -->
<div class="phase">
<div class="phase-header">
<div class="phase-num">FASE 6</div>
<div class="phase-title">Extensiones de API y runtime</div>
<div class="phase-stats">
<span class="badge badge-done">0 done</span>
<span class="badge badge-todo">7 todo</span>
</div>
<div class="phase-desc">Tres features nuevas: modo dry-run, kill switch y SLA HITL. Depende de Fase 1 (hitl_timeout_minutes en AgentDefinition, dry_run en AgentState).</div>
</div>
<div class="task-list">
<!-- P6.T1 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P6.T1</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">api/executions.py — añadir dry_run a InvokeRequest y propagarlo</div>
<div class="task-files">
<span class="file-tag">core/src/agentforge_core/api/executions.py</span>
</div>
<div class="task-notes">
Añadir <code>dry_run: bool = False</code> a <code>InvokeRequest</code>.
En el handler <code>invoke_agent()</code>, añadir <code>dry_run=body.dry_run</code>
a la llamada <code>orchestrator.invoke()</code>. El orquestrador inyectará el valor
en el estado inicial del grafo (<code>initial["dry_run"] = dry_run</code>).
Si <code>dry_run=True</code>, la ejecución NO se escribe en el JSONL de historial
(solo en el checkpointer temporal). Añadir campo <code>dry_run: bool</code>
a <code>AgentExecution</code> en <code>domain/execution.py</code> para que el
response body refleje el modo.
</div>
</div>
</div>
<!-- P6.T2 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P6.T2</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">runtime/nodes.py — nodo finalize respeta dry_run</div>
<div class="task-files">
<span class="file-tag">core/src/agentforge_core/runtime/nodes.py</span>
</div>
<div class="task-notes">
En <code>build_node_finalize()</code>, añadir al inicio:
<code>if state.get("dry_run"): final_output = {**parsed, "approved_actions": final_actions, "dry_run": True}</code>
El comportamiento es idéntico excepto que el output está marcado con
<code>"dry_run": True</code>. 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
<code>dry_run=True</code> para trazabilidad.
</div>
</div>
</div>
<!-- P6.T3 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P6.T3</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">api/control.py — nuevo router con kill switch</div>
<div class="task-files">
<span class="file-tag new">core/src/agentforge_core/api/control.py</span>
</div>
<div class="task-notes">
Crear router con dos endpoints:
<ul>
<li><code>GET /control/status</code>: devuelve <code>{"halted": bool, "pending_hitl_count": int,
"message": str}</code>. Lee el estado global del sistema.</li>
<li><code>POST /control/halt</code>: activa el kill switch. Comportamiento:
<ol>
<li>Establece un flag global <code>system_halted = True</code> en el estado de la app
(variable de módulo o en <code>Settings</code> — MVP puede ser un módulo singleton).</li>
<li>Lee <code>execution_index.json</code>, encuentra todas las ejecuciones en
<code>awaiting_approval</code> y las rechaza automáticamente con reason
<code>"kill_switch_activated"</code> usando <code>orchestrator.resume()</code>.</li>
<li>Devuelve <code>{"halted": true, "auto_rejected": N}</code>.</li>
</ol>
</li>
<li><code>POST /control/resume</code>: desactiva el kill switch (restaura operación normal).</li>
</ul>
En el handler <code>invoke_agent()</code> de <code>executions.py</code>, añadir una
comprobación al inicio: si <code>system_halted</code>, devolver 503.
</div>
</div>
</div>
<!-- P6.T4 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P6.T4</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">main.py — montar router de control y añadir lifespan con SLA checker</div>
<div class="task-files">
<span class="file-tag">core/src/agentforge_core/main.py</span>
</div>
<div class="task-notes">
Dos cambios:
<ul>
<li>Importar y montar <code>control.router</code> con prefix <code>/control</code>,
tag <code>["control"]</code>.</li>
<li>Añadir función <code>lifespan</code> con <code>asyncio.create_task</code> para
el background checker de SLA HITL. La tarea corre en bucle cada
<code>settings.hitl_sla_check_interval_seconds</code> segundos.</li>
</ul>
El lifespan de FastAPI se declara con el context manager async:
<code>@asynccontextmanager async def lifespan(app): task = asyncio.create_task(sla_checker()); yield; task.cancel()</code>.
</div>
</div>
</div>
<!-- P6.T5 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P6.T5</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">SLA HITL checker — función background task</div>
<div class="task-files">
<span class="file-tag new">core/src/agentforge_core/runtime/sla.py</span>
</div>
<div class="task-notes">
Crear módulo <code>runtime/sla.py</code> con la función async
<code>run_hitl_sla_checker(settings, registry, policies, orchestrator)</code>.
Algoritmo en cada tick:
<ol>
<li>Leer <code>execution_index.json</code>.</li>
<li>Para cada trace_id, obtener snapshot del checkpointer.</li>
<li>Si status es <code>awaiting_approval</code>:
<ul>
<li>Obtener <code>agent_def</code> del registry.</li>
<li>Si <code>agent_def.hitl_timeout_minutes</code> no es None:
<ul>
<li>Comparar <code>execution.started_at + timedelta(minutes=hitl_timeout_minutes)</code>
con <code>datetime.now(UTC)</code>.</li>
<li>Si expirado: llamar <code>orchestrator.resume(decision={"approved_action_ids": [],
"rejected": True, "reason": "hitl_sla_expired"})</code> y escribir en JSONL.</li>
</ul>
</li>
</ul>
</li>
</ol>
Logear cada auto-rechazo con structlog a nivel WARNING incluyendo trace_id y agent_name.
</div>
</div>
</div>
<!-- P6.T6 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P6.T6</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">runtime/orchestrator.py — propagar dry_run en invoke()</div>
<div class="task-files">
<span class="file-tag">core/src/agentforge_core/runtime/orchestrator.py</span>
</div>
<div class="task-notes">
Añadir parámetro <code>dry_run: bool = False</code> a <code>invoke()</code>.
Incluirlo en el dict <code>initial</code>: <code>initial["dry_run"] = dry_run</code>.
No se propaga a <code>resume()</code> — el dry_run se decide en la invocación inicial
y queda embebido en el estado del checkpointer.
</div>
</div>
</div>
<!-- P6.T7 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P6.T7</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">Smoke test manual de dry-run y kill switch</div>
<div class="task-files">
<span class="file-tag">docs/manual_qa.md</span>
</div>
<div class="task-notes">
Actualizar <code>docs/manual_qa.md</code> con los pasos de prueba manual:
<ul>
<li>Dry-run: invocar <code>signal_analyzer</code> con <code>dry_run=true</code>,
verificar que el output tiene <code>"dry_run": true</code> y que NO aparece en
el historial (<code>GET /executions</code> no lo lista).</li>
<li>Kill switch: invocar agente, pausar en HITL, activar <code>POST /control/halt</code>,
verificar que la ejecución pasa a <code>failed</code> con error <code>kill_switch_activated</code>,
verificar que intentar invocar devuelve 503. Luego <code>POST /control/resume</code> y
verificar que vuelve a funcionar.</li>
<li>SLA HITL: configurar <code>hitl_timeout_minutes: 1</code> en un agente de test,
invocar, esperar 65 segundos, verificar auto-rechazo.</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════
FASE 7 · Dashboard — actualizaciones
══════════════════════════════════════════════════════════════ -->
<div class="phase">
<div class="phase-header">
<div class="phase-num">FASE 7</div>
<div class="phase-title">Dashboard — actualizaciones de UI</div>
<div class="phase-stats">
<span class="badge badge-done">0 done</span>
<span class="badge badge-todo">5 todo</span>
</div>
<div class="phase-desc">Mayoritariamente cosmético. El dashboard es agnóstico de dominio — solo cambiar strings y añadir campos de trading en la vista de aprobaciones. Sin dependencias de código.</div>
</div>
<div class="task-list">
<!-- P7.T1 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P7.T1</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">Página Ejecutar — actualizar strings y añadir checkbox dry-run</div>
<div class="task-files">
<span class="file-tag">dashboard/src/agentforge_dashboard/pages/2_▶️_Ejecutar.py</span>
</div>
<div class="task-notes">
Cambios:
<ul>
<li><code>st.title</code>: "▶️ Ejecutar Agente" → sin cambio (genérico).</li>
<li><code>st.text_area</code> label: <code>"Descripción del incidente"</code>
<code>"Señal de mercado / Instrucción de trading"</code>.</li>
<li><code>placeholder</code>: actualizar con ejemplo de señal de trading.</li>
<li>Añadir <code>dry_run = st.checkbox("🔬 Modo simulación (dry-run)", value=False)</code>
antes del botón de invocar.</li>
<li>Pasar <code>{"input": user_input, "dry_run": dry_run}</code> al client.</li>
<li>Si <code>execution["final_output"]["dry_run"]</code> es True, mostrar
badge amarillo "SIMULACIÓN — sin efecto real".</li>
</ul>
</div>
</div>
</div>
<!-- P7.T2 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P7.T2</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">Página Aprobaciones — mostrar campos de trading prominentemente</div>
<div class="task-files">
<span class="file-tag">dashboard/src/agentforge_dashboard/pages/3_🤝_Aprobaciones.py</span>
</div>
<div class="task-notes">
En el loop de acciones propuestas, si la acción tiene campos de trading, mostrarlos:
<ul>
<li><code>notional_usd</code>: si presente, mostrar en grande con color según tamaño
(&lt;100K verde, 100K1M amarillo, &gt;1M rojo).</li>
<li><code>stop_loss</code>: mostrar si presente.</li>
<li><code>quantity</code>: mostrar si presente.</li>
<li><code>asset_class</code>: badge de tipo de activo si presente.</li>
</ul>
Estos campos son opcionales en el modelo existente — mostrarlos solo si existen
(no romper compatibilidad si faltan).
</div>
</div>
</div>
<!-- P7.T3 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P7.T3</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">app.py — actualizar título y caption de la aplicación</div>
<div class="task-files">
<span class="file-tag">dashboard/src/agentforge_dashboard/app.py</span>
</div>
<div class="task-notes">
Cambiar <code>st.set_page_config</code> title a <code>"AgentForge Trading"</code>
y el icono a 📈. Actualizar la página principal (si existe contenido) para describir
la plataforma como fábrica de agentes de trading.
</div>
</div>
</div>
<!-- P7.T4 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P7.T4</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">main.py (FastAPI) — actualizar title y description</div>
<div class="task-files">
<span class="file-tag">core/src/agentforge_core/main.py</span>
</div>
<div class="task-notes">
En <code>create_app()</code>: <code>title="AgentForge Trading"</code>,
<code>description="Plataforma de gobernanza de agentes IA para trading — API REST."</code>,
<code>version="0.2.0"</code>. Esto actualiza la UI de /docs de Swagger.
(Este cambio puede hacerse junto con P6.T4 que también modifica main.py.)
</div>
</div>
</div>
<!-- P7.T5 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P7.T5</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">Página Políticas — verificar que muestra las nuevas políticas trading</div>
<div class="task-files">
<span class="file-tag">dashboard/src/agentforge_dashboard/pages/5_📐_Politicas.py</span>
</div>
<div class="task-notes">
Verificación funcional sin cambio de código. Abrir la página, confirmar que
<code>trading_equity</code>, <code>trading_fx</code>, <code>trading_crypto</code>
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í.
</div>
</div>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════
FASE 8 · Suite de tests
══════════════════════════════════════════════════════════════ -->
<div class="phase">
<div class="phase-header">
<div class="phase-num">FASE 8</div>
<div class="phase-title">Suite de tests</div>
<div class="phase-stats">
<span class="badge badge-done">0 done</span>
<span class="badge badge-todo">9 todo</span>
</div>
<div class="phase-desc">Actualizar fixtures y tests existentes. Añadir tests para features nuevas. Goal: make test-all verde con todos los cambios del repurpose. Depende de todas las fases de código anteriores.</div>
</div>
<div class="task-list">
<!-- P8.T1 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P8.T1</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">Actualizar fixtures de agente (tests/fixtures)</div>
<div class="task-files">
<span class="file-tag del">tests/fixtures/agents/incident_analyzer/</span>
<span class="file-tag new">tests/fixtures/agents/signal_analyzer/index.yaml</span>
<span class="file-tag new">tests/fixtures/agents/signal_analyzer/versions/v1.yaml</span>
<span class="file-tag">tests/fixtures/agents/test_agent/versions/v1.yaml</span>
</div>
<div class="task-notes">
<ul>
<li>Eliminar <code>tests/fixtures/agents/incident_analyzer/</code>.</li>
<li>Crear <code>tests/fixtures/agents/signal_analyzer/</code> con index y v1 (copia
simplificada de los YAML de producción, con <code>provider: mock</code>).</li>
<li>Actualizar <code>test_agent/versions/v1.yaml</code>: cambiar el <code>output_schema</code>
y <code>system_prompt</code> para que sean de trading (los tests que usan test_agent
deben funcionar con el nuevo schema de validación de políticas trading).</li>
</ul>
</div>
</div>
</div>
<!-- P8.T2 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P8.T2</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">Actualizar fixtures de política (tests/fixtures)</div>
<div class="task-files">
<span class="file-tag">tests/fixtures/policies/default/versions/v1.yaml</span>
<span class="file-tag">tests/fixtures/policies/test_policy/versions/v1.yaml</span>
<span class="file-tag new">tests/fixtures/policies/trading_equity/index.yaml</span>
<span class="file-tag new">tests/fixtures/policies/trading_equity/versions/v1.yaml</span>
</div>
<div class="task-notes">
Sincronizar las fixtures de política con los YAMLs de producción actualizados.
La fixture <code>test_policy</code> puede simplificarse a solo <code>schema_match</code>
para mantener los tests unitarios simples. Añadir fixture <code>trading_equity</code>
para los tests de integración que usan signal_analyzer.
</div>
</div>
</div>
<!-- P8.T3 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P8.T3</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">Tests de integración — actualizar para usar signal_analyzer</div>
<div class="task-files">
<span class="file-tag">tests/integration/conftest.py</span>
<span class="file-tag">tests/integration/test_invoke_happy_path.py</span>
<span class="file-tag">tests/integration/test_invoke_hitl.py</span>
<span class="file-tag">tests/integration/test_invoke_pii_block.py</span>
<span class="file-tag">tests/integration/test_invoke_resume_after_restart.py</span>
</div>
<div class="task-notes">
Reemplazar todas las referencias a <code>incident_analyzer</code> y al output schema
telecom con <code>signal_analyzer</code> y el output schema de trading.
El <code>MockProvider</code> devuelve un JSON hardcodeado — actualizar el mock response
en <code>conftest.py</code> para que devuelva el formato de señal de trading:
<code>{"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}]}</code>.
Los tests de PII, HITL y restart no necesitan cambios lógicos, solo las fixtures.
</div>
</div>
</div>
<!-- P8.T4 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P8.T4</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">Tests unitarios — nuevos validadores de trading</div>
<div class="task-files">
<span class="file-tag new">tests/unit/test_validators_trading.py</span>
</div>
<div class="task-notes">
Crear fichero con tests para los 4 validadores nuevos:
<ul>
<li><code>trading_safety_rules</code>: test happy path (con stop_loss), test que bloquea
sin stop_loss, test que bloquea mass liquidation sin tranche, test leveraged sin hedge.</li>
<li><code>position_size_limit</code>: test que pasa si notional &lt; max,
test que bloquea si notional &gt; max, test que emite warning si notional ausente.</li>
<li><code>forbidden_instruments</code>: test que pasa con ticker permitido,
test que bloquea con ticker restringido (case-insensitive).</li>
<li><code>market_hours_check</code>: mockear <code>datetime.now</code> para simular
dentro/fuera de horario para equity, fx, crypto.</li>
</ul>
</div>
</div>
</div>
<!-- P8.T5 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P8.T5</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">Tests unitarios — domain/agent.py con hitl_timeout_minutes</div>
<div class="task-files">
<span class="file-tag">tests/unit/test_domain_agent.py</span>
</div>
<div class="task-notes">
Añadir casos: <code>AgentDefinition</code> con <code>hitl_timeout_minutes=None</code>
(default), con valor válido (30), con valor fuera de rango (0 falla, 1441 falla).
Verificar que el Literal de <code>LLMConfig.provider</code> acepta <code>"anthropic"</code>.
</div>
</div>
</div>
<!-- P8.T6 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P8.T6</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">Test de integración — dry-run</div>
<div class="task-files">
<span class="file-tag new">tests/integration/test_invoke_dry_run.py</span>
</div>
<div class="task-notes">
Test que invoca <code>signal_analyzer</code> con <code>dry_run=True</code>:
<ol>
<li>El status debe ser <code>completed</code>.</li>
<li>El <code>final_output</code> debe contener <code>"dry_run": True</code>.</li>
<li>El historial (<code>executions.jsonl</code>) no debe contener la ejecución.</li>
<li>La ejecución SÍ debe aparecer en el índice de memoria del checkpointer
(temporalmente).</li>
</ol>
</div>
</div>
</div>
<!-- P8.T7 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P8.T7</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">Test unitario — kill switch (api/control.py)</div>
<div class="task-files">
<span class="file-tag new">tests/unit/test_api_control.py</span>
</div>
<div class="task-notes">
Tests con FastAPI <code>TestClient</code>:
<ul>
<li><code>GET /control/status</code> devuelve <code>{"halted": false, ...}</code> por defecto.</li>
<li><code>POST /control/halt</code> cambia el estado a <code>halted: true</code>.</li>
<li>Tras halt, <code>POST /agents/test_agent/invoke</code> devuelve 503.</li>
<li><code>POST /control/resume</code> restaura operación normal.</li>
</ul>
</div>
</div>
</div>
<!-- P8.T8 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P8.T8</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">Actualizar tests unitarios existentes que referencian telco</div>
<div class="task-files">
<span class="file-tag">tests/unit/test_guardrails_ai.py</span>
<span class="file-tag">tests/unit/test_runtime_nodes.py</span>
<span class="file-tag">tests/unit/test_runtime_orchestrator.py</span>
</div>
<div class="task-notes">
Eliminar cualquier referencia a <code>telco_safety_rules</code> en
<code>test_guardrails_ai.py</code>. Actualizar los mocks de <code>AgentDefinition</code>
en <code>test_runtime_nodes.py</code> y <code>test_runtime_orchestrator.py</code>
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).
</div>
</div>
</div>
<!-- P8.T9 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P8.T9</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">make test-all verde — validación final</div>
<div class="task-files">
<span class="file-tag">Makefile</span>
</div>
<div class="task-notes">
Ejecutar <code>make test-all</code> (unit + integration) y <code>make lint</code>
(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.
</div>
</div>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════
FASE 9 · Documentación
══════════════════════════════════════════════════════════════ -->
<div class="phase">
<div class="phase-header">
<div class="phase-num">FASE 9</div>
<div class="phase-title">Documentación</div>
<div class="phase-stats">
<span class="badge badge-done">0 done</span>
<span class="badge badge-todo">3 todo</span>
</div>
<div class="phase-desc">Actualizar README, ARCHITECTURE y roadmap. Los docs internos (explicacion.md, componentes.md) quedan como trabajo futuro si el usuario lo solicita. Sin dependencias técnicas — puede hacerse en paralelo.</div>
</div>
<div class="task-list">
<!-- P9.T1 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P9.T1</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">README.md — actualizar para contexto trading</div>
<div class="task-files">
<span class="file-tag">README.md</span>
</div>
<div class="task-notes">
Cambios:
<ul>
<li>Título y subtítulo: enfocado en trading.</li>
<li>Sección "¿Por qué?": adaptar al contexto de riesgo financiero y compliance.</li>
<li>Demo guiada: actualizar para usar <code>signal_analyzer</code> y los nuevos escenarios.</li>
<li>Tabla de capacidades: añadir dry-run, kill switch, SLA HITL, Anthropic provider.</li>
<li>Guardrails de trading: mencionar los 4 nuevos validadores.</li>
<li>Sección de agentes disponibles: listar los 4 agentes con su propósito.</li>
</ul>
</div>
</div>
</div>
<!-- P9.T2 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P9.T2</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">ARCHITECTURE.md — actualizar diagrama y descripciones</div>
<div class="task-files">
<span class="file-tag">ARCHITECTURE.md</span>
</div>
<div class="task-notes">
Actualizar el diagrama ASCII para incluir el nuevo componente <code>control</code>
(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.
</div>
</div>
</div>
<!-- P9.T3 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P9.T3</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">docs/futuro.md — actualizar roadmap para trading</div>
<div class="task-files">
<span class="file-tag">docs/futuro.md</span>
</div>
<div class="task-notes">
Reescribir el roadmap orientado a trading:
<ul>
<li>Integración con market data feeds (Polygon.io, Refinitiv).</li>
<li>Context injection: posiciones actuales de cartera en el prompt vía RAG.</li>
<li>Cuatro ojos: segundo approver para operaciones &gt;$1M.</li>
<li>P&amp;L attribution por agente/decisión.</li>
<li>Persistencia en Postgres + pgvector para alta disponibilidad.</li>
<li>OpenTelemetry: latencias por nodo, métricas de guardrail activations.</li>
<li>Stress testing: ejecutar agentes contra series históricas (2020 COVID crash, etc.).</li>
<li>Compliance reports: exportar audit trail en formato EMIR/MiFID II.</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════
FASE 10 · Configuración e infraestructura
══════════════════════════════════════════════════════════════ -->
<div class="phase">
<div class="phase-header">
<div class="phase-num">FASE 10</div>
<div class="phase-title">Configuración e infraestructura</div>
<div class="phase-stats">
<span class="badge badge-done">0 done</span>
<span class="badge badge-todo">2 todo</span>
</div>
<div class="phase-desc">Cambios menores a pyproject.toml y verificación del smoke test Docker. Las dependencias de requirements.txt ya se cubren en Fase 3.</div>
</div>
<div class="task-list">
<!-- P10.T1 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P10.T1</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">pyproject.toml — actualizar descripción del proyecto</div>
<div class="task-files">
<span class="file-tag">pyproject.toml</span>
</div>
<div class="task-notes">
Cambiar <code>description</code> de <code>"Plataforma profesional de gobernanza de agentes IA"</code>
a <code>"Plataforma de gobernanza de agentes IA para trading — fábrica de agentes con guardrails, HITL y auditoría"</code>.
Sin cambios en dependencias ni en la configuración de herramientas.
</div>
</div>
</div>
<!-- P10.T2 -->
<div class="task" data-status="todo">
<div class="task-left">
<div class="task-id">P10.T2</div>
<span class="status-badge">todo</span>
</div>
<div class="task-right">
<div class="task-title">make smoke — verificar que el stack Docker completo arranca</div>
<div class="task-files">
<span class="file-tag">docker-compose.yml</span>
<span class="file-tag">Makefile</span>
</div>
<div class="task-notes">
Ejecutar <code>make smoke</code> (docker compose up + curl /health) con todos los
cambios del repurpose. Verificar:
<ul>
<li>Core arranca sin errores de arranque (guardrails registrados, NeMo keywords).</li>
<li>Dashboard arranca y carga los nuevos agentes.</li>
<li><code>GET /agents</code> devuelve los 4 agentes de trading.</li>
<li><code>GET /policies</code> devuelve las 3 políticas de trading + default.</li>
<li><code>GET /control/status</code> devuelve <code>{"halted": false, ...}</code>.</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════
ORDEN DE EJECUCIÓN RECOMENDADO
══════════════════════════════════════════════════════════════ -->
<hr>
<div style="background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 1.5rem; margin-bottom: 2rem;">
<h2 style="color: #fff; margin-bottom: 1rem; font-size: 1.1rem;">Orden de ejecución recomendado</h2>
<div style="color: var(--muted); font-size: .85rem; line-height: 1.8;">
<p>Las fases son mayoritariamente independientes pero tienen algunas dependencias duras:</p>
<ol style="padding-left: 1.5rem; margin-top: .5rem;">
<li style="margin-bottom: .4rem;"><strong style="color: #d1d5db;">Fase 2</strong> debe completarse antes que <strong style="color: #d1d5db;">Fase 5</strong> (los validadores deben existir para que las políticas los usen en los YAML — validación en arranque) y antes de finalizar <strong style="color: #d1d5db;">P0.T2</strong>.</li>
<li style="margin-bottom: .4rem;"><strong style="color: #d1d5db;">Fase 1</strong> (extensiones de dominio) debe completarse antes que <strong style="color: #d1d5db;">Fases 6</strong> (dry_run en state, hitl_timeout_minutes en AgentDefinition).</li>
<li style="margin-bottom: .4rem;"><strong style="color: #d1d5db;">Fase 3</strong> (Anthropic) depende de <strong style="color: #d1d5db;">P1.T1</strong> y <strong style="color: #d1d5db;">P1.T3</strong>.</li>
<li style="margin-bottom: .4rem;"><strong style="color: #d1d5db;">Fases 4 y 5</strong> son mayoritariamente YAML — pueden hacerse en cualquier momento después de Fase 2.</li>
<li style="margin-bottom: .4rem;"><strong style="color: #d1d5db;">Fase 8</strong> (tests) debe hacerse tras completar todas las fases de código (07).</li>
<li style="margin-bottom: .4rem;"><strong style="color: #d1d5db;">Fases 7, 9 y 10</strong> son independientes y pueden hacerse en paralelo con las demás.</li>
</ol>
<p style="margin-top: .75rem;">
<strong style="color: #d1d5db;">Secuencia óptima por sesión:</strong>
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.
</p>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════
INVARIANTES — QUÉ NUNCA TOCAR
══════════════════════════════════════════════════════════════ -->
<div style="background: #1a1222; border: 1px solid #3d1f6e; border-radius: 10px; padding: 1.5rem; margin-bottom: 2rem;">
<h2 style="color: #c084fc; margin-bottom: 1rem; font-size: 1.1rem;">⚠️ Invariantes — no modificar</h2>
<div style="color: #a78bfa; font-size: .85rem; line-height: 1.8; font-family: var(--mono);">
<ul style="padding-left: 1.5rem;">
<li>runtime/graph.py — grafo LangGraph, topología de nodos</li>
<li>runtime/checkpointer.py — persistencia SQLite de ejecuciones</li>
<li>runtime/orchestrator.py — lógica de invoke/resume/snapshot (solo añadir dry_run)</li>
<li>guardrails/base.py — Protocol GuardrailEngine</li>
<li>guardrails/composite.py — CompositeGuardrailEngine</li>
<li>guardrails/validators.py: detect_pii, prompt_injection, toxic_language, schema_match, pii_leakage, forbidden_action_keywords</li>
<li>domain/guardrail.py — GuardrailViolation</li>
<li>domain/execution.py — AgentExecution, ProposedAction (solo añadir dry_run)</li>
<li>domain/policy.py — PolicyDefinition, fail_open/fail_closed</li>
<li>api/agents.py, api/policies.py, api/violations.py — endpoints sin cambios</li>
<li>api/executions.py — endpoints approve/reject (solo añadir dry_run en InvokeRequest)</li>
<li>registry/repository.py, registry/versioning.py, registry/policy_store.py</li>
<li>observability/logging.py, api/middlewares.py</li>
<li>llm/base.py, llm/mock.py, llm/azure.py, llm/openai.py</li>
</ul>
</div>
</div>
<div style="text-align: center; color: var(--muted); font-size: .78rem; padding: 1rem 0 2rem;">
plan.html — AgentForge Trading · Generado 2026-05-21 · Actualizar data-status="todo|in-progress|done" al ejecutar cada tarea
</div>
</body>
</html>