Complementa a docs/explicacion.md (alto nivel) con la vista de bajo nivel:
- grafo de dependencias de módulos por niveles topológicos (imports internos)
- el grafo de inyección de dependencias de api/deps.py (lru_cache + factories)
- las firmas exactas de los contratos: LLMProvider, GuardrailEngine, los
validadores, el registry/policy store, el AgentOrchestrator, AgentState y el
cableado del grafo LangGraph, y cómo orchestrator._build_execution mapea el
StateSnapshot a AgentExecution
- la cadena de llamada de cada endpoint (qué deps, qué llama, qué persiste)
- mapa de persistencia (quién escribe/lee cada artefacto YAML/JSON/JSONL/SQLite)
- mapa CoreClient ↔ endpoints ↔ páginas del dashboard
- arranque/ciclo de vida y tabla Settings → consumidor
- "gotchas" conocidos
README y docs/explicacion.md enlazan al nuevo documento.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Documento de alto nivel para terceros: las seis ideas clave, vista de los dos
servicios y las capas del core, recorrido módulo a módulo con sus dependencias
(quién depende de quién), el patrón Protocol+factory+Settings, el viaje completo
de una petición (con diagrama de secuencia), la estrategia de persistencia (YAML/
JSON/JSONL/SQLite), glosario, mapa del repo y ruta de lectura. README enlaza a él.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`GET /executions` solo leía `executions.jsonl`, donde nunca se escribe una ejecución
en `awaiting_approval` (esas viven solo en el checkpointer). La página de Aprobaciones
del dashboard hace `[e for e in list_executions() if e.status=="awaiting_approval"]`,
así que nunca encontraba aprobaciones pendientes. Ahora `list_executions` reconstruye
las no-terminales desde `execution_index.json` + `orchestrator.snapshot()` y las
fusiona con las del JSONL. Añade `test_list_executions_incluye_la_awaiting`.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Con Presidio real (imagen Docker) y `en_core_web_sm`, el recognizer de PERSON daba
falsos positivos sobre los textos de los escenarios (técnicos, en español, analizados
con el modelo `en`) → `01_sip` y `02_mos` salían `blocked_by_guardrail` en lugar de
`awaiting_approval`/`completed`. Se quitan `PERSON` y `PHONE_NUMBER` de
`detect_pii.entities` (input) y `PHONE_NUMBER` de `pii_leakage.entities` (output);
quedan los recognizers puramente de patrón (`EMAIL_ADDRESS`, `ES_NIF`, `IP_ADDRESS`,
`IBAN_CODE`), fiables. El bloqueo por NIF/email del demo sigue funcionando.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`detect_pii` instanciaba `AnalyzerEngine()` sin configuración, así que Presidio
intentaba cargar su modelo por defecto `en_core_web_lg` (~560 MB), que no está en
la imagen Docker — `core/Dockerfile` instala `en_core_web_sm`. Resultado: el
validador fallaba con `[E050] Can't find model 'en_core_web_lg'` y, al ser la
política fail-closed, *toda* invocación quedaba `blocked_by_guardrail`. Además se
reinstanciaba el engine en cada llamada (caro; probablemente la causa del crash del
proceso al segundo invoke). Ahora:
- `_presidio_analyzer()`: singleton perezoso con `NlpEngineProvider` → `en_core_web_sm`.
- si Presidio no está instalado o el modelo no carga, se hace fallback a la
detección por regex (antes solo se hacía fallback ante ImportError).
Detectado ejecutando el smoke de docker-compose (el venv local no tiene Presidio,
así que los tests usan la rama de regex y no lo veían).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Notas de desviación: Task 31 añade per-file-ignore N999 para pages/* (Streamlit
exige nombres con emoji); se quitan noqa BLE001 inútiles y la var muerta
chosen_example; Task 37 — el build de docker falló por typer 0.12.x vs click
>=8.2, se fijó click<8.2 y el smoke pasó (core healthy /health 200, dashboard
/_stcore/health ok, down limpio). Streamlit verificado con ruff + py_compile.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`guardrails-ai<0.6` arrastra `typer 0.12.x`, que es incompatible con click
>=8.2 ("Secondary flag is not valid for non-boolean flag"); el build de
core/Dockerfile fallaba en `python -m spacy download en_core_web_sm`. Detectado
al ejecutar el smoke de docker-compose.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
core (8000) y dashboard (8501); dashboard depende de core service_healthy.
Monta agents/ y policies/ read-only y data/ read-write en el core; el dashboard
también monta agents/ ro para leer los escenarios. `docker compose config` valida.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Selector de política; muestra descripción, on_validator_error y los validadores
de input/output (cada uno expandible con su config), más la tabla de versiones.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Tab "Ejecuciones": tabla resumen + detalle (status, error, output, violations,
traza) por trace_id. Tab "Violaciones": log filtrable por severidad.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Lista ejecuciones en awaiting_approval; por cada acción de needs_human_for
muestra risk_score coloreado, target y rollback_plan con un checkbox de
aprobación. Aprueba el subconjunto seleccionado (con comentario) o rechaza con
razón vía /approve y /reject; rerun tras la decisión.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- components/trace_view.render_trace: decision_path como timeline con duración.
- components/violation_view.render_violations: violaciones con badge de severidad.
- pages/2_▶️_Ejecutar.py: selector de agente, botones de escenarios pregrabados,
textarea, invoke y render de status/output/violations/traza. Aviso si queda en
awaiting_approval. (Se omite la variable muerta chosen_example del plan.)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- components/diff_view.render_unified_diff: pinta +/-/@@ del unified diff.
- pages/1_🏛️_Registro.py: selector de agente, detalle (estado, owner, propósito,
guardrails, system_prompt, output_schema, llm), tabla de versiones y comparador.
- pyproject: per-file-ignore N999 en pages/* — Streamlit exige nombres
"<n>_<emoji>_<Label>.py" para la navegación multipágina.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
CoreClient (httpx sync, 2 retries) cubre todos los endpoints del core y mapea
404/409/422 a {"error": ...}. app.py monta la página raíz con sidebar de branding
y un health-check del core.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Nota de desviación: Task 26 reescribe el escenario HSS para no colisionar con
'mos'; Task 27 colapsa EXAMPLES a una línea y reusa el __init__ del scaffolding;
Task 29 Step 2 (índice de ejecuciones persistido) ya estaba hecho en Task 24, así
que ese commit solo añade el test. Estilo del repo = ruff check (no ruff format).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Invoca el escenario SIP hasta awaiting_approval, descarta el TestClient (y las
caches DI) y crea otro sobre el mismo DATA_DIR; el /approve posterior reanuda
hasta completed gracias a execution_index.json + checkpoints.sqlite.
Desviación del plan: el Task 29 Step 2 ("persistir índice de ejecuciones") ya
se implementó en el Task 24 (api/executions.py: _record_execution/_load_index/
_resolve sobre data_dir/execution_index.json), así que este commit solo añade
el test.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- test_invoke_hitl: escenario SIP real → awaiting_approval; approve devuelve
status=completed con approved_actions; reject deja status=failed con
error=rejected_by_human.
- test_invoke_pii_block: input con NIF español y email → status=blocked_by_guardrail
y violación DetectPII con blocked=True.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Invoca el agente real incident_analyzer (assets del repo, LLM mock) vía
TestClient y comprueba status=completed, final_output y que el decision_path
recorre los seis nodos del grafo. Añade conftest con la fixture
integration_client (env apuntando a agents/ y policies/ del repo, caches DI
limpiadas).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
El escenario HSS evita la subcadena 'mos' (de 'últimos') para que el
MockProvider lo mapee a la respuesta canónica 'hss' y no a la de 'mos'.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Nota de desviación: fastapi/uvicorn instalados, routers con estilo Annotated[T, Depends],
orchestrator.snapshot() en vez de _build_graph_for_snapshot, fixture default + tests extra.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- POST /agents/{name}/invoke, GET /executions, GET /executions/{trace_id},
POST /executions/{trace_id}/{approve,reject}.
- execution_index.json persiste trace_id → (agent_name, version) para reconstruir
el contexto en approve/reject tras un reinicio.
- Refactor del orchestrator: split _snapshot → _build_execution + _snapshot, nuevo
método público snapshot() (lee estado sin avanzar). Sustituye a los helpers
_snapshot_execution/_build_graph_for_snapshot del plan, incompatibles con el
checkpointer por-llamada.
- Aliases Annotated[...] de dependencias movidos a deps.py.
- Fixture mínima tests/fixtures/policies/default/.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Incluye nota de desviación: SqliteSaver → AsyncSqliteSaver (async CM),
aiosqlite<0.21, orchestrator abre el checkpointer por llamada, +tests del orchestrator.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Punto único de entrada al runtime. Cada llamada abre su propio AsyncSqliteSaver
sobre data_dir/checkpoints.sqlite, así que un awaiting_approval sobrevive a un
reinicio del proceso (verificado en test_estado_persiste_entre_instancias).
El plan no incluía tests del orchestrator; se añaden 5 (invoke feliz, pausa HITL +
resume aprobar/rechazar, bloqueo por guardrail, persistencia entre instancias).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Los nodos del grafo son async, así que se invoca con `await graph.ainvoke`,
que el SqliteSaver síncrono no soporta (NotImplementedError). Se cambia a
AsyncSqliteSaver expuesto como context manager async. Requiere aiosqlite<0.21
(la 0.21 eliminó Connection.is_alive(), usado por langgraph-checkpoint-sqlite 2.x).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>