feat(pipeline): Redis + Postgres checkpointer backends (#147 phase 3a)#234
Merged
Merged
Conversation
Adds two durable Checkpointer implementations alongside FileCheckpointer. Single file (pipeline/checkpoint.py), guarded optional imports, no API changes to StatePipeline. Existing FileCheckpointer users are unaffected. - RedisCheckpointer (sync redis-py): SET+EX per checkpoint, ZADD/ZRANGE index of run_ids. TTL configurable, default 30 days. Accepts url= or a pre-built client= (for shared pools). - PostgresCheckpointer (sync psycopg3): single firefly_checkpoints table created idempotently on first save; INSERT … ON CONFLICT DO UPDATE for saves; SELECT … ORDER BY sequence DESC LIMIT 1 for load_latest. Accepts dsn= or a pre-built connection=. table_name is validated to prevent SQL injection from a misconfigured caller. - pyproject.toml: psycopg[binary]>=3 added to the existing `postgres` extra alongside asyncpg; `redis` extra already present. - Tests use unittest.mock only — no fakeredis, no testcontainers. A parametrized software-factory scenario runs across all three backends; per-backend tests verify the right calls are issued and that missing deps surface a clear ImportError naming the extra to install. - examples/pipeline_state.py: optional fourth scenario gated on PG_DSN env var demonstrating PostgresCheckpointer. - docs/pipeline.md: backend-comparison table + code snippet for swapping backends. Tests: 17 new in test_checkpoint_backends.py covering per-backend behaviour + cross-backend conformance. Full pipeline suite 114 passed (88 phase-1+2 baseline + 9 phase-2 features + 17 new phase-3a). Lints clean, pyright clean on touched modules.
ancongui
pushed a commit
that referenced
this pull request
May 31, 2026
feat(pipeline): Redis + Postgres checkpointer backends (#147 phase 3a)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on
issue-147-pipeline-evolution(the merged Phase 1+2 branch). Part of #147, phase 3a.Design spec:
docs/superpowers/specs/2026-05-27-pipeline-phase-3a-design.md.What
Two durable
Checkpointerbackends alongside the existingFileCheckpointer, all conforming to the same Protocol so they're swappable without touchingStatePipeline. Single file (pipeline/checkpoint.py), guarded optional imports — existingFileCheckpointerusers are unaffected.Schemas
Redis — one key per checkpoint with TTL, plus a sorted-set index of run IDs:
firefly:ckpt:{pipeline}:{run_id}:{seq:06d}_{node_id}→ JSON record (TTL configurable, default 30 days)firefly:ckpt:{pipeline}:runs→ ZSET of run_ids scored by last-update timestampPostgres — single table created idempotently on first
save:Uses
INSERT … ON CONFLICT DO UPDATEfor saves. Thetable_nameconstructor arg is validated against^[A-Za-z0-9_]+\$to prevent SQL injection from a misconfigured caller.Optional dependencies
The
postgresextra already existed for the async memory store (asyncpg); this PR addspsycopg[binary]alongside it because theCheckpointerProtocol's methods are sync. One extra, two consumers.Without the relevant extra installed, importing
RedisCheckpointer/PostgresCheckpointersucceeds (the classes always exist as names) but constructing them raises a clearImportError:Tests —
unittest.mockonlyNo
fakeredis, notestcontainers, no new dev deps. Real-service verification is out-of-band by the user against actual Redis and Postgres servers.tests/unit/pipeline/test_checkpoint_backends.py(17 tests):saveissues the right calls (SET key value EX ttl,ZADD index ts run_idfor Redis;CREATE TABLE IF NOT EXISTSonce +INSERT … ON CONFLICT DO UPDATEfor Postgres).load_latestround-trips.list_runsreturns the index contents._redis/_psycopgto None, confirmImportErrornames the install command.url=andclient=, rejecting neither, rejecting unsafetable_name.FileCheckpointer(real,tmp_path),RedisCheckpointer(MagicMock with in-memory dict),PostgresCheckpointer(MagicMock connection with in-memory dict). Catches Protocol drift between backends.Docs + example
docs/pipeline.mdCheckpoint+Resume section gains a backend-comparison table and a code snippet showing the swap.examples/pipeline_state.pygains an optional fourth scenario gated on thePG_DSNenv var. No-op when unset, so the example still runs anywhere.Verification
pytest tests/unit/pipeline/→ 114 passed (97 baseline + 17 new)pytest tests/unit/→ 1405 passed (no regressions)ruff check+ruff format --checkcleanpyrightclean on touched filesfrom fireflyframework_agentic.pipeline import RedisCheckpointer, PostgresCheckpointersucceeds; constructing either raises the documentedImportErrornaming the install command. (Verified live in the firefly venv where neither extra is installed.)Stacking
Base =
issue-147-pipeline-evolution(the merged Phase 1+2 branch). Once #232 lands onmain, this PR's base will retarget tomainautomatically.What lands next
StatePipelineEventHandlerProtocol + OTel spans per state-pipeline node.Pause(reason)HITL primitive +AuditLogProtocol (Postgres impl reuses the connection introduced here).