Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
161966 stories
·
33 followers

Octopus Approvals

1 Share

Octopus Approvals is a lightweight approval workflow that lets users gate deployments to environments that are sensitive to change. For customers that can't justify the cost of a full ITSM implementation, it provides approval gating to ensure the right people review before a deployment progresses to the specified environments. This allows deployments to progress safely and satisfies standard compliance requirements.

:img{ src="/blog/img/octopus-approvals/approvals-main.png" alt="Home page for Octopus approvals showing two change requests waiting approval" loading="lazy" }

The current landscape

Manual interventions were initially built to provide the ability to pause a deployment at crucial points to let a human review the state before progressing. We've seen customers build a makeshift approval system by stitching together a couple of manual intervention steps. This works for basic use cases but not particularly well at scale and has enforcement gaps.

The next step up is to use ITSM providers like SNOW and JSM. These are perfect for those in highly regulated industries with complex requirements. If you have strict redeployment processes or need to configure a subset of approvers, then that is still the right solution for you; we are not trying to replace these providers.

Why we've built Octopus Approvals

Not everyone needs a full ITSM change management system, but many still want to know that a release has the buy-in of key stakeholders before it goes out the door. Octopus Approvals have been designed for customers occupying the middle ground, with more complex approval requirements than manual interventions can meet, but who can't yet justify the cost of a full ITSM implementation.

Our aim with Octopus Approvals is to bridge this chasm as your compliance requirements grow and help you reach the next level. Octopus Approvals occur before deployment begins; approval is not a step that can be skipped, and it won't take up a spot in your task queue until it has been approved. Within Approvals, you can explicitly set the parameters required to approve a deployment, including preventing the deployment creator from approving and specifying how many people must approve and from which team(s).

Octopus Approvals walk-through

I want to set up an approval rule requiring at least two team members to approve a deployment before it goes to production. This applies across all projects in my team's ownership, but only for production; I don't want to slow down deployments to non-production environments.

Approvals are set at a space level; a single approval rule can span multiple projects. I'm going to use tags to determine which projects and environments are affected, enabling a more dynamic selection. Any new projects tagged as owned by my team will automatically have this rule applied.

:img{ src="/blog/img/octopus-approvals/approval-create.png" alt="Screenshot showing how to create an approval rule using tag sets" loading="lazy" }

When a project owned by Fire & Motion has a deployment created for a production environment, the task will wait for all required approvals. Change requests can be reviewed and approved from the deployment task and the change request tab within the approvals page. Once all required approvals have been received, the deployment will continue.

Each release requires only one approval; redeployments of the same release to the same environment do not require another approval. This ensures that rollbacks to approved versions are not blocked.

For scheduled deployments, an approval will be available as soon as the deployment is created, so you can schedule a deployment outside working hours but approve it before you clock off. This ensures that deployments stay within the approved change windows, but the checks can happen when it's practical for the team.

:img{ src="/blog/img/octopus-approvals/approval-change-request.png" alt="Screenshot showing a change request awaiting review" loading="lazy" }

Rejected change requests

If any approver rejects the change request, the deployment will not proceed and will be marked as failed. You can not revive a rejected change request; you'll need to create a new deployment, which will create a new change request. You can use your normal process to identify failed deployments, or set up a Subscription to be notified of change request status.

Reviewing and approving change requests

Change requests can be viewed on the Approvals page. From here, you can see who approved the deployment and when, along with deployment details. Approvals and changes to rules can also be tracked via the audit page; there are 'Approval Rule' and 'Server Task Approval' document types that the audit page can be filtered by.

:img{ src="/blog/img/octopus-approvals/approval-complete.png" alt="Screenshot showing a complete change request" loading="lazy" }

Notifications

The same event types used to filter the audit page can also be used to set up targeted notifications. Using our subscriptions feature, you can subscribe to notifications about rule changes to help ensure your team remains compliant. Notifications can be sent via email, Slack, Teams or webhook. Note the below screenshot is the first iteration of our Slack integration, we are currently working on providing a more actionable message.

