
I’m the technical leader and founder of the “Critter Stack” tools (Marten, Polecat, Wolverine, and Weasel) and the greater JasperFx organization on GitHub. After 15+ years of OSS community work of varying degrees of technical and project adoption success, I’ve got a few things to share that I think have helped us be more successful. Just know though, that there was plenty of iteration, friction, pain, and flat out failures in the rear view mirror before we arrived at most of the things I’m sharing as “positives” here — and of course, plenty of people would argue with me that at various times we’ve done things badly for them.
First for some soft, non technical things. If you can possibly get to this, having a community invested in the success of your tools and the community succeeding as well is immeasurably valuable. I think we’ve actually got that for the Critter Stack and it shows from the sheer number of contributions we’ve gotten in the past couple years. I won’t lie and say I know how to create that from scratch. The only concrete things I can recommend is to try to be as responsive as possible as a maintainer and at least acknowledge user requests or issues as they come in. We pride ourselves on being responsive and not letting issues linger, and we also try to aggressively improve our tools based on user feedback. I think this has quite clearly improved once I was able to work full time on the Critter Stack from the founding of JasperFx Software. The rise of AI tools has also made it much easier to stay on top of incoming issues and turn around fixes quickly.
Living Documentation
Just know that we’ve had hundreds of complaints over the years about the documentation, but much, much less over time as we’ve adapted and improved. Or maybe just because many people are only using our LLM friendly version of the docs through an AI agent. I’m still taking credit for the apparent reduction in complaints though!
You do not have an OSS project of much value unless you have a documentation website of some sort that helps people know what your project is and how to effectively use your published tools. As a maintainer, it’s also your first line of defense against people needing more of your time than you can afford to give.
First off, make your documentation be user centric. Try to organize the flow of content in terms of what users are needing to do and their use cases. Try to avoid the temptation to organization your documentation around the technical concepts or APIs in your system because that’s an awfully fast way to create an unusable website. One thing that I think has helped us more recently is investing in something like Martin Fowler’s “Duplex Book” idea from years ago where you have top level, prose style tutorials that link to pages with more specific information on various capabilities. Having tutorials that talk about use cases or sample applications that again link to separate pages with API details are also very helpful, if more time consuming for us the maintainers. And you’re likely to do that wrong anyway, so see my later comments about continuous improvement and adaptation in your documentation.
Just use Markdown for all your content now. GitHub and I assume other shells happily render it for you from web browsing anyway, many developers already know how to use it, tools like Vitepress already expect it for static website creation, and anyway, you pretty well have to know Markdown now for AI prompts anyway. I’m explicitly stating this because I remember trying to write documentation websites in straight up HTML or earlier competitors to Markdown that don’t seem to be common any more.
Colocate your documentation with your code. As a default, try to put your documentation content in the same code repository as the code it’s documenting. Again, not everybody does that, but I’ve found that to be hugely valuable compared to older approaches. If you’re using markdown, GitHub by itself helps render the raw doc content in a reasonably usable way.
Invest in some kind of quick automation to update your documentation website. Babu has us fully automated to build and publish our documentation websites built with Vitepress to Netlify via GitHub actions so we’ve got quick a 1-2 click process to update docs. Unsurprisingly, it turns out that if you make it mechanically cheap to republish your documentation, you’re much more likely to make improvements much more frequently.
Try to be responsive to what your users are being tripped up by and continuously evolve and improve your documentation structure, wording, samples, and explanations based off feedback from your users. And do a better job of staying on top of that than I do sometimes!
From bitter experience, it’s very easy for code samples in technical documentation to drift away from the tool’s public API, especially with a long lived project. To that end, I very strongly recommend using some kind of tool like MarkdownSnippets that can extract code samples from code that you know is compiling and runnable. That enables us to decorate sample code snippets from within either test projects or sample applications in the main .NET solution like this:

And have that code inserted live into our Markdown documentation files and on our documentation site. You can see that code snippet above in action here.
Make it as easy as possible for external contributors to suggest or make improvements to the documentation. Having Markdown files directly in your GitHub repository with enough README explanation to know how to edit those files certainly helps. We embed a footer on all of our pages like this with a direct link to fork and create a pull request for the current page:

