Operations

Durable Work Data Streams

Append-only workflow payload streams with strict resume cursors, restart-safe producer credit, large-object intents, retention gaps, and residency pins.

Commit the record first. Treat every live signal as a reason to reread.
Order
Strict ascending sequence with exclusive resume cursor
Pressure
Durable item and byte credit under a Postgres row lock
Placement

Immutable retention, redaction, residency, and egress pins

Workflow data streams carry progressive input or output payloads independently of run-state subscriptions.

Exact cursor rule

Every read returns records with sequence > cursor in ascending order. A live subscription is established before backfill; boundary duplicates are dropped and a live gap is repaired from Postgres. Notification is only a wake signal and never precedes durable commit.

If retention removed cursor + 1, the API returns HTTP 410 with requested, earliest, and latest sequences plus a resync reason. A future cursor is invalid input, not an implicit reset.

Continuity loopDurability repairs reconnects and live-delivery gaps
  1. Subscribe

    Establish the live wake channel before reading retained history.

  2. Backfill

    Return durable records with sequence strictly greater than the cursor.

  3. Follow

    Drop boundary duplicates and treat notifications only as wake signals.

  4. Repair

    Read any detected gap from Postgres before continuing live delivery.

HTTP surface

The authenticated API is mounted below /api/workflow-streams:

| Operation | Relative path | | ------------------- | ------------------------------------------------------------------ | --------------------------------------------- | | Create definition | POST /runs/{runID}/streams | | Read definition | GET /runs/{runID}/streams/{streamID} | | Append/read records | POST | GET /runs/{runID}/streams/{streamID}/records | | Subscribe with SSE | GET /runs/{runID}/streams/{streamID}/subscribe | | Acknowledge | POST /runs/{runID}/streams/{streamID}/consumers/{consumerID}/ack | | Close | POST /runs/{runID}/streams/{streamID}/close | | Legal hold | PUT /runs/{runID}/streams/{streamID}/legal-hold |

The /objects prepare, upload, commit, and abort operations implement recoverable large-payload delivery. Inline records are capped at 64 KiB; object bodies at 32 MiB.

Durable producer credit

Credit is computed under the Postgres stream-row lock from unread durable items and bytes after the minimum consumer acknowledgement. It never depends on process-local subscriber counts.

  • Within unread and total ceilings: append at the next sequence, then notify.
  • Unread ceiling reached: block/retry or reject according to pinned policy.
  • Total ceiling reached: return quota exhausted without appending.
  • Closed stream: reject without appending.

Acknowledgements only advance. Regression, future sequence, or cross-scope ack fails. Backpressure maps to HTTP 429 with Retry-After.

Input and large-object recovery

Input records and their delivery outbox commit together. A dispatcher publishes a deterministic event through the existing workflow wait service, so streams do not create another resume authority.

Large objects follow prepared → uploaded → committed, or abort/expiry. Prepare pins scope, idempotency key, digest, size, schema, authorization receipt, expiry, and a server-owned key. A record is visible only after object evidence and metadata commit agree. Duplicate commit returns the original sequence; orphan cleanup removes uncommitted objects.

Data handling

Definitions immutably pin metadata home/replicas, object regions, client-egress regions, log region, retention, redaction, and authorization. Compute movement cannot relocate data. Legal hold prevents pruning and object deletion. Raw hidden model reasoning is not an allowed stream content class.

No default MCP payload tools

Arbitrary stream payloads may be sensitive. Ductor does not register general MCP read/append tools without a separate explicit tool policy and entitlement contract.

Operations

Migration 476 creates stream records, append receipts, consumers, object intents, input delivery, deletion outboxes, and gap evidence. Workers persist claims and retry state in Postgres, so restart or replica takeover does not lose delivery.