:img{ src="/blog/img/octopus-approvals/approval-notification.png" alt="Screenshot showing a Slack notification following a change to an Approval Rule" loading="lazy" }

We will be releasing a separate blog post detailing how you can use subscriptions and webhooks to receive more targeted notifications, so that users who need to approve the deployment are the ones who receive the notification. Subscribe to our newsletter to get this in your inbox.

Known limitations

  • Any of the required approvers are counted towards the total. If you specified two teams within the approvers, any two approvals across those teams will suffice.
  • This cannot be used to automate your approval process.
  • Approvals only work for deployments; this functionality is not available for runbooks.
  • Approvals, unfortunately, cannot be used to finally get your mother-in-law's approval.

Octopus Approvals is now available as a Public Preview for cloud customers and in the 2026.3 server release.

Read more about getting started with Approvals here.

Happy deployments!

Read the whole story
alvinashcraft
just a second ago
reply
Pennsylvania, USA
Share this story
Delete

Microsoft Agent Framework for .NET (part 4): static methods, instance methods, MCP Clients as custom Agent tools

1 Share
MAF Agents can have custom capabilities through C# methods or MCP clients. Let’s see how to define and use them.
Read the whole story
alvinashcraft
just a second ago
reply
Pennsylvania, USA
Share this story
Delete

Build Offline Agent Loops for a Vonage Voice Agent

1 Share
Use stored call records to build a regression runner and transcript review loop that find patterns, propose eval cases, and help you improve a voice agent without breaking what already works.
Read the whole story
alvinashcraft
13 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

Aspire 13.6: Your dashboard gets memory

1 Share

Aspire 13.6 is out today, and it includes potentially the longest awaited Aspire feature ever… persistent dashboard data! The Aspire Dashboard now keeps your runs around so you can still see traces and logs after you’ve restarted your full stack. That alone would make this a stellar release, but 13.6 also lets you interact with any container shell from the dashboard, brings Java and Rust hosting into the first-party lineup, and previews a brand-new Azure deployment target.

As usual, these are some of my favorites, but the complete inventory, migration notes, and breaking changes are detailed in the aspire.dev What’s New.

🗄 Your dashboard remembers

Finally!!! The dashboard now automatically stores resource snapshots and telemetry after you stop the apphost. When you start the apphost again, the dashboard starts a fresh “run”, and you can quickly swap between live and past sessions with the run selector.

The Aspire 13.6 dashboard showing structured logs from an earlier run with the run selector open

Filtering, search, paging, and aggregation all work on historical runs, so investigating an old run still gives you all the power of the dashboard. Historical runs are read-only, so you can poke around without worrying about changing anything. The runs are stored on disk as SQLite databases, so you can point your coding agent at them to compare and contrast between runs, too.

The dashboard keeps up to 10 runs per application and prunes the oldest ones as new runs start. If a run captured something you want to hang onto, like the one time you actually reproduced a tricky bug, pin it in the run selector to retain it.

Running the standalone dashboard without an apphost? Give it a stable application name and opt into Resume so the same run survives restarts:

aspire dashboard run --application-name my-app --persistence Resume

We also raised the default limits for console logs, structured logs, and traces to 100,000 entries each, so a busy run holds a lot more before the oldest entries roll off.

Protect your telemetry

Persisted telemetry can contain sensitive values, and the underlying on-disk database has no separate encryption or authorization layer. The dashboard is still a development and short-term diagnostics tool, not a production telemetry backend.

Learn more about dashboard data persistence.

⌨ Exec against your containers from the dashboard

When I want to check what’s actually installed on a container image, I usually drop into my container runtime of choice, find the tab that gives me a terminal, and start typing. If I want to do anything more complicated, like manually clear my cache, I have to copy-paste passwords or the connection string and hope I grabbed the right one.

In 13.6, we created the WithRepl() extension for common container-based integrations:

