Temporal Workflow Development
Overview
Temporal is a durable execution platform that makes workflows survive failures automatically. This skill provides guidance for building Temporal applications.
Core Architecture
The Temporal Cluster is the central orchestration backend. It maintains three key subsystems: the Event History (a durable log of all workflow state), Task Queues (which route work to the right workers), and a Visibility store (for searching and listing workflows). There are three ways to run a Cluster:
- Temporal CLI dev server — a local, single-process server started with
temporal server start-dev. Suitable for development and testing only, not production. - Self-hosted — you deploy and manage the Temporal server and its dependencies (e.g., database) in your own infrastructure for production use.
- Temporal Cloud — a fully managed production service operated by Temporal. No cluster infrastructure to manage.
Workers are long-running processes that you run and manage. They poll Task Queues for work and execute your code. You might run a single Worker process on one machine during development, or run many Worker processes across a large fleet of machines in production. Each Worker hosts two types of code:
- Workflow Definitions — durable, deterministic functions that orchestrate work. These must not have side effects.
- Activity Implementations — non-deterministic operations (API calls, file I/O, etc.) that can fail and be retried.
Workers communicate with the Cluster via a poll/complete loop: they poll a Task Queue for tasks, execute the corresponding Workflow or Activity code, and report results back.
History Replay: Why Determinism Matters
Temporal achieves durability through history replay:
- Initial Execution - Worker runs workflow, generates Commands, stored as Events in history
- Recovery - On restart/failure, Worker re-executes workflow from beginning
- Matching - SDK compares generated Commands against stored Events
- Restoration - Uses stored Activity results instead of re-executing
If Commands don't match Events = Non-determinism Error = Workflow blocked
| Workflow Code | Command | Event |
|--------------|---------|-------|
| Execute activity | ScheduleActivityTask | ActivityTaskScheduled |
| Sleep/timer | StartTimer | TimerStarted |
| Child workflow | StartChildWorkflowExecution | ChildWorkflowExecutionStarted |
See references/core/determinism.md for detailed explanation.
Read All Relevant References
- First, read the getting started guide for the language you are working in:
- TypeScript -> read
references/typescript/typescript.md
- TypeScript -> read
- Second, read appropriate
coreand language-specific references for the task at hand.
Primary References
references/core/determinism.md- Why determinism matters, replay mechanics, basic concepts of activities- Language-specific info at
references/typescript/determinism.md
- Language-specific info at
references/core/patterns.md- Conceptual patterns (signals, queries, saga)- Language-specific info at
references/typescript/patterns.md
- Language-specific info at
references/core/gotchas.md- Anti-patterns and common mistakes- Language-specific info at
references/typescript/gotchas.md
- Language-specific info at
references/core/versioning.md- Versioning strategies and concepts - how to safely change workflow code while workflows are running- Language-specific info at
references/typescript/versioning.md
- Language-specific info at
references/core/troubleshooting.md- Decision trees, recovery proceduresreferences/core/error-reference.md- Common error types, workflow status referencereferences/core/interactive-workflows.md- Testing signals, updates, queriesreferences/core/dev-management.md- Dev cycle & management of server and workersreferences/core/ai-patterns.md- AI/LLM pattern concepts- Language-specific info at
references/typescript/ai-patterns.md, if available. Currently Python only.
- Language-specific info at
Additional Topics
references/typescript/observability.md- See for language-specific implementation guidance on observability in Temporalreferences/typescript/advanced-features.md- See for language-specific guidance on advanced Temporal features and language-specific features
Workflows Location
Temporal workflows and activities are located inside the NestJS apps in this monorepo.
apps/<NESTJS_APP>/
├── src/
│ ├── workflows/ # Workflow definitions
│ ├── main.ts # Export activities to be used in workflows (export * from "./app/activities/activities.service")
│ ├── config
│ │ └── temporal.config.ts # Temporal configuration (host, namespace, taskQueue)
│ └── app/
│ ├── activities/ # Activity implementations
│ │ ├── activities.module.ts # Module to export services
│ │ └── activities.service.ts # Service to define the activities used in the workflows
│ └── app.module.ts # Import WorkflowsModule.registerAsync to configure workflows and activities
└── nest-cli.json # compilerOptions.assets include the path src/workflows/**/*
Workflow Basics
Workflows are deterministic functions that orchestrate activities.
Example Defining a Workflow
// apps/order/src/workflows/order.workflow.ts
import {
proxyActivities,
sleep,
defineSignal,
setHandler,
condition,
} from '@temporalio/workflow';
import type * as activities from '../activities';
const { processPayment, sendOrderConfirmation, updateInventory, notifyShipping } =
proxyActivities<typeof activities>({
startToCloseTimeout: '5 minutes',
retry: {
maximumAttempts: 3,
initialInterval: '1 second',
backoffCoefficient: 2,
},
});
export const cancelOrderSignal = defineSignal('cancelOrder');
export interface OrderWorkflowInput {
orderId: string;
customerId: string;
items: Array<{ productId: string; quantity: number }>;
total: number;
}
export async function orderWorkflow(input: OrderWorkflowInput): Promise<string> {
let cancelled = false;
setHandler(cancelOrderSignal, () => {
cancelled = true;
});
// Step 1: Process payment
const paymentResult = await processPayment({
orderId: input.orderId,
amount: input.total,
customerId: input.customerId,
});
if (!paymentResult.success) {
return 'PAYMENT_FAILED';
}
// Check for cancellation
if (cancelled) {
// Refund payment
await refundPayment({ paymentId: paymentResult.paymentId });
return 'CANCELLED';
}
// Step 2: Update inventory
await updateInventory(input.items);
// Step 3: Send confirmation email
await sendOrderConfirmation({
orderId: input.orderId,
customerId: input.customerId,
});
// Step 4: Wait for shipping (with timeout)
await sleep('1 hour');
await notifyShipping({ orderId: input.orderId });
return 'COMPLETED';
}
Activities
Activities are the building blocks that perform actual work (Steps of the workflows).
Defining Activities
// apps/<NESTJS_APP>/src/app/activities/aproctivities.services.ts
import { Inject, Injectable } from "@nestjs/common";
import { StripeService } from "@projectx/payment";
export interface ProcessPaymentInput {
orderId: string;
amount: number;
customerId: string;
}
export interface ProcessPaymentResult {
success: boolean;
paymentId?: string;
error?: string;
}
@Injectable()
export class ActivitiesService {
constructor(
@Inject(StripeService) public readonly stripeService: StripeService,
) {}
async function processPayment(
input: ProcessPaymentInput
): Promise<ProcessPaymentResult> {
try {
const paymentIntent = await this.stripeService.createPaymentIntent({
amount: Math.round(input.amount * 100),
currency: 'usd',
metadata: {
orderId: input.orderId,
customerId: input.customerId,
},
});
return {
success: true,
paymentId: paymentIntent.id,
};
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Payment failed',
};
}
}
async function refundPayment(input: { paymentId: string }): Promise<void> {
await this.stripeService.refundPayment(input.paymentId);
}
}
Docker Setup
The project includes Temporal in docker-compose.yml:
# Start Temporal server + UI
docker-compose up -d temporal temporal-ui
# Access Temporal UI
open http://localhost:8080
Best Practices
- Keep workflows deterministic - No direct I/O, random, or time-dependent operations
- Use activities for side effects - All external calls go in activities
- Set appropriate timeouts - Configure startToCloseTimeout for activities
- Handle signals gracefully - Check for signals at appropriate points
- Use queries for state - Don't expose internal state directly
- Version workflows - Use workflow versioning for updates
- Test workflows - Use Temporal's testing framework