And every little pull request improving wording or (sorry) grammatical or spelling errors adds up over time. One defensive thing I started doing that turned out to be very helpful over time is to try to defang people up in arms about your documentation by asking them how they would suggest improving the documentation for whatever it was that wasn’t working for them. Some people just want to blow off steam at you, but often enough that’s led to a new contributor pitching in and contributing improvements to our documentation.
When someone complains about your documentation, ask them what they think should change or what would have helped them find the information they needed or how something should be explained differently. Some people just want to gripe, but I’ve found that just asking for feedback or even asking for pull requests to improve the documentation has actually led to quite a few improvements for us. And sometimes it even gets someone to stop yelling at you online, which is frequently my main goal as an OSS maintainer:)
Actually, let me generalize that to say that simply asking someone complaining about your tools what they think we should do instead has been very helpful to either eliminate some friction with the tools or at least defuse the situation.
One last, very important note about your technical documentation. Try very hard to clearly describe how you think your tools are meant to be used and what the intended idioms are for your OSS tools. At this point, I think most of the problems we deal with from users are coming from folks who try to use the tool non-idiomatically (or are just hitting permutations or scenarios we didn’t anticipate of course). You can theoretically head off some of those issues by describing and providing samples of “this is how you should use our tool.” That advice might be more germane to an application framework than a library that has much more limited usage patterns though.
For the record, we have frequently been told that we have much better documentation than most of our competitors. I will tell people that I think that Marten is the most capable event sourcing tool for .NET developers — but at one point the one tool I think might be in the running with us in capability has never invested enough in their documentation to prove it.
Ruthlessly Eliminate Friction in your Getting Started Story
My good friend, fellow OSS maintainer, and even a groomsman for me Dru Sellers once gut punched me by comparing an older project of mine to Bowser in Super Mario Kart — slow to get going, but really fast once he gets there!

Ouch.
Every since then I’ve put a lot of focus on making any OSS tool I’m a part of as easy to start with as possible. Let’s take Marten as an example. Here’s the absolute easiest way to add Marten to a .NET system that’s ready to roll (assuming that you have a PostgreSQL database, which is conveniently enough very cheap to spin up in a Docker container):
// This is the absolute, simplest way to integrate Marten into your// .NET application with Marten's default configurationbuilder.Services.AddMarten(options =>{ // Establish the connection string to your Marten database options.Connection(builder.Configuration.GetConnectionString("Marten")!); // If you want the Marten controlled PostgreSQL objects // in a different schema other than "public" options.DatabaseSchemaName = "other"; // There are of course, plenty of other options...});
With that minimal bit of documentation, you can literally start persisting and saving documents (entities) with Marten’s services. Let’s say you’ve got this little class you want to be persisted:
public class User{ public Guid Id { get; set; } public required string FirstName { get; set; } public required string LastName { get; set; } public bool Internal { get; set; }}
And now, here’s a working Minimal API endpoint that happily persists a new User on the very first usage with our setup from up above with no explicit configuration, no database schema migrations, or scripts, or anything but a working connection to a database:
app.MapPost("/user", async (CreateUserRequest create, // Inject a session for querying, loading, and updating documents [FromServices] IDocumentSession session) =>{ var user = new User { FirstName = create.FirstName, LastName = create.LastName, Internal = create.Internal }; session.Store(user); // Commit all outstanding changes in one // database transaction await session.SaveChangesAsync();});
So, a couple things and then I’ll talk about the concepts underneath the code above:
- We’ve tried to adopt an attitude of “it should just work” toward our tools. As a prime example of that, Marten in its default mode will happily make sure that the database schema is exactly what the Marten configuration needs it to be at runtime for you. That leads to a much faster getting started story than it is without that. Likewise, Wolverine can configure message brokers for you for the same experience with Rabbit MQ or Azure Service Bus. Please chill out a little bit if you’re thinking that you’ve personally had trouble with Marten and Wolverine because I specifically said the word “try.”
- I’m going to claim that we judiciously use some Sensible Defaults. Look up above at the
Usertype and notice that it has a property called “Id.” Without any explicit configuration, Marten will happily decide that’s the identity for theUsertype. That also tells Marten to use sequential Guid values for assigning identity if one isn’t assigned by the user - Marten and Polecat both support a pretty efficient “upsert” for documents that’s yet another way to remove friction and repetitive code. We’ve had that so long I’d really kind of forgotten about that, but I always miss that when I’m forced to use EF Core instead:)
- A little bit of “Convention over Configuration”, but that one works really well for some folks, and not so much for others, so you can’t take that as a globally applicable strategy
My kids love Jack Black after he was Bowser in the Super Mario Brothers movies and the Minecraft movie.
Technical Things
Just a potpourri of things that I think have contributed to whatever success we’ve had as an OSS community:
Semantic Model. Wolverine, Marten, and Polecat all use the Semantic Model approach to framework configuration. This allows us to accommodate a mix of conventions and explicit configuration while providing much more diagnostic information about our tools than anything else out there in .NET land. This strategy is also key to Wolverine’s composable middleware strategy that allows you to control the application and ordering of middleware on a handler by handler basis. I wrote much more about this recently in Wolverine Middleware and Some Random Observations, but see the section on “Wolverine’s Configuration vs Runtime Model”
Compliance Tests. Wolverine has a library of reusable “compliance” test suites for our message durability (think transactional outbox et al), messaging “transports”, and leadership election that try to cover every basic scenario you need to say that an integration to a new technology works correctly with Wolverine. Once we refactored those test suites out and made them reusable, that opened the door to add a lot more capabilities to Wolverine. At this point, Wolverine actually supports more message broker technologies than our much older competitors, and I attribute plenty of that to the compliance tests. Moreover, several of our supported options (GCP Pubsub, Redis, NATS.io) came from community contributors rather than core team members. Likewise, the compliance tests for message persistence enabled us to expand from our earlier PostgreSQL / SQL Server duality to Oracle, MySql, Sqlite, RavenDb, and CosmosDb now.
Orthogonal Code. All this means is that the internal code is relatively well factored and types have well defined responsibilities such that they can be composed in new ways. That’s a lot of gobbledygook, but the very real impact is that Wolverine’s internals allow us to support every possible type of message error handling strategy for every possible messaging technology that Wolverine supports without duplicating much code. As an example, one of our older competitors just added the ability to do delayed message retries when using Kafka. Because of the way Wolverine’s internals are structure, we support that capability for every single transport option and not just Rabbit MQ (but liek every other messaging tool, the Rabbit MQ integration is much more heavily used than everything else). As another example, Wolverine can mix and match its transactional inbox and outbox support for every supported database and every supported messaging transport.
Diagnostics. If you try to build out any kind of application framework like Wolverine or configuration intensive libraries like Marten or Polecat, you better damn well have diagnostics left and right to explain what, how, and why the tools are doing what they’re doing. CritterWatch is well underway, but even without that, we build in command line diagnostics pretty early and we’ve continued that investment. That turned out to be very advantageous for AI usage, but even before that, that helped us quite a bit in user support as is.
Standardizing Test Automation. In Marten we’ve built a couple shared test harness recipes over the years that help (especially me) contributors and yes, AI agents, fall into consistent and optimized patterns for automated tests. Here’s an example of using our OneOffConfigurationContext recipe for testing any kind of non-default Marten configuration:
public class event_statistics : OneOffConfigurationsContext{ [Fact] public async Task fetch_from_empty_store() { await theStore.Advanced.Clean.DeleteAllEventDataAsync(); var statistics = await theStore.Advanced.FetchEventStoreStatistics(); statistics.EventCount.ShouldBe(0); statistics.StreamCount.ShouldBe(0); statistics.EventSequenceNumber.ShouldBe(1); } [Fact] public async Task fetch_from_non_empty_event_store() { await theStore.Advanced.Clean.DeleteAllEventDataAsync(); theSession.Events.Append(Guid.NewGuid(), new AEvent(), new BEvent(), new CEvent(), new DEvent()); theSession.Events.Append(Guid.NewGuid(), new AEvent(), new CEvent(), new DEvent()); theSession.Events.Append(Guid.NewGuid(), new AEvent(), new BEvent(), new CEvent(), new DEvent()); theSession.Events.Append(Guid.NewGuid(), new BEvent(), new CEvent(), new DEvent()); theSession.Events.Append(Guid.NewGuid(), new AEvent(), new BEvent(), new CEvent(), new DEvent()); await theSession.SaveChangesAsync(); var statistics = await theStore.Advanced.FetchEventStoreStatistics(); statistics.EventCount.ShouldBe(18); statistics.StreamCount.ShouldBe(5); statistics.EventSequenceNumber.ShouldBe(18); }}
This base type recipe helps in a couple ways:
- It enforces some standardization that makes tests easier to read once you’re experienced with the codebase
- Notice the usage of
theStoreandtheSession? The test fixture base class is lazily giving you access to a document store and a document session based on your configuration in a declarative way. I think this helps make tests be more terse and declarative since there’s less junk code for setting up scenarios. - It handles resource clean up for you
- It’s quietly helping keep the test harnesses isolated from each other and “parallelizable” by using database schema names based on the actual class type name
Ask for reproduction code for bug reports. This obviously won’t help for every project, but at least for the Critter Stack tools we’ve been hugely successful at simply asking users reporting problems to either build a reproduction project on GitHub that demonstrates the problem or better yet, asking them to submit a pull request with failing tests. Not every issue requires that, but man, that’s been so helpful to myself and other maintainers in addressing issues fast. For whatever reason, our community is just absolutely fantastic about doing that for us.










