Parent
#32
What to build
The daemon's domain failures become Schema.TaggedErrors instead of thrown Error subclasses
matched by instanceof. In scope: ConcurrencyLimitError, DispatchCapError, DedupeKeyError,
and RunCancelledSignal.
The behaviour that motivates it: server/scheduler.ts decides whether a missed cron window is
reported as skipped-concurrency or as fire-failed by instanceof-checking a caught unknown.
That is a semantic decision resting on an untyped catch — if a future error type is thrown from the
same path, it silently lands in the wrong bucket and a suppressed window looks like a failed one.
Tagged errors make the set exhaustive and the match checked.
Schema.TaggedError is already used twice in the codebase (SchedulerError,
AgentStepChunkError), so this finishes a pattern rather than introducing one. It is also what
makes the error channel usable once services move into context (#36).
RunCancelledSignal deserves care: it is the run's own unwind signal, not a workflow-observable
failure (ADR 0001 §4 / runtime/run.ts's header), and it is caught in exactly one place. Keep that
property — this ticket changes how it is represented, not who catches it.
Acceptance criteria
Blocked by
None (can start immediately).
Parent
#32
What to build
The daemon's domain failures become
Schema.TaggedErrors instead of thrownErrorsubclassesmatched by
instanceof. In scope:ConcurrencyLimitError,DispatchCapError,DedupeKeyError,and
RunCancelledSignal.The behaviour that motivates it:
server/scheduler.tsdecides whether a missed cron window isreported as
skipped-concurrencyor asfire-failedbyinstanceof-checking a caughtunknown.That is a semantic decision resting on an untyped catch — if a future error type is thrown from the
same path, it silently lands in the wrong bucket and a suppressed window looks like a failed one.
Tagged errors make the set exhaustive and the match checked.
Schema.TaggedErroris already used twice in the codebase (SchedulerError,AgentStepChunkError), so this finishes a pattern rather than introducing one. It is also whatmakes the error channel usable once services move into context (#36).
RunCancelledSignaldeserves care: it is the run's own unwind signal, not a workflow-observablefailure (ADR 0001 §4 /
runtime/run.ts's header), and it is caught in exactly one place. Keep thatproperty — this ticket changes how it is represented, not who catches it.
Acceptance criteria
Schema.TaggedErrors carrying their existing fields (DedupeKeyErrorkeepskeyandholderRunId)instanceof, and is exhaustiveRunCancelledSignalis still caught in exactly one place and still producesRunCancelled, notRunFailedDispatchCollisionevents still record the key and the holding run idbun run checkpassesBlocked by
None (can start immediately).