mech.app
Automation

Trigger.dev V2: TypeScript Workflow Engine Built on Durable Execution Primitives

How Trigger.dev competes with Temporal using serverless primitives, state persistence, and retry guarantees without worker pools.

Source: trigger.dev
Trigger.dev V2: TypeScript Workflow Engine Built on Durable Execution Primitives

Trigger.dev launched V1 as a Zapier alternative for developers. After 745 points on Hacker News and months of user feedback, the team pivoted to V2: a durable execution engine for TypeScript that competes with Temporal without requiring worker pools, external databases, or Kubernetes clusters.

The shift matters for agent builders. Temporal gives you workflow guarantees but demands operational overhead. Trigger.dev promises the same durability using serverless primitives and managed state persistence. The V2 Show HN drew 172 points and 39 comments, signaling real interest in TypeScript-native orchestration that doesn’t force you to run infrastructure.

What Changed Between V1 and V2

V1 focused on webhook triggers and pre-built integrations. V2 exposes durable execution primitives:

  • Task definitions that survive process crashes and retries
  • State persistence without requiring Redis or Postgres
  • Scheduled jobs with cron syntax and no timeout limits
  • Concurrency controls and queue management
  • Observability hooks for tracing and monitoring

The pivot came from feedback. Developers wanted long-running workflows, not just event handlers. They wanted retries that actually worked, not best-effort HTTP callbacks.

Durable Execution Model

Trigger.dev tasks are functions wrapped in a task() call. The runtime intercepts execution, checkpoints state, and handles retries automatically.

export const processOrder = task({
  id: "process-order",
  retry: {
    maxAttempts: 5,
    factor: 2,
    minTimeout: 1000,
    maxTimeout: 60000,
  },
  run: async (payload: { orderId: string }) => {
    const order = await fetchOrder(payload.orderId);
    
    // State persisted here
    await chargePayment(order.paymentMethod, order.total);
    
    // If this fails, retry from checkpoint
    await sendConfirmationEmail(order.email);
    
    return { status: "complete", orderId: order.id };
  },
});

The runtime serializes task state after each await. If the process crashes or times out, the next execution resumes from the last checkpoint. You don’t write retry logic or manage state stores.

State Persistence Without External Databases

Trigger.dev persists task state to its managed backend. Each task execution gets a unique run ID. The runtime stores:

  • Function arguments
  • Return values from each await
  • Retry attempt count
  • Execution timeline

When a task retries, the runtime replays the function from the last successful checkpoint. This avoids re-executing idempotent steps like payment charges.

Temporal uses a similar model but requires you to run worker processes that poll for tasks. Trigger.dev runs tasks in ephemeral containers and stores state server-side. You deploy code, not infrastructure.

Serverless Execution Shape

Tasks run in isolated containers managed by Trigger.dev’s platform. Each invocation:

  1. Pulls the latest task code from your deployment
  2. Spins up a container with the Node.js or Bun runtime
  3. Executes the task function
  4. Checkpoints state after each await
  5. Tears down the container on completion or failure

Cold starts add latency (typically 200-500ms). For agent workflows that run for minutes or hours, this is negligible. For high-frequency tasks (thousands per second), cold starts become a bottleneck.

Concurrency limits prevent runaway costs. You set max concurrent executions per task. The platform queues excess invocations and processes them as capacity frees up.

Comparison: Trigger.dev vs. Temporal

DimensionTrigger.dev V2Temporal
RuntimeServerless containersSelf-hosted workers
State storageManaged backendCassandra/Postgres
DeploymentGit pushWorker binary + config
Cold start200-500msNone (long-running workers)
Retry modelExponential backoff, checkpointingWorkflow history replay
ConcurrencyPlatform-managed queuesWorker pool size
ObservabilityBuilt-in dashboardRequires external tracing
Cost modelPer-execution + durationInfrastructure + ops labor

Temporal wins on throughput and latency. Trigger.dev wins on operational simplicity and zero-infrastructure deployments.

Scheduled Tasks and Cron

Trigger.dev supports durable cron schedules without timeout limits. Traditional cron jobs fail if the process crashes mid-execution. Trigger.dev persists state and resumes from the last checkpoint.

