Skip to content

Our Plan for the Critter Stack on .NET 11

Jeremy Miller9th November 2026
The Critter Stack

We're going live on the JasperFx Software YouTube channel on November 9th at 2:00PM Central US time to walk through Stoat, the current plans for the "Critter Stack on .NET 11" wave of major releases, and answer questions about the default changes.

.NET 11 is landing, and the Critter Stack is going to take the "opportunity" to make a wave of major version releases across the entire stack to add .NET 11 support, drop .NET 9, and make a handful of other breaking changes that have been queued up in our backlog:

LibraryTodayNext
JasperFx2.x3.0
Weasel9.x10.0
Marten9.x10.0
Polecat5.x6.0
Fisher1.x2.0
Wolverine6.x7.0
CritterWatch1.x2.0

Every one of those releases targets net10.0 and net11.0 and drops net9.0. The existing lines (Wolverine 6.x, Marten 9.x, and so on) stay around for bug fixes the way the 5.x line of Wolverine does today.

Changing Defaults ​

We shipped a lot of improvements over the 6.x line of Wolverine and the 9.x line of Marten -- deterministic queue ordering, idempotent Kafka producers, strict tenant id handling, 409 responses on concurrency failures, extended projection progress tracking -- and nearly every one of them shipped opt-in because we hold a hard line on not breaking users or ever requiring database schema changes in a minor release. That's the right call, but it does mean a Wolverine or Marten application today starts with somewhat outdated defaults and has to fiddle with plenty of little Enable...() calls to get the behavior we'd actually recommend.

In October we went through every opt-in flag added in the Wolverine 6.x and Marten 9.x lines, one at a time, and decided for each one: flip it, remove it, or leave it alone. The results are below. The rule that governs all of them is the same one we used for Wolverine 6.0 and Marten 9.0:

TIP

