1902 lines
89 KiB
HTML
1902 lines
89 KiB
HTML
<!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 265–300).
|
||
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:30–16: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>=0.40,<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 >$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
|
||
(<100K verde, 100K–1M amarillo, >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 < max,
|
||
test que bloquea si notional > 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 >$1M.</li>
|
||
<li>P&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 (0–7).</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>
|