Add CheckpointBlob model and migration for incremental checkpoints

What does this MR do and why?

Introduces the duo_workflows_checkpoint_blobs table and CheckpointBlob model to support per-channel blob storage for LangGraph's incremental checkpointing mode.

In incremental mode the agent sends only the channels that changed at each step, instead of the full checkpoint. Each blob captures one channel's serialised state at a given thread_ts. The current_thread column groups blobs by compaction generation — every compaction bumps the counter, and a reader reconstructs the full channel map by loading all blobs in the current group (Workflow#accumulated_blobs_for, added in MR 2). The denormalised counter avoids a recursive parent_ts walk and reduces the read to a single indexed query.

This MR is part of a stack:

  1. This MR — DB layer: migration, model, feature flag
  2. API + service: write/read endpoint, entities, current_thread denormalisation onto p_duo_workflows_checkpoints (targets this branch)
  3. Cleanup worker: stale blob TTL worker (targets this branch)
  4. Workhorse + frontend: capability advertising (targets MR 2)

The feature is gated by the duo_workflow_incremental_checkpoints WIP flag (default off).

How to reproduce

Database queries

stale scope

Used by CleanStaleCheckpointBlobsWorker to delete blobs older than the retention window.

SELECT "duo_workflows_checkpoint_blobs".*
FROM "duo_workflows_checkpoint_blobs"
WHERE "duo_workflows_checkpoint_blobs"."created_at" < NOW() - INTERVAL '30 days'
EXPLAIN (ANALYZE, BUFFERS)
Index Scan using index_duo_wf_checkpoint_blobs_on_created_at on duo_workflows_checkpoint_blobs
  (cost=0.15..1.24 rows=1 width=604) (actual time=0.003..0.003 rows=0 loops=1)
  Index Cond: (created_at < (now() - '30 days'::interval))
  Buffers: shared hit=1
Planning Time: 3.558 ms
Execution Time: 0.013 ms

workflow.checkpoint_blobs association

Used when loading blobs for a workflow (e.g. accumulation across all checkpoints).

SELECT "duo_workflows_checkpoint_blobs".*
FROM "duo_workflows_checkpoint_blobs"
WHERE "duo_workflows_checkpoint_blobs"."workflow_id" = $1
EXPLAIN (ANALYZE, BUFFERS)
Index Scan using idx_duo_wf_checkpoint_blobs_unique on duo_workflows_checkpoint_blobs
  (cost=0.27..2.29 rows=1 width=604) (actual time=5.592..5.592 rows=0 loops=1)
  Index Cond: (workflow_id = 1)
  Buffers: shared hit=5
Planning Time: 0.366 ms
Execution Time: 5.610 ms

checkpoint.checkpoint_blobs association

Used when loading blobs for a specific checkpoint by (workflow_id, thread_ts).

SELECT "duo_workflows_checkpoint_blobs".*
FROM "duo_workflows_checkpoint_blobs"
WHERE "duo_workflows_checkpoint_blobs"."thread_ts" = $1
  AND "duo_workflows_checkpoint_blobs"."workflow_id" = $2
EXPLAIN (ANALYZE, BUFFERS)
Index Scan using idx_duo_wf_checkpoint_blobs_unique on duo_workflows_checkpoint_blobs
  (cost=0.27..2.29 rows=1 width=604) (actual time=0.008..0.008 rows=0 loops=1)
  Index Cond: ((workflow_id = 1) AND (thread_ts = 'abc'::text))
  Buffers: shared hit=5
Planning Time: 0.358 ms
Execution Time: 0.024 ms

MR acceptance checklist

  • Tests added
  • Migration added with db/docs entry
  • Feature flag added (duo_workflow_incremental_checkpoints, default off)
  • db:gitlabcom-database-testing triggered
  • Database review requested (new table + indexes)

References

!238814 (closed)

Edited by Eduardo Bonet

Merge request reports

Loading