Penghou.Zhinu.Sqlite 0.1.0-preview.14

This is a prerelease version of Penghou.Zhinu.Sqlite.
dotnet add package Penghou.Zhinu.Sqlite --version 0.1.0-preview.14
                    
NuGet\Install-Package Penghou.Zhinu.Sqlite -Version 0.1.0-preview.14
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Penghou.Zhinu.Sqlite" Version="0.1.0-preview.14" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Penghou.Zhinu.Sqlite" Version="0.1.0-preview.14" />
                    
Directory.Packages.props
<PackageReference Include="Penghou.Zhinu.Sqlite" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Penghou.Zhinu.Sqlite --version 0.1.0-preview.14
                    
#r "nuget: Penghou.Zhinu.Sqlite, 0.1.0-preview.14"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Penghou.Zhinu.Sqlite@0.1.0-preview.14
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Penghou.Zhinu.Sqlite&version=0.1.0-preview.14&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Penghou.Zhinu.Sqlite&version=0.1.0-preview.14&prerelease
                    
Install as a Cake Tool

Penghou.Zhinu

CI NuGet Core NuGet SQLite NuGet Hosting License

Penghou.Zhinu is a lightweight, embedded durable workflow engine for .NET. It persists workflow and step state in SQLite, allowing applications to recover from crashes and restarts without operating a separate workflow server, message broker, PostgreSQL cluster, Redis instance, or Docker infrastructure.

Zhinu is useful when work is expensive, long-running, or side-effecting and must not restart from zero after a process exits. Typical uses include AI and coding workflows, local automation, batch processing, media generation, and multi-stage application jobs.

ordinary async workflow code
        -> durable step boundaries
        -> transactional SQLite state
        -> crash recovery and inspection

Why Zhinu

  • Embedded: runs inside your .NET application.
  • Durable: committed step results survive process loss.
  • Replay-safe: completed steps are reused instead of executed again.
  • Operational: includes leases, fencing, retries, cancellation, signals, progress, diagnosis, artifacts, and OpenTelemetry.
  • Honest about side effects: interrupted delegates are at-least-once; stable idempotency keys support downstream deduplication.
  • Host-independent: use direct construction or the optional hosted worker.

Zhinu stores current durable state rather than replaying an event history. When a process restarts, the workflow method runs again from its entry point. Completed StepAsync calls return their committed results, reconstructing execution until the first unfinished boundary.

Packages

All packages target .NET 8 and .NET 10.

Package Purpose
Penghou.Zhinu Core workflow engine and contracts
Penghou.Zhinu.Sqlite Transactional SQLite store, leases, and recovery
Penghou.Zhinu.Hosting Microsoft.Extensions.Hosting execution loop and DI
Penghou.Zhinu.Hosting.AspNetCore Liveness, readiness, and diagnostics endpoints
Penghou.Zhinu.OpenTelemetry Trace and metric registration helpers
Penghou.Zhinu.Testing Isolated workflow test host and store conformance suite
Penghou.Zhinu.Agents Optional Microsoft Agent Framework checkpoint integration

For the common hosted setup:

dotnet add package Penghou.Zhinu.Sqlite --prerelease
dotnet add package Penghou.Zhinu.Hosting --prerelease

Five-minute quick start

Register SQLite, the hosted engine, and a workflow:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Penghou.Zhinu;
using Penghou.Zhinu.Hosting;
using Penghou.Zhinu.Sqlite;

var builder = Host.CreateApplicationBuilder(args);

builder.Services.AddZhinuSqlite(options =>
    options.DatabasePath = "zhinu.db");

builder.Services.AddZhinu(options =>
{
    options.MaxConcurrentWorkflows = 4;
    options.ShutdownTimeout = TimeSpan.FromSeconds(30);
});

builder.Services.AddZhinuWorkflow<OrderWorkflow, OrderRequest, OrderResult>(
    "process-order",
    "1");

using var host = builder.Build();
await host.StartAsync();

var engine = host.Services.GetRequiredService<WorkflowEngine>();
var handle = await engine.StartHandleAsync<OrderRequest, OrderResult>(
    "process-order",
    "1",
    new OrderRequest("order-42"));

OrderResult result = await handle.WaitAsync();
Console.WriteLine(result.Confirmation);

Define the workflow as ordinary async code with explicit durable steps:

