Skip to content

Trigger.dev: Durable Tasks via Process Snapshots, No Determinism Required

Aug 21, 2026 1 min
TL;DR Trigger.dev is an Apache 2.0 durable task platform (v4.5.12, checked 2026-08) that uses CRIU to snapshot entire Node.js processes for pause and resume. Unlike Temporal's replay model, it never re-executes your orchestration code and imposes no determinism constraint — LLM calls go directly in the task. The tradeoff: snapshots can't preserve TCP connections (you reconnect manually), and checkpointing is cloud-only — self-hosted deployments don't get it.
Table of Contents
  1. Recap: Why the Replay Model Creates Friction for LLM Workloads
  2. What CRIU Does
    1. What CRIU Cannot Snapshot
    2. How Trigger.dev Handles This
  3. When Snapshots Are Triggered
  4. Version Deployment: Snapshots Are Locked to a Version
  5. What This Means for AI Agent Workloads
  6. Self-Hosted vs Cloud: Checkpointing Is Cloud-Only
  7. Cost Structure
  8. When to Choose Trigger.dev vs Temporal
  9. References

🌏 中文版

This site already has a dedicated piece on Temporal, covering the replay model of durable execution — when a worker crashes, it re-executes the workflow code from the top, using the event history to skip completed steps. That article left an axis open: does recovery re-execute your orchestration code? Trigger.dev sits on the other side. It uses CRIU (Checkpoint/Restore In Userspace) to snapshot the entire Node.js process, then restores it at the exact point of interruption without re-executing a single line of your code.

Versions checked 2026-08: @trigger.dev/sdk 4.5.12 (published 2026-08-20), GitHub repo Apache 2.0 license, 16,000+ stars. Primary language support is TypeScript / Node.js; Python is available via the pythonExtension.

Recap: Why the Replay Model Creates Friction for LLM Workloads

The core constraint of the Temporal article: workflow code must be deterministic, because after a crash the worker replays it from line one, injecting recorded results from the event history at each activity call. Anything that might return a different answer on replay — datetime.now(), random(), HTTP requests, LLM calls — must be wrapped in an activity.

For AI agents, this means every model call becomes a separate activity, and the orchestration loop itself must be replayable. If the agent's logic is "each turn the model decides the next step" with a variable number of steps, forcing that control flow into a deterministic workflow becomes a fight against the sandbox.

Trigger.dev's answer: don't replay, just restore the memory.

What CRIU Does

CRIU (pronounced kree-oo) is a Linux userspace tool that freezes a running process, writes its complete state to disk, and later restores it on the same or a different machine. "Complete state" includes:

  • Memory pages (heap, stack, mmap regions)
  • CPU registers
  • Open file descriptors
  • Pipes
  • Signal handlers
  • Process tree structure

CRIU operates at the Linux kernel level, using ptrace to freeze processes, reading state from /proc, and injecting parasite code to extract process internals. It is the underlying mechanism behind Docker checkpoint and Kubernetes CRIU integration.

What CRIU Cannot Snapshot

This is the most critical part. Per CRIU's documentation, the following cannot be checkpointed:

Cannot snapshotReason
Socket types other than TCP / UDP / UNIXOnly TCP, UDP, UNIX domain, packet, and netlink are supported
Established TCP connections (need special handling)The remote end doesn't know you disconnected; connection state becomes inconsistent
Character and block devicesPoint to hardware; virtual devices (/dev/null, /dev/zero, TUN) are exceptions
Open files from unmounted filesystemsThe filesystem path no longer exists, so references can't be rebuilt
Processes with a debugger attachedCRIU itself uses ptrace, and the API doesn't allow multiple debuggers
Pipes opened with O_DIRECTPacketized pipe state can't be captured
File descriptors in transit over UNIX socketsCan't track fds being passed between processes
UDP sockets with cork optionApplication-layer buffer state can't be restored

For Trigger.dev users, the most important item is TCP connections. Database connections, HTTP keep-alive, WebSockets — all of these break at snapshot time. After restore, your code thinks the connection is alive, but the remote end closed it long ago.

How Trigger.dev Handles This

Trigger.dev provides onWait and onResume lifecycle hooks. onWait runs before the snapshot — you disconnect here. onResume runs after restore — you reconnect here. The official docs show a Prisma example: call $disconnect() in onWait, reinitialize the client in onResume.

This is not automatic. You must manage every connection that spans a checkpoint yourself. Miss one, and after restore you get a connection that looks alive but silently fails on writes.

When Snapshots Are Triggered

Not every line of code triggers a checkpoint. Trigger.dev snapshots at these points:

  1. triggerAndWait(): Call a child task and wait for its result. The parent is snapshotted, resources released; it restores when the child completes.
  2. wait.for() / wait.until(): Pause for a duration or until a specific time. Waits longer than 60 seconds trigger a real snapshot; waits under 60 seconds keep the process in memory without snapshotting, and the concurrency slot is not released.
  3. wait.forToken(): Wait for an external event (human approval, webhook callback). The process is snapshotted until the token completes.

After snapshotting, the Trigger.dev docs state that the checkpoint is "efficiently compressed and stored on disk," and the process's compute resources are released. When the condition is met (child completes, timer fires, token completes), the checkpoint is restored to a new execution environment and resumes from where it left off.

On Trigger.dev Cloud, wait time is not billed. You pay only for CPU time when code is actually executing.

Version Deployment: Snapshots Are Locked to a Version

Trigger.dev uses atomic version numbers in the format YYYYMMDD.N (e.g. 20260821.1). When a run starts, it locks to the current latest version. Even if you deploy a new version while it's waiting, the restored run still uses the old version's code.