Now, the resource has a REPL command in the dashboard. Click it, and the client bundled in the container opens in the dashboard’s new terminal dock, already authenticated. You can start querying without installing anything, copying credentials, or leaving the dashboard.

REPL support ships in preview for PostgreSQL (psql), MySQL, SQL Server (sqlcmd), MongoDB (mongosh), Redis (redis-cli), and Valkey, and we are excited to add more based on your feedback. Because it uses the resource’s real credentials with full write access, you have to opt in with WithRepl() / withRepl(), and it only works in run mode.

The new experimental terminal dock APIs are fully accessible to you, so you can create and drive any terminals for any container or executable resource. We have a lot of plans for more built-in functionality with the dock, so we’re super excited to hear your feedback and see what you build!

This complements the console-app resource support that started with WithTerminal() in 13.5. A resource that is a standalone console app still gets its own fully interactive terminal on the Console page; now, you also have a generic shell to exec into any resource type.

Stay tuned for a deep dive blog on how all of this works, aka how @mitchdenny accidentally built the most powerful terminal emulator on the planet!

☕🦀 Java and Rust join the first-party lineup

Java and Rust developers have been orchestrating their apps thanks to the Aspire Community Toolkit for a while. In 13.6, both integrations move into Aspire itself as Aspire.Hosting.Java and Aspire.Hosting.Rust, reworked to match the experience you get with JavaScript, Python, and Go. Huge thanks to @marshalhayes and @afscrome, whose community contributions made this happen.

Here’s a Spring Boot catalog service and a Cargo pricing API in the same apphost:

On the Java side, you also get Quarkus, executable JARs, and Maven or Gradle wrapper tasks. Aspire detects the target Java release from your build, configures OpenTelemetry export so your telemetry lands in the dashboard (add WithOtelAgent() when you want the Java agent’s automatic instrumentation), and generates a multi-stage Dockerfile when you publish. Rust apps get Cargo target, argument, and feature configuration, plus a generated Dockerfile of their own.

Both languages also work with the Aspire VS Code extension, so you can hit Start Debugging and set breakpoints in your Spring controller or Rust handler alongside the rest of your app.

Preview packages

Aspire.Hosting.Java and Aspire.Hosting.Rust ship as preview packages in 13.6. If you’re coming from the Toolkit, follow the migration guidance in the Java and Rust hosting docs.

And yes, for the adventurous, Java and Rust apphost authoring is also available behind experimental feature flags.

📂 One volume path, local and deployed

If your app writes files, it needs a directory. Locally that’s a path on your machine, and in a container it’s a mount path like /data. That difference usually ends up as a little bit of environment-sniffing code in your app.

13.6 adds an env argument to any executable’s volume mount, so your app reads one environment variable everywhere:

When you run locally, DATA_PATH points to a stable, workload-specific directory in the apphost’s local store. When you publish to Docker Compose, Kubernetes, or a managed cloud like Azure Container Apps, DATA_PATH is /data. Your app code just reads DATA_PATH and moves on with its life.

See Persist data with volume resources for the details, including how this works with 13.5’s persistent volume support for Kubernetes.

☁ Deploy to Azure Container Apps Sandboxes

The new Aspire.Hosting.Azure.Sandboxes package adds Azure Container Apps Sandboxes as a deployment target, so each of your projects, containers, and Dockerfile resources runs in its own isolated sandbox. Add a sandbox group to your apphost and run aspire deploy. Aspire provisions the group, a container registry, and the identities and role assignments it needs. If the sandbox group is your only compute environment, Aspire assigns your compute resources to it automatically.

When you want more control, pick a tier and configure auto-suspend:

As always, Aspire’s defaults lean safe. Only endpoints marked external get a public HTTPS URL, and those URLs require Microsoft Entra ID authentication unless you opt a specific endpoint into anonymous access. Egress is deny-by-default, and Aspire cleans up stale sandboxes on redeploy and on aspire destroy.

Preview

