Trigger.dev shipped V1 in February 2023 as a “developer-first Zapier alternative” (745 HN points). By October, they pivoted to V2 as a “Temporal alternative for TypeScript” (172 points). That shift tells you everything about what breaks when you move from simple event automation to durable agent workflows.
The V1→V2 pivot exposes a fundamental infrastructure gap. Event-driven automation (webhook → function → done) needs different plumbing than long-running agent tasks (LLM call → tool execution → retry → resume → eventual completion). Trigger.dev’s repositioning reveals the execution guarantees, state management, and failure semantics that TypeScript developers need without adopting Temporal’s Go runtime.
What Changed Between V1 and V2
V1 focused on event triggers and integrations. You connected webhooks, scheduled jobs, and API calls. The execution model assumed short-lived functions that either succeeded or failed quickly.
V2 introduced durable execution primitives:
- Task persistence: Execution state survives process crashes and restarts
- Automatic retries: Configurable backoff and retry logic at the task level
- Long-running support: Tasks can run for hours or days without timeout limits
- Concurrency control: Queue-based execution with configurable parallelism
- Observability: Real-time tracing and monitoring for multi-step workflows
The architecture shift moved from “trigger events” to “orchestrate durable tasks.” That distinction matters when you’re building AI agents that call external APIs, wait for human approval, or process media files that take 20 minutes.
Execution Model: TypeScript Without Determinism Constraints
Temporal enforces deterministic replay. Your workflow code must produce identical results when re-executed from history. That means no random numbers, no direct API calls inside workflows, and careful separation between workflow logic and activity code.
Trigger.dev takes a different approach. Tasks are TypeScript functions that can:
- Call external APIs directly
- Use any npm package
- Generate random values
- Access environment variables
The tradeoff: Trigger.dev doesn’t replay execution history to recover state. Instead, it checkpoints task progress and resumes from the last successful step. This gives you normal TypeScript programming at the cost of Temporal’s strict consistency guarantees.
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 code runs as a single durable task. If the process crashes during executeTool, Trigger.dev resumes from the last checkpoint. You don’t need to split workflow logic from activity code or worry about deterministic replay.
State Persistence and Checkpointing
Trigger.dev checkpoints task state at specific boundaries:
- Before and after each
awaitin your task function - At explicit checkpoint calls
- When tasks spawn sub-tasks or parallel work
The checkpoint mechanism serializes:
- Function arguments and return values
- Local variables marked for persistence
- Task metadata (retry count, execution time, queue position)
State lives in Postgres (managed cloud) or your own database (self-hosted). The system doesn’t store full execution history like Temporal’s event sourcing model. Instead, it keeps the minimum state needed to resume from the last successful step.
This approach reduces storage costs but limits time-travel debugging. You can’t replay a task from arbitrary points in history. You can only resume from the last checkpoint.
Retry Semantics and Failure Modes
Trigger.dev exposes retry configuration at three levels:
- Task-level retries: Configure max attempts, backoff strategy, and retry conditions
- Step-level retries: Individual API calls or operations can have separate retry logic
- Queue-level retries: Dead-letter queues for tasks that exhaust retry attempts
Default retry behavior:
- Exponential backoff starting at 1 second
- Maximum 3 attempts for transient failures
- Immediate retry for network errors
- No retry for validation errors or 4xx responses
You override these defaults per task:
export const fragileTask = task({
id: "fragile-task",
retry: {
maxAttempts: 5,
factor: 2,
minTimeout: 1000,
maxTimeout: 60000,
randomize: true,
},
run: async (payload) => {
// Task logic
},
});
Failure modes to consider:
- Checkpoint failure: If state serialization fails, the task aborts without retry
- Timeout exhaustion: Tasks that exceed max duration move to dead-letter queue
- Resource limits: Memory or CPU constraints kill tasks without checkpoint recovery
- Poison messages: Tasks that fail validation on every retry can block queue progress
Concurrency and Queue Management
Trigger.dev routes tasks through named queues with configurable concurrency:
export const heavyTask = task({
id: "heavy-task",
queue: {
name: "media-processing",
concurrencyLimit: 5,
},
run: async (payload) => {
// Process video
},
});
Queue behavior:
- Tasks in the same queue respect the concurrency limit
- Different queues run independently
- Priority ordering within queues (FIFO by default)
- Rate limiting per queue (requests per second)
The queue system handles backpressure by:
- Pausing task acceptance when limits are reached
- Buffering tasks in Postgres until capacity opens
- Exposing queue depth metrics for autoscaling decisions
For agent workflows, this means you can:
- Limit concurrent LLM API calls to stay under rate limits
- Isolate expensive operations (video processing) from cheap ones (database writes)
- Guarantee sequential execution for stateful operations
Observability and Debugging
Trigger.dev provides real-time task monitoring through:
- Execution traces: Step-by-step logs with timing and payload data
- Dependency graphs: Visual representation of task relationships
- Metrics dashboard: Queue depth, retry rates, execution duration
- Webhook notifications: Task completion, failure, or timeout events
The observability model differs from Temporal’s workflow history. You get:
- Live tail of task execution logs
- Checkpoint snapshots (not full replay capability)
- Aggregated metrics (not event-sourced audit trail)
For debugging failed tasks:
- Inspect the last checkpoint state
- Review error logs and stack traces
- Manually retry with modified input
- Deploy code fixes and resume from checkpoint
You cannot step backward through execution history or replay from arbitrary points. This limits post-mortem analysis but simplifies the runtime model.
Deployment Architecture
Trigger.dev runs as:
- Managed cloud: Hosted execution environment with Postgres state store
- Self-hosted: Docker containers with your own database
The managed cloud architecture consists of client TypeScript code that communicates with an API gateway. The gateway routes requests to task executors (which run in isolated containers and scale horizontally) and a Postgres state store. Task executors share nothing except the Postgres state and restart automatically on failure.
Self-hosted deployments require:
- Postgres 14+ for state persistence
- Redis for queue coordination (optional)
- Container orchestration (Kubernetes, ECS, or Docker Compose)
- Persistent volumes for checkpoint storage
Comparison: Trigger.dev vs. Temporal
| Dimension | Trigger.dev V2 | Temporal |
|---|---|---|
| Language | TypeScript only | Go, Java, Python, TypeScript |
| Execution model | Checkpoint resume | Deterministic replay |
| State storage | Postgres snapshots | Event-sourced history |
| Code constraints | Normal TypeScript | Workflow determinism required |
| Retry logic | Configurable per task | Built into workflow engine |
| Observability | Real-time logs + metrics | Full execution history |
| Deployment | Managed cloud or self-hosted | Self-hosted (complex) |
| Learning curve | Familiar async/await | Workflow/activity split |
| Time-travel debugging | Limited to checkpoints | Full replay from history |
The core tradeoff: Trigger.dev optimizes for developer experience by allowing normal TypeScript and checkpoint-based recovery. Temporal optimizes for correctness by enforcing determinism and maintaining full execution history. Choose based on whether you value ease of development or strict consistency guarantees.
When Checkpointing Breaks
Trigger.dev’s checkpoint model fails in specific scenarios:
Non-serializable state: If your task holds open connections, file handles, or closures, checkpointing will fail. You must close resources before each checkpoint boundary.
Large state objects: Checkpoints serialize all local variables. A task that accumulates 100MB of data in memory will write 100MB to Postgres on every checkpoint. This kills performance.
External side effects: If your task writes to a database, then crashes before the next checkpoint, the write persists but Trigger.dev resumes from before the write. You get duplicate operations.
Clock-dependent logic: Tasks that use Date.now() or setTimeout will see different values after resume. This breaks workflows that depend on wall-clock time.
Mitigation strategies:
- Use idempotency keys for external writes
- Store large data in object storage, checkpoint only references
- Close connections explicitly before await boundaries
- Use task-provided time functions instead of system clock
Security Boundaries
Trigger.dev isolates tasks at multiple levels:
- Process isolation: Each task runs in a separate container
- Network isolation: Tasks cannot access each other’s endpoints
- Secret management: Environment variables injected at runtime, not stored in checkpoints
- Execution limits: CPU, memory, and timeout constraints per task
The checkpoint database becomes a security boundary. If an attacker gains read access to Postgres, they can:
- Extract task payloads and intermediate state
- View retry counts and execution metadata
- Reconstruct workflow logic from checkpoint sequences
They cannot:
- Access environment secrets (not stored in checkpoints)
- Modify running tasks (executors validate checkpoints)
- Inject malicious code (tasks are deployed separately)
For HIPAA or SOC 2 compliance, you need:
- Encrypted Postgres storage
- Audit logging of checkpoint access
- Network policies restricting database connections
- Regular checkpoint pruning to limit data retention
Technical Verdict
Use Trigger.dev V2 when:
- You need durable execution for TypeScript agent workflows
- Your team wants to avoid Temporal’s determinism constraints
- You’re building on Next.js, Remix, or other TypeScript stacks
- Checkpoint-based recovery is sufficient for your consistency needs
- You prefer managed infrastructure over self-hosted complexity
Avoid Trigger.dev when:
- You need strict deterministic replay for audit or compliance
- Your workflows require multi-language support (Python agents, Go services)
- You must reconstruct execution history from arbitrary points in time
- Your tasks hold non-serializable state (open connections, file handles)
- You’re already invested in Temporal’s ecosystem and tooling
The V1→V2 pivot teaches a broader lesson: event automation and durable orchestration are different problems. Webhooks and cron jobs need triggers. AI agents and long-running workflows need checkpoints, retries, and state persistence. Trigger.dev chose to solve the second problem by giving TypeScript developers a simpler alternative to Temporal’s workflow engine.
Source Links
- Trigger.dev (Official product site)
- Hacker News: Show HN V2 (V2 pivot announcement discussion)
- Hacker News: Show HN V1 (Original V1 launch discussion)