Continuation tokens
- Durable meaning
- The exact canonical nodes and typed bindings where execution can continue.
Cohesive.Processes Guide
Author a typed asynchronous workflow in C#, run it through the Process execution model, and extend it with a durable event-or-deadline wait.
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.
From there, the guide shows how Cohesive prepares the Process for execution and adds a typed event-or-deadline wait. The later sections introduce the definition and runtime details needed for persistence and recovery.
Add the current prerelease packages to a .NET 10 project:
dotnet add package Cohesive.Processes --prerelease
dotnet add package Cohesive.Analyzers --prereleaseCohesive.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.
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 the Process with familiar asynchronous structure:
[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>(
relation: CustomerByEmail,
input: input);
return customer;
}
}The method is intentionally familiar. await binds the typed query result, which is then available to the rest of the workflow.
This method is Process authoring syntax rather than a method the application calls directly. Cohesive.Analyzers reads it and generates the definition used by the execution environment.
CustomerByEmail comes from the Relation or Query definition that owns the lookup. The Process retains its definition identity, semantic revision, and fingerprint so it cannot silently resume against a different query:
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 keeps the walkthrough self-contained; it is not a second implementation of the query.
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.
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.
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.
When the compiled plan runs through Cohesive.Storage.Processes.ProcessDurableRuntime, a checkpoint retains the coherent execution aggregate:
The async source method, its locals, and its compiler state machine are absent. Restore consumes the canonical document, compiled interpretation, and durable evidence.