| name | wolverine |
| description | WolverineFx (.NET) framework for in-process command/event handling, asynchronous messaging, durable inbox/outbox, sagas, and HTTP endpoints. Use when working in a .NET project that references `WolverineFx*` packages, calls `UseWolverine()`, or whenever the user asks about Wolverine handlers, message routing, transactional outbox, transports (RabbitMQ, Azure Service Bus, SQS, Kafka, NATS, Pulsar, PostgreSQL, SQL Server, Redis, MQTT, etc.), Wolverine.HTTP endpoints, Marten/EF Core/RavenDb integration, sagas/process managers, middleware, cascading messages, side effects, FluentValidation integration, tracked-session testing, or migrating to/from MediatR/NServiceBus/MassTransit. Also applies to Critter Stack and JasperFx conversations that mention Wolverine. |
Wolverine
WolverineFx is a .NET runtime for command execution and message handling. One
mental model covers many use cases: a method handles a message; the rest is just
where the message comes from and what happens after it succeeds.
- In-process mediator — invoke a command, run a handler. Lower ceremony than MediatR.
- Local message bus — fire-and-forget across in-memory queues with optional durability.
- Distributed messaging — same handler model, fronted by RabbitMQ, Azure Service Bus, SQS/SNS, Kafka, NATS, Pulsar, MQTT, Redis, GCP Pub/Sub, PostgreSQL, SQL Server, etc.
- HTTP endpoints —
WolverineFx.Http exposes methods as ASP.NET Core endpoints with the same conventions.
- Durable messaging — inbox/outbox on top of Marten, EF Core, or RavenDb for transactional consistency.
- Sagas / process managers — long-running stateful workflows.
CQRS and event sourcing are one thing Wolverine pairs well with (via Marten),
but most features apply equally to plain command/event-driven services,
modular monoliths, vertical-slice web apps, background workers, and ETL pipelines.
Do not assume a Wolverine question implies event sourcing.
When agent context is tight
If you only need the API shape for a specific topic, read the matching reference
file below and skip the rest. The list below is the index — load only what you
need for the current task.
| Reference | Load when... |
|---|
| references/handlers.md | Writing or modifying a handler: discovery rules, signatures, cascading messages, side effects, return types, error policies, FluentValidation, [Entity] loading. |
| references/messaging.md | Sending/publishing/scheduling messages, routing rules, listener config, local queues, conventional routing, topics. |
| references/transports.md | Configuring a specific broker (RabbitMQ, Azure Service Bus, SQS, Kafka, NATS, Pulsar, MQTT, Redis, PostgreSQL/SQL Server transport). |
| references/http.md | Building or migrating WolverineFx.Http endpoints, mediator-style routes, response/request conventions. |
| references/durability.md | Enabling inbox/outbox, idempotency, dead letter storage, claim checks, transactional middleware. |
| references/persistence.md | Picking/wiring Marten, EF Core, or RavenDb integration; sagas; multi-tenancy; event sourcing aggregate handlers (only when actually using event sourcing). |
| references/middleware-and-policies.md | Writing conventional middleware, policies, attributes; modifying handler chains. |
| references/testing-and-ops.md | Tracked-session integration tests, command-line diagnostics, codegen, logging, health checks, AOT/cold-start tuning. |
| references/patterns.md | Architectural shape: vertical-slice, modular monolith, mediator-only, railway, ping-pong, leader election, MediatR/MVC/MinAPI migration. |
Core mental model (always-loaded)
A Wolverine application is WolverineOptions configured on the .NET Generic Host:
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseWolverine(opts =>
{
});
var app = builder.Build();
return await app.RunJasperFxCommands(args);
IMessageBus is scoped, injected via DI, and is the single entry point at
runtime:
await bus.InvokeAsync(new DebitAccount(1111, 250));
var status = await bus.InvokeAsync<AccountStatus>(cmd);
await bus.SendAsync(new DebitAccount(1111, 250));
await bus.PublishAsync(new AccountOverdrawn(1111));
await bus.ScheduleAsync(new ReminderDue(id), 1.Days());
A handler is any public method on a public class with a name like
Handle / HandleAsync / Consume / ConsumeAsync on a class suffixed
Handler or Consumer (or marked [WolverineHandler] / IWolverineHandler).
The first parameter is the message; the rest are method-injected from DI:
public static class DebitAccountHandler
{
public static IssueDebited Handle(DebitAccount cmd, IDocumentSession session)
{
return new IssueDebited(cmd.AccountId);
}
}
Return values are not just data — they're cascading messages, side
effects, or storage actions (see handlers.md).
This is what lets handlers stay pure functions.
Critical conventions and gotchas
These are the things agents trip over. Read this section before touching
unfamiliar handler code.
- No runtime reflection in the hot path. Wolverine generates an adapter class per handler at startup (or ahead of time).
dotnet run -- codegen write writes the generated code to Internal/Generated/ so you can read what Wolverine is actually doing — use this when something seems "magic".
- IoC must be transparent. Prefer
AddSingleton<T>() / AddScoped<TInterface, TImpl>() / constructor injection. Opaque lambda factories (AddScoped<T>(sp => ...)) that handlers consume now throw at startup (Wolverine 6); use opts.CodeGeneration.AlwaysUseServiceLocationFor<T>() only as a deliberate escape hatch.
- Public-everything rule. Handler types, handler methods, and message types must all be public. So must FluentValidation validators (or set
IncludeInternalTypes).
- Don't abstract
IMessageBus. Inject it as a method parameter when you need it; otherwise prefer returning cascading messages so handlers stay testable as pure functions.
- Discovery is allow-list based. Only the application assembly is scanned by default. Other assemblies need
[assembly: WolverineModule] or opts.Discovery.IncludeAssembly(...).
- Generated code is not auto-regenerated. When you change a handler signature or middleware while using
Static/Auto codegen mode, delete the stale file under Internal/Generated/ (or run dotnet run -- codegen write).
InvokeAsync only auto-applies Retry policies from your error rules. Requeue, Discard, MoveToErrorQueue, and PauseThenRequeue are silently ignored when invoking inline — exhausted retries propagate the exception back to the caller. If you need dead-letter semantics on synchronous invocation, catch it yourself or use bus.SendAsync so a listener processes it.
SendAsync throws when no subscriber exists — IndicatesNoHandlersException. If a handler "doesn't fire" after SendAsync, check the exception first (see testing-and-ops.md → diagnostics recipes); use PublishAsync for events with 0+ subscribers.
- Fire-and-forget is only durable with the outbox.
PublishAsync / SendAsync are in-memory by default. A crash between the handler returning and the broker accepting the message loses the event. Enable the outbox in durability.md when you need guaranteed delivery — coming from MediatR's in-process INotification model, this is the trap most teams hit first.
- Don't double-commit. With
opts.Policies.AutoApplyTransactions() or [Transactional], the middleware calls SaveChangesAsync() / Marten SaveChangesAsync() for you and flushes the outbox in the same transaction. Calling it yourself produces a second commit and breaks atomicity.
- Don't read CQRS/event-sourcing language into a Wolverine question. Wolverine handles plain commands and events just as well; only the Marten integration adds event-sourcing-specific helpers (
[Aggregate], IEvent<T>, event forwarding). Skip persistence.md's event-sourcing section unless the user is actually using IDocumentSession.Events or Marten projections.
- Local queues are real. A message with a known handler is, by default, routed to a per-message-type in-process queue — not invoked synchronously. Use
InvokeAsync if you need synchronous semantics; configure opts.LocalQueue(...) to tune parallelism and durability.
Diagnostics first
Before debugging routing, handler discovery, or codegen issues, run:
dotnet run -- describe
dotnet run -- codegen preview
dotnet run -- codegen write
dotnet run -- check-env
dotnet run -- resources setup
opts.DescribeHandlerMatch(typeof(SomeHandler)) prints a textual report
explaining why Wolverine did or did not pick a type up as a handler. Reach
for it when handlers "go missing".
Picking the right next reference
- "I need to write/edit a handler" →
handlers.md
- "How do I send/publish/route messages" →
messaging.md, plus transports.md for the specific broker
- "I'm wiring up RabbitMQ / Kafka / SQS / ASB / NATS / ..." →
transports.md
- "HTTP endpoint with Wolverine" →
http.md
- "Transactional outbox / dead-letter / idempotency" →
durability.md, plus persistence.md for the store
- "Saga / long-running workflow" →
persistence.md (sagas section)
- "Custom middleware / cross-cutting policy" →
middleware-and-policies.md
- "Integration test / cold-start / CLI / logging" →
testing-and-ops.md
- "Migrate from MediatR/MVC/MinAPI" or "should this be a modular monolith" →
patterns.md
- "I just want a pure CQRS mediator, no broker, no ES" →
patterns.md#mediator-only (start here for MediatR-replacement use cases)