Cohesive Systems logoCOHESIVE SYSTEMS

Search Cohesive Systems

Ready

Search Cohesive Systems

Find product pages, building blocks, technical articles, and graph definitions.

Cohesive.Processes Guide

Getting Started

Author an asynchronous C# Process, generate its canonical document, validate exact semantic links, and extend the flow with a typed durable wait.

Start with One Process

This guide starts with a customer lookup. The C# source looks like a small asynchronous method: it receives input, awaits a typed query, and returns the result. The source generator turns that syntax into a fingerprinted canonical Process document.

From there, the guide adds stable metadata, strict persistence, exact Relation linking, compilation, and a typed event-or-deadline wait. None of those steps executes or persists the C# async state machine.

Install

Add the current prerelease packages to a .NET 10 project:

dotnet add package Cohesive.Processes --prerelease
dotnet add package Cohesive.Analyzers --prerelease

Cohesive.Analyzers provides the expression-first source generator. When using project references, include it as an analyzer reference.

Import the namespaces used by the walkthrough:

using Cohesive.Execution;
using Cohesive.Model;
using Cohesive.Model.Serialization;
using Cohesive.Processes.Authoring;
using Cohesive.Processes.Compilation;
using Cohesive.Processes.IR;

Keep the analyzer, Process package, shared Cohesive package, and linked semantic packages on compatible prereleases.

Declare an Exact Semantic Reference

A Process does not identify a query by a registry name. It retains the exact definition identity, semantic revision, and fingerprint of the Relation or Query it invokes:

public static class CustomerReferences
{
    public static ExecutionDefinitionReference CustomerByEmail { get; } =
        new(
            new("relation/customer-by-email"),
            new("revision/1"),
            new(
                ExecutionDefinitionFingerprinter.Algorithm,
                ExecutionDefinitionFingerprinter.Canonicalization,
                new string('9', 64)));
}

In an application, obtain this reference from the authored or restored Relation document. The literal above keeps the example self-contained while preserving the exact-reference shape.

Author the Process in C#

Define ordinary input and result records:

public sealed record CustomerLookup(string Email);
 
public sealed record Customer(
    string Id,
    string Email,
    string Status);

Mark a partial class and write a syntax-only Process method:

[GenerateProcessDefinition(nameof(Run))]
public static partial class FindCustomerProcess
{
    public static ExecutionDefinitionReference CustomerByEmail { get; } =
        CustomerReferences.CustomerByEmail;
 
    static async ProcessTask<Customer> Run(
        ProcessContext process,
        CustomerLookup input)
    {
        var customer = await process.Query<Customer>(
            CustomerByEmail,
            input);
 
        return customer;
    }
}

The method is intentionally familiar. await binds the typed query result. A local expression may transform input or the final value as long as it belongs to the portable expression closure.

The method is never called. Cohesive.Analyzers reads its syntax and generates a Define factory that constructs the closed canonical node graph.

Generate the Canonical Document

Supply stable identity, revision, recovery behavior, and provenance:

var metadata = new ProcessAuthoringMetadata(
    new("process/customer/find-by-email"),
    new("revision/1"),
    ProcessRecoveryPolicy.ContinueAttempt,
    new ExecutionProvenance(
        new("customer-app.process-authoring", "1"),
        new("src/processes/FindCustomerProcess.cs"),
        DocumentOrigin.User),
    displayName: "Find customer by email");

Call the generated factory:

var authored = FindCustomerProcess.Define(metadata);
 
if (!authored.IsValid)
{
    foreach (var diagnostic in authored.Validation.Diagnostics)
        Console.Error.WriteLine(
            $"{diagnostic.Code}: {diagnostic.Message}");
 
    return;
}
 
var document = authored.Document;
var reference = authored.Reference;

The generator derives deterministic identities for local structure. The document contains one Relation-evaluation node, its typed result binding and continuation, and one typed return node. It contains no delegate, expression tree, ProcessTask, or CLR state machine.

Persist and Restore

Serialize the canonical document:

var json = ExecutionDefinitionJsonSerializer.Serialize(document);

The persisted document carries the Process input and result contracts, entry node, recovery policy, complete node graph, definition metadata, provenance, source map, and semantic fingerprint.

