Repository F# setup
open System
open System.IO
open System.Threading
open System.Threading.Tasks
open Axial
open Axial.Layers
open Axial.Console
open Axial.FileSystem
open Axial.Hosting
open Axial.Hosting.Browser
open Axial.Hosting.Node
open Axial.HttpClient
open Axial.PlatformService
open Axial.Process
open Axial.State
open Axial.Telemetry
open Axial.Telemetry.JavaScriptAdd Axial to an existing Task application
You do not have to convert an application to adopt Axial. A Flow becomes a Task at one boundary, so you can
convert a single module, keep every caller above it on Task, and leave the host untouched.
This page assumes you have read Get started and repeats its checkout workflow so the example below
compiles on its own.
Choose the boundary
Put the boundary where a request, job, or message begins. For example, use a controller action, endpoint handler, hosted service loop, or message consumer.
Above the boundary, callers continue to use Task. Below it, the module uses workflows.
Do not scatter boundaries through the call tree. Each boundary starts its own runtime with its own scope, so converting a leaf function first gives you the costs of Flow and none of its cancellation or cleanup guarantees.
Start the workflow and translate the exit
StartAsTask supplies the environment, accepts the caller's cancellation token, and returns a
Task<Exit<'value, 'error>>. Match on the exit to turn typed failures into whatever your host already returns:
open System.Threading
open System.Threading.Tasks
open Axial
type CheckoutError =
| OrderNotFound of orderId: int
| PaymentDeclined of reason: string
type Receipt = { OrderId: int; Total: decimal; Reference: string }
type CheckoutEnv =
{ FindTotal: int -> CancellationToken -> Task<Result<decimal, CheckoutError>>
Charge: decimal -> CancellationToken -> Task<Result<string, CheckoutError>> }
let checkout orderId : Flow<CheckoutEnv, CheckoutError, Receipt> =
flow {
let! findTotal = Flow.envWith _.FindTotal
let! charge = Flow.envWith _.Charge
let! total = ColdTask(fun cancellationToken -> findTotal orderId cancellationToken)
let! reference = ColdTask(fun cancellationToken -> charge total cancellationToken)
return { OrderId = orderId; Total = total; Reference = reference }
}
let handleCheckout (env: CheckoutEnv) (orderId: int) (cancellationToken: CancellationToken) : Task<string> =
task {
let running = (checkout orderId).StartAsTask(env, cancellationToken = cancellationToken)
match! running with
| Exit.Success receipt -> return $"200 {receipt.Reference}"
| Exit.Failure(Cause.Fail(OrderNotFound id)) -> return $"404 order {id} not found"
| Exit.Failure(Cause.Fail(PaymentDeclined reason)) -> return $"402 {reason}"
| Exit.Failure cause -> return failwith (Cause.prettyPrint string cause)
}
SystemThreadingTasksAxial01-getting-started_03-existing-task-application.md_page.CheckoutErrorOrderNotFoundorderId: intintAn abbreviation for the CLI type . Basic Types
PaymentDeclinedreason: stringstringAn abbreviation for the CLI type . Basic Types
01-getting-started_03-existing-task-application.md_page.ReceiptOrderId: intTotal: decimaldecimalAn abbreviation for the CLI type . Basic Types
Reference: string01-getting-started_03-existing-task-application.md_page.CheckoutEnvFindTotal: int -> CancellationToken -> Task<Result<decimal,CheckoutError>>System.Threading.CancellationTokenPropagates notification that operations should be canceled.
System.Threading.Tasks.Task`1Represents an asynchronous operation that can return a value. The type of the result produced by this .
Microsoft.FSharp.Core.FSharpResult`2Helper type for error handling without exceptions. Choices and Results
Charge: decimal -> CancellationToken -> Task<Result<string,CheckoutError>>checkout: int -> Flow<CheckoutEnv,CheckoutError,Receipt>Axial.Flow`3Represents a cold workflow that reads an environment, returns a typed result, and is executed explicitly through one of its execution members such as ToTask, ToAsync, or RunSynchronously. The type of the environment dependency. The type of the failure value. The type of the success value.
flow: FlowBuilderThe universal flow { } computation expression.
findTotal: int -> CancellationToken -> Task<Result<decimal,CheckoutError>>Axial.FlowenvWith: ('env -> 'value) -> Flow<'env,'error,'value>Projects one value from the current environment. This is the primary way to access app dependencies, configuration, or request metadata stored in env. The projection runs only when the flow is executed, so constructing the flow is still pure and side-effect free. Prefer small projections over passing a large environment deeper into reusable helpers. A function that extracts a value from the environment. A containing the projected value. let currentTime () = Flow.envWith (fun (environment: BaseRuntime) -> environment.Clock.UtcNow())
_arg1: CheckoutEnvcharge: decimal -> CancellationToken -> Task<Result<string,CheckoutError>>_arg3: CheckoutEnvtotal: decimalColdTaskcancellationToken: CancellationTokenreference: stringhandleCheckout: CheckoutEnv -> int -> CancellationToken -> Task<string>env: CheckoutEnvtask: TaskBuilderBuilds a task using computation expression syntax.
running: Task<Exit<Receipt,CheckoutError>>StartAsTask: CheckoutEnv * CancellationToken option -> Task<Exit<Receipt,CheckoutError>>Starts the workflow immediately and returns a task handle for its final exit. The work is already in flight when this returns. Use ToAsync for a cold handle. The environment used by the workflow. The optional cancellation token. Defaults to . A task that completes with the workflow exit. .NET only
cancellationTokenAxial.Exit`2Represents the final outcome of a workflow execution. The type of the success value. The type of the domain-specific failure value.
SuccessThe workflow completed successfully.
receipt: ReceiptFailureThe workflow failed due to a specific cause.
Axial.Cause`1Represents the cause of a failed workflow. The type of the domain-specific failure value.
FailAn expected domain-specific failure.
id: intcause: Cause<CheckoutError>failwith: string -> 'TThrow a exception. The exception message. Never returns. let failingFunction() = failwith "Oh no" // Throws an exception true // Never reaches this failingFunction() // Throws a System.Exception
Axial.CauseprettyPrint: ('error -> string) -> Cause<'error> -> stringPretty prints a cause tree for diagnostics.
string: 'T -> stringConverts the argument to a string using ToString. For standard integer and floating point values and any type that implements IFormattable, ToString conversion uses CultureInfo.InvariantCulture. The input value. The converted string. string 'A' // evaluates to "A" string 0xff // evaluates to "255" string -10 // evaluates to "-10"
Three properties of that match matter:
- The compiler lists the
Cause.Failcases for you. Adding a case toCheckoutErrormakes every boundary that maps it incomplete, which is the point of putting failures in the type. - The final case covers
Cause.DieandCause.Interrupt: defects and interruption, not expected failures. Re-raise or log them the way your host already handles unhandled exceptions. - The host's cancellation token flows in, so cancelling the request cancels every inner call and closes the
workflow's scope.
ColdTasksupplies that token to each task dependency without adding it to the workflow's public parameters.
Keep the environment where the host already keeps services
The environment is a record, so build it once from whatever your host already resolves:
let checkoutEnv (provider: IServiceProvider) : CheckoutEnv =
let orders = provider.GetRequiredService<IOrderRepository>()
let payments = provider.GetRequiredService<IPaymentGateway>()
{ FindTotal = fun orderId cancellationToken -> orders.FindTotalAsync(orderId, cancellationToken)
Charge = fun total cancellationToken -> payments.ChargeAsync(total, cancellationToken) }Convert leaf functions last
Before binding a CancellationToken -> Task<_> function, wrap it in ColdTask. The wrapper keeps task creation cold
and receives Flow's runtime cancellation token.
When the task returns Result<'value,'error>, the builder sends Error to the expected-error channel.
Use Flow.fromTask or Flow.fromTaskResult when you compose without flow { }. Use Flow.awaitStartedTask only when
a host API has already started the operation.
Some legacy APIs do not accept a cancellation token. You can adapt one with ColdTask(fun _ -> legacyCall ()), but
Flow cancellation cannot stop the underlying operation.
Prefer cancellation-aware overloads. Add cancellation support to adapters that you control.
Check effect boundaries during migration
Add Axial.Guardrails while you migrate modules to find dependencies that the existing signatures do not expose.
The analyzer detects ambient access to clocks, randomness, the console, and other operational effects.
The default warning severity does not fail an existing build. Resolve findings in each module as you migrate it, then configure the analyzer to report errors when the project is clean.
For installation and configuration, see Installation.
Go further
- Task and async interop covers the full set of conversions in both directions.
- Expected errors and defects explains which failures belong in
'errorand which belong in the defect channel. - Your first application replaces the host entirely when a Flow is the application root.
- Effect-boundary guardrails covers what the analyzer checks and how to mark an intended boundary.

