mech.app
Automation

Trigger.dev V2: What a Temporal Alternative for TypeScript Reveals About Durable Execution Plumbing

How Trigger.dev pivoted from event triggers to durable execution, exposing state persistence, retry semantics, and TypeScript-first deployment models.

Source: trigger.dev
Trigger.dev V2: What a Temporal Alternative for TypeScript Reveals About Durable Execution Plumbing

Trigger.dev launched in February 2023 as a “developer-first Zapier alternative” and pulled 745 points on Hacker News. Eight months later, the team shipped V2 with a different pitch: “a Temporal alternative for TypeScript devs.” That pivot from event routing to durable execution tells you what developers building agent systems actually need. Not webhook glue, but resumable, long-running tasks with first-class retry logic and state persistence.

The shift exposes a real infrastructure gap. Temporal gives you durable workflows, but you write them in Go or Java and deploy workers that speak gRPC. Trigger.dev bets that TypeScript developers want the same execution guarantees without leaving their runtime or learning a new deployment model.

What Changed Between V1 and V2

V1 focused on event-driven triggers. You connected external services (GitHub, Stripe, Slack) and wrote handlers that fired when events arrived. Think Zapier, but in code.

V2 reframes the problem around durable execution. You define tasks that can run for hours, survive process crashes, and resume from checkpoints. The platform handles:

  • State persistence across restarts
  • Automatic retries with exponential backoff
  • Observability and tracing for long-running workflows
  • Concurrency controls and queue management

The API surface changed to reflect this. Instead of event listeners, you export task definitions with explicit retry semantics and timeout boundaries.

State Persistence and Resume Logic

Temporal uses event sourcing. Every workflow decision gets logged as an immutable event. When a worker crashes, the runtime replays events to reconstruct state. This gives you deterministic replay but requires careful handling of side effects.

Trigger.dev takes a simpler approach. Tasks checkpoint their state at explicit await points. If a task crashes mid-execution, the platform resumes from the last checkpoint. You don’t replay history; you pick up where you left off.

Key differences:

AspectTemporalTrigger.dev
State modelEvent sourcing with replayCheckpoint-based resume
Side effect handlingRequires deterministic code pathsIdempotent operations at checkpoints
DebuggingReplay events in local workerInspect persisted state snapshots
OverheadEvent log grows with workflow complexityFixed checkpoint size per task

The checkpoint model trades deterministic replay for simpler mental overhead. You don’t need to worry about non-deterministic code (random numbers, timestamps, external API calls) breaking replay. You just need to ensure operations between checkpoints are idempotent.

Retry Semantics and Failure Boundaries

Trigger.dev exposes retry configuration directly in the task definition:

export const processVideo = task({
  id: "process-video",
  retry: {
    maxAttempts: 5,
    factor: 2,
    minTimeout: 1000,
    maxTimeout: 60000,
  },
  run: async ({ videoUrl }: { videoUrl: string }) => {
    const download = await fetchVideo(videoUrl);
    const transcoded = await transcodeToH264(download);
    const uploaded = await uploadToS3(transcoded);
    return { s3Key: uploaded.key };
  },
});

Each await point becomes a potential retry boundary. If transcodeToH264 throws, the platform backs off and retries from that step. The download doesn’t re-execute.

Temporal handles retries at the activity level. You define activities (individual units of work) and workflows (orchestration logic). Activities get their own retry policies. Workflows coordinate activities but don’t retry themselves.

Failure modes to consider:

  • Poison messages: If a task always fails on the same input, it will exhaust retries and move to a dead-letter queue. Trigger.dev surfaces these in the dashboard.
  • Partial state corruption: If a checkpoint writes succeed but the task crashes before completing, you resume with partial state. Design operations to be idempotent.
  • Timeout vs. crash: Timeouts trigger retries. Crashes trigger resume-from-checkpoint. Know which you’re handling.

TypeScript-First Deployment Model

Temporal requires you to run worker processes that poll for tasks. You deploy workers as long-running services, configure them to connect to the Temporal server, and manage their lifecycle.

