• Articles
  • AI
  • 2026
  • 06
  • I failed to assemble a team for my pet project twice. Then I finished the entire project with Claude in six months.

I failed to assemble a team for my pet project twice. Then I finished the entire project with Claude in six months.

The story of how Fleans, a workflow engine built on .NET Orleans and BPMN 2, was born out of stubbornness, frustration, Camunda going paid, and one not-so-obvious conversation with AI.

Background: four reasons why I even dove into this mess in the first place

If a developer has been itching to tackle the same problem for ages, sooner or later they’ll just go ahead and do it. I had four such "itchy spots", and they all converged at a single point:

  1. I’ve always wanted to build something substantial on .NET Orleans. It’s pretty much the only mature actor framework in the .NET ecosystem, it powers Halo and Skype, and the number of publicly available live production projects built on it can be counted on one hand. I’ve been itching to work with it for ages.

  2. I love building projects. Not reading about them, not daydreaming about them, but actually coding. It’s my favorite state of mind, where I’m happy to stay up all night.

  3. Camunda 8 switched to a paid model. For many teams, this suddenly reopened the question of "what are we going to use to orchestrate our business processes". And when the market is wide open, it’s the perfect time to step in.

  4. I’ve already worked with Camunda in my professional work. And there were aspects of its architecture I disliked so much that I wanted to rework it. Not because I’m smarter than Camunda’s creators — but because I had a concrete vision of how to make the engine more scalable by building it on an actor model instead of the traditional engine with DB-centric execution.

On top of that, I’ve experienced firsthand just how convenient BPMN 2 is as a modeling language. When a business analyst draws little rectangles and arrows, and then a developer literally executes that diagram instead of "implementing something loosely based on it" — that’s a joy that’s hard to put into words for someone who hasn’t worked with it.

So Fleans was born. The name doesn't carry deep meaning — it sounds good, is memorable, and isn't taken. The essence is an open-source workflow engine on Orleans, which can execute standard BPMN 2 and scale horizontally according to the scheme "add silos — get throughput." Now it's distributed as a full release: signed NuGet packages for plugin authors, multi-arch container images on ghcr.io (amd64 + arm64), Helm chart with cosign signature, docker-compose bundle in one command. I'll return to the specifics later — first, about how we got there.

First attempt: a team that didn't take off

Any honest story about a pet project starts with failure. Mine started with two.

Attempt #1. I gathered a team of acquaintances. There were guys who found the idea interesting. But a pet project without a salary is only held together by energy and the feeling that you're close to something alive. And I myself didn't have an architecture in my head or an MVP in the repository — just theses. I couldn't motivate people in that state. Everything quietly faded away in a couple of weeks.

Attempt with AI (early). Over a year ago, I tried to understand the subject area with ChatGPT. It seemed logical: I'm not a workflow expert, let AI explain. I got many separate pieces — how BPMN works, what a token is in Camunda, how boundary events are modeled. But I couldn't assemble a coherent understanding from these pieces back then. AI answered exactly the question I asked, and I didn't always know the right question.

Attempt #2 (team). I decided I understood enough to lead the project technically and assembled a new team. By this time, I had the first domain models: Activity, a cycle that calls the activity method and moves the process to the next one, something like a state machine on top of actors. But the result was the same. People come, make a couple of commits, and disappear.

I left everything as is and didn't touch the repository for several months. The project seemed too big for one person. I studied analogues (Elsa, WorkflowCore, Zeebe), and each of these systems reminded me how much work I still had ahead.

December 2025: I returned to AI — and was surprised

In December 2025, I decided to give AI tools another chance. By this time, news about the level of code models had been frequently appearing in my feed, indicating that they had become quite different. I was hooked by the discussions around Claude Code - a CLI tool tailored specifically for agent-based development.

I installed it. I tried it. And I was surprised.