export const dailyReport = task({
  id: "daily-report",
  schedule: "0 9 * * *", // 9 AM daily
  run: async () => {
    const data = await fetchAnalytics();
    const report = await generatePDF(data);
    await emailReport(report);
  },
});

The scheduler guarantees at-least-once execution. If a task runs longer than the schedule interval, the next invocation waits until the current one completes.

Concurrency and Queue Management

You control how many instances of a task run concurrently:

export const processVideo = task({
  id: "process-video",
  queue: {
    concurrencyLimit: 5,
  },
  run: async (payload: { videoUrl: string }) => {
    // Only 5 videos process at once
  },
});

Excess invocations queue in FIFO order. The platform tracks queue depth and execution time in the dashboard. This prevents resource exhaustion when processing large batches.

Observability and Tracing

Each task execution generates a trace with:

  • Start and end timestamps
  • Checkpoint intervals
  • Retry attempts
  • Error stack traces
  • Return values

The dashboard shows real-time task status, queue depth, and failure rates. You can replay failed executions to debug state corruption or transient errors.

Temporal requires external tracing (Jaeger, Zipkin) and custom instrumentation. Trigger.dev bundles observability into the runtime.

Agent Workflow Patterns

Trigger.dev fits multi-step agent workflows where each step might fail independently. Example: research agent with tool calls.

export const researchAgent = task({
  id: "research-agent",
  run: async ({ topic }: { topic: string }) => {
    const messages: CoreMessage[] = [
      { role: "user", content: `Research: ${topic}` },
    ];

    for (let i = 0; i < 10; i++) {
      const { text, toolCalls } = await generateText({
        model: anthropic("claude-opus-4"),
        messages,
        tools: { search, browse, analyze },
        maxSteps: 5,
      });

      if (!toolCalls.length) {
        return { summary: text };
      }

      for (const call of toolCalls) {
        const result = await executeTool(call);
        messages.push({ role: "tool", content: result });
      }
    }
  },
});

If executeTool() fails (rate limit, timeout), the task retries from the last successful tool call. The LLM doesn’t re-generate text or re-execute prior tools.

Failure Modes and Limits

Cold start latency: First invocation of a task after deployment takes 200-500ms. Subsequent invocations reuse warm containers when possible.

State size limits: Task state is serialized and stored. Large payloads (multi-MB) slow down checkpointing and replay. Keep state lean or store blobs externally.

Retry exhaustion: After max retry attempts, the task moves to a dead-letter queue. You must manually inspect and re-trigger failed runs.

Concurrency bottlenecks: If queue depth grows faster than tasks complete, latency increases. Monitor queue metrics and adjust concurrency limits.

Vendor lock-in: State persistence and runtime are Trigger.dev-managed. Migrating to self-hosted Temporal requires rewriting task definitions.

Deployment and Self-Hosting

Trigger.dev offers a managed cloud platform. You push code via Git or CLI, and the platform handles container builds and deployments.

Self-hosting is possible using the open-source repository. You run the Trigger.dev server (Node.js), a Postgres database for task state, and a container runtime (Docker or Kubernetes). This removes vendor lock-in but reintroduces operational overhead.

Security Boundaries

Tasks run in isolated containers with no shared state. Each execution gets a fresh environment. Secrets are injected via environment variables, not hardcoded in task code.

The platform enforces rate limits and concurrency caps to prevent abuse. Multi-tenant workloads are isolated at the container level, not just process level.

For sensitive workflows, self-hosting gives you full control over data residency and network boundaries.

Technical Verdict

Use Trigger.dev when:

  • You want durable execution without running infrastructure
  • Your workflows are TypeScript-native and fit serverless execution
  • Cold starts under 500ms are acceptable
  • You need built-in observability and retry logic
  • You’re building agent workflows with multi-step tool calls

Avoid Trigger.dev when:

  • You need sub-100ms task latency
  • Your workflows process thousands of tasks per second
  • You require full control over state storage and replay logic
  • You already run Temporal and can’t justify migration costs
  • Your tasks generate multi-MB state payloads

Trigger.dev trades throughput and control for operational simplicity. It’s a strong fit for agent builders who want workflow guarantees without Kubernetes clusters. For high-frequency, low-latency orchestration, Temporal remains the better choice.