Skip to the content.

Task 108 — DurableTask checkpoint persistence

Phase: 8 Status: done Owner: — Slug: checkpoint-persistence Studio Stage: 8

Goal

Заменить stub в TaskExecutionService.CreateCheckpointAsync на реальную persistence. Checkpoints должны сохраняться и загружаться.

Acceptance criteria

Sub-tasks

Implementation notes

Что было готово до этого тика

Что добавил task_108

  1. ISessionStore.LoadCheckpointAsync(string checkpointId) — новый метод для выборки одного чекпоинта по ID (нужен для ResumeAsync(checkpointId=…)). Реализован в SqliteSessionStore (параметризованный SELECT по id); PostgresSessionStore бросает NotImplementedException как и остальные checkpoint-методы (task_103 follow-up).

  2. TaskExecutionService — рефакторинг поверх ISessionStore:
    • Внедрён ISessionStore через конструктор.
    • CreateCheckpointAsync теперь дёргает _sessionStore.SaveCheckpointAsync (раньше был stub, возвращавший ID без записи).
    • ListCheckpointsAsync теперь читает из _sessionStore.ListCheckpointsAsync (раньше возвращал пустой список).
    • IncrementStepAsync поднят с internal helper до публичного метода интерфейса (логика _repo.UpdateAsync уже была корректной — просто стала доступна извне).
    • LoadCheckpointAsync(string) — публичный прокси к _sessionStore.LoadCheckpointAsync.
    • ResumeAsync(taskId, checkpointId?):
      • Если checkpointId задан — пытается загрузить конкретный чекпоинт через LoadCheckpointAsync; при отсутствии логирует warning и фолбэчится на последний из ListCheckpointsAsync.
      • Если checkpointId не задан — берёт самый свежий чекпоинт из ListCheckpointsAsync (если есть).
      • Возвращает задачу в Running, обнуляет Error, логирует [Task] Resumed … from checkpoint {id} (step={n}, snapshotBytes={b}).
  3. TaskExecutionService.RecoverIncompleteTasksAsync — startup recovery:
    • Сканирует Running, Paused, Failed через ITaskRepository.ListAsync(statusFilter: …).
    • Для Running/Paused: ставит Status = Running, обновляет UpdatedAt, обнуляет Error. Логирует [Recovery] Resumed {PreviousStatus} task {Id} (checkpoint={id|None}, step={n}).
    • Для Failed: оставляет статус, логирует [Recovery] Skipped failed task {Id} (checkpoint={id|None}) — manual intervention required.
    • В конце — best-effort CleanupOldCheckpointsAsync(_config.CheckpointRetentionDays). Ошибка retention-cleanup не блокирует старт.
    • Возвращает количество реактивированных задач.
  4. DI wiring (в src/agent/Program.cs + src/agent/Hercules.WebApi/Program.cs):
    • ITaskExecutionService теперь резолвит ISessionStore через конструктор.
    • В Hercules.WebApi/Program.cs после инициализации MCP добавлен блок try { var recovered = await taskExec.RecoverIncompleteTasksAsync(); … } (под if (!isBuildTime)).
  5. Controller (TaskProgressController.cs):
    • POST /api/tasks/{id}/checkpoints — новый endpoint, body CreateCheckpointRequest { int StepNumber, string? StateSnapshot }. Создаёт чекпоинт + дёргает IncrementStepAsync, возвращает 201 Created с Location и метаданными.
    • GET /api/tasks/{id}/checkpoints/{ckptId} — новый endpoint, возвращает полный snapshot.
    • Все 8 существующих эндпойнтов получили .WithTags("Tasks") для консистентности с task_110/Orval tags-split (когда дойдут руки до web-UI codegen, тег уже будет на месте).
  6. Tests (tests/Hercules.Agent.Tests/Tasks/TaskExecutionServiceTests.cs):
    • 13 старых тестов остались (signature constructor изменён — ISessionStore теперь обязателен).
    • CreateCheckpointAsync_ReturnsCheckpointId → CreateCheckpointAsync_PersistsCheckpoint (теперь ассертит round-trip через LoadCheckpointAsync).
    • ListCheckpointsAsync_ReturnsEmptyList_Stub → ListCheckpointsAsync_ReturnsPersistedCheckpoints (теперь создаёт 2 чекпоинта и проверяет порядок).
    • Новые тесты: LoadCheckpointAsync_Nonexistent, IncrementStepAsync_Persists, ResumeAsync_LoadsCheckpointSnapshot, ResumeAsync_FallsBackToLatestCheckpoint_WhenIdMissing, RecoverIncompleteTasksAsync_ResumesRunningAndPaused, RecoverIncompleteTasksAsync_SkipsFailedTasks.

Дизайн-решения

Что осталось за рамками

Validation

Scope / Likely files

Dependencies

Scope / Likely files

src/agent/Tasks/TaskExecutionService.cs (refactor), src/agent/Tasks/ITaskExecutionService.cs, src/agent/Storage/SqliteSessionStore.cs (add table)