Summary
The saga engine's instance resolution never consults the definition's .correlate()
extractor. It keys instances on message.correlationKey ?? "<sagaId>:<messageType>".
Consequence: unless the publisher sets an explicit correlationKey on every message, all
logically distinct workflows collapse onto one shared saga instance. In the reproduction, every
incident in the system shared a single escalation saga.
.correlate() appears prominently in every example on the sagas capability page.
Found by a wave-four demo agent with no prior exposure to NetScript. Related: #1064, and the
sagaCompensate drop filed alongside this — same surface, same session.
Why this is the most dangerous of the three
The other two saga findings fail loudly enough to notice: one hangs, one produces no effect.
This one succeeds and produces wrong state. Two incidents raised minutes apart silently share
escalation level, deadline timers, and page history. Nothing errors. Nothing logs. The system looks
healthy and is corrupting workflow state.
It is also the hardest to catch in a demo or a test, because it only manifests once two
instances overlap — a single-workflow happy-path test passes cleanly.
Observed
Instance resolution uses:
message.correlationKey ?? `${sagaId}:${messageType}`
With no correlationKey on the published message, every IncidentTriggered maps to the same key,
so a second incident resumes the first incident's state machine rather than starting its own.
The agent found this by dumping KV keys directly — there is no error path that surfaces it.
Expected
Given a definition that declares an extractor:
defineSaga('escalation')
.correlate((message) => message.payload.incidentId)
...
...the engine should use that extractor to derive the instance key, and only fall back to
correlationKey / the composite default when no extractor is declared.
Acceptance
Provenance
Wave-four demo run 1 (Claude Fable 5, high effort), 2026-08-03. ~20 min lost (combined with the
sagaCompensate finding); would have cost far more without a direct KV key dump.
Full drift log: /home/codex/repos/wave4-fable/.llm/run/drift.md, finding #3 of 9.
Product repo: https://github.com/rickylabs/vigil
Summary
The saga engine's instance resolution never consults the definition's
.correlate()extractor. It keys instances on
message.correlationKey ?? "<sagaId>:<messageType>".Consequence: unless the publisher sets an explicit
correlationKeyon every message, alllogically distinct workflows collapse onto one shared saga instance. In the reproduction, every
incident in the system shared a single escalation saga.
.correlate()appears prominently in every example on the sagas capability page.Found by a wave-four demo agent with no prior exposure to NetScript. Related: #1064, and the
sagaCompensatedrop filed alongside this — same surface, same session.Why this is the most dangerous of the three
The other two saga findings fail loudly enough to notice: one hangs, one produces no effect.
This one succeeds and produces wrong state. Two incidents raised minutes apart silently share
escalation level, deadline timers, and page history. Nothing errors. Nothing logs. The system looks
healthy and is corrupting workflow state.
It is also the hardest to catch in a demo or a test, because it only manifests once two
instances overlap — a single-workflow happy-path test passes cleanly.
Observed
Instance resolution uses:
With no
correlationKeyon the published message, everyIncidentTriggeredmaps to the same key,so a second incident resumes the first incident's state machine rather than starting its own.
The agent found this by dumping KV keys directly — there is no error path that surfaces it.
Expected
Given a definition that declares an extractor:
...the engine should use that extractor to derive the instance key, and only fall back to
correlationKey/ the composite default when no extractor is declared.Acceptance
.correlate()extractor when one is declared, and uses itsresult as the instance key
correlationKey→default), stated on the sagas capability page
separate instances — the single-instance happy path does not cover this
.correlate()is genuinely not wired yet, it fails loudly at definition-build time ratherthan being accepted and ignored, and the examples stop showing it as functional
Provenance
Wave-four demo run 1 (Claude Fable 5, high effort), 2026-08-03. ~20 min lost (combined with the
sagaCompensatefinding); would have cost far more without a direct KV key dump.Full drift log:
/home/codex/repos/wave4-fable/.llm/run/drift.md, finding #3 of 9.Product repo: https://github.com/rickylabs/vigil