[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill-5a88e0c5-4908-46e2-96e0-0ca514d24c4a":3,"$ff6Nz4sKrCxJm7MJkV6LcEkQgS-lPt04aI3whCNXwVoA":43},{"id":4,"title":5,"description":6,"categoryId":7,"moduleId":8,"tags":9,"prompt":10,"icon":11,"source":12,"sourceUrl":13,"authorId":14,"authorName":15,"isPublic":16,"stars":17,"runs":18,"createdAt":19,"updatedAt":19,"module":20,"category":27,"packages":34},"5a88e0c5-4908-46e2-96e0-0ca514d24c4a","workflow-automation","工作流程自动化是使人工智能代理得以运行的架构","cat_prod_automation","mod_productivity","sickn33,productivity","---\nname: workflow-automation\ndescription: Workflow automation is the infrastructure that makes AI agents\n  reliable. Without durable execution, a network hiccup during a 10-step payment\n  flow means lost money and angry customers. With it, workflows resume exactly\n  where they left off.\nrisk: critical\nsource: vibeship-spawner-skills (Apache 2.0)\ndate_added: 2026-02-27\n---\n\n# Workflow Automation\n\nWorkflow automation is the infrastructure that makes AI agents reliable.\nWithout durable execution, a network hiccup during a 10-step payment\nflow means lost money and angry customers. With it, workflows resume\nexactly where they left off.\n\nThis skill covers the platforms (n8n, Temporal, Inngest) and patterns\n(sequential, parallel, orchestrator-worker) that turn brittle scripts\ninto production-grade automation.\n\nKey insight: The platforms make different tradeoffs. n8n optimizes for\naccessibility, Temporal for correctness, Inngest for developer experience.\nPick based on your actual needs, not hype.\n\n## Principles\n\n- Durable execution is non-negotiable for money or state-critical workflows\n- Events are the universal language of workflow triggers\n- Steps are checkpoints - each should be independently retryable\n- Start simple, add complexity only when reliability demands it\n- Observability isn't optional - you need to see where workflows fail\n- Workflows and agents co-evolve - design for both\n\n## Capabilities\n\n- workflow-automation\n- workflow-orchestration\n- durable-execution\n- event-driven-workflows\n- step-functions\n- job-queues\n- background-jobs\n- scheduled-tasks\n\n## Scope\n\n- multi-agent-coordination → multi-agent-orchestration\n- ci-cd-pipelines → devops\n- data-pipelines → data-engineer\n- api-design → api-designer\n\n## Tooling\n\n### Platforms\n\n- n8n - When: Low-code automation, quick prototyping, non-technical users Note: Self-hostable, 400+ integrations, great for visual workflows\n- Temporal - When: Mission-critical workflows, financial transactions, microservices Note: Strongest durability guarantees, steeper learning curve\n- Inngest - When: Event-driven serverless, TypeScript codebases, AI workflows Note: Best developer experience, works with any hosting\n- AWS Step Functions - When: AWS-native stacks, existing Lambda functions Note: Tight AWS integration, JSON-based workflow definition\n- Azure Durable Functions - When: Azure stacks, .NET or TypeScript Note: Good AI agent support, checkpoint and replay\n\n## Patterns\n\n### Sequential Workflow Pattern\n\nSteps execute in order, each output becomes next input\n\n**When to use**: Content pipelines, data processing, ordered operations\n\n# SEQUENTIAL WORKFLOW:\n\n\"\"\"\nStep 1 → Step 2 → Step 3 → Output\n  ↓         ↓         ↓\n(checkpoint at each step)\n\"\"\"\n\n## Inngest Example (TypeScript)\n\"\"\"\nimport { inngest } from \".\u002Fclient\";\n\nexport const processOrder = inngest.createFunction(\n  { id: \"process-order\" },\n  { event: \"order\u002Fcreated\" },\n  async ({ event, step }) => {\n    \u002F\u002F Step 1: Validate order\n    const validated = await step.run(\"validate-order\", async () => {\n      return validateOrder(event.data.order);\n    });\n\n    \u002F\u002F Step 2: Process payment (durable - survives crashes)\n    const payment = await step.run(\"process-payment\", async () => {\n      return chargeCard(validated.paymentMethod, validated.total);\n    });\n\n    \u002F\u002F Step 3: Create shipment\n    const shipment = await step.run(\"create-shipment\", async () => {\n      return createShipment(validated.items, validated.address);\n    });\n\n    \u002F\u002F Step 4: Send confirmation\n    await step.run(\"send-confirmation\", async () => {\n      return sendEmail(validated.email, { payment, shipment });\n    });\n\n    return { success: true, orderId: event.data.orderId };\n  }\n);\n\"\"\"\n\n## Temporal Example (TypeScript)\n\"\"\"\nimport { proxyActivities } from '@temporalio\u002Fworkflow';\nimport type * as activities from '.\u002Factivities';\n\nconst { validateOrder, chargeCard, createShipment, sendEmail } =\n  proxyActivities\u003Ctypeof activities>({\n    startToCloseTimeout: '30 seconds',\n    retry: {\n      maximumAttempts: 3,\n      backoffCoefficient: 2,\n    }\n  });\n\nexport async function processOrderWorkflow(order: Order): Promise\u003Cvoid> {\n  const validated = await validateOrder(order);\n  const payment = await chargeCard(validated.paymentMethod, validated.total);\n  const shipment = await createShipment(validated.items, validated.address);\n  await sendEmail(validated.email, { payment, shipment });\n}\n\"\"\"\n\n## n8n Pattern\n\"\"\"\n[Webhook: order.created]\n    ↓\n[HTTP Request: Validate Order]\n    ↓\n[HTTP Request: Process Payment]\n    ↓\n[HTTP Request: Create Shipment]\n    ↓\n[Send Email: Confirmation]\n\nConfigure each node with retry on failure.\nUse Error Trigger for dead letter handling.\n\"\"\"\n\n### Parallel Workflow Pattern\n\nIndependent steps run simultaneously, aggregate results\n\n**When to use**: Multiple independent analyses, data from multiple sources\n\n# PARALLEL WORKFLOW:\n\n\"\"\"\n        ┌→ Step A ─┐\nInput ──┼→ Step B ─┼→ Aggregate → Output\n        └→ Step C ─┘\n\"\"\"\n\n## Inngest Example\n\"\"\"\nexport const analyzeDocument = inngest.createFunction(\n  { id: \"analyze-document\" },\n  { event: \"document\u002Fuploaded\" },\n  async ({ event, step }) => {\n    \u002F\u002F Run analyses in parallel\n    const [security, performance, compliance] = await Promise.all([\n      step.run(\"security-analysis\", () =>\n        analyzeForSecurityIssues(event.data.document)\n      ),\n      step.run(\"performance-analysis\", () =>\n        analyzeForPerformance(event.data.document)\n      ),\n      step.run(\"compliance-analysis\", () =>\n        analyzeForCompliance(event.data.document)\n      ),\n    ]);\n\n    \u002F\u002F Aggregate results\n    const report = await step.run(\"generate-report\", () =>\n      generateReport({ security, performance, compliance })\n    );\n\n    return report;\n  }\n);\n\"\"\"\n\n## AWS Step Functions (Amazon States Language)\n\"\"\"\n{\n  \"Type\": \"Parallel\",\n  \"Branches\": [\n    {\n      \"StartAt\": \"SecurityAnalysis\",\n      \"States\": {\n        \"SecurityAnalysis\": {\n          \"Type\": \"Task\",\n          \"Resource\": \"arn:aws:lambda:...:security-analyzer\",\n          \"End\": true\n        }\n      }\n    },\n    {\n      \"StartAt\": \"PerformanceAnalysis\",\n      \"States\": {\n        \"PerformanceAnalysis\": {\n          \"Type\": \"Task\",\n          \"Resource\": \"arn:aws:lambda:...:performance-analyzer\",\n          \"End\": true\n        }\n      }\n    }\n  ],\n  \"Next\": \"AggregateResults\"\n}\n\"\"\"\n\n### Orchestrator-Worker Pattern\n\nCentral coordinator dispatches work to specialized workers\n\n**When to use**: Complex tasks requiring different expertise, dynamic subtask creation\n\n# ORCHESTRATOR-WORKER PATTERN:\n\n\"\"\"\n┌─────────────────────────────────────┐\n│          ORCHESTRATOR               │\n│  - Analyzes task                    │\n│  - Creates subtasks                 │\n│  - Dispatches to workers            │\n│  - Aggregates results               │\n└─────────────────────────────────────┘\n                │\n    ┌───────────┼───────────┐\n    ▼           ▼           ▼\n┌───────┐  ┌───────┐  ┌───────┐\n│Worker1│  │Worker2│  │Worker3│\n│Create │  │Modify │  │Delete │\n└───────┘  └───────┘  └───────┘\n\"\"\"\n\n## Temporal Example\n\"\"\"\nexport async function orchestratorWorkflow(task: ComplexTask) {\n  \u002F\u002F Orchestrator decides what work needs to be done\n  const plan = await analyzeTask(task);\n\n  \u002F\u002F Dispatch to specialized worker workflows\n  const results = await Promise.all(\n    plan.subtasks.map(subtask => {\n      switch (subtask.type) {\n        case 'create':\n          return executeChild(createWorkerWorkflow, { args: [subtask] });\n        case 'modify':\n          return executeChild(modifyWorkerWorkflow, { args: [subtask] });\n        case 'delete':\n          return executeChild(deleteWorkerWorkflow, { args: [subtask] });\n      }\n    })\n  );\n\n  \u002F\u002F Aggregate results\n  return aggregateResults(results);\n}\n\"\"\"\n\n## Inngest with AI Orchestration\n\"\"\"\nexport const aiOrchestrator = inngest.createFunction(\n  { id: \"ai-orchestrator\" },\n  { event: \"task\u002Fcomplex\" },\n  async ({ event, step }) => {\n    \u002F\u002F AI decides what needs to be done\n    const plan = await step.run(\"create-plan\", async () => {\n      return await llm.chat({\n        messages: [\n          { role: \"system\", content: \"Break this task into subtasks...\" },\n          { role: \"user\", content: event.data.task }\n        ]\n      });\n    });\n\n    \u002F\u002F Execute each subtask as a durable step\n    const results = [];\n    for (const subtask of plan.subtasks) {\n      const result = await step.run(`execute-${subtask.id}`, async () => {\n        return executeSubtask(subtask);\n      });\n      results.push(result);\n    }\n\n    \u002F\u002F Final synthesis\n    return await step.run(\"synthesize\", async () => {\n      return synthesizeResults(results);\n    });\n  }\n);\n\"\"\"\n\n### Event-Driven Trigger Pattern\n\nWorkflows triggered by events, not schedules\n\n**When to use**: Reactive systems, user actions, webhook integrations\n\n# EVENT-DRIVEN TRIGGERS:\n\n## Inngest Event-Based\n\"\"\"\n\u002F\u002F Define events with TypeScript types\ntype Events = {\n  \"user\u002Fsigned.up\": {\n    data: { userId: string; email: string };\n  };\n  \"order\u002Fcompleted\": {\n    data: { orderId: string; total: number };\n  };\n};\n\n\u002F\u002F Function triggered by event\nexport const onboardUser = inngest.createFunction(\n  { id: \"onboard-user\" },\n  { event: \"user\u002Fsigned.up\" },  \u002F\u002F Trigger on this event\n  async ({ event, step }) => {\n    \u002F\u002F Wait 1 hour, then send welcome email\n    await step.sleep(\"wait-for-exploration\", \"1 hour\");\n\n    await step.run(\"send-welcome\", async () => {\n      return sendWelcomeEmail(event.data.email);\n    });\n\n    \u002F\u002F Wait 3 days for engagement check\n    await step.sleep(\"wait-for-engagement\", \"3 days\");\n\n    const engaged = await step.run(\"check-engagement\", async () => {\n      return checkUserEngagement(event.data.userId);\n    });\n\n    if (!engaged) {\n      await step.run(\"send-nudge\", async () => {\n        return sendNudgeEmail(event.data.email);\n      });\n    }\n  }\n);\n\n\u002F\u002F Send events from anywhere\nawait inngest.send({\n  name: \"user\u002Fsigned.up\",\n  data: { userId: \"123\", email: \"user@example.com\" }\n});\n\"\"\"\n\n## n8n Webhook Trigger\n\"\"\"\n[Webhook: POST \u002Fapi\u002Fwebhooks\u002Forder]\n    ↓\n[Switch: event.type]\n    ↓ order.created\n[Process New Order Subworkflow]\n    ↓ order.cancelled\n[Handle Cancellation Subworkflow]\n\"\"\"\n\n### Retry and Recovery Pattern\n\nAutomatic retry with backoff, dead letter handling\n\n**When to use**: Any workflow with external dependencies\n\n# RETRY AND RECOVERY:\n\n## Temporal Retry Configuration\n\"\"\"\nconst activities = proxyActivities\u003Ctypeof activitiesType>({\n  startToCloseTimeout: '30 seconds',\n  retry: {\n    initialInterval: '1 second',\n    backoffCoefficient: 2,\n    maximumInterval: '1 minute',\n    maximumAttempts: 5,\n    nonRetryableErrorTypes: [\n      'ValidationError',      \u002F\u002F Don't retry validation failures\n      'InsufficientFunds',    \u002F\u002F Don't retry payment failures\n    ]\n  }\n});\n\"\"\"\n\n## Inngest Retry Configuration\n\"\"\"\nexport const processPayment = inngest.createFunction(\n  {\n    id: \"process-payment\",\n    retries: 5,  \u002F\u002F Retry up to 5 times\n  },\n  { event: \"payment\u002Finitiated\" },\n  async ({ event, step, attempt }) => {\n    \u002F\u002F attempt is 0-indexed retry count\n\n    const result = await step.run(\"charge-card\", async () => {\n      try {\n        return await stripe.charges.create({...});\n      } catch (error) {\n        if (error.code === 'card_declined') {\n          \u002F\u002F Don't retry card declines\n          throw new NonRetriableError(\"Card declined\");\n        }\n        throw error;  \u002F\u002F Retry other errors\n      }\n    });\n\n    return result;\n  }\n);\n\"\"\"\n\n## Dead Letter Handling\n\"\"\"\n\u002F\u002F n8n: Use Error Trigger node\n[Error Trigger]\n    ↓\n[Log to Error Database]\n    ↓\n[Send Alert to Slack]\n    ↓\n[Create Ticket in Jira]\n\n\u002F\u002F Inngest: Handle in onFailure\nexport const myFunction = inngest.createFunction(\n  {\n    id: \"my-function\",\n    onFailure: async ({ error, event, step }) => {\n      await step.run(\"alert-team\", async () => {\n        await slack.postMessage({\n          channel: \"#errors\",\n          text: `Function failed: ${error.message}`\n        });\n      });\n    }\n  },\n  { event: \"...\" },\n  async ({ step }) => { ... }\n);\n\"\"\"\n\n### Scheduled Workflow Pattern\n\nTime-based triggers for recurring tasks\n\n**When to use**: Daily reports, periodic sync, batch processing\n\n# SCHEDULED WORKFLOWS:\n\n## Inngest Cron\n\"\"\"\nexport const dailyReport = inngest.createFunction(\n  { id: \"daily-report\" },\n  { cron: \"0 9 * * *\" },  \u002F\u002F Every day at 9 AM\n  async ({ step }) => {\n    const data = await step.run(\"gather-metrics\", async () => {\n      return gatherDailyMetrics();\n    });\n\n    await step.run(\"generate-report\", async () => {\n      return generateAndSendReport(data);\n    });\n  }\n);\n\nexport const syncInventory = inngest.createFunction(\n  { id: \"sync-inventory\" },\n  { cron: \"*\u002F15 * * * *\" },  \u002F\u002F Every 15 minutes\n  async ({ step }) => {\n    await step.run(\"sync\", async () => {\n      return syncWithSupplier();\n    });\n  }\n);\n\"\"\"\n\n## Temporal Cron Workflow\n\"\"\"\n\u002F\u002F Schedule workflow to run on cron\nconst handle = await client.workflow.start(dailyReportWorkflow, {\n  taskQueue: 'reports',\n  workflowId: 'daily-report',\n  cronSchedule: '0 9 * * *',  \u002F\u002F 9 AM daily\n});\n\"\"\"\n\n## n8n Schedule Trigger\n\"\"\"\n[Schedule Trigger: Every day at 9:00 AM]\n    ↓\n[HTTP Request: Get Metrics]\n    ↓\n[Code Node: Generate Report]\n    ↓\n[Send Email: Report]\n\"\"\"\n\n## Sharp Edges\n\n### Non-Idempotent Steps in Durable Workflows\n\nSeverity: CRITICAL\n\nSituation: Writing workflow steps that modify external state\n\nSymptoms:\nCustomer charged twice. Email sent three times. Database record\ncreated multiple times. Workflow retries cause duplicate side effects.\n\nWhy this breaks:\nDurable execution replays workflows from the beginning on restart.\nIf step 3 crashes and the workflow resumes, steps 1 and 2 run again.\nWithout idempotency keys, external services don't know these are retries.\n\nRecommended fix:\n\n# ALWAYS use idempotency keys for external calls:\n\n### Stripe example:\nawait stripe.paymentIntents.create({\n  amount: 1000,\n  currency: 'usd',\n  idempotency_key: `order-${orderId}-payment`  # Critical!\n});\n\n### Email example:\nawait step.run(\"send-confirmation\", async () => {\n  const alreadySent = await checkEmailSent(orderId);\n  if (alreadySent) return { skipped: true };\n  return sendEmail(customer, orderId);\n});\n\n### Database example:\nawait db.query(`\n  INSERT INTO orders (id, ...) VALUES ($1, ...)\n  ON CONFLICT (id) DO NOTHING\n`, [orderId]);\n\n# Generate idempotency key from stable inputs, not random values\n\n### Workflow Runs for Hours\u002FDays Without Checkpoints\n\nSeverity: HIGH\n\nSituation: Long-running workflows with infrequent steps\n\nSymptoms:\nMemory consumption grows. Worker timeouts. Lost progress after\ncrashes. \"Workflow exceeded maximum duration\" errors.\n\nWhy this breaks:\nWorkflows hold state in memory until checkpointed. A workflow that\nruns for 24 hours with one step per hour accumulates state for 24h.\nWorkers have memory limits. Functions have execution time limits.\n\nRecommended fix:\n\n# Break long workflows into checkpointed steps:\n\n### WRONG - one long step:\nawait step.run(\"process-all\", async () => {\n  for (const item of thousandItems) {\n    await processItem(item);  \u002F\u002F Hours of work, one checkpoint\n  }\n});\n\n### CORRECT - many small steps:\nfor (const item of thousandItems) {\n  await step.run(`process-${item.id}`, async () => {\n    return processItem(item);  \u002F\u002F Checkpoint after each\n  });\n}\n\n## For very long waits, use sleep:\nawait step.sleep(\"wait-for-trial\", \"14 days\");\n\u002F\u002F Doesn't consume resources while waiting\n\n## Consider child workflows for long processes:\nawait step.invoke(\"process-batch\", {\n  function: batchProcessor,\n  data: { items: batch }\n});\n\n### Activities Without Timeout Configuration\n\nSeverity: HIGH\n\nSituation: Calling external services from workflow activities\n\nSymptoms:\nWorkflows hang indefinitely. Worker pool exhausted. Dead workflows\nthat never complete or fail. Manual intervention needed to kill stuck\nworkflows.\n\nWhy this breaks:\nExternal APIs can hang forever. Without timeout, your workflow waits\nforever. Unlike HTTP clients, workflow activities don't have default\ntimeouts in most platforms.\n\nRecommended fix:\n\n# ALWAYS set timeouts on activities:\n\n### Temporal:\nconst activities = proxyActivities\u003Ctypeof activitiesType>({\n  startToCloseTimeout: '30 seconds',  # Required!\n  scheduleToCloseTimeout: '5 minutes',\n  heartbeatTimeout: '10 seconds',  # For long activities\n  retry: {\n    maximumAttempts: 3,\n    initialInterval: '1 second',\n  }\n});\n\n### Inngest:\nawait step.run(\"call-api\", { timeout: \"30s\" }, async () => {\n  return fetch(url, { signal: AbortSignal.timeout(25000) });\n});\n\n## AWS Step Functions:\n{\n  \"Type\": \"Task\",\n  \"TimeoutSeconds\": 30,\n  \"HeartbeatSeconds\": 10,\n  \"Resource\": \"arn:aws:lambda:...\"\n}\n\n# Rule: Activity timeout \u003C Workflow timeout\n\n### Side Effects Outside Step\u002FActivity Boundaries\n\nSeverity: CRITICAL\n\nSituation: Writing code that runs during workflow replay\n\nSymptoms:\nRandom failures on replay. \"Workflow corrupted\" errors. Different\nbehavior on replay than initial run. Non-determinism errors.\n\nWhy this breaks:\nWorkflow code runs on EVERY replay. If you generate a random ID in\nworkflow code, you get a different ID each replay. If you read the\ncurrent time, you get a different time. This breaks determinism.\n\nRecommended fix:\n\n# WRONG - side effects in workflow code:\nexport async function orderWorkflow(order) {\n  const orderId = uuid();  \u002F\u002F Different every replay!\n  const now = new Date();  \u002F\u002F Different every replay!\n  await activities.process(orderId, now);\n}\n\n# CORRECT - side effects in activities:\nexport async function orderWorkflow(order) {\n  const orderId = await activities.generateOrderId();  # Recorded\n  const now = await activities.getCurrentTime();       # Recorded\n  await activities.process(orderId, now);\n}\n\n# Also CORRECT - Temporal workflow.now() and sideEffect:\nimport { sideEffect } from '@temporalio\u002Fworkflow';\n\nconst orderId = await sideEffect(() => uuid());\nconst now = workflow.now();  # Deterministic replay-safe time\n\n# Side effects that are safe in workflow code:\n# - Reading function arguments\n# - Simple calculations (no randomness)\n# - Logging (usually)\n\n### Retry Configuration Without Exponential Backoff\n\nSeverity: MEDIUM\n\nSituation: Configuring retry behavior for failing steps\n\nSymptoms:\nOverwhelming failing services. Rate limiting. Cascading failures.\nRetry storms causing outages. Being blocked by external APIs.\n\nWhy this breaks:\nWhen a service is struggling, immediate retries make it worse.\n100 workflows retrying instantly = 100 requests hitting a service\nthat's already failing. Backoff gives the service time to recover.\n\nRecommended fix:\n\n# ALWAYS use exponential backoff:\n\n### Temporal:\nconst activities = proxyActivities({\n  retry: {\n    initialInterval: '1 second',\n    backoffCoefficient: 2,       # 1s, 2s, 4s, 8s, 16s...\n    maximumInterval: '1 minute',  # Cap the backoff\n    maximumAttempts: 5,\n  }\n});\n\n### Inngest (built-in backoff):\n{\n  id: \"my-function\",\n  retries: 5,  # Uses exponential backoff by default\n}\n\n### Manual backoff:\nconst backoff = (attempt) => {\n  const base = 1000;\n  const max = 60000;\n  const delay = Math.min(base * Math.pow(2, attempt), max);\n  const jitter = delay * 0.1 * Math.random();\n  return delay + jitter;\n};\n\n# Add jitter to prevent thundering herd\n\n### Storing Large Data in Workflow State\n\nSeverity: HIGH\n\nSituation: Passing large payloads between workflow steps\n\nSymptoms:\nSlow workflow execution. Memory errors. \"Payload too large\" errors.\nExpensive storage costs. Slow replays.\n\nWhy this breaks:\nWorkflow state is persisted and replayed. A 10MB payload is stored,\nserialized, and deserialized on every step. This adds latency and\ncost. Some platforms have hard limits (e.g., Step Functions 256KB).\n\nRecommended fix:\n\n# WRONG - large data in workflow:\nawait step.run(\"fetch-data\", async () => {\n  const largeDataset = await fetchAllRecords();  \u002F\u002F 100MB!\n  return largeDataset;  \u002F\u002F Stored in workflow state\n});\n\n# CORRECT - store reference, not data:\nawait step.run(\"fetch-data\", async () => {\n  const largeDataset = await fetchAllRecords();\n  const s3Key = await uploadToS3(largeDataset);\n  return { s3Key };  \u002F\u002F Just the reference\n});\n\nconst processed = await step.run(\"process-data\", async () => {\n  const data = await downloadFromS3(fetchResult.s3Key);\n  return processData(data);\n});\n\n# For Step Functions, use S3 for large payloads:\n{\n  \"Type\": \"Task\",\n  \"Resource\": \"arn:aws:states:::s3:putObject\",\n  \"Parameters\": {\n    \"Bucket\": \"my-bucket\",\n    \"Key.$\": \"$.outputKey\",\n    \"Body.$\": \"$.largeData\"\n  }\n}\n\n### Missing Dead Letter Queue or Failure Handler\n\nSeverity: HIGH\n\nSituation: Workflows that exhaust all retries\n\nSymptoms:\nFailed workflows silently disappear. No alerts when things break.\nCustomer issues discovered days later. Manual recovery impossible.\n\nWhy this breaks:\nEven with retries, some workflows will fail permanently. Without\ndead letter handling, you don't know they failed. The customer\nwaits forever, you're unaware, and there's no data to debug.\n\nRecommended fix:\n\n# Inngest onFailure handler:\nexport const myFunction = inngest.createFunction(\n  {\n    id: \"process-order\",\n    onFailure: async ({ error, event, step }) => {\n      \u002F\u002F Log to error tracking\n      await step.run(\"log-error\", () =>\n        sentry.captureException(error, { extra: { event } })\n      );\n\n      \u002F\u002F Alert team\n      await step.run(\"alert\", () =>\n        slack.postMessage({\n          channel: \"#alerts\",\n          text: `Order ${event.data.orderId} failed: ${error.message}`\n        })\n      );\n\n      \u002F\u002F Queue for manual review\n      await step.run(\"queue-review\", () =>\n        db.insert(failedOrders, { orderId, error, event })\n      );\n    }\n  },\n  { event: \"order\u002Fcreated\" },\n  async ({ event, step }) => { ... }\n);\n\n# n8n Error Trigger:\n[Error Trigger]  →  [Log to DB]  →  [Slack Alert]  →  [Create Ticket]\n\n# Temporal: Use workflow.failed or workflow signals\n\n### n8n Workflow Without Error Trigger\n\nSeverity: MEDIUM\n\nSituation: Building production n8n workflows\n\nSymptoms:\nWorkflow fails silently. Errors only visible in execution logs.\nNo alerts, no recovery, no visibility until someone notices.\n\nWhy this breaks:\nn8n doesn't notify on failure by default. Without an Error Trigger\nnode connected to alerting, failures are only visible in the UI.\nProduction failures go unnoticed.\n\nRecommended fix:\n\n# Every production n8n workflow needs:\n\n1. Error Trigger node\n   - Catches any node failure in the workflow\n   - Provides error details and context\n\n2. Connected error handling:\n   [Error Trigger]\n       ↓\n   [Set: Extract Error Details]\n       ↓\n   [HTTP: Log to Error Service]\n       ↓\n   [Slack\u002FEmail: Alert Team]\n\n3. Consider dead letter pattern:\n   [Error Trigger]\n       ↓\n   [Redis\u002FPostgres: Store Failed Job]\n       ↓\n   [Separate Recovery Workflow]\n\n# Also use:\n- Retry on node failures (built-in)\n- Node timeout settings\n- Workflow timeout\n\n### Long-Running Temporal Activities Without Heartbeat\n\nSeverity: MEDIUM\n\nSituation: Activities that run for more than a few seconds\n\nSymptoms:\nActivity timeouts even when work is progressing. Lost work when\nworkers restart. Can't cancel long-running activities.\n\nWhy this breaks:\nTemporal detects stuck activities via heartbeat. Without heartbeat,\nTemporal can't tell if activity is working or stuck. Long activities\nappear hung, may timeout, and can't be gracefully cancelled.\n\nRecommended fix:\n\n# For any activity > 10 seconds, add heartbeat:\n\nimport { heartbeat, activityInfo } from '@temporalio\u002Factivity';\n\nexport async function processLargeFile(fileUrl: string): Promise\u003Cvoid> {\n  const chunks = await downloadChunks(fileUrl);\n\n  for (let i = 0; i \u003C chunks.length; i++) {\n    \u002F\u002F Check for cancellation\n    const { cancelled } = activityInfo();\n    if (cancelled) {\n      throw new CancelledFailure('Activity cancelled');\n    }\n\n    await processChunk(chunks[i]);\n\n    \u002F\u002F Report progress\n    heartbeat({ progress: (i + 1) \u002F chunks.length });\n  }\n}\n\n# Configure heartbeat timeout:\nconst activities = proxyActivities({\n  startToCloseTimeout: '10 minutes',\n  heartbeatTimeout: '30 seconds',  # Must heartbeat every 30s\n});\n\n# If no heartbeat for 30s, activity is considered stuck\n\n## Validation Checks\n\n### External Calls Without Idempotency Key\n\nSeverity: ERROR\n\nStripe\u002Fpayment calls should use idempotency keys\n\nMessage: Payment call without idempotency_key. Add idempotency key to prevent duplicate charges on retry.\n\n### Email Sending Without Deduplication\n\nSeverity: WARNING\n\nEmail sends in workflows should check for already-sent\n\nMessage: Email sent in workflow without deduplication check. Retries may send duplicate emails.\n\n### Temporal Activities Without Timeout\n\nSeverity: ERROR\n\nAll Temporal activities need timeout configuration\n\nMessage: proxyActivities without timeout. Add startToCloseTimeout to prevent indefinite hangs.\n\n### Inngest Steps Calling External APIs Without Timeout\n\nSeverity: WARNING\n\nExternal API calls should have timeouts\n\nMessage: External API call in step without timeout. Add timeout to prevent workflow hangs.\n\n### Random Values in Workflow Code\n\nSeverity: ERROR\n\nRandom values break determinism on replay\n\nMessage: Random value in workflow code. Move to activity\u002Fstep or use sideEffect.\n\n### Date.now() in Workflow Code\n\nSeverity: ERROR\n\nCurrent time breaks determinism on replay\n\nMessage: Current time in workflow code. Use workflow.now() or move to activity\u002Fstep.\n\n### Inngest Function Without onFailure Handler\n\nSeverity: WARNING\n\nProduction functions should have failure handlers\n\nMessage: Inngest function without onFailure handler. Add failure handling for production reliability.\n\n### Step Without Error Handling\n\nSeverity: WARNING\n\nSteps should handle errors gracefully\n\nMessage: Step without try\u002Fcatch. Consider handling specific error cases.\n\n### Potentially Large Data Returned from Step\n\nSeverity: INFO\n\nLarge data in workflow state slows execution\n\nMessage: Returning potentially large data from step. Consider storing in S3\u002FDB and returning reference.\n\n### Retry Without Backoff Configuration\n\nSeverity: WARNING\n\nRetries should use exponential backoff\n\nMessage: Retry configured without backoff. Add backoffCoefficient and initialInterval.\n\n## Collaboration\n\n### Delegation Triggers\n\n- user needs multi-agent coordination -> multi-agent-orchestration (Workflow provides infrastructure, orchestration provides patterns)\n- user needs tool building for workflows -> agent-tool-builder (Tools that workflows can invoke)\n- user needs Zapier\u002FMake integration -> zapier-make-patterns (No-code automation platforms)\n- user needs browser automation in workflow -> browser-automation (Playwright\u002FPuppeteer activities)\n- user needs computer control in workflow -> computer-use-agents (Desktop automation activities)\n- user needs LLM integration in workflow -> llm-architect (AI-powered workflow steps)\n\n## Related Skills\n\nWorks well with: `multi-agent-orchestration`, `agent-tool-builder`, `backend`, `devops`\n\n## When to Use\n- User mentions or implies: workflow\n- User mentions or implies: automation\n- User mentions or implies: n8n\n- User mentions or implies: temporal\n- User mentions or implies: inngest\n- User mentions or implies: step function\n- User mentions or implies: background job\n- User mentions or implies: durable execution\n- User mentions or implies: event-driven\n- User mentions or implies: scheduled task\n- User mentions or implies: job queue\n- User mentions or implies: cron\n- User mentions or implies: trigger\n\n## Limitations\n- Use this skill only when the task clearly matches the scope described above.\n- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.\n- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.\n","","imported","https:\u002F\u002Fgithub.com\u002Fsickn33\u002Fantigravity-awesome-skills","user_system_seed","SkillOPIC",true,122,195,"2026-05-16 13:47:35",{"id":8,"name":21,"slug":22,"icon":23,"description":24,"sort":25,"createdAt":26},"效率工具","productivity","mdi-lightning-bolt-outline","文档处理、数据分析、自动化工作流",4,"2026-05-16 12:53:40",{"id":7,"name":28,"slug":29,"icon":30,"description":31,"moduleId":8,"sort":32,"skillCount":33,"createdAt":26},"自动化","automation","mdi-robot-outline","工作流自动化、批处理",3,101,[35],{"id":36,"skillId":4,"version":37,"fileName":38,"fileSize":39,"filePath":40,"fileHash":41,"manifest":42,"createdAt":19},"0c466ac3-64cd-44c7-980d-6bd59423bc15","1.0.0","workflow-automation.zip",9461,"uploads\u002Fskills\u002F5a88e0c5-4908-46e2-96e0-0ca514d24c4a\u002Fworkflow-automation.zip","7e1b8aecf1088e983e749635647fa12480da9e6f0aa2cfecbae03d9b43dd6253","[{\"path\":\"SKILL.md\",\"isDirectory\":false,\"size\":28200}]",{"code":44,"message":45,"data":46},200,"success",{"items":47,"stats":48,"page":51},[],{"averageRating":49,"totalRatings":49,"ratingCounts":50},0,[49,49,49,49,49],{"limit":52,"offset":49,"hasMore":53,"nextOffset":52,"ratedOnly":16},15,false]