Creating Workflows
This guide walks through building a complete workflow from scratch — a template with role slots, an instance with tasks and dependencies, and monitoring it to completion.
The pattern
Template (blueprint) → Instance (live run) → Tasks (work items)
A template is optional. You can also create instances directly and add tasks programmatically without any template.
Option A: Template-first
Use a template when you'll run the same workflow shape repeatedly.
1. Define the template
typescript
import { Clockwork } from '@clockwork/sdk'
const client = new Clockwork({ apiKey: process.env.CLOCKWORK_API_KEY })
const { data: template } = await client.workflowTemplates.create({
name: 'New Client Onboarding',
description: 'Standard workflow for onboarding a new client',
roles: [
{ slotName: 'account-manager', required: true },
{ slotName: 'legal', required: true },
],
})2. Publish a version with a manifest
typescript
await client.workflowTemplates.publish(template.id, {
manifest: {
nodes: [
{ id: 'n1', name: 'Send welcome email', durationMinutes: 15 },
{ id: 'n2', name: 'Schedule kickoff call', durationMinutes: 30 },
{ id: 'n3', name: 'Collect signed NDA', durationMinutes: 60 },
{ id: 'n4', name: 'Set up client portal', durationMinutes: 45 },
],
edges: [
{ from: 'n1', to: 'n2' },
{ from: 'n2', to: 'n3' },
{ from: 'n2', to: 'n4' },
],
},
roles: [
{ slotName: 'account-manager', required: true },
{ slotName: 'legal', required: true },
],
})3. Instantiate for each new client
typescript
const { data: instance } = await client.workflowInstances.create({
templateId: template.id,
subjectType: 'client',
subjectId: 'client_acme',
})
// Fill role slots with real people
await client.roles.fill(instance.id, 'account-manager', 'res_alice')
await client.roles.fill(instance.id, 'legal', 'res_bob')Option B: Programmatic (no template)
Use this when each workflow is unique or generated dynamically.
typescript
// 1. Create a bare instance
const { data: instance } = await client.workflowInstances.create({
subjectType: 'deal',
subjectId: 'deal_987',
})
// 2. Add tasks
const { data: t1 } = await client.tasks.create({
name: 'Due diligence review',
workflowInstanceId: instance.id,
durationMinutes: 240,
deadline: '2026-07-15T17:00:00Z',
priority: 'high',
})
const { data: t2 } = await client.tasks.create({
name: 'Legal sign-off',
workflowInstanceId: instance.id,
durationMinutes: 60,
})
const { data: t3 } = await client.tasks.create({
name: 'Send countersigned agreement',
workflowInstanceId: instance.id,
durationMinutes: 15,
})
// 3. Define ordering
await client.dependencies.create({ fromTaskId: t1.id, toTaskId: t2.id })
await client.dependencies.create({ fromTaskId: t2.id, toTaskId: t3.id })
// 4. Assign tasks
await client.tasks.assign(t1.id, 'user_analyst')
await client.tasks.assign(t2.id, 'user_legal')Monitoring progress
typescript
// Check instance status
const { data: instance } = await client.workflowInstances.get('wi_abc123')
console.log(instance.status) // 'running'
// List tasks with their scheduling info
const { data: tasks } = await client.tasks.list({ workflowInstanceId: 'wi_abc123' })
for (const task of tasks) {
console.log(
task.name,
task.status,
task.isCritical ? '⚠ CRITICAL' : '',
task.slackSec !== null ? `${Math.round(task.slackSec / 3600)}h float` : '',
)
}Handling completion
typescript
// When a user completes a task
const { data: result } = await client.tasks.complete('tsk_abc123')
// Log which tasks just became available
for (const unlocked of result.unlockedTasks) {
console.log(`Now ready: ${unlocked.name}`)
await notifyAssignee(unlocked.id)
}Next steps
- Event-Driven Patterns — react to task events instead of polling
- Human-in-the-Loop — add approval gates to your workflow
- Conflict Resolution — respond automatically when the engine detects scheduling violations