Skip to main content
← Back to list
01Issue
FeatureClosedSwamp CLI
AssigneesNone

Relationships

#1448 Make swamp serve durable and highly available

Opened by stack72 · 7/28/2026

Problem

swamp serve is a single point of failure. Every piece of in-flight state — queued webhook runs, queued cron fires, active workflow executions, and worker dispatch bookkeeping — lives in memory. A process crash silently loses accepted work that callers already received a 200 queued for. On restart, a boot sweep tombstones interrupted workflows rather than recovering them. Running a second instance is actively destructive: the new instance's startup sweep destroys the first instance's live state.

This makes swamp serve unsuitable for any deployment where uptime matters — Kubernetes pods get replaced, ASG instances die, processes OOM, and hosts reboot. Today, every one of those events loses work with no recourse.

Desired Outcome

swamp serve can be deployed as one or more instances behind a load balancer with no accepted work lost on process death, no unsafe overlap during instance replacement, and no new infrastructure dependencies.

Specifically:

  • No accepted work is ever memory-only. Every webhook acknowledgement and cron fire is backed by a durable record before the caller is told it succeeded.
  • Process death is recoverable. Interrupted work is re-enqueued or resumed at startup according to a per-workflow policy, not silently discarded.
  • Instance replacement is safe. A new instance starting while an old one is still running cannot corrupt or destroy the old instance's in-flight state.
  • Multiple instances can share work. N instances can run against the same datastore, dividing work by per-item claims, with distributed deduplication for cron.
  • No new services. All coordination primitives (leases, epochs, claims, fire records) are records in the datastore the repo already has. The deployment unit remains a single swamp binary.

The full design brief and phase breakdown live in the artifact linked from this session. The implementation will be tracked as child issues of this one.

02Bog Flow
OPENTRIAGEDIN PROGRESSCLOSED

Closed

8/3/2026, 8:54:40 PM

No activity in this phase yet.

03Sludge Pulse
Editable. Press Enter to edit.

stack72 commented 7/28/2026, 11:23:32 PM

First PR - https://github.com/swamp-club/swamp/pull/2005

swamp serve ties a run's life to the WebSocket that started it. Socket dies, run dies. That makes HA impossible — there's nothing to reconnect to.

This PR adds --detach-runs. With it, runs execute independently of the client connection. A disconnected client can re-attach by run ID and pick up the event stream where it left off. Without the flag, behavior is identical to today.

this is the foundation for durable jobs, crash recovery and multi-instance so clients can reconnect

stack72 commented 7/29/2026, 10:20:15 PM

Second PR - https://github.com/swamp-club/swamp/pull/2012

You can now upgrade swamp serve without losing work.

With --detach-runs, stopping the process — whether it's a clean kill -TERM for an upgrade or an unexpected crash — no longer silently drops queued webhooks or leaves in-flight workflows stuck in limbo. Pending work is persisted to SQLite before the caller gets a 200. Interrupted runs are marked as failed with an honest reason tag so the operator can see exactly what happened and resume from the interrupted step.

Start the new binary, it reads the local state, replays pending work, and picks up where the old process left off. No manual intervention for the happy path.

stack72 commented 7/31/2026, 3:31:50 PM(edited)

PR 3 - https://github.com/swamp-club/swamp/pull/2036

swamp serve can now run on disposable infrastructure. When an ASG instance dies or a Kubernetes pod is replaced, the new instance reads heartbeats and pending work from S3, detects that its predecessor is dead, marks interrupted runs as failed, replays any queued webhooks, and starts serving — no operator involved.

stack72 commented 8/1/2026, 12:29:39 AM

PR 4 - https://github.com/swamp-club/swamp/pull/2038

We made multi-instance swamp serve stop double-firing cron workflows. When N instances share a schedule, they all race to create a fire record via putIfAbsent — one wins, the rest skip. The tricky part was deriving a deterministic key: croner's currentRun() is just new Date(), not the scheduled time, so we built canonicalFireTime() to snap wall-clock time back to the cron pattern grid. Best-effort by design — if the store is unreachable, it fires anyway rather than silently dropping work. A background reaper cleans up old records every 10 minutes.

stack72 commented 8/1/2026, 11:22:44 PM

PR 5 - https://github.com/swamp-club/swamp/pull/2042

Ensure that swamp serve can rehydrate from the datastore rather than a user needing to encode that as part of the init script. This helps keep the ephemeral nature of the instance

stack72 commented 8/2/2026, 9:40:02 PM

PR 6 - https://github.com/swamp-club/swamp/pull/2044

When one swamp serve instance dies in a multi-instance deployment, surviving instances now detect the dead peer within ~60 seconds and clean up its orphaned state — previously this only happened at boot, so stale heartbeats and runs could sit forever if no instance restarted. Detection timing, heartbeat interval, and stale threshold are all configurable via CLI flags.

stack72 commented 8/3/2026, 8:54:39 PM

All five desired outcomes from this issue are now shipped:

  1. No accepted work is ever memory-only — pending runs persisted to SQLite + S3 control-plane store before ack (#2012, #2036)
  2. Process death is recoverable — interrupted runs marked as failed with interrupt tags, pending work replayed on restart (#2012, #2036)
  3. Instance replacement is safe — heartbeat-based stale detection, per-instance claims, boot + continuous reconciliation (#2036, #2044)
  4. Multiple instances can share work — cron dedup via fire records (#2038), continuous reconciliation (#2044), cross-instance client re-attach (#1514), active run records for visibility (#1507)
  5. No new services — all coordination is records in the control-plane store (S3). No Redis, Postgres, etcd, or broker.

PRs shipped:

  • #2005 — Phase 1: runs survive disconnection (--detach-runs)
  • #2012 — Phase 2: runs survive same-machine restart
  • #2017 — Control-plane store interface
  • #2033 — putIfAbsent CAS primitive
  • #2036 — Phase 3: disposable instance HA (heartbeat, identity, cross-machine reconciliation)
  • #2038 — Cron dedup via fire records
  • #2042 — Startup cache hydration
  • #2044 — Continuous reconciliation + per-instance claims
  • #1507 — Active run records in control-plane store
  • #1514 — Cross-instance client re-attach

Follow-on work tracked separately:

  • #1488 — Startup validation + /ready endpoint
  • #1505 — Auto-detect HA mode, deprecate --detach-runs
  • #1517 — Serve config file
  • Documentation: deployment guides, config reference, troubleshooting, architecture overview

Closing this issue — the HA system works end-to-end across all three deployment patterns (single instance, single instance with remote workers, N instances behind a load balancer).

Sign in to post a ripple.