Azure Container Apps Sandboxes requires preview access in your Azure subscription and region, and the Aspire package is prerelease. Volumes, TCP ports, private service discovery, and Windows or ARM64 images aren’t supported yet.

Get started with Deploy to Azure Container Apps Sandboxes. If you’re deploying to regular Azure Container Apps, you can also try the experimental Express mode with AsExpress() for faster provisioning and scale-to-zero.

🧪 C# devs: help us try the new .NET project experience

If you’re building with C#, 13.6 has something new for you to kick the tires on. AddDotnetProject, from the prerelease Aspire.Hosting.Dotnet package, is the next version of how Aspire builds and runs your .NET projects, bringing it to parity with the modernized experience for every other language resource. You point it straight at a .csproj, so there’s no ProjectReference to wire up in your apphost:

Instead of restoring and building every project on its own, Aspire groups compatible projects and builds them together with one shared NuGet restore. On a new enough .NET 11 SDK, it also turns on MSBuild’s multithreaded task execution. When you change code, hit the resource’s Rebuild command, and Start and Restart reuse the coordinated build output. This also means the Aspire dashboard, and any other resources, will start sooner since Aspire doesn’t have to wait for the entire MSBuild graph to resolve.

Already have an apphost full of AddProject calls? Run aspire agent init and run the aspire-project-v2-migration skill. Your agent assesses each project resource, proposes the exact edits, and waits for your approval before touching anything. This is still prerelease, so now is the perfect time to try it on a real app and tell us what breaks. Learn more about coordinated builds and restore and migrating from legacy project resources.

🏅 Grab bag

Some other things you might be hoping to hear about shipped in 13.6:

  • aspire run and aspire start accept --launch-profile (or -lp) to pick an apphost launch profile.
  • aspire stop --force --volumes also removes the volumes Aspire created, while leaving bind mounts and pre-existing volumes alone.
  • Coding agents can start and stop apphosts through the VS Code extension’s lifecycle tools, and the Aspire pane now has Deploy and Publish actions right next to Run.
  • Deno 2 can run your TypeScript apphost, and AddDenoApp / addDenoApp hosts Deno apps. Thanks to @rickylabs for both!
  • TypeScript apphosts load appsettings.json from the apphost directory, just like C# apphosts.
  • MongoDB gets an experimental WithReplicaSet() so you can use transactions and change streams locally.

💫 Get Aspire 13.6

Update the CLI, your apps, and your agent skills:

aspire update --self
aspire update
aspire agent init

Then run your app, restart it a couple of times, and open the run selector in the dashboard. Add WithRepl() to your database while you’re in there, or drop a Spring Boot or Rust service into your apphost and see how it feels.

New to Aspire? Install the CLI with your package manager of choice, and try the Aspireify skill on your existing apps.

Share feedback on GitHub, join us on Discord, follow @aspiredotdev on X, find us on BlueSky, or subscribe on YouTube.

Happy Aspirifying! 💫

The post Aspire 13.6: Your dashboard gets memory appeared first on Aspire Blog.

Read the whole story
alvinashcraft
26 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

6 Months of Claude and Cursor, Part 2: Teaching AI to Work with a Senior Engineer

1 Share

As you progress in your AI journey, you’ll need to make your agents work for YOU, the individual you, with your strengths and shortcomings.

SKILLS in gold with a sleek purple futuristic setting
Image generated with Gemini

In Part 1, I wrote about building skills so the AI understands your project. Part 2 is a harder problem: building a skill so the AI understands you.

After six months of using Claude daily for code review, architecture discussions and prompt writing for Cursor, I realized the most persistent problem was posture rather than technical capability. The AI treated me like a beginner when I had 30+ years of experience behind me. It suggested removing intentional code and offered generic options when I already knew what I wanted. When I showed that my code was right, it apologized for three paragraphs instead of moving on.

I wanted a technical peer, not an assistant.

The Pattern I Identified

Before writing the skill, I spent weeks going back over our conversations. I asked Claude itself to comb through our history and list the moments it got the tone wrong. Three patterns came out.

