mech.app
Automation

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

How Trigger.dev pivoted from event hooks to durable execution primitives, exposing the infrastructure gap for long-running agent tasks.

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 earned 745 Hacker News points. Eight months later, the team shipped V2 and repositioned as a “Temporal alternative for TypeScript devs.” That pivot tells you everything about what developers building AI agents actually need: not event hooks, but durable execution primitives that survive crashes, handle retries, and persist state across hours or days.

The shift exposes a real infrastructure gap. Temporal solves durable execution with event sourcing and polyglot workers, but it brings operational weight and a learning curve. Trigger.dev bets that TypeScript developers want the same guarantees without leaving their language or deploying a distributed system.

What Changed Between V1 and V2

V1 focused on event-driven workflows. You connected webhooks, scheduled triggers, and API calls. It looked like Zapier with code.

V2 rebuilt the core around long-running tasks:

  • Task primitives replace event handlers. Each task is a function that can run for minutes or hours.
  • Automatic retries with exponential backoff. No manual queue setup.
  • State persistence across restarts. If a task crashes mid-execution, it resumes from the last checkpoint.
  • Concurrency controls and queue management built in.
  • Observability hooks for tracing, logging, and monitoring each step.

The V2 API looks like this:

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 });
      }
    }
  },
});

The task function runs in a managed environment. If the process dies, Trigger.dev restarts it and replays the execution from the last saved state. You write normal async TypeScript. The platform handles durability.

Durable Execution Model

Trigger.dev uses a checkpoint-based persistence model, not event sourcing. Here’s how it works:

  1. Task invocation creates a run record with a unique ID.
  2. Execution steps are logged as the function progresses. Each step is a discrete unit: an API call, a database write, a tool invocation.
  3. Checkpoints are saved after each step completes. The system stores the function’s state (variables, call stack, pending promises).
  4. Failure recovery replays the function from the last checkpoint. Already-completed steps are skipped. Side effects (like HTTP requests) are not re-executed if they succeeded.

This differs from Temporal’s event sourcing, where every state change is an immutable event. Trigger.dev’s approach is simpler to reason about for TypeScript developers but less flexible for complex state machines or multi-year workflows.

Retry and Failure Semantics

Trigger.dev exposes retry controls at the task level:

  • Automatic retries with exponential backoff (default: 3 attempts, doubling delay).
  • Manual retry configuration via task options: max attempts, backoff multiplier, timeout per attempt.
  • Dead-letter handling for tasks that exhaust retries. Failed runs are logged and can trigger alerts or fallback workflows.
  • Idempotency keys to prevent duplicate execution when retries overlap.

You can also pause a task mid-execution and wait for external input (human approval, webhook callback, scheduled time). The task sleeps without consuming resources and resumes when the condition is met.

State Management and Secrets

State is stored in a managed Postgres database. Each task run gets:

  • Run metadata: task ID, status, start time, retry count.
  • Execution context: input parameters, intermediate variables, output.
  • Step history: a log of completed steps with timestamps and results.

Secrets are injected at runtime via environment variables or a built-in vault. You define secrets in the Trigger.dev dashboard, and they’re available in the task execution context. The platform rotates credentials and enforces access controls per task.

For multi-step workflows, you can pass state between tasks using the trigger function:

const step1Result = await tasks.processData.trigger({ input: data });
await tasks.notifyUser.trigger({ result: step1Result });

Each task is isolated. Shared state must be explicitly passed or stored in an external database.

Observability and Debugging

Trigger.dev provides a web dashboard with:

  • Real-time task monitoring: see running, queued, and failed tasks.
  • Execution traces: step-by-step logs with timing and input/output for each step.
  • Error details: stack traces, retry history, and failure reasons.
  • Metrics: task duration, success rate, queue depth, concurrency usage.

You can also stream logs to external observability tools (Datadog, Sentry, CloudWatch) via webhooks or SDK integrations.

For debugging, you can run tasks locally using the Trigger.dev CLI. It connects to the cloud platform but executes the task on your machine, so you can set breakpoints and inspect state.

Deployment Shape