public sealed class OrderWorkflow : IWorkflow<OrderRequest, OrderResult>
{
    public async Task<OrderResult> RunAsync(
        WorkflowContext workflow,
        OrderRequest request,
        CancellationToken cancellationToken)
    {
        var validated = await workflow.StepAsync(
            "validate",
            request,
            (input, ct) => ValidateAsync(input, ct),
            cancellationToken: cancellationToken);

        return await workflow.StepAsync(
            "submit",
            validated,
            (input, step, ct) => SubmitAsync(
                input,
                idempotencyKey: step.IdempotencyKey,
                ct),
            new StepOptions
            {
                Retry = new RetryPolicy
                {
                    MaxAttempts = 3,
                    InitialDelay = TimeSpan.FromSeconds(2)
                },
                ExecutionTimeout = TimeSpan.FromMinutes(2)
            },
            cancellationToken);
    }

    private static Task<ValidatedOrder> ValidateAsync(
        OrderRequest request,
        CancellationToken cancellationToken) =>
        Task.FromResult(new ValidatedOrder(request.OrderId));

    private static Task<OrderResult> SubmitAsync(
        ValidatedOrder order,
        string idempotencyKey,
        CancellationToken cancellationToken) =>
        Task.FromResult(new OrderResult(
            $"confirmed:{order.OrderId}:{idempotencyKey}"));
}

public sealed record OrderRequest(string OrderId);
public sealed record ValidatedOrder(string OrderId);
public sealed record OrderResult(string Confirmation);

Composable class-based steps

Large workflows can move substantial operations into keyed, independently testable step classes without moving orchestration out of RunAsync. The durable step key and implementation key remain separate:

public static class OrderSteps
{
    public static readonly WorkflowStepReference<ValidatedOrder, OrderResult>
        Submit = new(new("submit-order"));
}

builder.Services.AddZhinuStep<SubmitOrderStep>(OrderSteps.Submit);

public sealed class SubmitOrderStep(IOrderGateway gateway)
    : CompensatingWorkflowStep<ValidatedOrder, OrderResult>
{
    public override async Task<OrderResult> ExecuteAsync(
        WorkflowStepContext context,
        ValidatedOrder input,
        CancellationToken cancellationToken)
    {
        await context.EmitAsync(
            "order-submission-started",
            new { input.OrderId },
            cancellationToken);
        return await gateway.SubmitAsync(
            input,
            context.IdempotencyKey,
            cancellationToken);
    }

    public override Task CompensateAsync(
        WorkflowStepContext context,
        ValidatedOrder input,
        OrderResult output,
        CancellationToken cancellationToken) =>
        gateway.CancelAsync(output.Confirmation, cancellationToken);
}

Invoke it from the visible workflow graph and explicitly opt into durable compensation:

var submitted = await workflow.StepAsync(
    stepKey: "initial-submit",
    step: OrderSteps.Submit,
    input: validated,
    stepOptions: new StepOptions
    {
        Retry = new RetryPolicy { MaxAttempts = 3 }
    },
    cancellationToken: cancellationToken,
    compensation: StepCompensationMode.Enabled);

The shared WorkflowStepReference<TInput,TOutput> binds the implementation key to its serialization contract. It lets invocation infer both generic types and lets hosting reject an implementation with the wrong contract during registration. The raw StepImplementationKey overloads remain available for dynamic and custom-container scenarios.

Use the same reference for independently durable parallel work without manually constructing a Task.WhenAll wave:

IReadOnlyList<OrderResult> results = await workflow.FanOutAsync(
    "submit-orders",
    OrderSteps.Submit,
    validatedOrders,
    stepOptions,
    cancellationToken);

Fan-out keys are index-based (submit-orders.0, .1, and so on), so callers must provide a deterministic input order. Each item has its own durable result, retry lifecycle, scope, and optional compensation registration. Results retain input order.

Use LoopAsync when later work depends on state produced by the previous iteration. This is intentionally different from independent fan-out:

ReviewState final = await workflow.LoopAsync(
    "refinement",
    initialState,
    state => state.Score < 0.90,
    async (iteration, ct) =>
    {
        Review review = await iteration.StepAsync(
            "review",
            iteration.State.Draft,
            (draft, step, token) => reviewer.ReviewAsync(
                draft,
                step.IdempotencyKey,
                token),
            cancellationToken: ct);

        ReviewState nextState = iteration.State with
        {
            Draft = review.RevisedDraft,
            Score = review.Score
        };

        return review.Approved
            ? iteration.Break(nextState)
            : iteration.Continue(nextState);
    },
    new LoopOptions(maxIterations: 10)
    {
        TimeBudget = TimeSpan.FromMinutes(15),
        Deadline = reviewWindowClosesAt
    },
    cancellationToken);