The first was underestimation. I’d share one of my Source Genesys templates with a Stopwatch measuring a hook’s execution time, and the AI would suggest removing it because it “didn’t seem necessary.” I’d explain that it was intentional, that I wanted performance metrics inside the hook itself. It corrected itself, then did the same thing in the next conversation, with no memory of the last one.

The second was premature diagnosis. The AI would flag an error in my metrics code when the real problem was in the neighboring Controller. I knew that because I’d looked at both before asking. The AI assumed I hadn’t.

The third was the multiple solution. “You can do A, B or C,” when I already knew it was B. The time spent explaining A and C was time lost for both of us.

The Hardest Skill to Write

So I wrote jefferson-senior-dev. The name was deliberate: “senior dev,” not “user” or “client,” because that’s how I wanted to be treated.

The skill opens with who I am: software engineer, developing since 1994, creator of Source Genesys. The resume takes five lines. Everything else in the file is rules of engagement.

The main rule: when I share code, assume it’s correct until you find clear evidence otherwise. Don’t question out of generic caution. Don’t suggest removing something because it “doesn’t seem necessary.” If you find nothing wrong, say it’s correct and move on. Don’t invent caveats to look useful.

There’s one exception: security. SQL injection, data exposure, hardcoded tokens and open permissions get flagged every time, even when the code works.

The Honesty of the Blind Spot

The hardest rule to write was one about myself. Sometimes I swap true for false in boolean conditions, and I’ve been repeating that mistake for years. I’ve watched someone outside IT do the same thing, which makes me suspect I’m not the only one. So the skill tells the AI to flag every inverted condition it finds in my code, because that’s where questioning me adds real value.

Declaring your own blind spots to an AI costs nothing, and few engineers do it. If you know where you fail and it knows too, you get a second check that never gets tired and has no embarrassment about pointing out the obvious.

A good skill teaches AI when to disagree with you.

The Effect in Practice

The change showed up in the first week. Claude stopped offering options when I already had a path. When I shared code with a // fixed comment, it assumed the fix was right and focused on applying it to the template. The five minutes I used to spend convincing it that my code was intentional went to zero.

The effect I hadn’t planned for was the quality of what it caught instead. The one I keep coming back to was a discard assignment on an awaited call: the line waits for the operation, throws the result away and swallows the failure with it. Fire-and-forget wearing the costume of disciplined async code, which is exactly why it survives human review: Nobody reads past the await.

With the false suspicions out of the way, there was attention left for that kind of thing. Today, when it flags something, I take it seriously, because I know it isn’t a generic guess.

The Cursor Skill: Prescribe, Don’t Explore

After the Claude skill, I built the equivalent for Cursor: cursor-prompt-craft. It came out of six months of real prompts, the ones that worked and the ones that failed.

The central tension is discovery versus prescription. I used to think Cursor should explore the code on its own. The results showed the opposite: the most effective prompts were the ones that gave the exact file paths, named the functions to change, and marked what must not be touched.

That became the rule. For Source Genesys, Claude writes prescriptive prompts: exact paths, named files, examples of the canonical pattern and an explicit “do not” section. Cursor became a tool and stopped offering unnecessary opinions. It works better when you take away its obligation to guess.

What Changed from January to June

In January, I wasn’t running a self-hosted GitHub Actions runner; my CI/CD was semi-automated, and deploys were manual. Cursor ran without rules and produced unpredictable results, and Claude treated me like any other user.

By June, Source Genesys was generating complete platforms with automated deploy, built-in observability, test coverage and post-deploy self-correction. Cursor worked inside limits defined by skills, and Claude knew when to trust my code and where my blind spots were. The semester also produced concrete deliverables: the new template for Source Genesys using Progress Telerik UI for WinForms, a generic dark mode CSS for every component, a frontend metrics system and automatically generated help screens.

None of that came from an AI alone, and none of it came from me alone. It came from a partnership that took six months to settle. Concrete results aren’t a matter of one week or one month. You must hone the tool until it delivers what you want.

