Acta. apache-2.0 · .net 10 · pg / mssql / sqlite

durable execution · a fit guide

Considering Temporal? Read this first.

Temporal, Dapr Workflow, and Azure Durable Functions are serious systems, built by people who understand failure. If you need what only they do, use them; this page will tell you when that is. It answers the other question: you have outgrown a job runner, you looked at durable execution, and the machinery feels out of proportion to the problem in front of you. That instinct deserves a straight answer, not a sales page.

The shared model

One model, and its two costs.

All three are replay engines. Recovery does not resume your code where it stopped: it re-executes your orchestrator from the beginning, deterministically, against the event history recorded so far. The model is powerful, and it bills you twice.

Cost one · a contract on your code
The orchestrator must be deterministic, because replay must reproduce the exact decisions the first execution made. No wall clock, no random, no ordinary IO inside it; every nondeterministic thing is routed through the framework as an activity, a durable timer, or a framework-supplied value. And because recovery replays old histories through current code, deploying while work is in flight is a versioning problem with framework rules of its own.
Cost two · a second system
Temporal runs as a server cluster or a cloud service. Dapr Workflow requires the Dapr runtime and its sidecar. Durable Functions is the exception on this axis: with a bring-your-own backend the engine runs inside your process, and what remains is the storage provider and the replay contract itself. Where the second system exists, it is real infrastructure next to your application: deployed, secured, versioned, and paid for on its own schedule.

Neither cost is a flaw. Both follow directly from making replayed history the source of truth, and for some problems that trade is right. The question is whether it is right for yours.

The other way

Record outcomes. Never replay.

Acta persists recorded outcomes at explicit boundaries instead of re-executing history. A handler is an ordinary re-entrant .NET method: when a worker dies mid-job, another worker re-enters it, and everything already recorded is returned instead of re-run.

The code, side by side

A determinism contract, next to a re-entrant handler.

Replay model · orchestrator + activities
// The shape every replay engine shares; names vary by engine.
// Recovery re-executes this method from the top against recorded
// history, so it must be deterministic: no wall clock, no random,
// no ordinary IO on this side of the boundary.
public async Task RunOrchestration(IOrchestrationContext ctx)
{
    var artifact = await ctx.RunActivity<Artifact>(
        "build-artifacts", input.ReleaseId);

    var approved = await ctx.WaitExternalEvent<bool>(
        "release-approval");

    if (approved)
    {
        await ctx.RunActivity("publish", artifact);
    }
}

// BuildArtifacts and Publish live elsewhere, registered as
// activities: ordinary code is legal only on that side.
Acta · a re-entrant handler
public sealed record PublishRelease(string ReleaseId);

public sealed class ReleaseJobs
{
    [Job("publish-release")]
    public async Task Handle(PublishRelease input, JobContext ctx, CancellationToken ct)
    {
        // A named durable step: its outcome is recorded in SQL. Kill the worker after
        // it completes and re-entry returns the stored result; the body does not run again.
        var artifact = await ctx.RunStepAsync("build-artifacts",
            token => BuildArtifactsAsync(input.ReleaseId, token));

        // Suspends the job durably: no worker thread waits. Approve it in an hour or in
        // three days, from code, the CLI, or the dashboard; it resumes on any peer worker.
        var approved = await ctx.WaitSignalAsync<bool>("release-approval", ct);

        if (approved)
        {
            await ctx.RunStepAsync("publish", token => PublishAsync(artifact, token));
        }
    }
}

The left panel is deliberately generic; the boundary is what every engine shares, whatever it names the methods. On the right there is no boundary: BuildArtifactsAsync is ordinary code called in place, and durability comes from the recorded step outcome, not from re-executing the method. A worker kill between any two lines loses no completed work.

A fair split

When you genuinely need a replay engine.

Some requirements are exactly what these systems were built for. If one of these is yours, choose them: Acta does not compete there, and says so on its own homepage (no deterministic replay, no BPMN, no hosted control plane).

Polyglot workers
Orchestrations coordinating workers written in Go, Java, Python, or TypeScript alongside .NET. Acta is .NET only.
Replay as a guarantee
Deterministic re-execution itself is the requirement: an auditable guarantee that recovery reproduces the exact decision path, not just the recorded outcomes.
Very long histories at scale
Orchestrations spanning months and many deploys, with framework-managed versioning of in-flight work as the code underneath it changes.
One orchestrator, many teams
An organization standardizing on a single orchestration platform across teams and languages, with the operating investment that decision assumes.
Multi-region failover
Orchestration state that must fail over across regions under the platform's own replication, beyond what your database's HA story provides.

Stated plainly

What Acta gives up.

The fit

Most jobs do not need an orchestration platform.

Use Acta when a background job stops being a task and starts being application state: durable steps, long waits, signals, children with lineage, and operators who need to ask what happened. If that is your problem, it does not require a replay engine, a determinism contract, or a second system. It requires the work recorded where you can see it, in the database you already run, so you can kill the worker, keep the work. And that recovery path is exercised, not promised: certification runs kill real worker processes mid-job and assert the recovery with SQL checks anyone can re-run.

Also: how Acta compares·from Hangfire·from Quartz·from TickerQ