Agent skill
Temporal Workflow Guidelines
Comprehensive guide for developing Temporal.io workflows and activities in the A4C-AppSuite. Covers Workflow-First architecture, deterministic workflow design, event-driven activities, saga compensation patterns, CQRS integration, and testing strategies for durable workflow orchestration in healthcare compliance contexts (HIPAA audit trails).
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/temporal-workflow-guidelines
SKILL.md
Temporal Workflow Guidelines
This skill provides comprehensive guidance for developing Temporal.io workflows and activities following the Workflow-First architecture pattern used in A4C-AppSuite.
Quick Start
Creating a New Workflow
- Create workflow file:
temporal/src/workflows/{category}/{name}-workflow.ts - Define
*Paramsand*Resultinterfaces - Import and proxy activities with retry policies
- Implement deterministic orchestration logic
- Add saga compensation for rollback
- Export workflow function
- Create tests:
temporal/src/workflows/{category}/__tests__/{name}-workflow.test.ts - Register in worker:
temporal/src/workers/index.ts
Creating a New Activity
- Create activity file:
temporal/src/activities/{category}/{name}.ts - Define
*Paramsinterface and return type - Perform side effect (API call, database operation)
- Emit domain event to
domain_eventstable - Return result
- Create tests:
temporal/src/activities/{category}/__tests__/{name}.test.ts - Export from category index
- Use in workflows via
proxyActivities
Common Imports
// Workflows
import { proxyActivities, sleep, uuid4, patched } from '@temporalio/workflow'
import type * as activities from '../../activities/organization'
// Activities
import { Context } from '@temporalio/activity'
import { ApplicationFailure } from '@temporalio/common'
import { createClient } from '@supabase/supabase-js'
// Testing
import { TestWorkflowEnvironment } from '@temporalio/testing'
import { Worker } from '@temporalio/worker'
Topics Overview
Workflow Patterns
Learn deterministic workflow design, versioning with patched(), saga compensation, and durable state management. Workflows orchestrate processes without side effects.
📖 resources/workflow-patterns.md
Activity Best Practices
Master idempotent activity design, retry policies, error handling, and timeout configuration. Activities perform all side effects and integrate with external systems.
📖 resources/activity-best-practices.md
Event Emission
Understand domain event patterns, CQRS integration, event schema design, and PostgreSQL event store usage. Every state change becomes an immutable audit trail.
📖 resources/event-emission.md
Testing Workflows
Explore workflow replay testing, activity mocking, local development setup, and integration testing strategies against the dev Temporal cluster.
📖 resources/testing-workflows.md
Navigation Table
| Resource | Focus | Key Concepts |
|---|---|---|
| workflow-patterns.md | Workflow design | Determinism, versioning, saga, child workflows |
| activity-best-practices.md | Activity implementation | Idempotency, retries, timeouts, error handling |
| event-emission.md | Event-driven architecture | Domain events, CQRS, audit trails, metadata |
| testing-workflows.md | Testing strategies | Replay tests, mocks, local setup, debugging |
Core Principles
1. Workflow-First Architecture
Workflows orchestrate, activities execute.
Workflows define the what and when of business processes. Activities handle the how (side effects).
// Workflow orchestrates steps
export async function BootstrapOrganization(params: BootstrapParams) {
// Step 1: Create organization
const orgId = await createOrganizationActivity(params)
// Step 2: Configure DNS
await configureDNSActivity({ orgId, subdomain: params.subdomain })
// Step 3: Send invitations
await sendInvitationsActivity({ orgId, emails: params.invitations })
return { orgId, subdomain: params.subdomain }
}
2. Determinism is Non-Negotiable
Workflows must be deterministic - same input always produces same execution history.
Use Temporal APIs for workflow operations:
import { uuid4, sleep } from '@temporalio/workflow'
// ✅ Correct - deterministic
const workflowId = uuid4()
await sleep('5 minutes')
// ❌ Wrong - non-deterministic
const workflowId = Math.random().toString() // Different each replay!
await new Promise(resolve => setTimeout(resolve, 300000)) // Breaks replay!
No side effects in workflows:
// ❌ Wrong - side effects in workflow
export async function BadWorkflow(params) {
const response = await fetch('https://api.example.com') // NO!
await supabase.from('orgs').insert({ name: params.name }) // NO!
console.log('Started at', new Date()) // Different on replay!
}
// ✅ Correct - side effects in activities
export async function GoodWorkflow(params) {
const result = await fetchDataActivity() // YES!
await createOrgActivity({ name: params.name }) // YES!
}
3. Event-Driven Activities
Every activity that changes state MUST emit a domain event.
This creates an immutable audit trail for HIPAA compliance and enables CQRS projections.
export async function createOrganizationActivity(params: CreateOrgParams) {
// 1. Perform side effect
const { data: org, error } = await supabase
.from('organizations')
.insert({ name: params.name, subdomain: params.subdomain })
.select()
.single()
if (error) throw new Error(`Failed to create org: ${error.message}`)
// 2. Emit domain event
const workflowInfo = Context.current().info
await supabase.from('domain_events').insert({
event_type: 'OrganizationCreated',
aggregate_type: 'Organization',
aggregate_id: org.id,
event_data: { name: org.name, subdomain: org.subdomain },
metadata: {
workflow_id: workflowInfo.workflowId,
workflow_run_id: workflowInfo.runId,
workflow_type: workflowInfo.workflowType
}
})
// 3. Return result
return org.id
}
4. Saga Compensation Pattern
Workflows must handle partial failures by rolling back completed steps in reverse order.
export async function ProvisionTenantWorkflow(params: ProvisionParams) {
let orgId: string | undefined
let dnsConfigured = false
try {
// Step 1
orgId = await createOrganizationActivity(params)
// Step 2
await configureDNSActivity({ orgId, subdomain: params.subdomain })
dnsConfigured = true
// Step 3
await provisionDatabaseActivity({ orgId })
return { orgId, success: true }
} catch (error) {
// Compensation: rollback in reverse order
if (dnsConfigured && orgId) {
await deleteDNSRecordActivity({ subdomain: params.subdomain })
}
if (orgId) {
await deleteOrganizationActivity({ orgId })
}
throw error
}
}
5. CQRS with Event Sourcing
Events are the source of truth. Projections (read models) are derived from the event stream.
Flow:
- Activity performs action → Emits event to
domain_events - PostgreSQL trigger processes event → Updates projection table
- Frontend queries projection → Gets denormalized read model
// Activity emits event
await supabase.from('domain_events').insert({
event_type: 'UserInvitationSent',
aggregate_type: 'Invitation',
aggregate_id: invitation.id,
event_data: { email: invitation.email, role: invitation.role }
})
// Trigger updates projection (PostgreSQL)
// CREATE TRIGGER process_invitation_events
// AFTER INSERT ON domain_events
// FOR EACH ROW EXECUTE FUNCTION update_invitation_projection();
// Frontend queries projection
const { data: invitations } = await supabase
.from('invitations_projection')
.select('*')
.eq('org_id', orgId)
6. Idempotent Activity Design
Activities must be safe to retry without duplicating effects. Check if operation already completed before executing.
See: resources/activity-best-practices.md for complete idempotency patterns.
7. Configurable Retry Policies
Different activities need different retry strategies. External APIs get aggressive retries, validations fail fast.
See: resources/activity-best-practices.md for retry policy patterns.
8. Workflow Versioning
Use patched() to safely update workflows without breaking in-flight executions.
See: resources/workflow-patterns.md for complete versioning guide.
Complete Workflow Template
// File: temporal/src/workflows/organization/my-workflow.ts
import { proxyActivities } from '@temporalio/workflow'
import type * as activities from '../../activities/organization'
// Proxy activities with retry policies
const {
step1Activity,
step2Activity,
compensateStep1Activity,
compensateStep2Activity
} = proxyActivities<typeof activities>({
startToCloseTimeout: '5 minutes',
retry: {
initialInterval: '1s',
backoffCoefficient: 2,
maximumInterval: '30s',
maximumAttempts: 3
}
})
export interface MyWorkflowParams {
orgId: string
config: Record<string, unknown>
}
export interface MyWorkflowResult {
success: boolean
resourceId: string
}
export async function MyWorkflow(
params: MyWorkflowParams
): Promise<MyWorkflowResult> {
// Track state for compensation
let step1Completed = false
let step2Completed = false
let resourceId: string
try {
// Step 1
resourceId = await step1Activity({ orgId: params.orgId })
step1Completed = true
// Step 2
await step2Activity({ resourceId, config: params.config })
step2Completed = true
return { success: true, resourceId }
} catch (error) {
// Saga compensation: rollback completed steps in reverse order
if (step2Completed) {
await compensateStep2Activity({ resourceId })
}
if (step1Completed) {
await compensateStep1Activity({ resourceId })
}
throw error
}
}
Complete Activity Template
// File: temporal/src/activities/organization/my-activity.ts
import { Context } from '@temporalio/activity'
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
)
export interface MyActivityParams {
orgId: string
config: Record<string, unknown>
}
export async function myActivity(
params: MyActivityParams
): Promise<string> {
// 1. Perform side effect
const { data: resource, error } = await supabase
.from('resources')
.insert({
org_id: params.orgId,
config: params.config
})
.select()
.single()
if (error) {
throw new Error(`Failed to create resource: ${error.message}`)
}
// 2. Emit domain event
const workflowInfo = Context.current().info
const { error: eventError } = await supabase
.from('domain_events')
.insert({
event_type: 'ResourceCreated',
aggregate_type: 'Resource',
aggregate_id: resource.id,
event_data: {
org_id: params.orgId,
config: params.config
},
metadata: {
workflow_id: workflowInfo.workflowId,
workflow_run_id: workflowInfo.runId,
workflow_type: workflowInfo.workflowType,
activity_id: workflowInfo.activityId
}
})
if (eventError) {
throw new Error(`Failed to emit event: ${eventError.message}`)
}
console.log(`[ACTIVITY] Created resource ${resource.id} for org ${params.orgId}`)
// 3. Return result
return resource.id
}
Anti-Pattern Example
What NOT to Do: Side Effects in Workflow
// ❌ WRONG - This workflow will break on replay!
export async function BadWorkflow(params) {
// Non-deterministic - Date.now() changes on each replay
const timestamp = Date.now()
// Side effect - API call in workflow
const response = await fetch('https://api.example.com/data')
const data = await response.json()
// Side effect - database write in workflow
await supabase.from('logs').insert({
message: 'Workflow started',
timestamp: new Date().toISOString() // Different each replay!
})
return data
}
// ✅ CORRECT - Deterministic workflow, side effects in activities
export async function GoodWorkflow(params) {
// Use activity for API call
const data = await fetchDataActivity()
// Use activity for database write
await logWorkflowStartActivity({ workflowId: params.id })
return data
}
Why this matters: Temporal replays workflows from event history to reconstruct state after crashes. Non-deterministic code or side effects will produce different results on replay, breaking workflow execution.
Quick Reference
# Local development
kubectl port-forward -n temporal svc/temporal-frontend 7233:7233
export TEMPORAL_ADDRESS=localhost:7233
npm run dev
# View Temporal Web UI
kubectl port-forward -n temporal svc/temporal-web 8080:8080
# Run tests
npm test
See temporal/CLAUDE.md for complete environment variables and commands.
When to Use This Skill
Activate this skill when:
- Creating or modifying Temporal workflows
- Implementing activities that emit domain events
- Adding saga compensation logic
- Debugging workflow execution issues
- Writing workflow or activity tests
- Working in
temporal/src/workflows/ortemporal/src/activities/ - Implementing event-driven CQRS patterns
This skill complements temporal/CLAUDE.md with detailed implementation patterns and examples.
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?