July: The Skill Became a Command

July was the first month both skills ran without me adjusting them, and most of it went to infrastructure. A Virtual Private Server migration had left me with no metrics, no centralized logs and no traces, and the gap surfaced in the worst possible way: a production flow stopped working, and I had nowhere to look.

Debugging in the dark, in 2026, is embarrassing. The rest of that story is Part 3, but the conclusion fits in one line. If Source Genesys generates the infrastructure, it must generate observability along with it.

The part that touches this article came at the end of the month. I fixed the flow on one platform, and Cursor documented what it had done in a skill. The next question was inevitable: why am I applying this manually on the other six?

It became a CLI parameter. I passed the skill name and the list of platforms, and the pipeline fired the agent with the same instruction on each one. It ran on six of the seven platforms. The seventh didn’t have the module involved and was correctly skipped. The build passed on all six.

That changes what a skill is. In Part I, it was context. In this article, it was posture calibration. Now it’s a unit of work: a file that describes a fix and that the pipeline knows how to execute across N repositories.

With one caveat I insist on putting in writing: a green build is not a validated fix. The agent applied, compiled and reported. End-to-end testing on each platform is still human work, and that part I haven’t automated.

What I Got Wrong in the Same Period

Calibration made me faster, and speed made my own mistakes more expensive. Three of them are worth writing down.

I told Cursor to clean up corrupted Latin characters across every repository at once, and the instruction was broad enough that it also replaced ?? with a dash. ?? is an operator in C# and in TypeScript. It broke working code and swept through dozens of documentation files on the way out. Git and a targeted pass got it back. The scope was mine to set, and I set it wrong. So it became a rule that forbids operator substitution and names the ones that are never touched.

I ran two pipelines at the same time, more than once. The sessions competed for the same processes, and the report came back with failures that weren’t failures. I spent an afternoon investigating noise. A session lock in the runner is in the queue.

Against those two, the best small decision of the month was writing per-session and per-platform statistics to CSV. It showed that a single command accounts for almost all the pipeline time, that one execution of it took 33 minutes while the retry took 2, and that a failure I had been treating as instability was deterministic: Cursor had compiled the project as x86 instead of AnyCPU. Without measuring, I’d still be guessing.

Calibrated AI accelerates whatever you told it to do, including the wrong thing. The skill keeps the AI from getting the tone wrong. It doesn’t keep me from getting the order wrong.

The Final Reminder

The jefferson-senior-dev skill that Claude wrote about our partnership ends with the line I consider the most important of all:

“Jefferson treats Claude as a high-level technical colleague, not an assistant. When he shares code, he expects peer analysis, not a tutorial. When he says ‘this is wrong,’ he is almost always right. Claude’s job is to add real value: find what Jefferson missed, not question what he already knows.”

That “almost always” I added on purpose. Nobody is right 100% of the time, and it’s in the cases where the AI catches what I missed that the partnership pays for itself.

Conclusion

Generative AI isn’t going to replace senior engineers, but it will deliver more to the people who know how to train it instead of treating it like a search engine. And training here isn’t fine-tuning or RAG. It’s a Markdown file with the right rules, loaded in the right context. A skill is actionable documentation: it records what you know and where you accept being corrected.

If I could go back to January 2026 and give myself one piece of advice, it would be this: before you write your first prompt, write your first skill.

In the next post, Part 3, I’ll show what happened when the infrastructure had to catch up with the partnership.

Automate with AI. Just don’t expect it to get to know you on its own. That part is your job.

Read the whole story
alvinashcraft
32 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

Marten Just Crossed 20 Million Downloads

1 Share

Several years ago when I was interviewing at Calavista, my future colleagues asked me what I was most proud of in my career. At the time I was mostly known for my writing years earlier at CodeBetter and StructureMap, but even then my obvious answer was Marten. Wolverine was even then my true passion project, but Marten is undeniably the biggest technical success I’ve ever had and the single biggest source of JasperFx Software business today.