Trigger.dev abstracts this. You write tasks in your codebase and deploy them to the Trigger.dev platform. The platform manages workers, scaling, and task distribution. You get a managed execution environment without deploying infrastructure.

Local development loop:

  1. Run npx trigger.dev dev to start a local worker
  2. Tasks execute in your local environment
  3. State and logs sync to the Trigger.dev dashboard
  4. Deploy with npx trigger.dev deploy

This feels closer to Vercel or Railway than to Temporal. You push code; the platform handles execution. For teams that want to own the infrastructure, Trigger.dev offers self-hosting, but the default path is managed.

Observability and Debugging

Both platforms expose task execution traces. Temporal gives you event histories and workflow state machines. Trigger.dev shows you checkpoint snapshots and retry timelines.

What you can inspect:

  • Task execution timeline with duration per step
  • Retry attempts and backoff intervals
  • Input and output payloads at each checkpoint
  • Error stack traces and context

The Trigger.dev dashboard surfaces this without requiring you to query a separate observability backend. For production debugging, you can export traces to OpenTelemetry-compatible systems.

When Trigger.dev Fits Your Stack

Use Trigger.dev when:

  • You’re already writing TypeScript and don’t want to add Go or Java workers
  • You need durable execution but don’t want to manage Temporal infrastructure
  • Your tasks are long-running (minutes to hours) but don’t require complex workflow orchestration
  • You want a managed platform with a fast local dev loop

Avoid it when:

  • You need multi-language workflows (Temporal supports Go, Java, Python, .NET)
  • You require deterministic replay for compliance or auditing
  • You’re building complex state machines with parallel branches and conditional logic (Temporal’s workflow DSL is more expressive)
  • You want full control over worker deployment and scaling

Concurrency and Queue Management

Trigger.dev lets you define concurrency limits per task:

export const sendEmail = task({
  id: "send-email",
  queue: {
    name: "email",
    concurrencyLimit: 10,
  },
  run: async ({ to, subject, body }) => {
    await emailProvider.send({ to, subject, body });
  },
});

This prevents you from overwhelming downstream services. The platform queues tasks and releases them based on available concurrency slots.

Temporal handles this with worker pools and task queues. You configure workers to poll specific queues and set concurrency limits at the worker level. Trigger.dev moves this configuration into the task definition, making it easier to reason about per-task limits.

Architecture for AI Agent Workflows

The code snippet in the platform docs shows an AI agent with tool calling:

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, steps } = await generateText({
        model: anthropic("claude-opus-4-20250514"),
        system: "You are a research assistant with web access.",
        messages,
        tools: { search, browse, analyze },
        maxSteps: 5,
      });

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

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

This pattern works because each await generateText and await executeTool becomes a checkpoint. If the LLM call times out or the tool execution fails, the task retries from that point. The conversation history persists across retries.

Failure modes:

  • If generateText returns a malformed tool call, executeTool might throw. The retry logic kicks in, but the same malformed call will fail again. You need validation before calling tools.
  • If the loop hits 10 iterations without completing, the task returns partial results. You need a strategy for incomplete research.

Technical Verdict

Trigger.dev solves a real problem: durable execution for TypeScript developers who don’t want to deploy Temporal workers. The checkpoint-based resume model is simpler than event sourcing, and the managed platform removes infrastructure overhead.

Use it for long-running background tasks (video processing, data pipelines, AI agent loops) where you need retries and state persistence but don’t need complex workflow orchestration. The TypeScript-first API and fast local dev loop make it a good fit for teams already in the Node.js ecosystem.

Avoid it if you need multi-language workflows, deterministic replay, or fine-grained control over worker deployment. Temporal’s event sourcing model and broader language support make it a better choice for complex, compliance-heavy workflows.

The V1-to-V2 pivot shows that developers building agent systems care more about execution guarantees than event routing. Trigger.dev’s bet is that a managed, TypeScript-native platform can deliver those guarantees without the operational complexity of running your own Temporal cluster.