Workflows
A WorkflowSpec orchestrates multi-step processes. It defines the sequence of operations, handles failures with retries and compensation, and provides observability into long-running tasks.
Core concepts
Identifiers
Each workflow has a unique workflowId and a version. This allows you to run multiple versions of the same workflow simultaneously during migrations or A/B tests.
Steps
A workflow is composed of steps. Each step invokes a CapabilitySpec, passes inputs, and receives outputs. Steps can run sequentially or in parallel.
Transitions
Transitions define the flow between steps. They can be conditional (e.g., "if payment succeeds, go to step 3; otherwise, go to step 5") or unconditional.
Retries
If a step fails, the workflow can retry it with exponential backoff. You specify the maximum number of retries and the backoff strategy in the spec.
Compensation
When a workflow fails partway through, compensation steps undo the effects of completed steps (e.g., refunding a payment, releasing a reservation). This ensures consistency even in failure scenarios.
SLAs
You can define Service Level Agreements (SLAs) for each step or the entire workflow. If a step exceeds its SLA, the system can trigger alerts or escalations.
Example WorkflowSpec (TypeScript)
Here's a simplified example of a payment workflow in TypeScript:
import { defineWorkflow } from '@lssm/lib.contracts';
import { ValidatePaymentMethod, ChargePayment, SendEmail } from './specs';
export const PaymentFlow = defineWorkflow({
meta: {
name: 'payment.flow',
version: 1,
description: 'End-to-end payment processing',
owners: ['team-payments'],
tags: ['payments'],
stability: 'stable',
},
steps: [
{
id: 'validate-payment',
operation: ValidatePaymentMethod,
inputs: (ctx, input) => ({
userId: ctx.userId,
paymentMethodId: input.paymentMethodId,
}),
retry: {
maxAttempts: 3,
backoff: 'exponential',
},
onSuccess: 'charge-payment',
onFailure: 'notify-user',
},
{
id: 'charge-payment',
operation: ChargePayment,
inputs: (ctx, input, steps) => ({
amount: input.amount,
paymentMethodId: input.paymentMethodId,
}),
compensation: 'refund-payment',
onSuccess: 'send-receipt',
onFailure: 'notify-admin',
},
{
id: 'send-receipt',
operation: SendEmail,
inputs: (ctx, input, steps) => ({
to: ctx.userEmail,
template: 'payment-receipt',
data: steps['charge-payment'].output,
}),
},
],
sla: {
maxDuration: 30000, // milliseconds
alertOnBreach: true,
},
});Triggers
Workflows can be triggered in several ways:
- Manual invocation – A user or system calls the workflow via an API endpoint.
- Event-driven – The workflow starts automatically when a specific event occurs (e.g., a new order is created).
- Scheduled – The workflow runs on a cron schedule (e.g., nightly batch processing).
- Chained – One workflow can invoke another as a step.
Monitoring and versioning
ContractSpec automatically instruments workflows with telemetry. You can view:
- Real-time execution status for each step
- Historical run data and success/failure rates
- Latency distributions and SLA compliance
- Compensation events and retry attempts
When you update a workflow, you increment its version. Running workflows continue on their original version, while new invocations use the latest version. This allows safe, zero-downtime deployments.
Best practices
- Keep steps idempotent – they should be safe to retry without side effects.
- Define compensation for any step that modifies external state.
- Use meaningful step IDs that describe the operation.
- Set realistic SLAs and monitor them in production.
- Test failure scenarios locally before deploying.