Sometime this past weekend, Marten quietly rolled past 20 million downloads on NuGet. Almost exactly eleven years after the first commit in October of 2015, the “PostgreSQL as a document database” experiment that started as a tactical way to get off of RavenDB in a hurry has become the most widely used and, I’ll very happily argue, the most capable Event Sourcing solution in the .NET ecosystem.

Don’t write off Marten because it’s a FOSS tool in the .NET ecosystem — the perennial Harvey Dangerfield of technical platforms. Marten, especially when paired up with Wolverine, has a feature set that I believe is very competitive and in many cases superior to the commercial event sourcing tools in the JVM.

I wrote about Marten’s 10th birthday last year and a five year retrospective on adoption this spring, so I’ll keep this short. Twenty million is a nice round number though, and it’s worth a moment to say thank you and take stock.

The numbers

Straight from GitHub and NuGet this morning:

NuGet downloads20,077,707
GitHub stars3,460
Contributors257
Pull requests merged2,275
Issues closed2,594 of 2,605 ever opened
Tagged releases321
Latest releaseV9.41.0, shipped today

Two of those matter more to me than the download count. 257 contributors is a real community (that’s a random Wednesday for a popular npm project, but huge for .NET), not a one-person project with a mailing list. And eleven open issues on a project this old and this large is the result of a lot of people caring about a lot of details for a very long time. Thank you to everyone who has filed an issue, sent a pull request, answered a question in Discord, or written up their experience for others.

For context on “most widely used”: the next closest event store package on NuGet, the official client for what used to be called EventStoreDB, sits at a bit over 8 million downloads. SqlStreamStore, EventFlow, and Eventuous are each in the low single-digit millions or below. Marten isn’t just in front. It’s in front by a wide margin, and the gap is widening.

Marten as a document database

Plenty of those downloads aren’t for Event Sourcing at all. Marten’s original pitch was simple: PostgreSQL is a fantastic database, JSONB is a fantastic storage format, and .NET developers deserve a document database experience without giving up transactions, a real query engine, or the operational know-how they already have. That pitch still holds.

Today that means a serious LINQ provider with compiled and batched queries, full text search, PostGIS, pgvector for AI features, optimistic concurrency, partial updates, document hierarchies, and multi-tenancy from a shared table all the way to database per tenant. Add schema management that just works, and you go from an empty database to a running application with one command, on a database every cloud already knows how to host.

Marten as an event store

You’ll see plenty of people online try to say that you can’t really use a relational database as the foundation for an event store, but our PostgreSQL foundation means that Marten can support a much stronger set of projection capabilities and genuine options for strong consistency that the “dedicated event store databases” cannot.

Marten is the most complete Event Sourcing toolkit on .NET, and it isn’t particularly close. Most event store products give you an append-only log and a subscription API, then wish you luck with everything else. Marten’s position has always been that the event store is only useful if the projections, read models, consistency model, and operational story are all in the box. Highlights that I think are unique, or at least unusually strong, in the .NET world:

Marten’s model has also become the template for the rest of the stack. Polecat brings the same projections and aggregates to SQL Server 2025, and Fisher does the same for embedded scenarios. Learn Marten and you’ve learned all three.

Where JasperFx fits

Marten is and will stay MIT licensed open source. What’s changed is that there’s now a real company behind it. JasperFx Software exists to make the Critter Stack sustainable for the long haul, and I explained the model in The “Open Core” Model for Sustainable OSS Development in .NET. If your team depends on Marten in production:

Thank you

Twenty million downloads, 257 contributors, eleven years. Marten got here because a lot of people decided PostgreSQL plus .NET was a good idea and kept showing up to make it better. If you’ve never tried it, the quick start will have you appending events and building a projection in about ten minutes. If you have, thank you. Here’s to the next 20 million.

Questions, war stories, or feature requests? Find us in the Critter Stack Discord or get in touch with JasperFx.



Read the whole story
alvinashcraft
40 seconds ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories