chore(plan): marca Phase E (Tasks 17-20) completada

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>
This commit is contained in:
Juan
2026-05-10 15:21:07 +02:00
co-authored by Claude Opus 4.7
parent 5738bd3118
commit de738e5f15
+35 -24
View File
@@ -2728,6 +2728,13 @@ git commit -m "feat(policies): añade política default v1 con guardrails comple
## Phase E — LangGraph Runtime (Tasks 1720) ## Phase E — LangGraph Runtime (Tasks 1720)
> **Nota de implementación (desviación del plan):** los nodos del grafo son `async def`, así que se invoca con `await graph.ainvoke(...)`, que el `SqliteSaver` síncrono no soporta. El checkpointing se cambió a **`AsyncSqliteSaver`** expuesto como context manager asíncrono (`async with build_checkpointer(data_dir) as cp: ...`). Consecuencias:
> - `core/requirements.txt` fija `aiosqlite>=0.20,<0.21` (la 0.21 eliminó `Connection.is_alive()`, usado por `langgraph-checkpoint-sqlite` 2.x).
> - `AgentOrchestrator` no guarda el checkpointer en `__init__`; abre uno por llamada sobre `self._data_dir`. El estado igual persiste en `data_dir/checkpoints.sqlite` y un `awaiting_approval` sobrevive a un reinicio.
> - Se añadió `tests/unit/test_runtime_orchestrator.py` (5 tests; el plan no incluía tests del orchestrator).
> - Los bloques de código de Tasks 18-20 abajo son la versión original del plan; la **versión autoritativa es la committeada**. Fases siguientes que usen `build_checkpointer` deben usar el patrón `async with`.
### Task 17: AgentState y nodos de validación ### Task 17: AgentState y nodos de validación
**Files:** **Files:**
@@ -2737,7 +2744,7 @@ git commit -m "feat(policies): añade política default v1 con guardrails comple
LangGraph define el estado como `TypedDict`. El reducer `operator.add` sobre `decision_path` permite que cada nodo acumule pasos sin pisar lo previo. LangGraph define el estado como `TypedDict`. El reducer `operator.add` sobre `decision_path` permite que cada nodo acumule pasos sin pisar lo previo.
- [ ] **Step 1: Test** - [x] **Step 1: Test**
```python ```python
"""Tests de los nodos del grafo (lógica pura, sin compilación de grafo).""" """Tests de los nodos del grafo (lógica pura, sin compilación de grafo)."""
@@ -2818,7 +2825,7 @@ async def test_validate_input_pasa_sin_pii(
assert out["status"] == "running" assert out["status"] == "running"
``` ```
- [ ] **Step 2: Implementar `state.py`** - [x] **Step 2: Implementar `state.py`**
```python ```python
"""Estado tipado del grafo LangGraph.""" """Estado tipado del grafo LangGraph."""
@@ -2844,7 +2851,7 @@ class AgentState(TypedDict, total=False):
human_decision: dict | None human_decision: dict | None
``` ```
- [ ] **Step 3: Implementar `nodes.py`** - [x] **Step 3: Implementar `nodes.py`**
```python ```python
"""Factory de nodos LangGraph parametrizados por engine, policy, llm, agent_def.""" """Factory de nodos LangGraph parametrizados por engine, policy, llm, agent_def."""
@@ -3038,7 +3045,7 @@ def build_node_finalize() -> NodeFn:
return finalize return finalize
``` ```
- [ ] **Step 4: Verificar y commit** - [x] **Step 4: Verificar y commit**
```bash ```bash
pytest tests/unit/test_runtime_nodes.py -v pytest tests/unit/test_runtime_nodes.py -v
@@ -3053,35 +3060,39 @@ git commit -m "feat(runtime): añade AgentState y nodos LangGraph (validate, llm
**Files:** **Files:**
- Create: `core/src/agentforge_core/runtime/checkpointer.py` - Create: `core/src/agentforge_core/runtime/checkpointer.py`
- [ ] **Step 1: Implementar** - [x] **Step 1: Implementar**
```python ```python
"""Wrapper sobre SqliteSaver de LangGraph. Centraliza el path y el ciclo de vida.""" """Wrapper sobre AsyncSqliteSaver de LangGraph. Centraliza el path y el ciclo de vida."""
from __future__ import annotations from __future__ import annotations
import sqlite3 from contextlib import AbstractAsyncContextManager
from pathlib import Path from pathlib import Path
from langgraph.checkpoint.sqlite import SqliteSaver from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver
def build_checkpointer(data_dir: Path) -> SqliteSaver: def checkpointer_path(data_dir: Path) -> Path:
"""Devuelve un SqliteSaver apuntando a data_dir/checkpoints.sqlite. """Ruta al fichero SQLite de checkpoints, creando data_dir si no existe."""
La base se crea si no existe. La conexión usa `check_same_thread=False`
porque LangGraph ejecuta nodos en hilos del executor.
"""
data_dir.mkdir(parents=True, exist_ok=True) data_dir.mkdir(parents=True, exist_ok=True)
db_path = data_dir / "checkpoints.sqlite" return data_dir / "checkpoints.sqlite"
conn = sqlite3.connect(str(db_path), check_same_thread=False)
return SqliteSaver(conn)
def build_checkpointer(data_dir: Path) -> AbstractAsyncContextManager[AsyncSqliteSaver]:
"""Context manager async con un AsyncSqliteSaver sobre data_dir/checkpoints.sqlite.
Uso: ``async with build_checkpointer(dir) as cp: ...``. La conexión aiosqlite se
abre al entrar y se cierra al salir; LangGraph crea las tablas (setup()) de forma
perezosa. El estado persiste, así que un awaiting_approval sobrevive a un reinicio.
"""
return AsyncSqliteSaver.from_conn_string(str(checkpointer_path(data_dir)))
``` ```
- [ ] **Step 2: Commit** - [x] **Step 2: Commit**
```bash ```bash
git add core/src/agentforge_core/runtime/checkpointer.py git add core/src/agentforge_core/runtime/checkpointer.py
git commit -m "feat(runtime): añade build_checkpointer wrapper sobre SqliteSaver" # (committeado como) git commit -m "fix(runtime): build_checkpointer asíncrono con AsyncSqliteSaver"
``` ```
--- ---
@@ -3092,7 +3103,7 @@ git commit -m "feat(runtime): añade build_checkpointer wrapper sobre SqliteSave
- Create: `core/src/agentforge_core/runtime/graph.py` - Create: `core/src/agentforge_core/runtime/graph.py`
- Create: `tests/unit/test_runtime_graph.py` - Create: `tests/unit/test_runtime_graph.py`
- [ ] **Step 1: Test (lógica de routing post-validate_input)** - [x] **Step 1: Test (lógica de routing post-validate_input)**
```python ```python
"""Tests del grafo compilado: happy path con MockProvider y guardrails básicos.""" """Tests del grafo compilado: happy path con MockProvider y guardrails básicos."""
@@ -3196,7 +3207,7 @@ async def test_grafo_pausa_en_hitl_si_riesgo_alto(tmp_path: Path) -> None:
assert state.next # tiene siguiente paso pendiente (interrupted) assert state.next # tiene siguiente paso pendiente (interrupted)
``` ```
- [ ] **Step 2: Implementar `graph.py`** - [x] **Step 2: Implementar `graph.py`**
```python ```python
"""Compilación del grafo LangGraph para un AgentDefinition concreto.""" """Compilación del grafo LangGraph para un AgentDefinition concreto."""
@@ -3266,7 +3277,7 @@ def build_graph(
return g.compile(checkpointer=checkpointer) return g.compile(checkpointer=checkpointer)
``` ```
- [ ] **Step 3: Verificar y commit** - [x] **Step 3: Verificar y commit**
```bash ```bash
pytest tests/unit/test_runtime_graph.py -v pytest tests/unit/test_runtime_graph.py -v
@@ -3284,7 +3295,7 @@ git commit -m "feat(runtime): añade build_graph (LangGraph StateGraph + interru
El orchestrator encapsula el ciclo de vida (build graph → invoke → leer estado → serializar a `AgentExecution`). El router de FastAPI lo llamará. El orchestrator encapsula el ciclo de vida (build graph → invoke → leer estado → serializar a `AgentExecution`). El router de FastAPI lo llamará.
- [ ] **Step 1: Implementar** - [x] **Step 1: Implementar**
```python ```python
"""Orchestrator que envuelve build_graph + ainvoke + serialización a AgentExecution.""" """Orchestrator que envuelve build_graph + ainvoke + serialización a AgentExecution."""
@@ -3432,7 +3443,7 @@ class AgentOrchestrator:
) )
``` ```
- [ ] **Step 2: Commit** - [x] **Step 2: Commit**
```bash ```bash
git add core/src/agentforge_core/runtime/orchestrator.py core/src/agentforge_core/runtime/__init__.py git add core/src/agentforge_core/runtime/orchestrator.py core/src/agentforge_core/runtime/__init__.py