the forgeeeee

This commit is contained in:
2026-05-27 21:28:10 +02:00
parent 34b11dfc66
commit 7d0f10d21f
14 changed files with 605 additions and 515 deletions
@@ -3,12 +3,12 @@
<div> <div>
<div class="flex items-center gap-3 flex-wrap"> <div class="flex items-center gap-3 flex-wrap">
<span class="text-3xl font-semibold tracking-tight">{{ agent.name }}</span> <span class="text-3xl font-semibold tracking-tight">{{ agent.name }}</span>
<span class="text-sm px-2.5 py-0.5 rounded-lg bg-zinc-800 text-zinc-400">v{{ agent.version }}</span> <span class="text-sm px-2.5 py-0.5 rounded bg-forge-surface2 text-forge-muted border border-forge-iron">v{{ agent.version }}</span>
<span class="text-sm px-2.5 py-0.5 rounded-lg {% if agent.state == 'active' %}bg-emerald-900 text-emerald-300{% else %}bg-zinc-800 text-zinc-300{% endif %}"> <span class="text-sm px-2.5 py-0.5 rounded {% if agent.state == 'active' %}bg-emerald-950 text-emerald-400 border border-emerald-900{% else %}bg-forge-surface2 text-forge-steel border border-forge-iron{% endif %}">
{{ agent.state }} {{ agent.state }}
</span> </span>
</div> </div>
<div class="text-zinc-400 mt-2 text-[15px] leading-snug"> <div class="text-forge-muted mt-2 text-[15px] leading-snug">
{{ agent.purpose }} {{ agent.purpose }}
</div> </div>
</div> </div>
@@ -16,16 +16,16 @@
<!-- Metadata principal --> <!-- Metadata principal -->
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 text-sm"> <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 text-sm">
<div> <div>
<div class="text-zinc-400 text-xs mb-1">Owner</div> <div class="text-forge-muted text-xs mb-1">Smith</div>
<div class="font-medium">{{ agent.owner }}</div> <div class="font-medium">{{ agent.owner }}</div>
</div> </div>
<div> <div>
<div class="text-zinc-400 text-xs mb-1">Risk threshold (HITL)</div> <div class="text-forge-muted text-xs mb-1">Risk Threshold (Judgment)</div>
<div class="font-medium">{{ agent.risk_threshold_for_hitl }}</div> <div class="font-medium">{{ agent.risk_threshold_for_hitl }}</div>
</div> </div>
{% if agent.category %} {% if agent.category %}
<div> <div>
<div class="text-zinc-400 text-xs mb-1">Category</div> <div class="text-forge-muted text-xs mb-1">Blade Type</div>
<div>{{ agent.category }}</div> <div>{{ agent.category }}</div>
</div> </div>
{% endif %} {% endif %}
@@ -34,68 +34,68 @@
<!-- LLM + Guardrails --> <!-- LLM + Guardrails -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div> <div>
<div class="text-zinc-400 text-xs mb-1.5">LLM</div> <div class="text-forge-muted text-xs mb-1.5">LLM Steel</div>
<div class="font-mono text-xs bg-zinc-950 border border-zinc-800 p-3 rounded-xl"> <div class="font-mono text-xs bg-black/40 border border-forge-iron p-3 rounded-xl">
{{ agent.llm.provider }} / {{ agent.llm.model }}<br> {{ agent.llm.provider }} / {{ agent.llm.model }}<br>
<span class="text-zinc-500">temp={{ agent.llm.temperature }} · max={{ agent.llm.max_tokens }}</span> <span class="text-forge-muted">temp={{ agent.llm.temperature }} · max={{ agent.llm.max_tokens }}</span>
</div> </div>
</div> </div>
<div> <div>
<div class="text-zinc-400 text-xs mb-1.5">Guardrails</div> <div class="text-forge-muted text-xs mb-1.5">Guardrails (Forge Laws)</div>
<div class="flex flex-wrap gap-1.5"> <div class="flex flex-wrap gap-1.5">
{% for g in agent.guardrails %} {% for g in agent.guardrails %}
<span class="text-xs px-3 py-1 bg-zinc-900 border border-zinc-800 rounded-full">{{ g }}</span> <span class="text-xs px-3 py-1 bg-forge-surface2 border border-forge-iron rounded-full">{{ g }}</span>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
</div> </div>
<!-- Comportamiento (Prompt + Output Schema) --> <!-- Behavior (Prompt + Output Schema) -->
<div> <div>
<div class="text-zinc-400 text-xs mb-1.5">Comportamiento</div> <div class="text-forge-muted text-xs mb-1.5">Edge &amp; Temper</div>
<!-- System Prompt colapsado --> <!-- System Prompt collapsed -->
<details class="mb-3 group"> <details class="mb-3 group">
<summary class="cursor-pointer select-none text-sm font-medium px-4 py-2 bg-zinc-900 hover:bg-zinc-800 border border-zinc-800 rounded-xl flex items-center justify-between"> <summary class="cursor-pointer select-none text-sm font-medium px-4 py-2 bg-forge-surface2 hover:bg-forge-surface border border-forge-iron rounded-xl flex items-center justify-between">
<span>View system prompt</span> <span>View blade oath</span>
<span class="text-xs text-zinc-500 group-open:hidden">expandir</span> <span class="text-xs text-forge-muted group-open:hidden">show</span>
<span class="text-xs text-zinc-500 hidden group-open:inline">colapsar</span> <span class="text-xs text-forge-muted hidden group-open:inline">hide</span>
</summary> </summary>
<div class="mt-2 p-4 bg-black border border-zinc-800 rounded-xl text-sm whitespace-pre-wrap font-light leading-relaxed"> <div class="mt-2 p-4 bg-black/60 border border-forge-iron rounded-xl text-sm whitespace-pre-wrap font-light leading-relaxed">
{{ agent.system_prompt }} {{ agent.system_prompt }}
</div> </div>
</details> </details>
<!-- Output Schema --> <!-- Output Schema -->
<div> <div>
<div class="text-xs text-zinc-400 mb-1.5">Output schema</div> <div class="text-xs text-forge-muted mb-1.5">Output schema</div>
<pre class="text-[10px] bg-zinc-950 border border-zinc-800 p-3 rounded-xl overflow-auto max-h-48 text-zinc-300">{{ agent.output_schema | tojson(indent=2) }}</pre> <pre class="text-[10px] bg-black/40 border border-forge-iron p-3 rounded-xl overflow-auto max-h-48 text-forge-steel">{{ agent.output_schema | tojson(indent=2) }}</pre>
</div> </div>
</div> </div>
<!-- Metadatos adicionales --> <!-- Additional metadata -->
{% if agent.input_label or agent.input_placeholder or agent.updated_at %} {% if agent.input_label or agent.input_placeholder or agent.updated_at %}
<div class="pt-2 border-t border-zinc-800"> <div class="pt-2 border-t border-forge-iron">
<div class="text-zinc-400 text-xs mb-2">Metadatos</div> <div class="text-forge-muted text-xs mb-2">Anvil Marks</div>
<div class="text-sm space-y-1 text-zinc-300"> <div class="text-sm space-y-1 text-forge-steel">
{% if agent.input_label %} {% if agent.input_label %}
<div><span class="text-zinc-500">Input label:</span> {{ agent.input_label }}</div> <div><span class="text-forge-muted">Input label:</span> {{ agent.input_label }}</div>
{% endif %} {% endif %}
{% if agent.input_placeholder %} {% if agent.input_placeholder %}
<div><span class="text-zinc-500">Placeholder:</span> {{ agent.input_placeholder }}</div> <div><span class="text-forge-muted">Example:</span> {{ agent.input_placeholder }}</div>
{% endif %} {% endif %}
<div><span class="text-zinc-500">Last updated:</span> {{ agent.updated_at.strftime('%Y-%m-%d %H:%M') if agent.updated_at else '—' }}</div> <div><span class="text-forge-muted">Last forged:</span> {{ agent.updated_at.strftime('%Y-%m-%d %H:%M') if agent.updated_at else '—' }}</div>
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% if agent.tags %} {% if agent.tags %}
<div> <div>
<div class="text-zinc-400 text-xs mb-1.5">Tags</div> <div class="text-forge-muted text-xs mb-1.5">Runes</div>
<div class="flex flex-wrap gap-1.5"> <div class="flex flex-wrap gap-1.5">
{% for tag in agent.tags %} {% for tag in agent.tags %}
<span class="text-xs px-3 py-0.5 bg-zinc-900 border border-zinc-800 rounded-full">{{ tag }}</span> <span class="text-xs px-3 py-0.5 bg-forge-surface2 border border-forge-iron rounded-full">{{ tag }}</span>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
@@ -2,7 +2,10 @@
{% block content %} {% block content %}
<div class="max-w-3xl"> <div class="max-w-3xl">
<h1 class="text-2xl font-semibold mb-4">{{ agent.name }} <span class="text-sm text-zinc-400">v{{ agent.version }}</span></h1> <div class="mb-4 flex items-center gap-2">
<a href="/agents" class="text-xs text-forge-muted hover:text-forge-ember">← Back to Arsenal</a>
</div>
<h1 class="text-2xl font-semibold tracking-tight">{{ agent.name }} <span class="text-sm text-forge-muted">v{{ agent.version }}</span></h1>
{% include "agent_detail.html" %} {% include "agent_detail.html" %}
</div> </div>
{% endblock %} {% endblock %}
+18 -12
View File
@@ -1,41 +1,47 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block content %} {% block content %}
<h1 class="text-3xl font-semibold mb-6">Registered Agents</h1> <div>
<div class="flex items-baseline gap-3 mb-6">
<span class="text-2xl text-forge-ember"></span>
<h1 class="text-3xl font-semibold tracking-tight">Arsenal</h1>
<span class="text-xs px-2 py-px bg-forge-surface2 border border-forge-iron rounded text-forge-muted">FORGED BLADES</span>
</div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6"> <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- Lista de agentes --> <!-- List of blades -->
<div class="lg:col-span-1"> <div class="lg:col-span-1">
<div class="border border-zinc-800 bg-zinc-900 rounded-xl overflow-hidden"> <div class="border border-forge-iron bg-forge-surface rounded-xl overflow-hidden">
{% for a in agents %} {% for a in agents %}
<a href="/agents/{{ a.name }}" <a href="/agents/{{ a.name }}"
hx-get="/agents/{{ a.name }}" hx-get="/agents/{{ a.name }}"
hx-target="#agent-detail" hx-target="#agent-detail"
hx-swap="innerHTML" hx-swap="innerHTML"
class="block px-4 py-3 border-b border-zinc-800 last:border-b-0 hover:bg-zinc-800 cursor-pointer"> class="block px-4 py-3 border-b border-forge-iron last:border-b-0 hover:bg-forge-surface2 hover:border-l-2 hover:border-forge-ember cursor-pointer transition">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<span class="text-2xl flex-shrink-0" <span class="text-2xl flex-shrink-0"
{% if a.color %}style="color: {{ a.color }}"{% endif %}> {% if a.color %}style="color: {{ a.color }}"{% endif %}>
{{ a.icon or "🤖" }} {{ a.icon or "" }}
</span> </span>
<div class="min-w-0"> <div class="min-w-0">
<div class="font-medium truncate">{{ a.name }}</div> <div class="font-medium truncate">{{ a.name }}</div>
<div class="text-xs text-zinc-400">v{{ a.version }} · {{ a.state }}</div> <div class="text-xs text-forge-muted">v{{ a.version }} · {{ a.state }}</div>
</div> </div>
</div> </div>
</a> </a>
{% else %} {% else %}
<div class="px-4 py-6 text-zinc-400">No agents registered.</div> <div class="px-4 py-6 text-forge-muted">No forged blades in the Arsenal yet.</div>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
<!-- Área de detalle --> <!-- Detail / inspection area -->
<div class="lg:col-span-2"> <div class="lg:col-span-2">
<div id="agent-detail" class="border border-zinc-800 bg-zinc-900 rounded-xl p-6 h-full"> <div id="agent-detail" class="border border-forge-iron bg-forge-surface rounded-xl p-6 h-full">
<div class="h-full flex items-center justify-center"> <div class="h-full flex items-center justify-center">
<div class="text-zinc-400 text-sm text-center"> <div class="text-forge-muted text-sm text-center max-w-[260px]">
Select an agent from the list to see its configuration and behavior. <span class="text-forge-ember">🛡</span> Select a blade from the Arsenal to inspect its temper, purpose and edge.
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -1,43 +1,51 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block content %} {% block content %}
<h1 class="text-3xl font-semibold mb-6">🤝 Pending Approvals (HITL)</h1> <div class="max-w-3xl">
<p class="text-sm text-zinc-400 mb-4 max-w-2xl">Executions with high-risk actions are paused here. Approve or reject so the graph can continue (or fail). The list updates after every action.</p> <div class="mb-2 flex items-center gap-2">
<span class="text-forge-gold text-xl"></span>
<span class="text-forge-ember text-sm tracking-[2px]">JUDGMENT CHAMBER</span>
</div>
<h1 class="text-3xl font-semibold tracking-tight mb-2">The Judgment</h1>
<p class="text-sm text-forge-muted mb-6 max-w-xl">High-risk forgings pause here to be tempered or shattered. Approve or reject. The anvil waits.</p>
<div class="space-y-4 max-w-4xl" id="approvals-list"> <div class="space-y-4" id="approvals-list">
{% for ex in pending %} {% for ex in pending %}
<div class="border border-amber-800 bg-zinc-900 rounded-xl p-4"> <div class="forge-card border border-amber-900/60 rounded-2xl p-5">
<div class="flex justify-between items-start"> <div class="flex justify-between items-start">
<div> <div>
<div class="font-medium">{{ ex.agent_name }} v{{ ex.agent_version }}</div> <div class="font-medium">{{ ex.agent_name }} <span class="text-forge-muted">v{{ ex.agent_version }}</span></div>
<div class="text-xs text-zinc-400">{{ ex.trace_id }}</div> <div class="text-xs text-forge-muted font-mono mt-0.5">{{ ex.trace_id }}</div>
</div> </div>
<div class="text-amber-400 text-xs">AWAITING APPROVAL</div> <div class="text-amber-400 text-xs font-mono tracking-widest flex items-center gap-1">🛡️ AWAITING TEMPERING</div>
</div> </div>
<div class="mt-3 flex gap-2"> <div class="mt-4 flex gap-3">
<button <button
hx-post="/api/executions/{{ ex.trace_id }}/approve" hx-post="/api/executions/{{ ex.trace_id }}/approve"
hx-vals='{"approved_action_ids": [], "comment": "Aprobado desde UI"}' hx-vals='{"approved_action_ids": [], "comment": "Approved from UI"}'
hx-target="#approvals-list" hx-target="#approvals-list"
hx-swap="none" hx-swap="none"
hx-on::after-request="setTimeout(() => location.reload(), 400)" hx-on::after-request="setTimeout(() => location.reload(), 400)"
class="px-3 py-1 text-xs bg-emerald-600 hover:bg-emerald-500 rounded"> class="px-4 py-1.5 text-xs font-medium bg-emerald-600 hover:bg-emerald-500 text-white rounded transition">
Aprobar todo 🛡 BLESS THE BLADE
</button> </button>
<button <button
hx-post="/api/executions/{{ ex.trace_id }}/reject" hx-post="/api/executions/{{ ex.trace_id }}/reject"
hx-vals='{"reason": "Rechazado desde interfaz web"}' hx-vals='{"reason": "Rejected from web UI"}'
hx-target="#approvals-list" hx-target="#approvals-list"
hx-swap="none" hx-swap="none"
hx-on::after-request="setTimeout(() => location.reload(), 400)" hx-on::after-request="setTimeout(() => location.reload(), 400)"
class="px-3 py-1 text-xs bg-red-600 hover:bg-red-500 rounded"> class="px-4 py-1.5 text-xs font-medium bg-red-700 hover:bg-red-600 text-white rounded transition">
Rechazar ⚔ SHATTER THE BLADE
</button> </button>
</div> </div>
</div> </div>
{% else %} {% else %}
<div class="text-zinc-400">No executions awaiting approval.</div> <div class="forge-card border border-forge-iron rounded-2xl p-8 text-center text-forge-muted">
{% endfor %} <span class="text-forge-gold">🛡️</span> No forgings currently await judgment. The Forge is quiet.
</div>
{% endfor %}
</div>
</div> </div>
{% endblock %} {% endblock %}
@@ -0,0 +1,61 @@
{% extends "base.html" %}
{% block content %}
<div class="max-w-4xl mx-auto">
<div class="flex items-center gap-3 mb-6">
<span class="text-3xl"></span>
<div>
<h1 class="text-3xl font-semibold tracking-tight">The Armory</h1>
<p class="text-forge-muted text-sm">The Hall of Tempered Blades — every successful forging you have completed.</p>
</div>
</div>
{% if tempered %}
<div class="space-y-4">
{% for ex in tempered %}
<div class="forge-card border border-forge-iron rounded-2xl p-5">
<div class="flex items-start justify-between">
<div>
<div class="font-semibold text-lg">{{ ex.agent_name }} <span class="text-sm text-forge-muted">v{{ ex.agent_version }}</span></div>
<div class="text-xs text-forge-muted font-mono mt-0.5">{{ ex.trace_id }}</div>
</div>
<div class="text-right">
<div class="text-emerald-400 text-xs font-mono tracking-widest">TEMPERED</div>
<div class="text-xs text-forge-muted mt-1">
{{ ex.finished_at.strftime('%Y-%m-%d %H:%M') if ex.finished_at else ex.started_at.strftime('%Y-%m-%d %H:%M') }}
</div>
</div>
</div>
<div class="mt-4 flex gap-6 text-sm">
<div>
<span class="text-forge-muted text-xs">ACTIONS</span><br>
<span class="font-semibold text-forge-gold">{{ ex.n_proposed_actions }}</span>
</div>
<div>
<span class="text-forge-muted text-xs">VIOLATIONS</span><br>
<span class="font-semibold {% if ex.n_violations > 0 %}text-amber-400{% else %}text-emerald-400{% endif %}">{{ ex.n_violations }}</span>
</div>
</div>
<div class="mt-4 text-xs">
<a href="/api/executions/{{ ex.trace_id }}" target="_blank"
class="text-forge-ember hover:underline">View full forging record →</a>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="forge-card border border-forge-iron rounded-2xl p-12 text-center">
<div class="text-forge-gold text-4xl mb-4">🛡️</div>
<div class="text-xl font-semibold mb-2">No tempered blades yet</div>
<p class="text-forge-muted max-w-xs mx-auto">
Successfully complete a forging on the Anvil and it will appear here as a proven, tempered blade.
</p>
<a href="/run" class="inline-block mt-6 text-sm text-forge-ember hover:underline">Go to the Anvil →</a>
</div>
{% endif %}
</div>
{% endblock %}
+88 -40
View File
@@ -3,28 +3,26 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Forja • {{ title or "Cadena de Montaje" }}</title> <title>The Forge • {{ title or "The Forge" }}</title>
<script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.tailwindcss.com"></script>
<script> <script>
// Tokyo Night Tailwind config (dark purple + yellow highlights) // Forge Tailwind config (gamified blacksmith aesthetic)
function initializeTokyoNight() { function initializeForgeTheme() {
if (window.tailwind && window.tailwind.config) { if (window.tailwind && window.tailwind.config) {
window.tailwind.config = { window.tailwind.config = {
theme: { theme: {
extend: { extend: {
colors: { colors: {
'tokyo': { 'forge': {
'bg': '#1a1b26', 'bg': '#0c0a09',
'surface': '#24283b', 'surface': '#171513',
'surface2': '#1f2335', 'surface2': '#1f1c18',
'border': '#414868', 'iron': '#57534e',
'purple': '#bb9af7', 'ember': '#f97316',
'blue': '#7aa2f7', 'gold': '#fcd34d',
'yellow': '#e0af68', 'steel': '#94a3b8',
'green': '#9ece6a', 'text': '#e7e5e4',
'red': '#f7768e', 'muted': '#78716c',
'text': '#c0caf5',
'muted': '#565f89',
} }
} }
} }
@@ -32,7 +30,7 @@
}; };
} }
} }
window.onload = initializeTokyoNight; window.onload = initializeForgeTheme;
</script> </script>
<script src="https://unpkg.com/htmx.org@2.0.3/dist/htmx.min.js"></script> <script src="https://unpkg.com/htmx.org@2.0.3/dist/htmx.min.js"></script>
<style> <style>
@@ -41,46 +39,96 @@
.htmx-request .htmx-indicator { display: inline; } .htmx-request .htmx-indicator { display: inline; }
.htmx-request.htmx-indicator { display: inline; } .htmx-request.htmx-indicator { display: inline; }
/* Tokyo Night Theme - dark + purple + yellow highlights */ /* Forge gamified theme - blacksmith forge with embers */
:root { :root {
--tokyo-bg: #1a1b26; --forge-bg: #1c1c1c;
--tokyo-surface: #24283b; --forge-surface: #151515;
--tokyo-surface2: #1f2335; --forge-surface2: #242424;
--tokyo-border: #414868; --forge-iron: #333333;
--tokyo-purple: #bb9af7; --forge-ember: #f97316;
--tokyo-blue: #7aa2f7; --forge-gold: #fcd34d;
--tokyo-yellow: #e0af68; --forge-steel: #94a3b8;
--tokyo-green: #9ece6a; --forge-text: #e7e5e4;
--tokyo-red: #f7768e; --forge-muted: #78716c;
--tokyo-text: #c0caf5;
--tokyo-muted: #565f89;
} }
body { body {
background-color: var(--tokyo-bg); background-color: var(--forge-bg);
color: var(--tokyo-text); color: var(--forge-text);
} }
.tokyo-header { .forge-header {
background-color: var(--tokyo-surface); background-color: var(--forge-surface);
border-color: var(--tokyo-border); border-color: var(--forge-iron);
} }
.tokyo-card { .forge-card {
background-color: var(--tokyo-surface); background-color: var(--forge-surface);
border-color: var(--tokyo-border); border-color: var(--forge-iron);
}
.forge-ember { color: var(--forge-ember); }
.forge-gold { color: var(--forge-gold); }
.forge-steel { color: var(--forge-steel); }
/* Subtle ember glow on interactive cards */
.forge-glow:hover {
box-shadow: 0 0 0 1px var(--forge-ember), 0 10px 15px -3px rgb(0 0 0 / 0.3);
border-color: var(--forge-ember);
}
.forge-fire {
animation: ember-pulse 2.5s ease-in-out infinite;
}
@keyframes ember-pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.75; }
} }
.tokyo-purple { color: var(--tokyo-purple); }
.tokyo-yellow { color: var(--tokyo-yellow); }
.tokyo-blue { color: var(--tokyo-blue); }
</style> </style>
</head> </head>
<body> <body>
<div class="min-h-screen"> <div class="min-h-screen">
<main class="max-w-6xl mx-auto px-6 py-8 text-tokyo-text"> <!-- Forge Header: gamified persistent nav -->
<header class="forge-header border-b sticky top-0 z-50">
<div class="max-w-6xl mx-auto px-6 h-14 flex items-center justify-between">
<a href="/" class="flex items-baseline gap-2 hover:text-forge-ember transition">
<span class="font-semibold tracking-[3px] text-lg">THE FORGE</span>
<span class="text-[10px] text-forge-muted tracking-[1px] font-normal">Fire • Hammer • Judgment</span>
</a>
<nav class="flex items-center gap-x-6 text-sm">
<a href="/agents" class="hover:text-forge-ember transition">Arsenal</a>
<a href="/run" class="hover:text-forge-ember transition">The Anvil</a>
<a href="/approvals" class="hover:text-forge-ember transition">The Judgment</a>
</nav>
<div class="hidden md:flex items-center gap-x-4 text-[10px] font-mono text-forge-muted">
<div class="flex items-center gap-1.5 text-forge-gold">
<span></span>
<span class="tracking-wider">FORGE MASTER</span>
</div>
<div>LV <span class="text-forge-gold font-semibold">12</span></div>
<div class="flex items-center gap-1.5">
<span>HEAT</span>
<div class="w-8 h-1.5 bg-forge-iron rounded overflow-hidden">
<div class="h-full bg-forge-ember w-[87%]"></div>
</div>
<span class="text-forge-ember font-semibold">87%</span>
</div>
</div>
</div>
</header>
<main class="max-w-6xl mx-auto px-6 py-8 text-forge-text">
{% block content %}{% endblock %} {% block content %}{% endblock %}
</main> </main>
</div> </div>
<!-- Floating Tutorial Button -->
{% if not hide_tutorial_button %}
<a href="/tutorial"
class="fixed bottom-6 right-6 z-50 flex items-center justify-center px-5 py-2.5 bg-forge-surface border border-forge-iron text-forge-steel hover:text-forge-ember hover:border-forge-ember rounded-2xl shadow-lg transition text-sm">
<span>Tutorial</span>
</a>
{% endif %}
</body> </body>
</html> </html>
@@ -0,0 +1,48 @@
{% extends "base.html" %}
{% block content %}
<div class="max-w-3xl mx-auto">
<div class="flex items-center gap-3 mb-8">
<span class="text-3xl"></span>
<div>
<h1 class="text-3xl font-semibold tracking-tight">The Battle</h1>
<p class="text-forge-muted">Where tempered agents finally face the real world in The Battle. Send them to Azure, AWS, GCP or on-prem.</p>
</div>
</div>
<div class="grid md:grid-cols-2 gap-6">
<div class="forge-card border border-forge-iron rounded-2xl p-6">
<div class="font-semibold text-lg mb-2">☁️ Azure</div>
<div class="text-sm text-forge-muted">Deploy as Azure Functions, Container Apps, or AKS with full governance baked in.</div>
<div class="mt-4 text-xs text-forge-ember">Coming soon</div>
</div>
<div class="forge-card border border-forge-iron rounded-2xl p-6">
<div class="font-semibold text-lg mb-2">🟠 AWS</div>
<div class="text-sm text-forge-muted">Lambda, ECS, EKS or SageMaker endpoints — with the same guardrails and audit trail.</div>
<div class="mt-4 text-xs text-forge-ember">Coming soon</div>
</div>
<div class="forge-card border border-forge-iron rounded-2xl p-6">
<div class="font-semibold text-lg mb-2">🔵 Google Cloud</div>
<div class="text-sm text-forge-muted">Cloud Run, GKE or Vertex AI with complete decision traceability.</div>
<div class="mt-4 text-xs text-forge-ember">Coming soon</div>
</div>
<div class="forge-card border border-forge-iron rounded-2xl p-6">
<div class="font-semibold text-lg mb-2">🏠 On-Prem / Self-hosted</div>
<div class="text-sm text-forge-muted">Docker, Kubernetes, or bare metal — the Forge travels with you.</div>
<div class="mt-4 text-xs text-forge-ember">Coming soon</div>
</div>
</div>
<div class="mt-10 text-sm text-forge-muted border-t border-forge-iron pt-6">
A successfully tempered blade only becomes truly valuable once it enters <strong>The Battle</strong>.
This is where your agents (with their policies, versions and full audit history) are sent into real action on Azure, AWS, GCP or on-prem — carrying the strength they earned in the Forge.
</div>
</div>
{% endblock %}
@@ -1,23 +0,0 @@
{% extends "base.html" %}
{% block content %}
<h1 class="text-3xl font-semibold mb-6">📜 Execution History</h1>
<div class="space-y-3 max-w-4xl">
{% for ex in executions %}
{% set sc = 'emerald' if ex.status == 'completed' else ('amber' if 'awaiting' in ex.status else 'red') %}
<div class="border border-zinc-800 bg-zinc-900 rounded-xl p-4 text-sm">
<div class="flex justify-between items-center">
<div><span class="font-mono text-xs">{{ ex.trace_id }}</span></div>
<div class="text-xs text-zinc-400">{{ ex.agent_name }} v{{ ex.agent_version }}</div>
</div>
<div class="mt-1 flex items-center gap-2">
<span class="text-xs px-2 py-px rounded bg-{{ sc }}-500/10 text-{{ sc }}-400 font-mono">{{ ex.status }}</span>
<span class="text-[10px] text-zinc-500">{{ ex.n_proposed_actions }} acciones</span>
</div>
</div>
{% else %}
<div class="text-zinc-400">No executions yet.</div>
{% endfor %}
</div>
{% endblock %}
+29 -78
View File
@@ -1,89 +1,40 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block content %} {% block content %}
<div class="max-w-[1200px] mx-auto pb-12"> <div class="max-w-[1100px] mx-auto pt-4">
<!-- Branding --> <div class="pt-6 mb-8 text-left">
<div class="pt-6 pb-6"> <p class="text-3xl font-medium text-forge-text tracking-tight">Define at The Hearth. Prove in The Crucible. Win in The Battle.</p>
<div>
<span class="text-6xl font-semibold tracking-tighter">Forja</span>
</div> </div>
<!-- Small legend --> <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="flex flex-wrap gap-x-5 gap-y-1 text-xs mt-4 text-tokyo-muted">
<div><span class="inline-block w-2.5 h-2.5 rounded bg-tokyo-purple mr-1.5 align-middle"></span>Preparation</div>
<div><span class="inline-block w-2.5 h-2.5 rounded bg-tokyo-blue mr-1.5 align-middle"></span>Execution Pipeline</div>
<div><span class="inline-block w-2.5 h-2.5 rounded bg-tokyo-yellow mr-1.5 align-middle"></span>Human Oversight &amp; Completion</div>
</div>
</div>
<!-- Phases (no enclosing boxes) --> <!-- Station 1: Definition -->
<a href="/agents" class="forge-card forge-glow border-2 border-forge-iron rounded-2xl p-6 group transition block hover:border-forge-ember hover:shadow-[0_0_0_1px_#f97316]">
<!-- PHASE 1 --> <div class="text-forge-ember text-2xl font-semibold tracking-tight mb-2">The Hearth</div>
<div class="mb-8"> <p class="text-sm text-forge-muted">Define your agents: prompts, models, output schemas, risk thresholds and guardrails.</p>
<div class="mb-4">
<span class="text-xs tracking-[2px] text-tokyo-purple font-semibold">PHASE 1 — PREPARATION</span>
<div class="text-xl font-semibold tracking-tight mt-0.5">Definition &amp; Guardrails</div>
</div>
<div class="flex flex-wrap gap-4">
<div class="flex-1 min-w-[260px] bg-[#24283b] border border-tokyo-purple/60 rounded-2xl px-5 py-4 hover:border-tokyo-purple hover:shadow-[0_0_0_1px_#bb9af7] transition">
<div class="font-semibold">1. Agent Definition</div>
<div class="text-xs text-tokyo-muted mt-1">Versioned YAML with prompt, model, output schema and risk threshold</div>
</div>
<div class="flex-1 min-w-[260px] bg-[#24283b] border border-tokyo-purple/60 rounded-2xl px-5 py-4 hover:border-tokyo-purple hover:shadow-[0_0_0_1px_#bb9af7] transition">
<div class="font-semibold">2. Policy Application</div>
<div class="text-xs text-tokyo-muted mt-1">Guardrails policy loaded and attached to the agent</div>
</div>
</div>
</div>
<!-- PHASE 2 -->
<div class="mb-8">
<div class="mb-4">
<span class="text-xs tracking-[2px] text-white font-semibold">PHASE 2 — EXECUTION PIPELINE</span>
<div class="text-xl font-semibold tracking-tight mt-0.5 text-white">The Main Processing Line</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-4 gap-3">
<div class="bg-[#24283b] border border-tokyo-border rounded-2xl px-4 py-4 hover:border-tokyo-purple hover:shadow-md transition text-sm">
<span class="text-[#bb9af7] font-semibold">3. Input Guardrails</span>
<div class="text-[10px] text-tokyo-muted mt-1 leading-snug">Validates input against policy before it reaches the LLM</div>
</div>
<div class="bg-[#24283b] border border-tokyo-border rounded-2xl px-4 py-4 hover:border-tokyo-purple hover:shadow-md transition text-sm">
<span class="text-[#e0af68] font-semibold">4. LLM Reasoning</span>
<div class="text-[10px] text-tokyo-muted mt-1 leading-snug">The agent generates structured output using the configured model</div>
</div>
<div class="bg-[#24283b] border border-tokyo-border rounded-2xl px-4 py-4 hover:border-tokyo-purple hover:shadow-md transition text-sm">
<span class="text-[#9ece6a] font-semibold">5. Output Guardrails</span>
<div class="text-[10px] text-tokyo-muted mt-1 leading-snug">Validates the LLM response for safety and correctness</div>
</div>
<div class="bg-[#24283b] border border-tokyo-border rounded-2xl px-4 py-4 hover:border-tokyo-purple hover:shadow-md transition text-sm">
<span class="text-[#f7768e] font-semibold">6. Action Proposal</span>
<div class="text-[10px] text-tokyo-muted mt-1 leading-snug">Generates actions with risk scores and rollback plans</div>
</div>
</div>
</div>
<!-- PHASE 3 -->
<div>
<div class="mb-4">
<span class="text-xs tracking-[2px] text-tokyo-yellow font-semibold">PHASE 3 — HUMAN OVERSIGHT &amp; COMPLETION</span>
<div class="text-xl font-semibold tracking-tight mt-0.5 text-tokyo-yellow">Critical Gate + Finalization</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
<a href="/approvals" class="col-span-1 md:col-span-1 bg-[#2a2f45] border-2 border-tokyo-yellow rounded-2xl px-5 py-5 hover:border-tokyo-yellow hover:shadow-[0_0_0_1px_#e0af68] hover:brightness-105 transition group">
<div class="font-semibold text-tokyo-yellow text-lg">7. Human Approval Gate</div>
<div class="text-xs text-tokyo-muted mt-2 leading-snug">High-risk actions pause here. Human must approve or reject.</div>
<div class="mt-3 text-xs text-tokyo-yellow group-hover:underline">Enter approval bay →</div>
</a> </a>
<div class="bg-[#1f2335] border border-tokyo-green/60 rounded-2xl px-5 py-5 hover:border-tokyo-green hover:shadow-[0_0_0_1px_#9ece6a] transition">
<div class="font-semibold">8. Finalization</div> <!-- Station 2: Testing & Proving -->
<div class="text-xs text-tokyo-muted mt-1.5">Execution completes with full decision path.</div> <a href="/run" class="forge-card forge-glow border-2 border-forge-iron rounded-2xl p-6 group transition block hover:border-forge-ember hover:shadow-[0_0_0_1px_#f97316]">
</div> <div class="text-forge-ember text-2xl font-semibold tracking-tight mb-2">The Crucible</div>
</div> <p class="text-sm text-forge-muted">Put agents to the test. Run them with guardrails, handle Human-in-the-Loop decisions, and review successful runs.</p>
</a>
<!-- Station 3: The Battle (Deployment) -->
<a href="/deploy" class="forge-card forge-glow border-2 border-forge-iron rounded-2xl p-6 group transition block hover:border-forge-ember hover:shadow-[0_0_0_1px_#f97316]">
<div class="text-forge-ember text-2xl font-semibold tracking-tight mb-2">The Battle</div>
<p class="text-sm text-forge-muted">Deploy your proven agents to Azure, AWS, GCP or on-prem, ready for real action.</p>
</a>
</div> </div>
</div> <!-- Forge Creed -->
<div class="mt-8 border-t border-forge-iron pt-8 text-sm max-w-prose">
<div class="uppercase text-xs tracking-[2px] text-forge-text mb-2">The Forge Creed</div>
<p class="text-forge-muted leading-relaxed">
The risk threshold is the critical temper point. If the blade glows white-hot, The Judgment must intervene.
Nothing leaves the Forge without passing through fire and hammer.
</p>
</div>
{% endblock %} {% endblock %}
@@ -1,15 +0,0 @@
{% extends "base.html" %}
{% block content %}
<h1 class="text-3xl font-semibold mb-6">📐 Policies</h1>
<div class="grid gap-4 max-w-3xl">
{% for p in policies %}
<div class="border border-zinc-800 bg-zinc-900 rounded-xl p-4">
<div class="font-medium">{{ p.name }} <span class="text-xs text-zinc-500">v{{ p.version }}</span></div>
<div class="text-sm text-zinc-400 mt-1">{{ p.description or 'No description' }}</div>
<div class="text-xs mt-2 text-zinc-500">Validadores: {{ p.validators | length }}</div>
</div>
{% endfor %}
</div>
{% endblock %}
+18 -13
View File
@@ -1,14 +1,18 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block content %} {% block content %}
<h1 class="text-3xl font-semibold mb-2">▶️ Run Agent</h1>
<p class="text-zinc-400 mb-6">Invoke an agent with full governance (guardrails + HITL + observability).</p>
<div class="max-w-2xl"> <div class="max-w-2xl">
<form hx-post="/run" hx-target="#result" hx-swap="innerHTML" class="space-y-4"> <div class="mb-2 flex items-center gap-2">
<span class="text-forge-gold text-2xl"></span>
<h1 class="text-3xl font-semibold tracking-tight">The Anvil</h1>
</div>
<p class="text-forge-muted mb-6">Choose a blade, dictate the task and strike. The Forge will execute with fire and rules.</p>
<div class="forge-card border rounded-2xl p-6">
<form hx-post="/run" hx-target="#result" hx-swap="innerHTML" class="space-y-5">
<div> <div>
<label class="block text-sm mb-1">Agent</label> <label class="block text-xs tracking-widest text-forge-gold mb-1.5">🗡️ BLADE TO FORGE</label>
<select name="agent_name" class="w-full bg-zinc-900 border border-zinc-700 rounded-lg px-3 py-2 text-sm"> <select name="agent_name" class="w-full bg-black/50 border border-forge-iron rounded-lg px-3 py-2.5 text-sm focus:border-forge-ember outline-none">
{% for a in agents %} {% for a in agents %}
<option value="{{ a.name }}">{{ a.name }} (v{{ a.version }})</option> <option value="{{ a.name }}">{{ a.name }} (v{{ a.version }})</option>
{% endfor %} {% endfor %}
@@ -16,18 +20,19 @@
</div> </div>
<div> <div>
<label class="block text-sm mb-1">Input / Scenario</label> <label class="block text-xs tracking-widest text-forge-gold mb-1.5">🔥 THE STEEL TO STRIKE (SCENARIO / INPUT)</label>
<textarea name="input" rows="4" class="w-full bg-zinc-900 border border-zinc-700 rounded-lg px-3 py-2 font-mono text-sm" <textarea name="input" rows="4" class="w-full bg-black/50 border border-forge-iron rounded-lg px-3 py-2 font-mono text-sm focus:border-forge-ember outline-none"
placeholder='{"scenario": "01_sip_registration_drop"}'>{"scenario": "01_sip_registration_drop"}</textarea> placeholder='{"scenario": "01_sip_registration_drop"}'>{"scenario": "01_sip_registration_drop"}</textarea>
</div> </div>
<button type="submit" <button type="submit"
class="px-4 py-2 bg-white text-black rounded-lg text-sm font-medium flex items-center gap-2 hover:bg-zinc-200"> class="w-full sm:w-auto px-6 py-2.5 bg-forge-ember text-black font-semibold rounded-lg text-sm flex items-center justify-center gap-2 hover:brightness-105 active:scale-[0.985] transition">
<span>Invoke</span> <span>⚒ STRIKE THE ANVIL</span>
<span class="htmx-indicator">...</span> <span class="htmx-indicator"></span>
</button> </button>
</form> </form>
</div> </div>
<div id="result" class="mt-6"></div> <div id="result" class="mt-6"></div>
</div>
{% endblock %} {% endblock %}
@@ -0,0 +1,90 @@
{% extends "base.html" %}
{% block content %}
<div class="max-w-3xl mx-auto pb-12">
<div class="mb-8">
<div class="flex items-center gap-2 mb-1">
<span class="text-forge-ember text-2xl"></span>
<h1 class="text-4xl font-semibold tracking-tighter">Forge Tutorial</h1>
</div>
<p class="text-forge-muted">Learn the ancient craft of forging governed agents — step by step, strike by strike.</p>
</div>
<!-- Step 1 -->
<div class="mb-10">
<div class="flex items-center gap-3 mb-2">
<span class="font-mono text-xs px-2 py-px border border-forge-iron rounded bg-forge-surface2">01</span>
<span class="uppercase tracking-[2px] text-xs text-forge-ember">STATION I — THE HEARTH</span>
</div>
<h3 class="text-xl font-semibold mb-2">Visit the Arsenal</h3>
<p class="text-sm text-forge-muted mb-3">
Every agent in the Forge is a blade that has already been tempered. Inspect existing blades to understand their purpose, risk threshold, and the laws (guardrails) that bind them.
</p>
<a href="/agents" class="inline-block text-sm text-forge-ember hover:underline">Open the Arsenal →</a>
</div>
<!-- Step 2 -->
<div class="mb-10">
<div class="flex items-center gap-3 mb-2">
<span class="font-mono text-xs px-2 py-px border border-forge-iron rounded bg-forge-surface2">02</span>
<span class="uppercase tracking-[2px] text-xs text-forge-ember">STATION II — THE ANVIL</span>
</div>
<h3 class="text-xl font-semibold mb-2">Strike at the Anvil</h3>
<p class="text-sm text-forge-muted mb-3">
This is where the real work happens. Choose a forged blade, give it a concrete task (the "steel"), and strike. The Forge will run the full pipeline: input guardrails → LLM reasoning → output guardrails → action proposal.
</p>
<div class="text-sm text-forge-muted mb-3">
Try a real example. Use the incident analyzer blade with a short network incident:
</div>
<!-- Live practice strike form (reuses the real /run endpoint) -->
<div class="forge-card border border-forge-iron rounded-2xl p-5 mb-4">
<form hx-post="/run" hx-target="#tutorial-result" hx-swap="innerHTML" class="space-y-4">
<input type="hidden" name="agent_name" value="incident_analyzer">
<div>
<label class="block text-xs tracking-widest text-forge-muted mb-1.5">TASK (the steel you strike)</label>
<textarea name="input" rows="3" class="w-full bg-black/50 border border-forge-iron rounded-lg px-3 py-2 font-mono text-sm focus:border-forge-ember outline-none">Network incident: sudden 80% drop in SIP registrations after a recent image promotion. Need root cause hypothesis and rollback plan if required.</textarea>
</div>
<button type="submit"
class="px-5 py-2 bg-forge-ember text-black font-semibold rounded-lg text-sm hover:brightness-105 active:scale-[0.985] transition">
STRIKE THE ANVIL (LIVE)
</button>
</form>
</div>
<div id="tutorial-result" class="mb-4"></div>
<p class="text-xs text-forge-muted">The result below will show the real Strike Sequence, Flaws, and Proposed Edges — exactly like a production forging.</p>
</div>
<!-- Step 3 -->
<div class="mb-10">
<div class="flex items-center gap-3 mb-2">
<span class="font-mono text-xs px-2 py-px border border-forge-iron rounded bg-forge-surface2">03</span>
<span class="uppercase tracking-[2px] text-xs text-forge-ember">STATION III — THE JUDGMENT</span>
</div>
<h3 class="text-xl font-semibold mb-2">Face the Judgment (HITL)</h3>
<p class="text-sm text-forge-muted mb-3">
When an action's risk score crosses the blade's threshold, the forging pauses. A human (you) must bless or shatter the proposed edges before the agent can continue. This is the heart of governed autonomy.
</p>
<a href="/approvals" class="inline-block text-sm text-forge-ember hover:underline">Enter the Judgment Chamber →</a>
</div>
<!-- Step 4 -->
<div class="mb-10">
<div class="flex items-center gap-3 mb-2">
<span class="font-mono text-xs px-2 py-px border border-forge-iron rounded bg-forge-surface2">04</span>
<span class="uppercase tracking-[2px] text-xs text-forge-ember">MASTERY</span>
</div>
<h3 class="text-xl font-semibold mb-2">Repeat and Improve</h3>
<p class="text-sm text-forge-muted">
Every successful forging increases the overall heat of the Forge. Review history (when available), tune risk thresholds on your blades, and strengthen guardrails. True mastery comes from deliberate, observed repetition.
</p>
</div>
<div class="border-t border-forge-iron pt-8 text-sm text-forge-muted">
The Forge does not forgive sloppy steel. The risk threshold is the critical temper point. If the blade glows white-hot, The Judgment must intervene.
Nothing leaves the Forge without passing through fire and hammer.
</div>
</div>
{% endblock %}
+77 -51
View File
@@ -1,4 +1,4 @@
"""HTMX UI router — montado dentro del propio forja-core (Opción A).""" """HTMX UI router — mounted inside forja-core (Option A)."""
from __future__ import annotations from __future__ import annotations
@@ -10,7 +10,8 @@ from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from forja_core.api.deps import get_orchestrator, get_policy_store, get_registry, get_settings from forja_core.api.deps import get_orchestrator, get_policy_store, get_registry, get_settings
from forja_core.api.persistence import read_execution_summaries from forja_core.api.executions import _record_execution
from forja_core.api.persistence import append_execution, append_violation, read_execution_summaries
from forja_core.domain.execution import AgentExecution from forja_core.domain.execution import AgentExecution
router = APIRouter(prefix="", tags=["ui"]) router = APIRouter(prefix="", tags=["ui"])
@@ -20,12 +21,12 @@ TEMPLATES_DIR = Path(__file__).parent / "templates"
templates = Jinja2Templates(directory=str(TEMPLATES_DIR)) templates = Jinja2Templates(directory=str(TEMPLATES_DIR))
SLOGANS = [ SLOGANS = [
"Forging agents with judgment", "Every strike, a tempered decision",
"Agents forged, not guessed", "Blades forged, not guessed",
"Version. Validate. Approve.", "Fire. Hammer. Judgment.",
"Every decision well-tempered", "The anvil never lies",
"Governance that protects", "High risk demands the Judgment",
"From prototype to real control", "True steel leaves the Forge",
] ]
def get_slogan() -> str: def get_slogan() -> str:
@@ -57,7 +58,7 @@ async def agent_detail(request: Request, name: str) -> HTMLResponse:
try: try:
agent = registry.get_agent(name) agent = registry.get_agent(name)
except FileNotFoundError: except FileNotFoundError:
return HTMLResponse('<div class="text-red-400">Agent not found</div>', status_code=404) return HTMLResponse('<div class="text-red-400">Blade not found in the Arsenal</div>', status_code=404)
# Si viene por HTMX, devolvemos solo el fragmento (sin layout) # Si viene por HTMX, devolvemos solo el fragmento (sin layout)
if request.headers.get("HX-Request"): if request.headers.get("HX-Request"):
@@ -89,12 +90,13 @@ async def run_invoke(
agent_name: str = Form(...), agent_name: str = Form(...),
input: str = Form(...), input: str = Form(...),
) -> HTMLResponse: ) -> HTMLResponse:
"""Ejecuta el agente usando el orchestrator directamente (mismo proceso).""" """Execute the agent using the orchestrator directly (same process)."""
from forja_core.api.deps import get_policy_store from forja_core.api.deps import get_policy_store
registry = get_registry() registry = get_registry()
policy_store = get_policy_store() policy_store = get_policy_store()
orchestrator = get_orchestrator() orchestrator = get_orchestrator()
settings = get_settings()
try: try:
agent_def = registry.get_agent(agent_name) # resuelve la versión activa agent_def = registry.get_agent(agent_name) # resuelve la versión activa
@@ -108,93 +110,117 @@ async def run_invoke(
user_input=input, user_input=input,
) )
) )
except Exception as e:
return HTMLResponse(f'<div class="text-red-400">Error: {str(e)[:300]}</div>', status_code=400)
# Resultado enriquecido (FASE I) # Persist terminal forgings (including successful "BLADE TEMPERED" ones)
status_colors = { # so they appear in the Armory / Vault of Tempered Blades.
"completed": "emerald", _record_execution(settings.data_dir, str(execution.trace_id), agent_def.name, agent_def.version)
"awaiting_approval": "amber", if execution.status in {"completed", "failed", "blocked_by_guardrail"}:
"failed": "red", append_execution(settings.data_dir, execution)
"blocked_by_guardrail": "red", for v in execution.violations:
append_violation(settings.data_dir, v)
except Exception as e:
return HTMLResponse(f'<div class="text-red-400 border border-red-900 bg-red-950/40 rounded p-3 text-sm">Forge error: {str(e)[:300]}</div>', status_code=400)
# Forging result (gamified)
status_map = {
"completed": ("text-emerald-400", "bg-emerald-950/60 border-emerald-900", "BLADE TEMPERED"),
"awaiting_approval": ("text-amber-400", "bg-amber-950/60 border-amber-900", "ON THE ANVIL · JUDGMENT REQUIRED"),
"failed": ("text-red-400", "bg-red-950/60 border-red-900", "SHATTERED IN THE FIRE"),
"blocked_by_guardrail": ("text-red-400", "bg-red-950/60 border-red-900", "BLOCKED BY THE FORGE"),
} }
color = status_colors.get(execution.status, "zinc") text_color, badge_style, status_label = status_map.get(
execution.status, ("text-forge-steel", "bg-forge-surface2 border-forge-iron", execution.status.upper())
)
steps_html = "" steps_html = ""
for step in execution.decision_path: for step in execution.decision_path:
dur = f"{step.duration_ms}ms" if step.duration_ms else "" dur = f"{step.duration_ms}ms" if step.duration_ms else ""
extra = "" extra = ""
if step.n_approved is not None: if step.n_approved is not None:
extra = f" · {step.n_approved} acciones aprobadas" extra = f" · {step.n_approved} blessings"
if step.rejected: if step.rejected:
extra = " · RECHAZADO" extra = " · SHATTERED"
if step.hitl: if step.hitl:
extra = " · HITL" extra = " · JUDGMENT"
steps_html += f'<div class="flex justify-between text-xs py-0.5 border-b border-zinc-800"><span class="font-mono">{step.node}</span><span class="text-zinc-500">{dur}{extra}</span></div>' steps_html += f'<div class="flex justify-between text-xs py-0.5 border-b border-forge-iron/60"><span class="font-mono text-forge-text">{step.node}</span><span class="text-forge-muted">{dur}{extra}</span></div>'
violations_html = "" violations_html = ""
if execution.violations: if execution.violations:
for v in execution.violations: for v in execution.violations:
sev = "red" if v.severity == "block" else "amber" sev = "text-red-400" if v.severity == "block" else "text-amber-400"
violations_html += f'<div class="text-xs text-{sev}-400">• {v.validator}: {v.message} ({v.severity})</div>' violations_html += f'<div class="text-xs {sev}">• {v.validator}: {v.message} ({v.severity})</div>'
actions_html = "" actions_html = ""
if execution.proposed_actions: if execution.proposed_actions:
for a in execution.proposed_actions: for a in execution.proposed_actions:
risk = a.risk_score risk = a.risk_score
badge = "emerald" if risk < 3 else ("amber" if risk < 5 else "red") badge = "text-emerald-400" if risk < 3 else ("text-amber-400" if risk < 5 else "text-red-400")
actions_html += f'<div class="text-xs bg-zinc-950 border border-zinc-700 rounded p-2">• {a.description} <span class="font-mono text-{badge}-400">(risk={risk})</span></div>' actions_html += f'<div class="text-xs bg-black/50 border border-forge-iron rounded p-2">• {a.description} <span class="font-mono {badge}">(risk={risk})</span></div>'
needs_html = "" needs_html = ""
if execution.needs_human_for: if execution.needs_human_for:
needs_html = f'<div class="mt-2 text-amber-400 text-sm">⏸️ Paused for human approval ({len(execution.needs_human_for)} action(s)). Go to <a href="/approvals" class="underline">Approvals</a>.</div>' needs_html = f'<div class="mt-3 p-3 border border-amber-900 bg-amber-950/40 rounded text-amber-300 text-sm">This blade awaits the Judgment ({len(execution.needs_human_for)} action(s)). <a href="/approvals" class="underline hover:text-amber-200">Enter the Chamber →</a></div>'
html = f""" html = f"""
<div class="border border-zinc-700 rounded-xl p-4 bg-zinc-900"> <div class="forge-card border border-forge-iron rounded-2xl p-5">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span class="font-semibold">Execution</span> <span class="font-semibold tracking-tight">Forging Record</span>
<span class="text-xs px-2 py-0.5 rounded bg-{color}-500/20 text-{color}-400 font-mono">{execution.status}</span> <span class="text-[10px] px-2 py-px rounded border font-mono {badge_style} {text_color}">{status_label}</span>
</div> </div>
<div class="text-[10px] text-zinc-500 mt-0.5">trace_id: {execution.trace_id}</div> <div class="text-[10px] text-forge-muted font-mono">trace {execution.trace_id}</div>
<div class="mt-3">
<div class="text-xs uppercase tracking-wider text-zinc-500 mb-1">Decision path</div>
<div class="font-mono text-[10px] bg-black/60 rounded p-2">{steps_html or '<span class="text-zinc-500">no steps</span>'}</div>
</div> </div>
{f'<div class="mt-3"><div class="text-xs uppercase tracking-wider text-red-500 mb-1">Violations</div>{violations_html}</div>' if violations_html else ''} <div class="mt-4">
<div class="uppercase text-xs tracking-[1.5px] text-forge-muted mb-1">Strike Sequence</div>
<div class="font-mono text-[10px] bg-black/60 border border-forge-iron rounded p-2.5 space-y-px">{steps_html or '<span class="text-forge-muted">no strikes</span>'}</div>
</div>
{f'<div class="mt-3"><div class="text-xs uppercase tracking-wider text-amber-500 mb-1">Proposed actions</div><div class="space-y-1">{actions_html}</div></div>' if actions_html else ''} {f'<div class="mt-4"><div class="uppercase text-xs tracking-[1.5px] text-red-400 mb-1">Flaws (Violations)</div>{violations_html}</div>' if violations_html else ''}
{f'<div class="mt-4"><div class="uppercase text-xs tracking-[1.5px] text-amber-400 mb-1">Proposed Edges</div><div class="space-y-1">{actions_html}</div></div>' if actions_html else ''}
{needs_html} {needs_html}
{f'<div class="mt-3 text-emerald-400 text-sm">✓ Completed with {len(execution.proposed_actions or [])} actions.</div>' if execution.status == 'completed' else ''} {f'<div class="mt-4 text-emerald-400 text-sm">✓ Forging complete. {len(execution.proposed_actions or [])} edges ready.</div>' if execution.status == 'completed' else ''}
</div> </div>
""" """
return HTMLResponse(html) return HTMLResponse(html)
@router.get("/policies", response_class=HTMLResponse) # ====================== APPROVALS (HITL) ======================
async def policies_page(request: Request) -> HTMLResponse:
policy_store = get_policy_store() @router.get("/tutorial", response_class=HTMLResponse)
policies = policy_store.list_policies() async def tutorial_page(request: Request) -> HTMLResponse:
"""Gamified step-by-step guide that teaches how to forge an agent."""
return templates.TemplateResponse( return templates.TemplateResponse(
"policies.html", "tutorial.html",
{"request": request, "title": "Policies", "policies": policies, "slogan": get_slogan()}, {"request": request, "title": "Tutorial", "slogan": get_slogan(), "hide_tutorial_button": True},
) )
@router.get("/history", response_class=HTMLResponse) @router.get("/armory", response_class=HTMLResponse)
async def history_page(request: Request) -> HTMLResponse: async def armory_page(request: Request) -> HTMLResponse:
"""The Armory — collection of successfully tempered blades (completed forgings)."""
data_dir = get_settings().data_dir data_dir = get_settings().data_dir
executions = read_execution_summaries(data_dir)[:50] all_execs = read_execution_summaries(data_dir)
tempered = [e for e in all_execs if e.status == "completed"]
tempered.sort(key=lambda e: e.finished_at or e.started_at, reverse=True)
return templates.TemplateResponse( return templates.TemplateResponse(
"history.html", "armory.html",
{"request": request, "title": "History", "executions": executions, "slogan": get_slogan()}, {"request": request, "title": "Armory", "tempered": tempered, "slogan": get_slogan()},
) )
# ====================== APROBACIONES (HITL) ====================== @router.get("/deploy", response_class=HTMLResponse)
async def deploy_page(request: Request) -> HTMLResponse:
"""Deployment station — where tempered agents are sent to Azure, AWS, GCP, etc."""
return templates.TemplateResponse(
"deploy.html",
{"request": request, "title": "Deploy", "slogan": get_slogan()},
)
@router.get("/approvals", response_class=HTMLResponse) @router.get("/approvals", response_class=HTMLResponse)
async def approvals_page(request: Request) -> HTMLResponse: async def approvals_page(request: Request) -> HTMLResponse:
+69 -187
View File
@@ -3,26 +3,26 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Forja — Seguimiento de Progreso</title> <title>Forja — Estado Actual</title>
<script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.tailwindcss.com"></script>
<style> <style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&amp;family=Space+Grotesk:wght@500;600&amp;display=swap'); @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&amp;family=Space+Grotesk:wght@500;600&amp;display=swap');
:root { :root {
--tokyo-bg: #1a1b26; --forge-bg: #1c1c1c;
--tokyo-surface: #24283b; --forge-surface: #151515;
--tokyo-border: #414868; --forge-surface2: #242424;
--tokyo-purple: #bb9af7; --forge-iron: #333333;
--tokyo-blue: #7aa2f7; --forge-ember: #f97316;
--tokyo-yellow: #e0af68; --forge-gold: #fcd34d;
--tokyo-text: #c0caf5; --forge-text: #e7e5e4;
--tokyo-muted: #565f89; --forge-muted: #78716c;
} }
body { body {
font-family: 'Inter', system_ui, sans-serif; font-family: 'Inter', system_ui, sans-serif;
background-color: var(--tokyo-bg); background-color: var(--forge-bg);
color: var(--tokyo-text); color: var(--forge-text);
} }
.font-display { .font-display {
@@ -30,211 +30,93 @@
font-weight: 600; font-weight: 600;
} }
.tokyo-card { .station-card {
background-color: var(--tokyo-surface); background-color: var(--forge-surface);
border: 1px solid var(--tokyo-border); border: 2px solid var(--forge-iron);
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
} }
.section-header { .section-header {
font-family: 'Space Grotesk', 'Inter', sans-serif; font-family: 'Space Grotesk', 'Inter', sans-serif;
letter-spacing: -0.025em; letter-spacing: -0.025em;
} }
.progress-bar {
transition: width 1s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.station {
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
.milestone {
position: relative;
}
.milestone::before {
content: '';
position: absolute;
left: 15px;
top: 0;
bottom: -24px;
width: 2px;
background: var(--tokyo-border);
}
.milestone:last-child::before {
display: none;
}
</style> </style>
</head> </head>
<body class="min-h-screen"> <body class="min-h-screen">
<div class="max-w-5xl mx-auto px-6 py-12"> <div class="max-w-4xl mx-auto px-6 py-12">
<!-- Header --> <!-- Header -->
<div class="flex items-center gap-4 mb-10"> <div class="flex items-center gap-3 mb-8">
<div class="w-14 h-14 rounded-2xl bg-gradient-to-br from-[#bb9af7] to-[#7aa2f7] flex items-center justify-center"> <span class="text-4xl">⚒︎</span>
<span class="text-3xl">🔨</span>
</div>
<div> <div>
<h1 class="font-display text-5xl font-semibold tracking-tighter">Forja</h1> <h1 class="text-4xl font-semibold tracking-tighter">Forja</h1>
<p class="text-[#565f89] text-xl">Seguimiento de Progreso</p> <p class="text-forge-muted">Estado actual del proyecto (Gamificado)</p>
</div> </div>
</div> </div>
<!-- Current Vision --> <div class="mb-10">
<div class="tokyo-card rounded-3xl p-8 mb-10"> <div class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-forge-surface border border-forge-iron text-sm">
<div class="flex items-center gap-3 mb-4"> <span class="w-2 h-2 bg-forge-ember rounded-full animate-pulse"></span>
<div class="px-3 py-1 bg-[#bb9af7]/10 text-[#bb9af7] rounded-full text-sm font-medium">Visión Actual</div> <span class="font-medium">Versión actual: 3 estaciones</span>
</div> </div>
<h2 class="text-3xl font-semibold tracking-tight mb-3">Cadena de Montaje (Assembly Line)</h2> </div>
<p class="text-[#c0caf5]/80 text-lg max-w-3xl">
La interfaz se ha transformado en una <strong>vista única de fábrica</strong>. <!-- Resumen del estado actual -->
El objetivo es que el usuario entienda de un vistazo cómo se "forja" un agente, <div class="mb-12">
mostrando solo los bloques que generan <strong>inputs configurables</strong> o <h2 class="text-2xl font-semibold tracking-tight mb-4 section-header">Resumen del Estado Actual</h2>
<strong>outputs significativos</strong>.
<div class="prose prose-invert max-w-none text-forge-muted">
<p class="text-lg">
El proyecto ha sido simplificado a <strong>3 estaciones principales</strong>, siguiendo una estructura clara y lógica:
</p> </p>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 my-6">
<!-- Station 1 -->
<div class="station-card rounded-2xl p-5">
<div class="text-forge-ember text-xs tracking-widest font-semibold mb-1">STATION I</div>
<div class="text-xl font-semibold mb-2">The Hearth</div>
<div class="text-sm">Definición de agentes (Arsenal)</div>
</div> </div>
<!-- Timeline --> <!-- Station 2 -->
<div class="mb-12"> <div class="station-card rounded-2xl p-5">
<h3 class="font-display text-2xl font-semibold tracking-tight mb-6">Evolución Principal</h3> <div class="text-forge-ember text-xs tracking-widest font-semibold mb-1">STATION II</div>
<div class="text-xl font-semibold mb-2">The Crucible</div>
<div class="space-y-6"> <div class="text-sm">Pruebas, guardrails y Human-in-the-Loop (Agrupación de Anvil + Judgment + Armory)</div>
<!-- Milestone 1 -->
<div class="milestone flex gap-4">
<div class="w-8 h-8 rounded-full bg-[#bb9af7] flex-shrink-0 flex items-center justify-center text-[#1a1b26] font-bold text-sm">1</div>
<div class="tokyo-card rounded-2xl p-5 flex-1">
<div class="flex justify-between items-start">
<div>
<div class="font-semibold">Eliminación de la Navbar</div>
<div class="text-sm text-[#565f89] mt-1">Se eliminó completamente la navegación superior tradicional.</div>
</div>
<div class="text-xs text-[#565f89] font-mono">Paso 1</div>
</div> </div>
<!-- Station 3 -->
<div class="station-card rounded-2xl p-5">
<div class="text-forge-ember text-xs tracking-widest font-semibold mb-1">STATION III</div>
<div class="text-xl font-semibold mb-2">The Battle</div>
<div class="text-sm">Despliegue de agentes forjados (Azure, AWS, GCP, On-prem)</div>
</div> </div>
</div> </div>
<!-- Milestone 2 --> <h3 class="text-xl font-semibold tracking-tight mt-8 mb-3">Cambios principales realizados:</h3>
<div class="milestone flex gap-4">
<div class="w-8 h-8 rounded-full bg-[#bb9af7] flex-shrink-0 flex items-center justify-center text-[#1a1b26] font-bold text-sm">2</div>
<div class="tokyo-card rounded-2xl p-5 flex-1">
<div class="font-semibold">Paso a Vista Única de Fábrica</div>
<div class="text-sm text-[#565f89] mt-1">Toda la experiencia se concentra en una "cadena de montaje" visual en lugar de páginas separadas.</div>
</div>
</div>
<!-- Milestone 3 -->
<div class="milestone flex gap-4">
<div class="w-8 h-8 rounded-full bg-[#bb9af7] flex-shrink-0 flex items-center justify-center text-[#1a1b26] font-bold text-sm">3</div>
<div class="tokyo-card rounded-2xl p-5 flex-1">
<div class="font-semibold">Eliminación de las Cajas de Fases</div>
<div class="text-sm text-[#565f89] mt-1">Se quitaron los contenedores grandes que englobaban cada fase. Ahora solo quedan los encabezados de fase + las estaciones sueltas.</div>
</div>
</div>
<!-- Milestone 4 -->
<div class="milestone flex gap-4">
<div class="w-8 h-8 rounded-full bg-[#bb9af7] flex-shrink-0 flex items-center justify-center text-[#1a1b26] font-bold text-sm">4</div>
<div class="tokyo-card rounded-2xl p-5 flex-1">
<div class="font-semibold">Unificación de Fondo + Colores Llamativos en Texto</div>
<div class="text-sm text-[#565f89] mt-1">Todas las cajas de la línea de ensamblado usan el mismo gris. Los títulos tienen colores muy saturados y distintos (cyan, púrpura, amarillo, verde, rojo).</div>
</div>
</div>
<!-- Milestone 5 -->
<div class="milestone flex gap-4">
<div class="w-8 h-8 rounded-full bg-[#bb9af7] flex-shrink-0 flex items-center justify-center text-[#1a1b26] font-bold text-sm">5</div>
<div class="tokyo-card rounded-2xl p-5 flex-1">
<div class="font-semibold">Limpieza de Estaciones sin Valor de I/O</div>
<div class="text-sm text-[#565f89] mt-1">
Se eliminaron los bloques que no generan inputs configurables ni outputs significativos:
<span class="font-medium text-[#e0af68]">Input Intake</span> y
<span class="font-medium text-[#e0af68]">Audit &amp; Logging</span>.
</div>
</div>
</div>
</div>
</div>
<!-- Current Assembly Line -->
<div class="mb-12">
<h3 class="font-display text-2xl font-semibold tracking-tight mb-6">Estado Actual de la Cadena de Montaje</h3>
<div class="tokyo-card rounded-3xl p-8">
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<!-- Phase 1 -->
<div>
<div class="text-[#bb9af7] text-xs tracking-[1.5px] font-semibold mb-2">FASE 1 — PREPARACIÓN</div>
<div class="space-y-2">
<div class="bg-[#24283b] border border-[#bb9af7]/40 rounded-xl px-4 py-3 text-sm">1. Agent Definition</div>
<div class="bg-[#24283b] border border-[#bb9af7]/40 rounded-xl px-4 py-3 text-sm">2. Policy Application</div>
</div>
</div>
<!-- Phase 2 -->
<div>
<div class="text-[#7aa2f7] text-xs tracking-[1.5px] font-semibold mb-2">FASE 2 — LÍNEA DE ENSAMBLADO</div>
<div class="space-y-2">
<div class="bg-[#24283b] border border-[#7aa2f7]/40 rounded-xl px-4 py-3 text-sm">
<span class="text-[#bb9af7]">3.</span> Input Guardrails
</div>
<div class="bg-[#24283b] border border-[#7aa2f7]/40 rounded-xl px-4 py-3 text-sm">
<span class="text-[#e0af68]">4.</span> LLM Reasoning
</div>
<div class="bg-[#24283b] border border-[#7aa2f7]/40 rounded-xl px-4 py-3 text-sm">
<span class="text-[#9ece6a]">5.</span> Output Guardrails
</div>
<div class="bg-[#24283b] border border-[#7aa2f7]/40 rounded-xl px-4 py-3 text-sm">
<span class="text-[#f7768e]">6.</span> Action Proposal
</div>
</div>
</div>
<!-- Phase 3 -->
<div>
<div class="text-[#e0af68] text-xs tracking-[1.5px] font-semibold mb-2">FASE 3 — SUPERVISIÓN HUMANA</div>
<div class="space-y-2">
<div class="bg-[#24283b] border border-[#e0af68]/40 rounded-xl px-4 py-3 text-sm">7. Human Approval Gate</div>
<div class="bg-[#24283b] border border-[#9ece6a]/40 rounded-xl px-4 py-3 text-sm">8. Finalization</div>
</div>
</div>
</div>
<div class="mt-6 pt-6 border-t border-[#414868] text-xs text-[#565f89]">
Total de estaciones activas: <span class="font-semibold text-[#c0caf5]">8</span>
(todas producen inputs configurables o outputs significativos)
</div>
</div>
</div>
<!-- Principles -->
<div class="grid md:grid-cols-2 gap-6">
<div class="tokyo-card rounded-3xl p-6">
<div class="text-sm font-medium text-[#bb9af7] mb-2">PRINCIPIOS APLICADOS</div>
<ul class="space-y-2 text-sm"> <ul class="space-y-2 text-sm">
<li class="flex gap-2"><span>Strict adherence to <strong>karpathy.md</strong> guidelines</span></li> <li>✓ Reducción de 5 estaciones a <strong>3 estaciones</strong> bien definidas.</li>
<li class="flex gap-2"><span>Simplicity First</span></li> <li>✓ Eliminación de etiquetas redundantes ("STATION I", "STATION II"...).</li>
<li class="flex gap-2"><span>Surgical changes (solo tocar lo necesario)</span></li> <li>✓ Títulos de estaciones ahora en naranja (ember) y tamaño grande.</li>
<li class="flex gap-2"><span>Eliminar lo que no aporta valor al flujo visual</span></li> <li>✓ Texto guía superior en blanco y tamaño grande (text-3xl).</li>
<li>✓ Recuperación del <strong>Forge Creed</strong> original como elemento inspirador.</li>
<li>✓ Eliminación de los enlaces "Go to the..." de las tarjetas para mayor limpieza.</li>
<li>✓ Botón flotante de Tutorial simplificado y centrado.</li>
<li>✓ Navbar con slogan: <strong>"Fire • Hammer • Judgment"</strong></li>
<li>✓ The Armory ya no es una estación principal (se integra conceptualmente dentro de The Crucible).</li>
</ul> </ul>
</div> </div>
<div class="tokyo-card rounded-3xl p-6">
<div class="text-sm font-medium text-[#bb9af7] mb-2">PRÓXIMOS PASOS (SUGERIDOS)</div>
<div class="text-sm text-[#565f89]">
<p class="mb-3">Este documento puede servir como registro vivo del proyecto.</p>
<p>Añade aquí nuevas decisiones, experimentos visuales o cambios de rumbo cuando ocurran.</p>
</div>
</div>
</div> </div>
<div class="mt-12 text-center text-xs text-[#565f89]"> <div class="border-t border-forge-iron pt-8 text-sm text-forge-muted">
Documento generado para seguimiento del proyecto Forja • <p><strong>Próximos pasos sugeridos:</strong></p>
<span class="font-mono">Tokyo Night Theme</span> <ul class="mt-2 space-y-1 text-xs">
<li>• Mejorar la página de The Battle (/deploy) con más contenido real.</li>
<li>• Conectar visualmente The Crucible con las páginas de ejecución y aprobaciones.</li>
<li>• Añadir feedback visual de "éxito" cuando un agente se forja correctamente.</li>
<li>• Posiblemente añadir un estado de "agentes desplegados" en The Battle.</li>
</ul>
</div> </div>
</div> </div>