Continuation tokens
- Durable meaning
- The exact canonical nodes and typed bindings where execution can continue.
Cohesive.Processes Guide
Author an asynchronous C# Process, generate its canonical document, validate exact semantic links, and extend the flow with a typed durable 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. 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.
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.
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.
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.
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.