Every flipped default has a single, one-line opt-out. Wolverine 7 adds opts.RestoreV6Defaults() (wolverine#5191) and Marten 10 adds StoreOptions.RestoreV9Defaults() (marten#5762). Call that one method and you get exactly the behavior you have today, so you can take the major upgrade first and adopt the new defaults on your own schedule. There's a test behind each helper that asserts every flip is registered with it, so a future flip can't sneak past.

Wolverine 7 Defaults ​

Envelope ids become GuidV7 (wolverine#5197). Wolverine has used the NewId scheme for message ids since the beginning. It's fast and time-ordered, but it derives part of the id from the network identity, and we've now seen it collide when several processes share a host or containers share a network identity. Guid.CreateVersion7() keeps the time ordering for PostgreSQL, MySQL and SQLite and uses genuinely random bits for the rest. The one exception: SQL Server keeps NewIdSqlServerOrder, because SQL Server sorts uniqueidentifier by a different byte order and we're not going to give up the sequential insert pattern there.

Tenancy gets strict (wolverine#5196). Today, if a message shows up carrying a tenant id Wolverine doesn't know about, the default is to fall back to the default broker. In 7.0 a message broker is treated as a trust boundary: an unknown tenant id on either the receiving or the sending side goes to the dead letter queue with a reason that names the tenant, the message type and the endpoint. It never silently lands on another tenant's broker. The NATS transport also stops trusting the tenant id header by default once you've registered tenants, and the gRPC transport stops trusting a client-supplied tenant header when you haven't configured tenant detection. This is a security-flavored change, and it's the one we're testing hardest.

Wolverine.Http gets the defaults it should have had (wolverine#5195). Five flips here: unparseable query string values are rejected instead of ignored, optimistic concurrency failures answer 409 Conflict, an unknown tenant answers 404 instead of a 500, the correlation id header is applied to every endpoint so appended events record who called, and push receivers deduplicate through the inbox when you have a message store.

Queue tables, carefully (wolverine#5192). Deterministic queue ordering and the SQL Server throughput layout become the default -- but only for queue tables Wolverine creates in 7.0. An existing table keeps its layout, full stop. Adding an identity column to a live PostgreSQL table rewrites it under an exclusive lock, and we're not going to do that to you on a routine deploy. The migration guide will show how to convert an existing table deliberately.

Schema objects that are cheap get created always; schema objects that are expensive get created on use. The outbox timestamp index, the load_factor column on the nodes table and the inbox/outbox timestamp column are small, additive DDL, so they stop hiding behind flags (wolverine#5193). The deduplication tables and their cleanup jobs go the other way: they're created only when something in your application actually uses deduplication (wolverine#5194).

Three core flips and three transport flips. On the core side (wolverine#5200): agent commands between nodes are compressed, batched messages in a partitioned topology group by their group id so they keep the ordering guarantee their unbatched siblings have, and NativeAck endpoints get in-memory idempotency automatically. On the transport side (wolverine#5201): SQS inline listeners extend message visibility while handling, the Kafka producer is idempotent (the Java client has defaulted to this since Kafka 3.0), and RabbitMQ waits for prefetched deliveries during shutdown instead of letting them be redelivered.

And a pile of dead flags go away (wolverine#5198), including a few that nothing has read in a long time.

WARNING

Two of these flips have a cluster floor. Compressed agent commands need every node on Wolverine 6.44 or later before the first 7.0 node starts, and because envelope tables only stopped dropping unknown columns in 6.50, every application sharing a schema needs to be on 6.50 or later. Both will be at the top of the migration guide.

Marten 10 Defaults ​

Marten's list is shorter, mostly because Marten 9.x was more conservative about adding flags in the first place.

Extended projection progress tracking is on (marten#5763). This is the feature that lets CritterWatch and the daemon tell you not just where a projection is, but how far behind it is and when it last moved. We tried to flip this in 9.7.0, reverted it in 9.7.1 because of a schema problem, made the columns unconditional in 9.31, and said then that the default would wait for a major. This is that major.

The event store schema stops depending on your configuration (marten#5765). The failure mode we're fixing is two stores, or two applications, pointed at one schema with different settings, each dropping the other's objects on startup. The cheap objects (the is_skipped column, the skip table, the metadata columns) now always exist; only the expensive or semantic ones like tenant partitioning stay conditional. Behavior is still governed by the flags. Only the schema stops caring.

Side effects are gated behind the prior version, in every store (jasperfx#1075). This one actually lives in JasperFx 3.0, and applies to Marten, Polecat and Fisher alike. During a blue/green or rolling deploy, a projection rebuild on the new version shouldn't re-fire side effects the old version already fired. That gate shipped opt-in in JasperFx 2.28 and becomes the default now.

One we're measuring before we decide (marten#5764). Taking an exclusive lock on concurrent appends against the same stream has an obvious correctness appeal and a non-obvious throughput cost, so we're going to run it through the performance rig under real contention and publish the numbers before picking a default.

Dead flags and stale docs get cleaned up too (marten#5766).

Polecat 6 and Fisher 2, Briefly ​

The SQL Server and SQLite stores are coming along for the ride, and for them the train is less about defaults and more about catching up to Marten. Both pick up the JasperFx 3.0 side-effect gate default above, and both get the same kind of single restore-the-old-defaults helper as their big sibling.

Polecat 6.0 gets a real byte-level path through the serializer and SqlBytes reads instead of round-tripping strings, cached QuickAppend statement templates, dirty tracking on documents (which it never had), and a parity pass against what SQL Server 2025 can do now: vector search with a vector index, JSON_CONTAINS and JSON indexes, and dropping the forced cast of the json column to nvarchar(max) (polecat#804).

Fisher 2.0 settles the SQLite bundle question (and with it encryption at rest through SQLite3MC, fisher#462), benchmarks a single-writer queue against the Polly-absorbed BEGIN IMMEDIATE races it lives with today (fisher#463), and gets an AddFisher() Aspire hosting helper for the file path. Both stores also get union support on .NET 11 through the same shared LINQ skeleton Marten uses.

What Else Is in the Train ​

Defaults are the headline, but there's more going on. Very briefly:

  • One handler, one chain. Wolverine 7 makes HandlerChain strictly one-to-one with a handler type and retires the MultipleHandlerBehavior enum. If you've been using Separated, you're already there. If you've been relying on Wolverine combining several handlers for one message into a single chain, Wolverine has been warning you about it since 6.52 (wolverine#5133), well ahead of 7.0. This is one of Wolverine's "original sins": it was first optimized for micro-services, and some decisions were made years before anyone had muttered the words "modular monolith".
  • One Wolverine integration for all three stores. The Marten, Polecat and Fisher integrations are near copies of each other today. The shared parts move down into JasperFx.Events and Weasel, and Wolverine grows a single integration over them. This fixes a handful of bugs that only exist because a fix landed in one copy and not the others.
  • C# unions in your documents and events, on .NET 11 only, across Marten, Polecat and Fisher.
  • Observability naming. The Wolverine meter becomes exactly "Wolverine", metrics move to dotted wolverine.* names, and OTEL_SERVICE_NAME is honored (wolverine#5128). This is breaking for dashboards, which is exactly why it's waiting for a major.
  • Aspire everywhere. Named connections for every transport and store, a AddCritterStackServiceDefaults() that registers every source and meter in the stack, and an Aspire.Hosting.CritterWatch package. The non-breaking parts of this ship before 7.0 in the Wolverine 6.x and CritterWatch 1.x lines.
  • Dependency majors we've been holding back: Npgsql 10, Microsoft.Data.SqlClient 7, Roslyn 5.9, OpenTelemetry 1.20, and the rest.
  • .NET 11 runtime async is going to be measured on the performance rig before we decide whether it's on by default (wolverine#5150).

How We're Running It: A Stoat Plan ​

Thirteen repositories, seven major versions, and a dependency chain that runs JasperFx, then Weasel, then the three stores, then Wolverine, then CritterWatch, with local NuGet packs flowing between each stage so nothing has to hit nuget.org until the very end. That is not something I want to coordinate out of a text file and my own memory, so the whole train is a single Stoat plan:

The critter-stack-net11 plan in Stoat

For anyone who hasn't seen it yet, Stoat is the Critter Stack's coordination tool for AI agent development across repositories. A plan is a directed graph of nodes -- GitHub issues, local package publishes, "consume this version" repins, test-run gates that have to go green on both net10.0 and net11.0, review steps, and the handful of things only a person can do like giving a go-ahead or merging a release. Agents pick up ready nodes, report what they did (and what they left out), park on nodes that are waiting for an upstream package, and ask a person when they're stuck instead of guessing. The plan above has 216 nodes at the time of writing, and every issue linked in this post is bound to one of them.

INFO

I'll be honest about the second motive here. We're using this release train to dogfood Stoat's own execution -- the agents, the gates, the local feeds, the cross-repo publish and consume steps -- on something big and real before we even think about a Stoat 1.0 release. If a coordination tool can't get the Critter Stack itself through a major release spanning thirteen repositories, it has no business being 1.0, and the things the train teaches us are already being filed back as Stoat issues.

Migration Guides and Timing ​

Every library gets a migration guide written against the actual shipped diff, not the plan (wolverine#5154, marten#5737), and the defaults section of each guide leads with the restore helper. The ai-skills repository gets matching wolverine-migration-v6-to-v7 and marten-migration-v9-to-v10 skills so your coding agent can do most of the upgrade for you.

If you have opinions on any of these defaults, now is the time. Every decision above is an open issue in the Wolverine 7.0 and Marten 10.0 milestones, and nothing is merged yet.