When Trigger.dev launched V1 in February 2023 as an “open source Zapier alternative,” it attracted 745 upvotes and 190 comments on Hacker News. Eight months later, the team shipped V2 with a different pitch: a Temporal alternative for TypeScript developers. That pivot exposes a critical infrastructure gap between event-driven webhook automation and the durable execution primitives that multi-step agent workflows actually need.
The shift wasn’t cosmetic. V1 focused on connecting APIs through event triggers. V2 rebuilt the core around long-running tasks that survive failures, resume after timeouts, and maintain state across retries. For teams building agents that orchestrate multiple tool calls, query external APIs, or wait for human approval, this distinction determines whether your agent can survive a 20-minute API call or fails after 15 minutes.
Why Durable Execution Matters for Agent Workflows
Traditional serverless functions time out. Lambda gives you 15 minutes. Cloud Run gives you 60. When an agent needs to scrape a dataset, wait for a third-party webhook, then summarize results, you hit three problems:
- Timeout boundaries: You can’t wait 20 minutes for a slow API inside a single function invocation.
- Retry semantics: If step 3 fails, you don’t want to re-execute steps 1 and 2.
- State persistence: Intermediate results need to survive across retries and restarts.
Temporal solves this with workflow engines written in Go, using event sourcing to reconstruct state. Trigger.dev V2 takes a different approach: TypeScript-native tasks with built-in retry logic, queue management, and observability hooks.
Architecture: How Trigger.dev Implements Durable Tasks
Trigger.dev runs tasks in isolated execution environments. Each task is a TypeScript function wrapped in a task() call. The platform handles:
- State checkpointing: After each successful step, the runtime persists state to Postgres.
- Automatic retries: Failed tasks re-execute from the last checkpoint, not from the beginning.
- Queue management: Tasks can specify concurrency limits, priority, and rate limits.
- Observability: Built-in tracing shows which step failed, how long each retry took, and what data passed between steps.
The execution model looks like this:
export const processDocument = task({
id: "process-document",
retry: {
maxAttempts: 3,
factor: 2,
minTimeout: 1000,
maxTimeout: 10000,
},
run: async ({ documentId }: { documentId: string }) => {
// Step 1: Fetch document (checkpointed)
const doc = await fetchDocument(documentId);
// Step 2: Extract text (checkpointed)
const text = await extractText(doc.url);
// Step 3: Analyze with LLM (checkpointed)
const analysis = await llm.analyze(text);
// Step 4: Store results (checkpointed)
await storeResults(documentId, analysis);
return { documentId, status: "complete" };
},
});
If extractText() fails on the second attempt, the third retry skips fetchDocument() and starts from the extraction step. The platform tracks which lines executed successfully using a combination of function instrumentation and explicit checkpoint markers.
State Persistence and Retry Boundaries
Trigger.dev uses Postgres as the state store. Each task execution gets a unique run ID. After each awaited promise resolves, the runtime writes:
- The step name (derived from the function call or an explicit label)
- The serialized output
- The timestamp
- The retry count
On retry, the runtime replays the task function but returns cached results for completed steps instead of re-executing them. This works because Trigger.dev instruments async calls and replaces them with state lookups during replay.
The boundary conditions matter:
- Non-deterministic code: If your task uses
Math.random()orDate.now(), replays will see different values. You need to checkpoint these explicitly. - External side effects: Database writes, API calls, and file uploads happen every time unless you guard them with idempotency keys.
- Memory limits: Large intermediate results (multi-GB datasets) can exceed serialization budgets. You need to store them externally and pass references.
| Aspect | Trigger.dev V2 | Temporal |
|---|---|---|
| State store | Postgres | Event log (Cassandra/MySQL) |
| Replay mechanism | Function instrumentation + caching | Event sourcing |
| Language support | TypeScript only | Go, Java, Python, TypeScript, PHP |
| Local dev | CLI with local runtime | Temporal CLI + Docker |
| Observability | Built-in dashboard | Temporal UI |
| Deployment | Managed cloud or self-hosted | Self-hosted or Temporal Cloud |
Developer Experience: TypeScript-First vs. Polyglot SDKs
Temporal’s architecture separates the workflow engine (written in Go) from language SDKs. Workflows communicate with the Go-based Temporal Server over gRPC. You write workflows in TypeScript, but the execution happens in a Go worker process. This creates friction:
- Type safety: Workflow definitions and activity signatures need manual synchronization.
- Debugging: Stack traces span multiple processes and languages.
- Local dev: You run Temporal Server in Docker, then connect your TypeScript worker.
Trigger.dev collapses this. The runtime is TypeScript (Node.js or Bun). Tasks run in the same process as your application code. Local development uses a CLI that spins up a local executor:
npx trigger.dev@latest dev
This watches your task files, hot-reloads on changes, and streams logs to your terminal. No Docker, no separate worker process, no gRPC configuration.
The trade-off: you lose polyglot flexibility. If your stack includes Python data pipelines or Go microservices, Temporal’s multi-language support matters. If you’re building a TypeScript-only agent platform, Trigger.dev’s single-runtime model reduces operational complexity.
Concurrency and Queue Management
Trigger.dev exposes queue primitives directly in the task definition:
export const sendEmail = task({
id: "send-email",
queue: {
name: "email-queue",
concurrencyLimit: 10,
},
run: async ({ to, subject, body }) => {
await emailProvider.send({ to, subject, body });
},
});
The platform enforces the concurrency limit across all running instances. If 15 tasks arrive simultaneously, 10 execute immediately and 5 wait in the queue. This matters for:
- Rate-limited APIs: You can match the concurrency limit to the API’s rate limit.
- Resource contention: Database connection pools, memory budgets, and external service quotas all benefit from explicit concurrency control.
- Cost management: Limiting parallel executions caps your compute spend.
Temporal requires you to configure worker pools and task queues separately. Trigger.dev bakes it into the task definition, which simplifies configuration but reduces flexibility for complex routing scenarios.
Observability: What You See When Tasks Fail
The Trigger.dev dashboard shows:
- Run timeline: Each step’s start time, duration, and status.
- Retry history: How many attempts, which steps failed, and the error messages.
- Payload inspection: Input and output for each step, serialized as JSON.
- Logs: Structured logs from your task code, correlated with the execution timeline.
When an agent workflow fails on the 47th API call after 12 minutes of execution, you need to know:
- Which call failed (the step name)
- What data it received (the input payload)
- What error it threw (the exception trace)
- Whether previous steps succeeded (the checkpoint history)
Trigger.dev surfaces this without requiring you to instrument your code with custom logging or tracing libraries. The runtime captures it automatically because it controls the execution environment.
Failure Modes and Edge Cases
Durable execution introduces new failure modes (these are general challenges in checkpoint-based systems, not Trigger.dev-specific limitations):
- Checkpoint corruption: If the state store becomes inconsistent (partial writes, transaction rollbacks), replays can produce incorrect results.
- Version skew: If you deploy a new task version while old runs are in progress, the replay logic might execute against incompatible code.
- Serialization limits: Postgres has a 1 GB limit for TOAST values. Large intermediate results need external storage.
- Non-deterministic replays: If your task reads from a database or external API during replay, it might see different data than the original execution.
Trigger.dev mitigates these with:
- Versioning: Each task deployment gets a unique version ID. In-progress runs continue on the old version.
- Idempotency keys: The SDK provides helpers for marking steps as idempotent, preventing duplicate side effects.
- Payload size warnings: The dashboard alerts you when step outputs exceed recommended sizes.
But you still need to design for idempotency. If your task sends an email, charges a credit card, or writes to a ledger, you need application-level guards to prevent duplicate operations during retries.
When to Use Trigger.dev vs. Temporal
Use Trigger.dev if:
- Your stack is TypeScript-only (Node.js, Next.js, Remix, Bun).
- You want a managed platform with built-in observability and queue management.
- Your workflows are agent orchestration, background jobs, or scheduled tasks.
- You prefer a single-runtime model over polyglot SDKs.
Use Temporal if:
- You need multi-language support (Python data pipelines, Go services, Java batch jobs).
- You’re building mission-critical workflows that require event sourcing guarantees.
- You want full control over the deployment topology (self-hosted clusters, custom persistence layers).
- Your team already operates Temporal and understands its operational model.
Avoid both if:
- Your tasks complete in under 60 seconds and don’t need retries (use Lambda or Cloud Run).
- You’re building real-time streaming pipelines (use Kafka or Flink).
- You need sub-second latency (durable execution adds 10-100ms overhead per checkpoint).
Technical Verdict
Trigger.dev V2 solves the orchestration problem for TypeScript-native agent workflows. The developer experience is cleaner than Temporal’s multi-process architecture, and the managed platform removes operational complexity. But you pay for that simplicity with reduced flexibility: no polyglot support, no custom persistence backends, and less control over the execution topology.
For teams building AI agents in TypeScript, Trigger.dev hits the sweet spot between serverless functions (too short-lived) and Temporal (too complex for single-language stacks). The checkpoint-based retry model works well for multi-step tool calls, and the built-in observability makes debugging agent failures tractable.
The main risk is vendor lock-in. While Trigger.dev is open source, the managed platform is the primary deployment target. Self-hosting requires running Postgres, the executor runtime, and the dashboard separately. If you need air-gapped deployments or custom infrastructure integrations, Temporal’s architecture gives you more escape hatches.
For agent projects built entirely in TypeScript, the time you save on infrastructure setup and debugging outweighs the flexibility you lose.
Source Links
- Trigger.dev V2 announcement (Hacker News, 172 points, Oct 2023)
- Trigger.dev V1 launch (Hacker News, 745 points, Feb 2023)
- Trigger.dev GitHub repository
- Trigger.dev documentation