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:
- This MR — DB layer: migration, model, feature flag
- API + service: write/read endpoint, entities,
current_threaddenormalisation ontop_duo_workflows_checkpoints(targets this branch) - Cleanup worker: stale blob TTL worker (targets this branch)
- Workhorse + frontend: capability advertising (targets MR 2)
The feature is gated by the duo_workflow_incremental_checkpoints WIP flag
(default off).
How to reproduce
-
In Rails, checkout
feature/duo-workflow-checkpoint-blobs-capability, Advertise incremental_checkpoints capability in... (!239249 - merged) -
In gitlab-ai-gateway, checkout feat(checkpointer): phase 1 shadow writes for i... (gitlab-org/modelops/applied-ml/code-suggestions/ai-assist!5672 - merged), and restart the service
gdk restart duo-workflow-service -
Enable the feature flag;
Feature.enable(:duo_workflow_incremental_checkpoints) -
Rebuild workhorse
make -C $GDK_PATH/gitlab/workhorse gdk restart workhorse -
Trigger a workflow.
-
In dws logs (
gdk tail duo-workflow-service), logs will appear that the incremental deltas are being stored -
in rails (
gdk psql), check if the blobs were stored:SELECT thread_ts, current_thread, count(*) AS blobs, array_agg(channel) AS channels FROM duo_workflows_checkpoint_blobs WHERE workflow_id = <workflow_id> GROUP BY thread_ts, current_thread ORDER BY MIN(id);
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 msworkflow.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" = $1EXPLAIN (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 mscheckpoint.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" = $2EXPLAIN (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 msMR acceptance checklist
- Tests added
- Migration added with
db/docsentry - Feature flag added (
duo_workflow_incremental_checkpoints, default off) -
db:gitlabcom-database-testingtriggered - Database review requested (new table + indexes)