Conditions are evaluated before the body. Every successful body explicitly returns either Continue(nextState) or Break(finalState). Break commits the final state and completes normally without another condition evaluation. Completed body steps and committed control outcomes survive replay. Restarting a body step with dependent invalidation preserves earlier iterations and reruns that and later iterations. If the condition remains true after the configured maximum, the workflow fails with LoopLimitExceededException and records durable limit evidence. An optional Deadline is absolute. An optional TimeBudget is wall-clock time measured from the loop's first durable entry; Zhinu persists the resolved boundary, so a worker restart does not grant a new budget. When both are supplied, the earlier boundary wins. Limits are checked before uncommitted condition/body work and again before the iteration commit. They do not forcibly interrupt user code already running; use a step ExecutionTimeout when an individual attempt must be bounded. Perform body work and create outcomes through the supplied iteration context so Zhinu can preserve dependencies and reject cross-scope control.

Create a nested loop through its owning outer iteration:

ReviewState innerResult = await outer.LoopAsync(
    "inner-review",
    innerInitialState,
    state => !state.Approved,
    async (inner, ct) =>
    {
        ReviewState next = await inner.StepAsync(
            "review",
            inner.State,
            ReviewAsync,
            cancellationToken: ct);
        return next.Approved
            ? inner.Break(next)
            : inner.Continue(next);
    },
    new LoopOptions(maxIterations: 5),
    cancellationToken);

The nested identity includes the outer loop and iteration. Inner loops with the same name in different outer iterations therefore cannot collide. Inner loop control is lexical: complete the inner loop and let the outer body explicitly decide whether its own outcome should continue or break. Configure lexical depth with ZhinuOptions.MaxLoopNestingDepth.

Inspect or selectively restart loop work through semantic references; callers do not need to construct Zhinu's encoded persistence keys:

WorkflowLoopReference outer = WorkflowLoopReference.Root("refinement");
WorkflowLoopReference inner = outer.Iteration(2).NestedLoop("inner-review");

WorkflowLoopProgress? progress = await handle.GetLoopProgressAsync(
    inner,
    cancellationToken);

WorkflowLoopStepReference target = inner.Iteration(1).BodyStep("review");
RestartPlan preview = await handle.PlanLoopRestartAsync(
    target,
    cancellationToken: cancellationToken);

RestartReceipt receipt = await handle.RestartLoopStepWithReceiptAsync(
    target,
    new RestartStepOptions
    {
        OperationId = commandId,
        Actor = "operator",
        Reason = "review evidence changed"
    },
    cancellationToken);

Progress groups current condition, body, and commit rows by one-based loop iteration, summarizes committed Continue/Break outcomes and failures, and exposes final/limit boundaries. A final false condition may be the current observed iteration even though its body was never entered. Restart preview remains non-mutating, while a stable operation ID makes the eventual restart safe to retry after an ambiguous client failure.

Penghou.Zhinu.Hosting creates and asynchronously disposes a fresh DI scope for every execution and compensation attempt. Completed-step replay creates no scope and resolves no implementation. Step instances are ephemeral; durable compensation state must come from the persisted input and output, not fields. Scope disposal completes before Zhinu commits success, and disposal failure is handled as an attempt failure. Scoped lifetime is not a distributed transaction, so external effects still require idempotency or compensation.

Use WorkflowStep<TInput, TOutput> for execution-only steps and CompensatingWorkflowStep<TInput, TOutput> when compensation is supported. Enabling compensation for an execution-only implementation fails before its forward operation runs. Duplicate registrations for the same implementation key and contract are rejected rather than resolved by registration order. WorkflowStepContext.EmitAsync buffers an event with the forward attempt: the event and step result commit together, while a failed attempt publishes neither. Manually created and compensation contexts do not offer event emission.

Microsoft DI is optional. Other containers can implement IWorkflowStepResolver and return an IWorkflowStepLease<TStep> that owns one attempt's scope, then configure it through WorkflowEngineBuilder.WithStepResolver(...) or the resolver-aware WorkflowEngine constructor.

