diff --git a/dashboard/src/agentforge_dashboard/components/diff_view.py b/dashboard/src/agentforge_dashboard/components/diff_view.py
new file mode 100644
index 0000000..f5d6b24
--- /dev/null
+++ b/dashboard/src/agentforge_dashboard/components/diff_view.py
@@ -0,0 +1,26 @@
+"""Componente para renderizar diffs unificados con coloreado básico."""
+
+from __future__ import annotations
+
+import streamlit as st
+
+
+def render_unified_diff(diff_text: str) -> None:
+ if not diff_text.strip():
+ st.info("Sin diferencias entre las versiones seleccionadas.")
+ return
+ lines: list[str] = []
+ for line in diff_text.splitlines():
+ if line.startswith("+") and not line.startswith("+++"):
+ lines.append(f'{line}')
+ elif line.startswith("-") and not line.startswith("---"):
+ lines.append(f'{line}')
+ elif line.startswith("@@"):
+ lines.append(f'{line}')
+ else:
+ lines.append(line)
+ html = (
+ "
" + "\n".join(lines) + "
"
+ )
+ st.markdown(html, unsafe_allow_html=True)
diff --git a/dashboard/src/agentforge_dashboard/pages/1_🏛️_Registro.py b/dashboard/src/agentforge_dashboard/pages/1_🏛️_Registro.py
new file mode 100644
index 0000000..3a183b4
--- /dev/null
+++ b/dashboard/src/agentforge_dashboard/pages/1_🏛️_Registro.py
@@ -0,0 +1,60 @@
+"""Página de registro de agentes: lista, detalle, versiones y diff."""
+
+from __future__ import annotations
+
+import streamlit as st
+
+from agentforge_dashboard.client import CoreClient
+from agentforge_dashboard.components.diff_view import render_unified_diff
+
+
+@st.cache_resource
+def get_client() -> CoreClient:
+ return CoreClient()
+
+
+client = get_client()
+st.title("🏛️ Registro de Agentes")
+st.caption("Catálogo central de agentes con versionado tipo Git.")
+
+agents = client.list_agents()
+if not agents:
+ st.warning("No hay agentes registrados. Coloca YAMLs bajo `agents//`.")
+ st.stop()
+
+names = [a["name"] for a in agents]
+selected_name = st.selectbox("Agente", names)
+agent = client.get_agent(selected_name)
+
+col1, col2 = st.columns([2, 1])
+with col1:
+ st.subheader(f"{agent['name']} @ {agent['version']}")
+ st.write(f"**Estado:** `{agent['state']}` | **Owner:** {agent['owner']}")
+ st.write(f"**Propósito:** {agent['purpose']}")
+ st.write(f"**Guardrails activos:** {', '.join(agent['guardrails'])}")
+ st.write(f"**Threshold HITL:** risk_score ≥ {agent['risk_threshold_for_hitl']}")
+ with st.expander("System prompt"):
+ st.code(agent["system_prompt"], language="markdown")
+ with st.expander("Output schema"):
+ st.json(agent["output_schema"])
+
+with col2:
+ st.subheader("LLM")
+ st.json(agent["llm"])
+
+st.divider()
+st.subheader("Historial de versiones")
+versions = client.list_versions(selected_name)
+st.dataframe(versions, hide_index=True, use_container_width=True)
+
+if len(versions) >= 2:
+ st.subheader("Comparar versiones")
+ ids = [v["id"] for v in versions]
+ cf, ct = st.columns(2)
+ with cf:
+ v_from = st.selectbox("Desde", ids, index=0)
+ with ct:
+ v_to = st.selectbox("Hasta", ids, index=len(ids) - 1)
+ if v_from != v_to:
+ diff = client.diff_versions(selected_name, v_from, v_to)
+ render_unified_diff(diff["unified_diff"])
diff --git a/pyproject.toml b/pyproject.toml
index cfba581..f03f58e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -17,6 +17,11 @@ target-version = "py311"
select = ["E", "F", "W", "I", "N", "UP", "B", "C4", "SIM", "ASYNC", "RUF"]
ignore = ["E501"]
+[tool.ruff.lint.per-file-ignores]
+# Streamlit exige que las páginas se llamen "__