It wasn't just "pleasantly surprised, like when I first tried ChatGPT." It was "I sat down in the evening, and two days later, I had a working prototype of a new engine version." The scale of the difference from models a year ago was the same as between "I'm searching on Google to remember the syntax of Task.WhenAll" and "I'm talking to an engineer who has already been reading my code for 20 minutes."

First, as the documentation suggested, I wrote CLAUDE.md - a file with instructions for the agent on the project. And that's when something important became clear: my investment in architectural principles made this easy. I initially built Fleans using DDD + Clean Architecture. This means that the project description didn't sound like "we have this folder, that folder, this class does that," but rather "the domain is separated from infrastructure, use cases are in the application, persistence is behind interfaces, Orleans actors are adapters for domain aggregates, the WorkflowInstance grain is event-sourced through JournaledGrain, all mutations go through a single pipeline DrainAndRaiseEvents." This is a language that AI understands flawlessly because it's trained on tons of similar code.

Over time, CLAUDE.md outgrew itself. I split it (issue #649): the root file became a rulebook "how to behave in this repo"; everything else went into docs/conventions/ (how to add a new BPMN element, how to write a custom plugin, how Orleans streams work) and docs/runbooks/ (how to release a version, how to roll back, how to assemble a compose bundle). This isn't just cosmetics - without such separation, one file grows into a monster that the agent rereads on every request, and the tool pays for it with latency.

I also decided to use ready-made skills for development - a set called superpowers. In short, these are process templates that make the agent not rush to write code immediately, but go through stages - task analysis, design doc, plan, implementation, review, tests. For a solo developer with ambitions, it's like hiring an external process that wouldn't happen otherwise.

I made the first features together with Claude in strict checking mode. Every step - from the design and plan document to the code and tests - I read and reviewed. I was constantly surprised how the agent was always in the context of my project, how it remembered the agreements of the previous session, and suggested ideas that I would have come up with myself after a day of reflection.

Domain activity in Fleans is an Orleans-serializable record. Each activity has an ActivityId and an ExecuteAsync method, which returns a list of execution commands (what the engine should do next) and publishes domain events through the execution context. The base class looks like this:

[GenerateSerializer]
public abstract record Activity([property: Id(0)] string ActivityId)
{
    internal virtual bool IsJoinGateway => false;

    internal virtual async Task<List<IExecutionCommand>> ExecuteAsync(
        IWorkflowExecutionContext workflowContext,
        IActivityExecutionContext activityContext,
        IWorkflowDefinition definition)
    {
        await activityContext.Execute();
        await activityContext.PublishEvent(new WorkflowActivityExecutedEvent(
            await workflowContext.GetWorkflowInstanceId(),
            definition.WorkflowId,
            await activityContext.GetActivityInstanceId(),
            ActivityId,
            GetType().Name));
        return [];
    }

    internal abstract Task<List<ActivityTransition>> GetNextActivities(
        IWorkflowExecutionContext workflowContext,
        IActivityExecutionContext activityContext,
        IWorkflowDefinition definition);
}

Here's an example of a custom-task activity - it inherits from the base TaskActivity, adds TaskType (e.g., "rest-call" or "hello-world"), variable mappings, and on top of the base ExecuteAsync publishes an ExecuteCustomTaskEvent event, which will be subscribed to by the plugin host with its own grain handler:

[GenerateSerializer]
public record CustomTaskActivity : TaskActivity
{
    [Id(1)] public string TaskType { get; init; }
    [Id(2)] public List<InputMapping> InputMappings { get; init; }
    [Id(3)] public List<OutputMapping> OutputMappings { get; init; }

    internal override async Task<List<IExecutionCommand>> ExecuteAsync(
        IWorkflowExecutionContext workflowContext,
        IActivityExecutionContext activityContext,
        IWorkflowDefinition definition)
    {
        var commands = await base.ExecuteAsync(workflowContext, activityContext, definition);

        var variablesId = await activityContext.GetVariablesStateId();
        await activityContext.PublishEvent(new ExecuteCustomTaskEvent(
            await workflowContext.GetWorkflowInstanceId(),
            definition.WorkflowId,
            definition.ProcessDefinitionId,
            await activityContext.GetActivityInstanceId(),
            ActivityId,
            TaskType,
            InputMappings,
            OutputMappings,
            variablesId));

        return commands;
    }
}

The WorkflowInstance grain acts as the orchestrator over all this - it is event-sourced through Orleans JournaledGrain, with a custom storage provider on top of EF Core. All mutations go through a single pipeline DrainAndRaiseEvents → RaiseEvent → ConfirmEvents, so that there are no "half-disassembled" states between events. Declaration of the grain (it is divided into three partial files; only the root one is shown below):

public partial class WorkflowInstance :
    JournaledGrain<WorkflowInstanceState, IDomainEvent>,
    IWorkflowInstanceGrain,
    ICustomStorageInterface<WorkflowInstanceState, IDomainEvent>
{
    public ValueTask<Guid> GetWorkflowInstanceId()
        => ValueTask.FromResult(this.GetPrimaryKey());

    public override Task OnActivateAsync(CancellationToken ct) { /* replay journal, restore timers */ }

    public async Task SetWorkflow(IWorkflowDefinition workflow, string? startActivityId = null) { /* ... */ }
    public async Task StartWorkflow() { /* ... */ }
    public async Task CompleteActivity(string activityId, ExpandoObject variables) { /* ... */ }
    public async Task FailActivity(string activityId, Exception exception) { /* ... */ }

    // Custom event-sourcing storage
    public Task<KeyValuePair<int, WorkflowInstanceState>> ReadStateFromStorage() { /* ... */ }
    public Task<bool> ApplyUpdatesToStorage(IReadOnlyList<IDomainEvent> updates, int expectedVersion) { /* ... */ }
}

Variables in the workflow are ExpandoObject with dynamic fields: process variables appear and disappear during execution, BPMN modeling does not like hard typing at the engine level. Serialization of ExpandoObject through Orleans is a separate surprise, for which we had to write our own surrogate converter (which, by the way, initially lived in the engine assembly, and because of this, external plugin hosts crashed with CodecNotFoundException - we had to move to the leaf-package; also a story).

This is exactly the layer I used to struggle with for weeks, tripping over persistence issues, boundary events, and deadlocks in signal correlation. With the AI partner, these weeks collapsed into days — but I’ll repeat: not thanks to magic, but because I already had a solid architecture that I could just drop code into.

Regression tests: when development outran my attention

A week after launching the new wave, Claude’s Chrome browser extension — Claude in Chrome — came out. And that’s when it clicked for me.

Fleans has an admin panel (Fleans.Web) and a REST API (Fleans.Api). What if I handed off the regression test run to an AI agent that would click through the interface itself, hit the API itself, and check that behavior wasn’t broken on its own? I tried it. It worked.

By that point, development was moving so fast that I could no longer manually check everything thoroughly. So I had a choice: either slow down the pace to make room for my own ability to verify work, or change the verification process itself.

I chose the second option. And that was the moment something shifted inside me.

I started thinking not as a programmer, but as a product owner. Instead of asking myself "what code do I need to write to make this work", I started asking a different question: "what system properties do I need to guarantee, and how do I structure the process so these guarantees hold without my manual involvement?" It’s a different layer of thinking. Claude took over the programmer role in my project — and, to be honest, it handles it faster than I do.

At the same time, I asked Claude to build Fleans its own MCP server — the Model Context Protocol, through which other agents can read and modify the state of my engine directly. It came out elegantly: instead of the AI clicking buttons in my own admin panel, it accesses the project via a programmatic interface that the same AI wrote. The loop closed. Now fleans-mcp is one of four containers in the release bundle, on equal footing with the API, Web and Worker containers.

Two more automation layers have been added to the regression process. The first is standard CI: build, unit, integration, pg-tests, plus a separate Playwright E2E job. The second is the manual test plans directory at tests/manual/. Right now it holds over 25 automated Playwright scenarios, and every feature is required to place a test-plan.md file and a .bpmn fixture there. This rule is documented in CLAUDE.md, and the agent executors follow it without needing reminders.

OpenClaw and the team of small agents

Around that same time, OpenClaw blew up in the feed — it's an agent from the "here's an autonomous worker, give it a task and forget about it" series. I looked into it and realized: I needed the same kind of small agents working on my project's tasks, but I didn't want to switch away from Claude.

Right at that moment, Claude added the ability to run scheduled tasks — on a regular basis or triggered by events, without me needing to be at the keyboard. That was enough.

I sat down and assigned roles. I created a project board in GitHub (by the way, Claude works extremely well with GitHub specifically — issues, PRs, labels, milestones, all that machinery is second nature to it). The board is a full end-to-end state machine: Backlog → In Analysis → Review Analysis → Ready → In Progress → In Review → Testing → Review by Human → Done. I set up separate "agents" with different profiles and instructions:

  • Worker — dispatcher. Every 10 minutes, it looks at the board, selects one task from the priority table, and routes it to a specialized skill. It takes a blocking per-machine lock to prevent two instances from conflicting.

  • Analytic — takes a raw issue, analyzes requirements, writes design & plan, formulates blocking questions, and leaves the task waiting for an answer.

  • Analysis-review — critically reads the design, separates blockers from important and minor issues, and sends it back for revision if regressions are found.

  • Developer — implements the approved plan: branch, commits, PR.

  • Hourly-code-review — reviews fresh PRs, sets the bot-approve or bot-block marker in the first line of the review. Worker reads this marker and automatically promotes the green PR to Testing if CI is green and the marker is newer than the last commit.

  • Manual-regression-testing — for everything that passed code review: runs regression against the PR branch and signs the verdict.

Tasks are now completed faster and without my direct involvement. Sounds like a dream, but there's a downside to this, which I'll be honest about.

Scalability Auditor: the most needed role of all

The more code was written without my direct involvement, the more I feared one thing: that at some point, we'd make an architectural decision that would lead to having to redo the entire machine in three months.

I created a separate role — Scalability Auditor. Its task is simple: regularly look at the codebase from a bird's eye view and answer questions: "where's the bottleneck", "where did we implement a non-scalable solution", "where does the state live where it shouldn't", "where will we have problems with 10× load". And most importantly, it doesn't fix everything itself, but creates tasks on the same board.

This turned out to be the role without which the entire agent system would have slid into technical debt in a couple of weeks. A separate agent that can say "no, this can't be done this way" is not about speed, it's about preserving the project over time.

A good live example from recent times: while running a freshly compiled version locally, I encountered an intermittent error in the management Web. When I clicked "Start" on the process - sometimes it threw Unable to load Fleans.Application.Grains.IWorkflowInstanceGrain, Fleans.Application, sometimes it didn't. Here's what happened next: within half an hour, the analyst agent localized the bug (the silo plugin was registering as an Orleans gateway, although it doesn't load Fleans.Application), the developer agent assembled a PR with a one-line fix plus a regression test, I conducted a self-review with the bot-approve marker, and both PRs - the engine fix and the workaround in the template repository - were merged into main. In the old "one person + one night" paradigm, this would have taken at least three hours.

After this, I'll be honest: I'm understanding less and less of the specific code in every corner of the project. There's a lot of it, and it's growing rapidly. I've focused on the architecture and pay less attention to the individual lines.

This is a bit scary. And at the same time, this is exactly the shift that everyone talks about, but it's hard to feel until you're inside it. You don't stop controlling the system - you just control it at a different level of abstraction.

What's inside Fleans (so that it's clear what you can use it for)

If you remove everything unnecessary, Fleans is built on ten key things. These are exactly the theses I posted on the main page of the website, because they are the product:

  • Actors, not orchestrators. Each workflow is an independent Orleans grain. There's no common coordinator, no lock contention. You add a silo and get linear throughput growth.

  • Read scales separately from write. The engine has a built-in CQRS split: read traffic goes through a NoTracking query context, and if desired, to a separate Postgres replica. Admin panel and pollers don't interfere with execution.

  • Event-sourced, crash-safe. Append-only event journal with deterministic replay. If a silo crashes in the middle of a process, Orleans will raise the grain on another silo from the same point, and there won't be any orphaned state.

  • BPMN recovery as first-class constructs. Compensation, error boundaries, escalation — these are explicit language semantics, not ad-hoc try/catch. Error branches are modeled just like successful ones and are visible to the business on the same diagram.

  • Hot path works from memory. Active workflows live in RAM, and intermediate steps don't go to the database. Events from a single grain call are batched into one transaction at the end — instead of being written one by one.

  • Materialized projections for reading. The admin panel and REST queries about the state read a pre-assembled EF projection. No replay journal is needed to render a list or poll "has it finished yet?".

  • Deploy on your own — that's it. Fleans is delivered as a single container image that runs on a laptop, virtual machine, or in Kubernetes. Data doesn't leave your perimeter; there's no SaaS dependency.

  • Choose your own database. SQLite for development, PostgreSQL for production — switch with a single config key, without code changes. The provider model is open: if you need a different database, you write your own adapter.

  • Choose your own stream provider too. Domain events go through Orleans Streams. By default, it's in-memory; the flag Fleans:Streaming:Provider=kafka switches to the built-in Kafka adapter for cross-silo durability. You can also plug in your own — the extension point is the same.

  • Your own tasks and workers. REST call, queue, or any custom integration is implemented as a BPMN task using the template repo fleans-custom-worker-example. The plugin host scales independently of the engine — one workflow can be distributed across your own fleet of workers.

Installation in one command:

# downloads the docker-compose bundle from the latest release
gh release download --repo nightBaker/fleans -p 'docker-compose-*.zip'
unzip docker-compose-*.zip && docker compose up -d

This brings up the API (8081), management web (8080), worker, MCP, Postgres, Redis and Aspire dashboard. Within a minute you can deploy .bpmn via REST or design processes directly in the web. The Helm chart is also available in the same release, signed with cosign.

Continuous learning: AI that learns from its own mistakes

AI still makes mistakes. I would really love to write that Claude has never failed a single task for me, but that would be a lie. Sometimes it suggests a solution that looks fine on paper, but breaks some non-obvious invariant in the context of my project. Sometimes it forgets about the agreements we made two weeks ago.

Here's what I did about that: I asked the agent to learn from its own mistakes. After every task where there were issues, it updates its own instructions — adding a rule that "you can't do it this way in this project, because [reason]". Over time, this creates a growing set of rules that is essentially a history of all the pitfalls we've stepped on together. Now in CLAUDE.md there is a separate section called "load-bearing invariants" — it lists things where breaking them = sending a production bug: "each activity instance is executed exactly once", "compensation handlers run in isolated variable scopes", "registration-vs-cleanup error asymmetry" and so on. These rules weren't written out of a love for order — each of them corresponds to a specific incident in our history.

I also asked him to maintain the project's documentation website and update all documents with every task. This way, anyone joining the project today would see not a "README from three months ago", but an up-to-date description of how everything is structured. To separate the two audiences, a strict rule was introduced: repository documentation (README.md, docs/conventions/) — for contributors working with the source code; the website (website/...) — for users who only need the release artifact. The pin-guard CI check separately monitors that inline links to source code tied to specific line numbers do not leak into public documentation. Then I went a step further and asked to add a 3D scene to the website's homepage that visually demonstrates Fleans' key strengths: the actor model, BPMN execution, and distributed architecture. It may be overkill from a "is it really necessary" perspective, but it looks impressive and grabs attention. And for a project looking for its first users, attention is more important than moderation.

What I learned over these six months

I feel awkward writing "conclusions" — it always sounds pretentious. But without them, a story about AI development turns into a straightforward report. I'll try to be honest, without the "everything will be amazing" optimism or the "everyone will die" apocalypse.

AI is evolving faster than we can wrap our heads around it. I'm not kidding. Less than 18 months passed between "ChatGPT couldn't compile a domain overview for me a year ago" and "Claude now works in my codebase better than I do". I have no idea what will happen in another 18 months. And as far as I can tell, no one else knows either.

I shipped the first working release in three months. By the end of the six-month period, I had signed artifacts, full documentation, a plugin template, and automated regression testing. This isn't praise for me, it's a statement of fact about the tool. I'm the same developer I was a year ago. The difference lies in the tool, my willingness to adapt my workflow to fit it, and the fact that I already had an existing architecture to build on.

AI with project context surpasses the senior level. This is the most controversial claim I am ready to defend. Not "AI is generally smarter than a senior" — no. Exactly "AI that has read your CLAUDE.md, your domain, your commit messages, and your past conversations, offers better solutions than an experienced engineer you onboarded a week ago". Because an engineer needs several months to start thinking "in your frame of reference". AI absorbs context in minutes.

Architecture and principles are more critical than ever. If you don't have solid context, AI will amplify and multiply your mess. If you have DDD, Clean Architecture, clear module boundaries — AI will fly along them like a train on rails. Investments in architecture pay off with a multiplier.

Competencies are multiplied, but an inexperienced developer can get left behind by their own project. This is the dark side. The speed of AI is such that if you don't have time to understand what is happening in your code, in a month you will no longer be in control of the project. This is not a scare story, this is a real risk. The only protection is to keep your head at the level of architecture, not lines of code.

You need to think like a product owner, not just an engineer. This is a consequence of the previous point. Your value shifts from "I will write it" to "I will define what needs to be written and why, and organize verification".

Ideas are more important than they used to be. Average specialists are less so. This is harsh, but honest. Before, an idea without executors was just empty chatter. Now an idea paired with solid architectural thinking and an AI agent is a working product in a quarter. For those who have ideas, this is an excellent time. For those who made a living in execution roles — it is unsettling.

AI will replace our roles, but not us. For now. I truly think this. The role of a programmer in my project was taken by Claude. But me as a person who has a taste for architecture, understanding of the subject domain, and desire to lead the project, it did not replace. A role is a function. A person is something else. For now.

AI is here, and it's not going anywhere. Adaptation will happen faster than skeptics think. And not as fast as innovators think. Somewhere in the middle, as always happens.

Why I wrote all this

Honestly - to talk about Fleans. The project is live, open, I need the first developers who want to play with it, test it, criticize it. If you have worked with Camunda and have a specific "why wasn't it done this way", I want to hear it. If you want to write your first custom task - there is a template-repo fleans-custom-worker-example, which is built with the latest NuGet packages and starts with one command. If you operate Orleans clusters in production and see that I'm doing something wrong - write, interrupt, correct.

But in the process of writing, I realized that the article is not about the engine. It's about how development is generally arranged in 2026, when you're alone and have a tool that nobody knew how to use until recently. And that this tool is no longer a "helper", but a full-fledged participant in the process, from which you take tasks, to which you delegate subsystems, and which you sometimes scold for architectural decisions.

If this helps someone decide on their project, which has long been lying in the "someday" folder, I'll be glad. Starting now is really a good time.

Fleans - open-source workflow engine on .NET Orleans and BPMN 2. Repository: github.com/nightBaker/fleans. Plugin-host template: github.com/nightBaker/fleans-custom-worker-example. Documentation is in the profile. Questions, criticism, and ideas are in the comments, I read each one.

Comments

    Also read