workflowRunId is an optional idempotency key for starting the run. Repeating the same workflow, version, input, and ID returns the existing run. Reusing the ID with a different contract or input fails explicitly.

Administrative step restarts can also be made retry-safe. Supply a stable RestartStepOptions.OperationId—typically the caller's approval or command ID—and request the durable receipt:

RestartReceipt receipt = await engine.RestartStepWithReceiptAsync(
    runId,
    "generate",
    new RestartStepOptions
    {
        OperationId = commandId,
        Actor = userId,
        Reason = "Approved regenerated output"
    },
    cancellationToken);

An identical retry returns the original event sequence and generation with WasApplied == false; conflicting reuse throws WorkflowOperationConflictException. SQLite commits the restart state, event, and receipt atomically.

External signals support the same safe ambiguous-retry pattern without changing the existing additive API:

SignalSendReceipt receipt = await engine.SendSignalWithReceiptAsync(
    runId,
    "approval",
    new SignalSendOptions { SignalId = responseId },
    approvedPayload,
    cancellationToken);

The caller keeps responseId stable across retries. SQLite atomically commits the inbox row, signal-sent event, and durable receipt. Identical retries return the original event with WasBuffered == false; reuse with another run, signal name, or canonical JSON payload throws WorkflowOperationConflictException. The receipt remains available after the inbox row is purged.

Focused runtime interfaces

Hosted applications can depend on the smallest capability surface they need:

  • IWorkflowRuntime starts and executes work and is suitable for workers or external schedulers;
  • IWorkflowClient queries runs, waits for completion, and sends signals;
  • IIdempotentWorkflowClient sends retry-safe signals with durable receipts;
  • IWorkflowAdministration performs administrative cancellation.

All four resolve to the same WorkflowEngine singleton when using AddZhinu. The concrete engine remains available for typed handles and advanced inspection or recovery operations. Caller-facing wait and signal deadlines throw WorkflowTimeoutException; duplicate workflow or activity identities throw WorkflowRegistrationException.

Run deadlines and step or compensation execution timeouts also persist WorkflowTimeoutException as their durable failure type, allowing diagnostics and recovery tools to classify time-bound failures without parsing messages.

Cancellation has two deliberately different meanings. Cancelling the token passed to ExecuteAsync stops the current worker attempt and releases its lease; the durable run remains Running and can resume elsewhere. CancelAsync records terminal user or administrative intent, cancels the current run generation and its child subtree, and fences late completion even if in-process user code ignores cancellation. See execution semantics for the full contract.

Delivery guarantee

Zhinu provides:

  • effectively-once reuse of durably completed step results;
  • at-least-once execution of a step interrupted before its result commits;
  • transactional state transitions and diagnostic events;
  • lease fencing so stale workers cannot commit after ownership changes.

Zhinu cannot guarantee exactly-once external side effects. A process can exit after a remote operation succeeds but before its step result is committed. Put external effects inside durable steps and pass WorkflowStepContext.IdempotencyKey to downstream systems whenever possible.

Code outside durable steps may run again after recovery. Control flow should be derived from workflow input and previously committed step results.

See execution semantics and idempotency guidance for the precise contract.

Inspecting a run

A typed handle contains the common operations for one durable run:

WorkflowResult<OrderResult> snapshot = await handle.GetResultAsync();

if (!snapshot.IsTerminal)
{
    WorkflowRunProgress? progress = await handle.GetRunProgressAsync();
    Console.WriteLine($"{progress?.CompletedSteps} steps completed");
}

RunDiagnosis? diagnosis = await handle.DiagnoseAsync();
IReadOnlyList<WorkflowEvent> events = await handle.GetEventsAsync();

GetResultAsync returns a non-throwing snapshot for pending, completed, failed, cancelled, and compensated runs. WaitAsync uses exception-based application flow and returns only a successful typed result.

Subscribe to durable events and reconnect from the last observed sequence:

await foreach (var progressEvent in handle.SubscribeAsync(afterSequence: 42))
    Console.WriteLine($"{progressEvent.Sequence}: {progressEvent.EventType}");

Current state is authoritative. Events support diagnostics, audit, and progress; they are not used to replay workflow execution.

Recovery and hosting

Penghou.Zhinu.Hosting continuously scans for runnable work, renews leases, and recovers expired ownership. Without the hosting package, construct WorkflowEngine directly and call RunAvailableAsync during startup.

