Trigger.dev launched in February 2023 as a “developer-first Zapier alternative” (745 HN points). By October, the team shipped V2 as a “Temporal alternative for TypeScript” (172 points). That pivot tells you everything about what developers actually need for long-running agent tasks: not event hooks, but durable execution primitives that don’t require learning a workflow DSL or running separate worker processes.
The shift exposes a real infrastructure gap. Temporal gives you workflow durability and replay semantics, but forces you into Go-based workers and a complex mental model. Zapier-style tools give you event triggers, but collapse under multi-step orchestration with retries, timeouts, and state persistence. Trigger.dev V2 targets the middle: TypeScript-native tasks that survive crashes, scale elastically, and let you write normal async functions instead of workflow definitions.
What Changed Between V1 and V2
V1 was event-driven. You connected webhooks, scheduled cron jobs, and chained integrations. It worked for simple automations but fell apart when developers tried to build multi-step processes that needed to wait hours, retry on failure, or coordinate parallel branches.
V2 introduced tasks as the core primitive. A task is a TypeScript function decorated with retry logic, timeout boundaries, and automatic state checkpointing. You write:
export const processDocument = task({
id: "process-document",
run: async ({ documentId }: { documentId: string }) => {
const doc = await fetchDocument(documentId);
const extracted = await extractText(doc);
const analyzed = await analyzeContent(extracted);
await saveResults(documentId, analyzed);
return { status: "complete", wordCount: extracted.length };
},
});
Behind the scenes, Trigger.dev persists state after each await. If the worker crashes mid-execution, the task resumes from the last checkpoint. You don’t write workflow code. You write async functions and get durability for free.
Durable Execution Plumbing
The architecture separates control plane from execution runtime:
- Control plane: Manages task scheduling, queues, and observability. Hosted or self-hosted.
- Execution runtime: Runs your TypeScript code in isolated containers. Spins up on demand, scales to zero.
- State persistence: After each async boundary, Trigger.dev snapshots function state to Postgres. On resume, it replays from the last checkpoint.
This differs from Temporal’s approach. Temporal uses event sourcing: every workflow decision gets logged, and replay reconstructs state by re-executing deterministic code. Trigger.dev uses explicit checkpointing: it serializes local variables and stack frames at await points. Simpler mental model, but less flexible for complex branching logic.
Retry and Timeout Boundaries
Tasks declare retry policies inline:
export const unstableApiCall = task({
id: "unstable-api",
retry: {
maxAttempts: 5,
factor: 2,
minTimeout: 1000,
maxTimeout: 60000,
},
run: async ({ url }) => {
const response = await fetch(url);
if (!response.ok) throw new Error("API failed");
return response.json();
},
});
Timeouts work at the task level, not the function level. If a task runs longer than its configured timeout, the runtime kills the container and marks the task as failed. No need to wrap every await in a timeout guard.
Concurrency and Queues
Trigger.dev exposes queue primitives for controlling parallelism:
- Concurrency limits: Cap how many instances of a task run simultaneously.
- Priority queues: High-priority tasks jump the line.
- Rate limiting: Throttle tasks to respect external API limits.
You configure these in the task definition, not in infrastructure YAML. The control plane enforces limits globally, so you don’t need to coordinate across workers.
Deployment Shape
Trigger.dev runs tasks in ephemeral containers. When a task triggers, the platform:
- Pulls your Docker image (or builds from source).
- Spins up a container with your code.
- Injects environment variables and secrets.
- Executes the task function.
- Persists state at each checkpoint.
- Tears down the container on completion or failure.
This model trades cold start latency for isolation and elastic scaling. Tasks that run once a day don’t consume resources between executions. Tasks that spike to 1,000 concurrent runs get fresh containers without pre-provisioning workers.
Comparison to Temporal Workers
| Dimension | Trigger.dev | Temporal |
|---|---|---|
| Runtime | Ephemeral containers | Long-lived worker processes |
| Language | TypeScript only | Polyglot (Go, Java, Python, etc.) |
| State model | Checkpoint snapshots | Event sourcing + replay |
| Deployment | Managed or self-hosted control plane | Self-hosted cluster (server + workers) |
| Cold start | 1-3 seconds | None (workers always running) |
| Scaling | Automatic (container per task) | Manual (worker pool sizing) |
Temporal gives you more control and better performance for high-throughput workflows. Trigger.dev gives you faster onboarding and simpler ops for bursty, long-running tasks.
Agent Orchestration Fit
The V2 architecture maps cleanly to multi-step agent workflows:
- Tool calls with retries: Each LLM tool invocation runs as a checkpointed step. If the tool API fails, Trigger.dev retries without re-running the LLM call.
- Human-in-the-loop: Pause a task, wait for user input (hours or days), then resume from the same state.
- Streaming and real-time updates: Trigger.dev Realtime connects frontend apps to running tasks via WebSockets. Agents can stream partial results as they generate.
Example agent task:
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 });
}
}
},
});
Each await generateText() and await executeTool() creates a checkpoint. If the agent crashes mid-loop, it resumes with the same message history and tool results. No manual state management.
Observability and Debugging
The control plane UI shows:
- Task runs: Start time, duration, status, retry count.
- Logs: Structured logs from each checkpoint.
- Traces: Nested spans for LLM calls, tool invocations, and external API requests.
- Replays: Re-run a failed task with the same inputs and environment.
Traces integrate with OpenTelemetry, so you can pipe data to Datadog, Honeycomb, or your own observability stack. The platform automatically instruments LLM calls if you use supported SDKs (OpenAI, Anthropic, Vercel AI SDK).
Failure Modes
Checkpoint Overhead
Every await triggers a state snapshot. If your task has tight loops with hundreds of async calls, checkpoint latency adds up. Trigger.dev serializes local variables to JSON, writes to Postgres, and waits for confirmation. For high-frequency tasks, this becomes a bottleneck.
Mitigation: Batch operations. Instead of await inside a loop, collect promises and await Promise.all().
Non-Deterministic Code
Trigger.dev doesn’t enforce determinism like Temporal. If your task uses Math.random(), Date.now(), or reads from external state, replays may produce different results. The platform won’t stop you, but debugging becomes harder.
Mitigation: Pass timestamps and random seeds as task inputs. Use idempotency keys for external API calls.
Container Cold Starts
Ephemeral containers mean every task invocation pays a cold start penalty (1-3 seconds). For latency-sensitive workflows, this adds noticeable delay.
Mitigation: Use Trigger.dev for long-running tasks (minutes to hours), not sub-second request handlers. For low-latency needs, run workers as long-lived processes (self-hosted mode).
State Size Limits
Checkpoints serialize the entire function scope. If you load a 50 MB dataset into memory, every checkpoint writes 50 MB to Postgres. The database fills up fast.
Mitigation: Stream large datasets instead of loading them into memory. Use external storage (S3, Redis) for intermediate results and pass references through checkpoints.
Security Boundaries
Tasks run in isolated containers with ephemeral filesystems. Secrets inject at runtime via environment variables, never persisted in logs or checkpoints. The control plane enforces RBAC: you can restrict which teams trigger which tasks.
For HIPAA compliance, Trigger.dev offers a managed tier with encrypted storage, audit logs, and BAA signing. Self-hosted deployments let you run the control plane in your VPC and keep all data on-premises.
When to Use Trigger.dev
Good fit:
- Multi-step agent workflows with LLM calls, tool invocations, and human approval gates.
- Background jobs that run for minutes to hours (data processing, report generation, batch imports).
- Bursty workloads that spike unpredictably (webhook handlers, scheduled tasks).
- Teams that want durable execution without learning Temporal’s workflow DSL.
Poor fit:
- High-throughput, low-latency workflows (thousands of tasks per second with sub-100ms latency).
- Complex branching logic that benefits from Temporal’s event sourcing and replay semantics.
- Polyglot environments where tasks need to run in Python, Go, or Java.
- Workflows that require strict determinism guarantees (financial transactions, compliance audits).
Technical Verdict
Use Trigger.dev if you need durable TypeScript tasks for agent orchestration, background jobs, or bursty workloads without managing worker infrastructure. The checkpoint model handles LLM tool calls and human-in-the-loop workflows cleanly, and automatic retries eliminate boilerplate error handling. Avoid it if you require sub-second latency (cold starts add 1-3 seconds), strict determinism (no replay enforcement), or polyglot workers (TypeScript only). Teams building high-throughput financial systems or complex workflow graphs with hundreds of branches should stick with Temporal’s event sourcing model. For everyone else shipping AI agents in TypeScript, Trigger.dev removes the infrastructure tax without forcing you into a workflow DSL.
The V1-to-V2 pivot reveals what developers actually need: not event hooks, but execution primitives that survive crashes and scale without manual tuning. That’s the infrastructure gap Trigger.dev fills.
Source Links
- Primary source: Trigger.dev
- Show HN discussion: Hacker News (172 points)
- V1 launch: Show HN: Trigger.dev V1 (745 points)