File size: 3,282 Bytes
f02fdcc | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | """
State checkpoint system β saves execution snapshots at every graph super-step.
LangGraph-style thread-level checkpointing:
- A snapshot is written BEFORE and AFTER every node execution
- Partial progress and pending writes survive mid-turn failures
- Point-in-time rollback is supported without data duplication
"""
from dataclasses import dataclass, field
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional
import uuid
@dataclass
class ExecutionCheckpoint:
checkpoint_id: str = field(default_factory=lambda: str(uuid.uuid4()))
workflow_id: str = ""
run_id: str = ""
thread_id: str = ""
node_id: str = ""
step: int = 0
state: Dict[str, Any] = field(default_factory=dict) # full $flow.state snapshot
pending: Dict[str, Any] = field(default_factory=dict) # writes not yet committed
status: str = "pending" # pending | committed | rolled_back
created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
class CheckpointStore:
"""
In-process checkpoint store (SQLite/PostgreSQL backend can be plugged in
by subclassing and overriding _persist / _load).
"""
def __init__(self):
self._store: Dict[str, List[ExecutionCheckpoint]] = {} # run_id β checkpoints
# ββ Write βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def save(self, checkpoint: ExecutionCheckpoint) -> ExecutionCheckpoint:
self._store.setdefault(checkpoint.run_id, []).append(checkpoint)
return checkpoint
def commit(self, checkpoint_id: str, run_id: str) -> None:
for cp in self._store.get(run_id, []):
if cp.checkpoint_id == checkpoint_id:
cp.status = "committed"
return
# ββ Read ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def latest(self, run_id: str) -> Optional[ExecutionCheckpoint]:
checkpoints = self._store.get(run_id, [])
committed = [c for c in checkpoints if c.status == "committed"]
return committed[-1] if committed else None
def history(self, run_id: str) -> List[ExecutionCheckpoint]:
return list(self._store.get(run_id, []))
# ββ Rollback βββββββββββββββββββββββββββββββββββββββββββββββββββββ
def rollback(self, run_id: str, to_step: int) -> Optional[ExecutionCheckpoint]:
"""Return the last committed checkpoint at or before `to_step`."""
checkpoints = self._store.get(run_id, [])
candidates = [
c for c in checkpoints
if c.status == "committed" and c.step <= to_step
]
return candidates[-1] if candidates else None
def all_runs(self) -> List[str]:
return list(self._store.keys())
# Singleton
checkpoint_store = CheckpointStore()
|