This differs from the versioning problem Temporal faces. Temporal replays code, so changing an in-flight workflow's code causes a non-determinism error. Trigger.dev's snapshot contains the code itself (the entire process image), so version conflicts don't happen — but you also don't get new bug fixes until you replay.

Child task version locking rules:

Trigger methodWhich version the child uses
trigger() / batchTrigger()Latest version (unlocked)
triggerAndWait() / batchTriggerAndWait()Inherits parent's version (locked)

Failed retries use the original version; replays use the latest version with the original input. Note that "replay" here means the opposite of Temporal's "replay": Temporal's replay is an internal state-reconstruction mechanism, while Trigger.dev's replay means "run the latest code with the same input again" — used to verify bug fixes.

What This Means for AI Agent Workloads

Back to the opening question. If your agent flow looks like:

Call LLM → decide next step based on response → maybe call LLM again → wait for human approval → hit a third-party API → write back to database

In Temporal, every LLM call must be wrapped in an activity, and the orchestration loop must be replayable. In Trigger.dev, you write a regular async function:

export const agentTask = task({
  id: "agent-loop",
  run: async (payload) => {
    const response = await openai.chat.completions.create({
      model: "gpt-4o",
      messages: payload.messages,
    });

    if (response.choices[0].message.content.includes("need_approval")) {
      const approval = await wait.forToken<{ approved: boolean }>({
        id: "human-review",
        timeout: "7d",
      });
      if (!approval.ok || !approval.output.approved) return;
    }

    // Continue processing...
  },
});

wait.forToken() triggers a snapshot. During the wait, no compute resources are used and nothing is billed. When the human approves, the process restores and continues from the wait.forToken() line. No need to extract LLM calls into activities, no need to ensure the control flow is replayable.

This is the most direct benefit of the checkpoint model for AI workloads: LLM calls are inherently non-deterministic, but you don't need to care.

The tradeoff is in two places. First, you don't get Temporal's full event history. Temporal's Event History records every activity's input, output, retry count, and duration — you can take production history and replay it locally for debugging. Trigger.dev has run logs and a dashboard, but no step-by-step replayable history.

Second, the snapshot contains the process's memory state at that moment. If your task accumulates large intermediate data in memory (e.g. a growing conversation history array), the snapshot grows accordingly. Trigger.dev's machine specs range from 0.25 GB (micro) to 16 GB (large-2x) — snapshot size is bounded by available memory, though the docs don't give an explicit ceiling.

Self-Hosted vs Cloud: Checkpointing Is Cloud-Only

This must be stated clearly. Per Trigger.dev's self-hosting docs, self-hosted deployments do not have checkpoint functionality. They also lack warm starts and auto-scaling.

In other words, if you self-host Trigger.dev, you get a background task platform with queues, retries, scheduling, and a dashboard — the same tier as BullMQ or Celery, but with native TypeScript support and a better UI. The checkpoint-resume capability that actually separates Trigger.dev from queue-based tools is cloud-only.

The self-hosted architecture uses two containers: Webapp (dashboard + Redis + Postgres) and Worker (supervisor + runner). v4.5.0 is the last version supporting v3 tasks; 4.5.1+ only runs v4 tasks.

Cost Structure

Trigger.dev's pricing (checked 2026-08) has four tiers:

PlanMonthlyIncluded creditsConcurrency limitSchedulesLog retention
Free$0$520101 day
Hobby$10$10501007 days
Pro$50$50200+1,000+30 days
EnterpriseCustomCustomCustomCustomCustom

Compute is billed per second: the smallest spec, micro (0.25 vCPU / 0.25 GB), costs $0.0000169/sec; the largest, large-2x (8 vCPU / 16 GB), costs $0.0006800/sec. Each run invocation has an additional $0.000025 charge ($0.25 per 10,000 runs).

Compared to Temporal Cloud: Temporal starts at $100/month (Essentials), billing by Actions (activity starts, timers, signals — server-side operations), and replays are free. Trigger.dev starts at $0, billing by CPU seconds, and wait time is free. Both make "you don't pay when your code isn't running" their selling point, just cutting at different places.

An important cost distinction: Temporal's replays are free because they only reconstruct state on the worker side, producing no server-side operations. Trigger.dev's waits are free because the process has been snapshotted and the CPU released. But Trigger.dev's snapshot and restore operations themselves require I/O (compression, storage, transfer, decompression), and that cost is included in compute time.

When to Choose Trigger.dev vs Temporal

ConsiderationChoose Trigger.devChoose Temporal
Workflow has many LLM callsNo activity wrapping needed, just write themEvery call must be wrapped in an activity
Need step-by-step replayable event historyNo (has run logs, but not replayable)Full Event History available
Team languagePrimarily TypeScript (Python via extension).NET / Go / Java / PHP / Python / Ruby / TypeScript — seven SDKs
Self-hosting requirementSelf-hosted has no checkpoint — becomes a regular queueSelf-hosted has full functionality (MIT, single binary to run)
Workflow needs to wait for monthsSnapshot on disk, waiting costs no CPUEvent History has a 51,200 event ceiling; long workflows need Continue-As-New
Need to audit every step's input/outputDashboard has logs, but not structured per-step historyEvent History is a complete audit trail
BudgetFree plan includes checkpointingCloud starts at $100/month

One final heuristic: is your code inherently non-deterministic? If your agent loop has the model deciding the next step each turn with a variable number of steps, Trigger.dev lets you skip redesigning your entire control flow for determinism constraints. If your workflow has fixed steps and needs strict traceability between them, Temporal's event history is something Trigger.dev can't offer.

The boundary with BullMQ and Celery is simpler: if the flow is a single step (receive message → do work → write back), a queue is enough. When you start having "step three failed but step two already sent" problems, that's when you need durable execution.

References