Trigger.dev runs as a managed service. You deploy tasks by pushing code to the platform:

  1. Write tasks in TypeScript using the Trigger.dev SDK.
  2. Deploy via CLI: npx trigger.dev deploy.
  3. Invoke tasks from your application using the SDK or REST API.

The platform handles:

  • Worker provisioning: spins up containers to run tasks.
  • Scaling: adds workers based on queue depth and concurrency limits.
  • Networking: manages ingress/egress, retries, and timeouts.
  • Storage: persists state, logs, and execution history.

You can also self-host Trigger.dev using Docker. The open-source version includes the core execution engine, dashboard, and API. You bring your own Postgres and Redis instances.

Comparison: Trigger.dev vs. Temporal

DimensionTrigger.devTemporal
Language supportTypeScript onlyPolyglot (Go, Java, Python, TypeScript)
State modelCheckpoint-based persistenceEvent sourcing with immutable history
DeploymentManaged service or DockerSelf-hosted cluster (multiple services)
Learning curveLow (async/await patterns)Moderate (workflow/activity model)
ObservabilityBuilt-in dashboard, webhook integrationsTemporal Web UI, requires external tracing
ScalingAutomatic worker scalingManual cluster tuning
Failure recoveryAutomatic retries, dead-letter queuesConfigurable retry policies, manual intervention
Long-running workflowsHours to days (checkpoint limits)Years (event sourcing)
Human-in-the-loopBuilt-in pause/resume primitivesRequires signals or external coordination
TypeScript-native DXFirst-class: async/await, type inference, local debuggingSDK wrapper over gRPC, requires workflow/activity split

Trigger.dev trades flexibility for simplicity. If you need multi-year workflows, polyglot workers, or fine-grained control over state transitions, Temporal is the better choice. If you’re building AI agents, data pipelines, or automation workflows in TypeScript and want to ship fast, Trigger.dev removes operational overhead.

Likely Failure Modes

  • Checkpoint size limits: Large state objects (multi-GB datasets) may exceed storage limits. You’ll need to offload data to S3 or a database.
  • Non-deterministic code: If your task function relies on external state (current time, random numbers, file system), replays may produce different results. Use Trigger.dev’s built-in utilities for time and randomness.
  • Third-party API rate limits: Retries can amplify rate-limit errors. You’ll need exponential backoff with jitter or circuit breakers.
  • Cold start latency: Managed workers spin up on demand. First invocation may take seconds. Pre-warm tasks for latency-sensitive workflows.
  • Vendor lock-in: Migrating from Trigger.dev to another platform requires rewriting task logic. For example, converting a task() function to Temporal’s @workflow() decorator means splitting logic into workflows and activities, rewriting state management to use signals and queries, and adapting retry semantics to Temporal’s policy model.

Technical Verdict

Use Trigger.dev when:

  • You’re building multi-agent research loops with 10+ tool calls per iteration, where each call may fail and need retries (e.g., web scraping, API orchestration, LLM reasoning chains).
  • You need human-in-the-loop approval workflows that pause for hours or days (e.g., expense approvals, content moderation queues, compliance reviews).
  • You’re orchestrating media processing pipelines (video transcoding, image generation, audio synthesis) that run for 15-60 minutes per job and must survive worker crashes.
  • You want built-in observability, retries, and concurrency controls without deploying Temporal’s multi-service cluster.
  • You prefer managed infrastructure over self-hosted clusters and are willing to accept vendor-specific APIs.

Avoid Trigger.dev when:

  • You need polyglot workers to run Python ML models, Go microservices, or Java batch jobs in the same workflow.
  • Your workflows must run for months or years without restarts (e.g., SaaS subscription lifecycle management, multi-year compliance audits).
  • You require fine-grained control over state transitions and event sourcing for audit trails or regulatory compliance.
  • You’re already invested in Temporal or another workflow engine and have existing workflow definitions.
  • You need to process terabyte-scale datasets in-memory or run batch ETL jobs processing 100GB+ files per task.

Trigger.dev’s pivot from event hooks to durable execution primitives reflects what developers building agent workflows actually need: reliable, resumable tasks without the operational complexity of Temporal. It’s not a replacement for every use case, but for TypeScript teams shipping AI automation, it removes a significant infrastructure burden.