The optional ASP.NET Core package exposes operational endpoints:

app.MapZhinuEndpoints("/zhinu");
  • GET /zhinu/liveness checks the process without touching storage.
  • GET /zhinu/readiness verifies that the store and schema are usable.
  • GET /zhinu/diagnostics reports bounded runtime and SQLite health data.

These endpoints do not expose workflow inputs, outputs, signal bodies, artifact contents, or other payload data.

Observability

Zhinu emits privacy-safe activities and metrics. Durable events remain the authoritative record of committed state. Inputs, outputs, prompts, signal payloads, SQL, and exception messages are excluded from built-in telemetry.

services.AddOpenTelemetry()
    .AddZhinuInstrumentation();

See observability for source names, metrics, correlation, privacy, and cardinality conventions.

Testing

Penghou.Zhinu.Testing provides:

  • ZhinuTestHost for isolated workflow integration tests;
  • WorkflowStoreConformanceSuite for custom durable stores;
  • reusable checks for concurrency, fencing, recovery, signals, artifacts, child workflows, transaction behavior, and retry-safe administration.

Store implementations must preserve the atomicity and fencing rules described in the store contract.

Advanced capabilities

The code-first runtime also supports:

  • parallel steps and explicit dependency graphs;
  • durable delays and external signals;
  • deterministic child workflows;
  • selective step restart and previewable forks;
  • durable, idempotent administrative restart receipts;
  • compensation, rollback, and rollback-and-restart;
  • durable external-artifact references with producing-step provenance;
  • run metadata, querying, pagination, retention, and bulk operations;
  • schema compatibility checks and failure diagnosis.

The runnable hosted sample demonstrates process recovery. The direct-construction sample demonstrates typed handles, signals, child workflows, artifacts, and cancellation without dependency injection.

Declarative workflows

The preview declarative layer separates portable workflow descriptions from executable activity implementations. Applications register activities in an ActivityCatalogue, compile against the public IActivityCatalogue contract, and register the resulting immutable definition with the durable runtime.

Compiled definitions are treated as untrusted input at registration: Zhinu revalidates the supported topology, activity contracts, catalogue descriptors, and canonical fingerprint. Hand-authored or modified compiled artifacts cannot bypass the compiler's structural rules.

Detailed contracts:

When not to use Zhinu

Zhinu is probably not the right choice when you need:

  • distributed workers, multi-region availability, or a managed workflow control plane;
  • a remote task queue or general-purpose message broker;
  • exactly-once external side effects without downstream idempotency;
  • cron scheduling as the primary product capability;
  • durable storage for large files or binary artifacts;
  • a model provider, autonomous agent framework, or application-specific user interface.

For those cases, use infrastructure designed for that responsibility and, when useful, compose it with Zhinu at explicit durable step boundaries.

Project direction

Zhinu is independently useful as a code-first durable workflow engine. Its roadmap adds validated declarative workflow definitions, activity catalogues, capability enforcement, revision-bound evidence, and bounded AI activities. Natural-language methodology compilation belongs above the runtime and will be pursued only after hand-authored declarative workflows are proven.

The API is currently preview and may evolve between preview releases. Public surface changes are tracked through shipped/unshipped API baselines and package validation.

License

MIT

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (3)

Showing the top 3 NuGet packages that depend on Penghou.Zhinu.Sqlite:

Package Downloads
Penghou.Zhinu.OpenTelemetry

OpenTelemetry registration helpers for Penghou.Zhinu.

Penghou.Zhinu.Agents

Microsoft Agent Framework (MAF) integration for Penghou.Zhinu: durable SQLite checkpoint storage and MAF graph workflows as durable Zhinu steps.

Penghou.Zhinu.Testing

Test fixtures and store conformance checks for Penghou.Zhinu.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.0-preview.14 156 9/18/2026
0.1.0-preview.13 65 9/18/2026
0.1.0-preview.12 135 9/3/2026
0.1.0-preview.11 97 8/30/2026
0.1.0-preview.10 95 8/29/2026
0.1.0-preview.9 91 8/27/2026
0.1.0-preview.8 92 8/26/2026
0.1.0-preview.7 94 8/26/2026
0.1.0-preview.6 109 8/22/2026
0.1.0-preview.5 105 8/20/2026
0.1.0-preview.3 90 8/20/2026
0.1.0-preview.2 80 8/20/2026
0.1.0-preview.1 75 8/16/2026