Best Practices
Guidelines and conventions for writing high-quality workflows.
Table of Contents
- Naming Conventions
- Structure & Organization
- Type Safety
- Error Handling
- Performance
- Security
- Maintainability
- Testing
Naming Conventions
Workflow Names
Use lowercase, hyphenated, descriptive names:
✅ Good:
1name: requirements-analysis
2name: github-pr-review
3name: weekly-report-generator❌ Bad:
1name: ReqAnalysis
2name: pr_review
3name: report1Job Names
Use lowercase, hyphenated, action-oriented names:
✅ Good:
1jobs:
2 analyze-requirements:
3 create-specification:
4 notify-stakeholders:❌ Bad:
1jobs:
2 job1:
3 doStuff:
4 req:Variable Names
Use lowercase, underscored descriptive names:
✅ Good:
1vars:
2 max_concurrent_jobs: 5
3 organization_name: "Acme"
4 default_timeout_minutes: 60❌ Bad:
1vars:
2 MAX_JOBS: 5
3 org: "Acme"
4 timeout: 60Structure & Organization
Keep Workflows Focused
Each workflow should have a single responsibility:
✅ Good:
1# Single responsibility: Analyze requirements
2name: analyze-requirements
3jobs:
4 analyze:
5 ...
6 create-spec:
7 ...❌ Bad:
1# Multiple responsibilities mixed
2name: full-pipeline
3jobs:
4 analyze:
5 ...
6 build:
7 ...
8 test:
9 ...
10 deploy:
11 ...Order Jobs Logically
Place related jobs together:
1jobs:
2 # Input processing
3 validate-input:
4 ...
5
6 # Analysis phase
7 analyze:
8 ...
9 review-analysis:
10 ...
11
12 # Output generation
13 create-deliverable:
14 ...
15
16 # Notification
17 notify-complete:
18 ...Use Descriptive Titles
Add title field for clarity:
✅ Good:
1jobs:
2 analyze-requirements:
3 title: Analyze Project Requirements
4 uses: xema/agent❌ Bad:
1jobs:
2 analyze-requirements:
3 uses: xema/agentType Safety
Always Type Inputs
Specify types explicitly:
✅ Good:
1on:
2 workflow_dispatch:
3 inputs:
4 budget:
5 type: number
6 required: true
7 include_testing:
8 type: boolean
9 required: false
10 defaultValue: true
11 priority:
12 type: string
13 enum: [low, medium, high]
14 required: true❌ Bad:
1on:
2 workflow_dispatch:
3 inputs:
4 budget: # Type not specified
5 include_testing: # Required missing
6 priority: nullUse Enums for Closed Sets
Don't accept arbitrary strings for known values:
✅ Good:
1inputs:
2 environment:
3 type: string
4 enum: [dev, staging, production]
5 required: true❌ Bad:
1inputs:
2 environment:
3 type: string
4 required: true # Accepts ANY stringDeclare Outputs
Always extract outputs explicitly:
✅ Good:
1jobs:
2 analyze:
3 uses: xema/agent
4 with: { task: Analyze }
5 outputs:
6 summary: ${{ result.summary }}
7 issues: ${{ result.issues }}❌ Bad:
1jobs:
2 analyze:
3 uses: xema/agent
4 with: { task: Analyze }
5 # Outputs not declaredError Handling
Define Retry Policies
Set clear retry expectations:
✅ Good:
1defaults:
2 retry:
3 maxAttempts: 3
4 backoffCoefficient: 2.0
5 initialInterval: 1s
6
7jobs:
8 external-call:
9 retry:
10 maxAttempts: 5 # Override for flaky service❌ Bad:
1# No retry policy definedHandle Failures Explicitly
Don't let failures silently stop workflows:
✅ Good:
1jobs:
2 primary:
3 uses: xema/agent
4 with: { task: Primary }
5
6 fallback:
7 if: ${{ failure() }}
8 uses: xema/agent
9 with: { task: Fallback }
10
11 notify:
12 if: ${{ always() }} # Always run
13 uses: xema/webhook❌ Bad:
1jobs:
2 primary:
3 uses: xema/agent
4 # No fallback or notificationSet Timeouts
Prevent workflows from hanging:
✅ Good:
1defaults:
2 timeout: "24h"
3
4jobs:
5 approval:
6 timeout: "7 days"
7 uses: xema/decision-gate❌ Bad:
1# No timeout defined
2# Workflow could hang indefinitelyPerformance
Parallelize When Possible
Use matrix for parallel execution:
✅ Good:
1jobs:
2 test-all:
3 strategy:
4 matrix:
5 version: [20, 21, 22]
6 maxParallel: 4
7 uses: xema/agent❌ Bad:
1jobs:
2 test-20:
3 uses: xema/agent
4 test-21:
5 needs: test-20 # Sequential!
6 uses: xema/agentAvoid Sequential Dependencies
When not needed:
✅ Good:
1jobs:
2 build-backend:
3 uses: xema/agent
4
5 build-frontend:
6 uses: xema/agent # No dependency
7
8 test-integration:
9 needs: [build-backend, build-frontend]
10 uses: xema/agent❌ Bad:
1jobs:
2 build-backend:
3 uses: xema/agent
4
5 build-frontend:
6 needs: build-backend # Unnecessary!
7 uses: xema/agentLimit Matrix Expansion
Prevent runaway jobs:
✅ Good:
1strategy:
2 dynamic:
3 from: ${{ needs.discover.outputs.services }}
4 as: service
5 maxEntries: 50 # Safe limit❌ Bad:
1strategy:
2 dynamic:
3 from: ${{ needs.discover.outputs.services }}
4 as: service
5 # Could explode to 1000+ jobs!Security
Protect Secrets
Never hardcode secrets:
✅ Good:
1with:
2 api_key: ${{ secrets.API_KEY }}
3 password: ${{ secrets.DB_PASSWORD }}❌ Bad:
1with:
2 api_key: "sk-1234567890abcdef" # Exposed!Declare Permissions
Be explicit about resource access:
✅ Good:
1permissions:
2 repos: read
3 kb: limited
4 backlog: write
5 artifacts: read❌ Bad:
1# No permissions declared
2# Defaults to maximum?Validate Webhook Filters
Don't trust all webhooks:
✅ Good:
1on:
2 webhook:
3 - event: scm.push
4 filters:
5 projectId: proj_123
6 branch: main❌ Bad:
1on:
2 webhook:
3 - event: scm.push
4 # Triggers on ALL pushes to ALL projects!Maintainability
Document Complex Workflows
Add descriptions and titles:
✅ Good:
1metadata:
2 name: requirements-analysis
3 version: 1.0.0
4 description: |
5 Analyzes project requirements, routes through technical and PM review,
6 and generates specification document. Supports simple/standard/enterprise
7 scope levels.
8
9vars:
10 max_scope: enterprise # Comment on non-obvious valuesUse Variables for Reusable Values
Don't repeat magic strings:
✅ Good:
1vars:
2 organization: "Acme Corp"
3 max_retries: 3
4 notification_url: "https://alerts.acme.com"
5
6jobs:
7 analyze:
8 with:
9 org: ${{ vars.organization }}
10 notify:
11 with:
12 url: ${{ vars.notification_url }}❌ Bad:
1jobs:
2 analyze:
3 with:
4 org: "Acme Corp"
5 job2:
6 with:
7 org: "Acme Corp" # Repeated!
8 notify:
9 with:
10 url: "https://alerts.acme.com"Keep Jobs Small
One responsibility per job:
✅ Good:
1jobs:
2 analyze:
3 uses: xema/agent
4 with: { task: Analyze }
5
6 review:
7 needs: analyze
8 uses: xema/decision-gate
9
10 publish:
11 needs: review
12 uses: xema/emit-artifact❌ Bad:
1jobs:
2 do-everything:
3 # Analyze + review + publish in one job
4 # Hard to debug, reuse, testVersion Everything
Track changes with semantic versioning:
✅ Good:
1metadata:
2 name: my-workflow
3 version: 1.2.3 # MAJOR.MINOR.PATCHChanges:
- 1.2.3 → 1.2.4 — Bug fix
- 1.2.3 → 1.3.0 — New feature
- 1.2.3 → 2.0.0 — Breaking change
Testing
Test with Mock Data
Before deploying:
1# Trigger workflow with test inputs
2curl -X POST https://workflow-engine-api.xema.dev/workflows/wf_123/dispatch \
3 -H "Authorization: Bearer $TOKEN" \
4 -d '{
5 "inputs": {
6 "project_name": "Test Project",
7 "scope": "simple"
8 }
9 }'Monitor Executions
Watch for issues:
1# Check workflow runs
2curl https://workflow-engine-api.xema.dev/workflows/wf_123/runs \
3 -H "Authorization: Bearer $TOKEN" | jq '.data[] | {runId, status}'Validate YAML
Check syntax before deploying:
1# Validate against schema
2yamllint workflow.yamlSummary
✅ Do:
- Use descriptive, consistent names
- Type all inputs with enums
- Declare outputs explicitly
- Handle errors gracefully
- Set timeouts
- Use variables for reusable values
- Parallelize when possible
- Protect secrets
- Document complex workflows
- Version workflows semantically
❌ Don't:
- Use generic names (job1, task)
- Accept arbitrary string inputs
- Leave workflows without error handling
- Let workflows hang indefinitely
- Hardcode configuration values
- Create sequential dependencies unnecessarily
- Expose secrets in code
- Document nothing
- Change workflows without versioning
Next: Read Troubleshooting for debugging help.