Aiki
Core Concepts

Tasks

Tasks are the building blocks of workflows. Each task represents a single unit of work that can be executed and retried independently.

Defining a Task

import { task } from "@aikirun/workflow";

const sendEmail = task({
	name: "send-email",
	handler(input: { email: string; message: string }) {
		// Your business logic
		return sendEmailToUser(input.email, input.message);
	},
});

Task Properties

name

A unique identifier for the task. Use descriptive names like "send-email" or "process-payment".

handler Function

The function that performs the actual work. It receives:

  • input - Input data for the task
const processPayment = task({
	name: "process-payment",
	handler(input: { paymentId: string; amount: number }) {
		console.log(`Processing payment for ${input.paymentId}`);

		return processPaymentWithId(input.paymentId, input.amount);
	},
});

Executing Tasks

Tasks are executed within workflows using .start():

const orderWorkflowV1 = orderWorkflow.v("1.0.0", {
	async handler(run, input: { orderData: any }) {
		const validation = await validateOrder.start(run, {
			orderData: input.orderData,
		});

		const payment = await processPayment.start(run, {
			paymentId: validation.paymentId,
			amount: validation.amount,
		});

		return { success: true };
	},
});

Task Retry

Configure automatic retries for failed tasks using the retry property:

const processPayment = task({
	name: "process-payment",
	handler(input: { paymentId: string; amount: number }) {
		return paymentService.charge(input.paymentId, input.amount);
	},
	retry: {
		type: "exponential",
		maxAttempts: 3,
		baseDelayMs: 1000,
	},
});

For available strategies and best practices, see the Retry Strategies Guide.

Schema Validation

Define schemas to validate task input and output at runtime:

import { z } from "zod";

const processPayment = task({
	name: "process-payment",
	schema: {
		input: z.object({
			paymentId: z.string(),
			amount: z.number().positive(),
		}),
		output: z.object({
			transactionId: z.string(),
			status: z.enum(["success", "failed"]),
		}),
	},
	handler(input) {
		return paymentService.charge(input);
	},
});

Schemas work with any validation library that implements Standard Schema (Zod, Valibot, ArkType, etc.).

Why use output schemas? When task results are cached, the schema validates cached data on replay. If the cached shape doesn't match (e.g., after refactoring), the workflow fails immediately rather than silently returning mismatched data. See Refactoring Workflows.

Task Input

The task receives input directly:

const exampleTask = task({
	name: "example",
	handler(input: { data: string }) {
		// input: Input data for this task
		console.log("Task input:", input);

		return { processed: true };
	},
});

Task Best Practices

  1. Keep tasks focused - One responsibility per task
  2. Make tasks idempotent - A task may re-execute after a crash; running it twice with the same input should be safe
  3. Put side effects in tasks - API calls, database writes, and anything non-deterministic belong in tasks, keeping the workflow handler deterministic
  4. Use meaningful names - Clear, descriptive task names

Next Steps

On this page