Recurring Jobs with Wolverine and CritterWatch
Wolverine has treated scheduled work as an ordinary message since 6.34, and 6.52/6.61 split schedules into a durable inbox path and a timer-agent path depending on how often they fire. CritterWatch 1.2 adds fleet-wide visibility to pause, resume and trigger every schedule, including over MCP.
Key points
- A recurring job is a normal Wolverine message plus one `ScheduleRecurring` registration, so retries, outbox, dead letters and tracing apply with no extra code.
- Schedules fire once per cluster via a failover-capable agent and a deduplication id built from schedule name and occurrence time.
- From 6.52 the shortest gap in a week selects the path: 15 minutes or more uses a pre-scheduled inbox envelope, anything faster uses an in-memory timer agent.
- Occurrences on the timer path leave no inbox row, so ticks lost during a node failover never run; scheduled-message occurrences still fire.
- `SkipWhileRunning()` added in 6.61 prevents a slow handler from piling up ticks, and timers now support per-second schedules.
- CritterWatch 1.2's Schedule Explorer shows last handled status, plain-language cron labels and lets operators pause, resume or trigger schedules, including via MCP.
Wolverine 6.34 turned scheduled work into an ordinary message: call `opts.Schedules.ScheduleRecurring<T>(cron)` and the framework publishes that message on the cron expression, so the handler picks up the transactional outbox, retries, dead letters, middleware and OpenTelemetry spans (`wolverine.recurring.occurrence`) that any broker message gets. Because a single cluster agent owns the schedule and each occurrence carries a deterministic deduplication id, it fires once per cluster rather than once per node.
From 6.61 the gap between occurrences decides the path: schedules whose shortest weekly gap is 15 minutes or more are pre-queued in the durable inbox, while faster ones run on an in-memory timer agent per schedule. `SkipWhileRunning()` drops overlapping ticks, and `UseTimer()`/`UseScheduledMessages()` override the choice. CritterWatch 1.2 adds a Schedule Explorer that shows, pauses, resumes and triggers schedules fleet-wide, also over MCP.
Real-world use
- A nightly rollup at 7 AM in a specific time zone that must still fire after a crash, so it stays on the pre-scheduled inbox path.
- A five-second inventory poll where overlapping ticks should be dropped and losing a tick during a rolling deploy is harmless.
- An operations team pausing a noisy payer-feed check during an incident and triggering it manually afterwards, without a deployment.
- An AI agent querying schedule health over MCP to report which recurring jobs failed on the last run.
Why it matters
Scheduled work inherits the reliability and observability you already paid for in your messaging layer, instead of living in a second scheduler that operators cannot see or stop without a deploy.
Related stories
- CritterWatch 1.1 groups dead letters by failure cause and acts on each group at onceJasperFx (Critter Stack) BlogOct 29
- Publish Visual Studio Marketplace extensions securely with Trusted PublishingMicrosoft for Developers BlogOct 28
- Running AWS Transform from GitHub Copilot through MCPAWS .NET on AWS BlogOct 27