Use strict execution-definition compatibility or ProcessDefinitionDocuments to restore it. Do not persist the generated builder callback, a compiled plan, or an authoring session as the definition.

Compilation requires evidence about every referenced semantic definition. The customer query accepts CustomerLookup and returns Customer, so project those CLR shapes into the same portable contracts used by the Relation document. For a compact example, assume those contracts are already available as customerLookupContract and customerContract.

var links = new ProcessDefinitionValidationContext(
    definitions:
    [
        new ProcessDefinitionLink(
            FindCustomerProcess.CustomerByEmail,
            ProcessDefinitionLinkKind.RelationQuery,
            customerLookupContract,
            customerContract)
    ]);
 
var compilation = ProcessStaticCompiler.Compile(
    document,
    links);
 
if (!compilation.IsSuccessful)
{
    foreach (var diagnostic in compilation.Validation.Diagnostics)
        Console.Error.WriteLine(
            $"{diagnostic.Code}: {diagnostic.Message}");
 
    return;
}
 
var plan = compilation.Plan!;

Compilation validates graph integrity, exact references, portable expression types, binding visibility, finite activation, and the structural policies used by the definition. It performs no I/O and selects no workflow engine or storage backend.

At activation, a reference or durable host receives the exact query invocation, evaluates the linked Relation, and returns a typed ProcessOperationResult. The Process interpreter advances the immutable continuation with that result.

Add a Durable Wait

Now add a human review task that may complete before its deadline. Define a closed source-only result family:

public abstract record CustomerReviewOutcome;
 
public sealed record DocumentReviewSubmitted(
    string TaskId,
    string Decision) : CustomerReviewOutcome;
 
public sealed record DocumentReviewTimedOut : CustomerReviewOutcome;
 
public sealed record ReviewResult(
    string TaskId,
    string Status,
    string? Decision);

Inside a generated Process method, author the durable race with normal C# pattern matching:

var review = await process.AwaitMatch<CustomerReviewOutcome>(
    clauses:
    [
        process.Event<DocumentReviewSubmitted>(
            ReviewSubmitted,
            priority: 10,
            when: submitted => submitted.TaskId == reviewTask.Id),
        process.Deadline<DocumentReviewTimedOut>(reviewTask.DueAt)
    ],
    arbitration:
        ProcessAwaitArbitration.ExclusivePriorityThenClauseId,
    lateInput: ProcessAwaitInputDisposition.Observe,
    staleInput: ProcessAwaitInputDisposition.Reject,
    duplicateInput:
        ProcessAwaitInputDisposition.ReusePriorDisposition,
    missingTarget:
        ProcessAwaitMissingTargetDisposition.DeadLetter,
    retentionHorizon: TimeSpan.FromDays(30));
 
switch (review)
{
    case DocumentReviewTimedOut _:
        return new(reviewTask.Id, "timed-out", Decision: null);
    case DocumentReviewSubmitted { Decision: var decision }:
        return new(reviewTask.Id, "completed", decision);
}

Every declared alternative must appear exactly once in the immediately following switch. Adding a clause makes the switch diagnostically incomplete until its case is handled.

The source-only result family is not serialized. Each case becomes the typed continuation of its canonical AwaitMatch clause. The durable definition retains the exact interaction contract, timer expression, guard, identities, arbitration, input dispositions, and retention policy.

What Becomes Durable

When the compiled plan runs through Cohesive.Storage.Processes.ProcessDurableRuntime, a checkpoint retains the coherent execution aggregate:

Continuation tokens

Durable meaning
The exact canonical nodes and typed bindings where execution can continue.

Attempt and activation evidence

Durable meaning
Which Process attempt accepted which finite activation under which definition fingerprint.

Inbox dispositions

Durable meaning
How targeted inputs, including late, stale, duplicate, and unresolved inputs, were handled.

Operation receipts

Durable meaning
Exact Relation, Transition, or host-operation work already accepted so replay does not repeat it logically.

Outbox envelopes

Durable meaning
Canonical interactions committed for later publication with stable logical emission identity.

Control state

Durable meaning
Pause, continue, restart, cancel, or terminate evidence with attempt and fencing semantics.

The async source method, its locals, and its compiler state machine are absent. Restore consumes the canonical document, compiled interpretation, and durable evidence.

Continue