Temporal, a process that survives a server restart
A process of seven steps where each can fail is a nightmare in ordinary code. You must persist state after every step, handle retries, plan what happens when the server dies between steps four and five, and ensure a retry does not take payment twice.
Temporal inverts that problem. You write the process as an ordinary function with loops and conditions, and the platform records execution history and replays it after a failure. A function interrupted midway resumes at the same place with every variable at its value.
What durable execution means
Replay is the key. The platform records not variable state but an event history: what was called and what it returned. After a failure it runs the function from the start, and rather than executing steps again it substitutes results from history until it reaches the interruption point.
That construction carries one consequence you must understand, since every limitation follows from it. Workflow code must be deterministic: given the same events it must run the same steps in the same order. Otherwise replay diverges from history.
In practice that means a workflow may not read the current time, draw a random number, or call an external interface directly. Those things happen in activities, meaning functions called from the workflow whose results enter the history.
The workflow and activity split is therefore the fundamental concept. A workflow is logic: what follows what, under which condition, how many retries. An activity is an action with an outside effect: an interface call, a database write, a message sent.
Your first workflow
import { proxyActivities, sleep } from '@temporalio/workflow'
import type * as activities from './activities'
const { chargePayment, sendConfirmation, reserveStock } =
proxyActivities<typeof activities>({
startToCloseTimeout: '1 minute',
retry: { maximumAttempts: 5 }
})
export async function fulfilOrder(orderId: string) {
await reserveStock(orderId)
const payment = await chargePayment(orderId)
await sleep('2 hours')
await sendConfirmation(orderId, payment.id)
return { status: 'fulfilled' }
}The two hour sleep blocks no process. The platform records the resume moment, releases resources, and runs the function again two hours later, rebuilding state from history. The same construction handles a thirty day sleep.
Retry configuration sits on the activity declaration, so there is no loop with backoff to write. A failed activity is retried per the policy, and the workflow knows nothing about it.
That is the main saving. The code reads like a description of the process, and everything that usually takes three quarters of the implementation happens outside it.
Compensation instead of transactions
A process touching several systems has no distributed transaction. When step three fails, the effects of steps one and two must be undone, and that is work you must design.
export async function fulfilOrder(orderId: string) {
const undo: Array<() => Promise<void>> = []
try {
await reserveStock(orderId)
undo.push(() => releaseStock(orderId))
const payment = await chargePayment(orderId)
undo.push(() => refundPayment(payment.id))
await dispatchShipment(orderId)
} catch (error) {
for (const step of undo.reverse()) await step()
throw error
}
}That pattern is the only sensible way to handle processes spanning many systems. The platform does not do it for you, and it makes writing it legibly possible, since compensation code sits beside the main code rather than in a separate recovery mechanism.
Design steps so they can repeat without side effects. An activity called twice with the same identifier should perform the operation once, since retries are the norm here rather than the exception.
Signals and queries
A process lasting weeks must accept information from outside and answer questions about its state.
import { defineSignal, defineQuery, setHandler, condition } from '@temporalio/workflow'
const approve = defineSignal<[string]>('approve')
const status = defineQuery<string>('status')
export async function approvalProcess(requestId: string) {
let decision: string | null = null
setHandler(approve, (who) => { decision = who })
setHandler(status, () => decision ? 'approved' : 'pending')
const arrived = await condition(() => decision !== null, '7 days')
if (!arrived) return { status: 'expired' }
await executeRequest(requestId)
return { status: 'executed', approvedBy: decision }
}A signal brings information into a running process, and a query reads state without affecting it. That mechanism replaces polling a database in a loop and supports processes with a step waiting on a human decision.
The same pattern serves agents with a language model, where an irreversible action needs approval. The process pauses, a person approves, the process continues, all without you maintaining state in a database.
Workflow versioning
This topic looks theoretical on a first rollout and becomes the most important one by the third.
The problem follows directly from replay. A process started a week ago and resumed today replays against the code in the repository now. If somebody added a step in the middle meanwhile, replay meets events in history the new code does not expect.
import { patched } from '@temporalio/workflow'
export async function fulfilOrder(orderId: string) {
await reserveStock(orderId)
if (patched('address-verification')) {
await verifyAddress(orderId)
}
await chargePayment(orderId)
}The marker lets new processes run the extra step while old ones skip it, so both versions replay correctly. Once every old process has finished, the marker can go.
The alternative is versioning by name: a new workflow version takes a new name and the old one runs until started processes expire. That is simpler for large changes, at the cost of two code versions in the repository for a while.
The practical rule: on processes lasting hours, changes carry little risk; on processes lasting weeks, every change needs a decision about what is already running. Weigh that when designing, since a thirty day process means thirty days of backward compatibility.
Visibility and diagnosis
One of the less obvious advantages is that every process's state is visible without writing your own logging.
The interface shows the execution history: which step ran, what it returned, how many times it retried, and where the process stands now. On a report that an order is stuck, answering "where is it" takes seconds rather than a log search.
The second thing is manual intervention. A process can be paused, terminated, reset to a chosen event, or sent a signal. That is sometimes the only sensible way out when an external service returned something nobody anticipated.
The third is that the history is complete by definition. There is no case of somebody forgetting to log a step, since the record is produced automatically and it is the basis of operation rather than an addition to it. On money touching processes that property is sometimes an argument in itself, since it yields an audit trail with no separate work.
Mind retention, though. History occupies storage and is billed, so at volume you must decide how long finished processes are kept. On the managed version the retention period reaches ninety days, and keeping history longer means exporting it off the platform, where the export itself generates actions and lands on the bill.
Pricing and deployment
| Variant | Cost | What the price covers |
|---|---|---|
| Open source self hosted | 0 USD | Server, database, and maintenance on your side |
| Cloud Essentials | from 100 USD monthly | 1M actions, 1 GB active storage, 40 GB retained |
| Cloud Business | from 500 USD monthly | 2.5M actions, 2.5 GB and 100 GB, commitment discounts |
| Cloud Enterprise and Mission Critical | quoted individually, annual term | 10M actions each, 10 GB and 400 GB, stronger support guarantees |
Managed billing combines a plan fee with usage counted in actions. An action is a specific billable operation: starting a workflow, executing or retrying an activity, starting a timer, a signal, an update, and a query. That is not the same as every event in history, and the difference works in your favour: replaying a workflow after a failure generates no actions at all, and every local activity within a single workflow task counts as one. A workflow with ten activities and five retries still generates more than the step count suggests, since each retry is a separate action.
The plan fee is not an ordinary subscription added on top of usage either, and that is where budgets go wrong most easily. You pay the greater of two amounts: the plan minimum or a percentage of your consumption bill, five percent on Essentials and ten on Business. The included allowance of actions and storage comes inside that figure, and you pay separately only for the overage, at 50 dollars for the first additional million actions, with discounts falling to 25 dollars at large volumes.
The open source version is free and needs a server, a database, and maintenance. For one team that cost usually exceeds the subscription; in a larger organisation the proportion inverts. Recent changes also added running workers in a serverless environment, which changes the arithmetic under uneven load. Check the stage, though: the AWS Lambda variant entered public preview in August 2026, and the one for Google Cloud Run sits at an even earlier prerelease stage.
Check current tiers before budgeting, since the plan structure changes from time to time.
Temporal against the alternatives
| Option | Strength | Weakness | Pick it when |
|---|---|---|---|
| Temporal | Durable execution, week long processes, state visibility | Determinism requirement, rollout cost | Multi step processes touching money |
| Job queue | Simple, familiar, cheap | You write state and retries yourself | Single background tasks |
| n8n or Make | Visual, fast to deploy | Less control, weaker on complex logic | Automations connecting services |
| Your own state machine | Full control | You maintain what the platform gives ready | Simple two step process |
| Airflow | Scheduling, a dependency graph, mature integrations | Recurring pipelines rather than on demand processes | Data processing at set times |
Choosing between the first two rows depends on how many steps the process has and what happens when it fails midway. Sending a message in the background is a queue's job. A process covering reservation, payment, shipping, and notification, where each step can fail and partial execution means a money problem, is where the platform repays quickly.
The third row is a different category. Automation tools suit connecting ready services rather than processes with elaborate logic and an exactly once requirement.
It also pays to count the cost of entry honestly. The platform introduces concepts the team has to learn: the split between workflows and activities, determinism, versioning, worker processes. That is a week or two before the first process reaches production, and another month before the team stops tripping over determinism.
That cost repays at the third process rather than the first. If one workflow and nothing more is planned, a simpler solution usually suffices. If the backend will hold a dozen or so processes touching payments, orders, and external integrations, a shared platform saves writing the same mechanism a dozen times over.
The last row entered the table not because it competes but because people confuse the two. A data pipeline orchestrator schedules repeated processing at set times or after an event and shows what got through. Durable execution handles a process started once per order that can wait a week for a payment. The overlap is small, and getting the category wrong costs a rewrite, since bending one tool into the other's job is doable and never maintainable.
Use with agents
Agent tasks turned out to be one of the more natural uses of this platform, and it helps to understand why.
An agent making ten model and tool calls is a multi step process where each step can fail and repeating everything costs tokens. Resuming from step seven rather than step one saves in direct proportion to how far the process got.
The second thing is time. An agent task can run a quarter of an hour, and with tools waiting on a human answer, days. Holding that in a server process's memory is fragile; in this model it is natural.
@workflow.defn
class ResearchAgent:
@workflow.run
async def run(self, question: str) -> str:
plan = await workflow.execute_activity(
plan_steps, question, start_to_close_timeout=timedelta(minutes=2)
)
results = []
for step in plan.steps:
results.append(await workflow.execute_activity(
run_step, step, start_to_close_timeout=timedelta(minutes=5)
))
return await workflow.execute_activity(compose_answer, results)Model calls go into activities, since they are nondeterministic. Loops and conditions stay in the workflow, so the process logic reads clearly and resilience comes free.
Agent libraries such as PydanticAI and LangGraph ship integrations with this platform, so the wrapper need not be written yourself. Check that before building your own.
Common mistakes
The first is nondeterministic code in a workflow. Reading the current time, drawing a random number, or calling an interface directly from a workflow breaks replay, and the bug surfaces only after a failure, meaning at the worst moment.
The second is activities with no repeat protection. Retries are normal here, so an activity charging a payment without an idempotency key bills the customer twice.
The third is changing workflow code while processes run. A week long process replays against the new code, so reordering steps diverges from history. Workflow versioning exists for that and must be applied from the start rather than after the first incident.
The fourth is activities that are too fine grained. Each generates history events, so a workflow with a thousand tiny calls costs more and replays slower than one with ten meaningful ones.
The fifth is missing timeouts. An activity without one can hang indefinitely, blocking the process and reporting nothing.
The sixth is reaching for the platform on tasks that are an ordinary queue. The rollout and maintenance cost makes sense on complex processes and is disproportionate for a single background step.
FAQ
What exactly is Temporal for?
For running multi step processes so they survive failures: a server restart, a network error, an outside service being unavailable. An interrupted process resumes from where it stopped with its variable state, so there is no manual persistence after every step.
Is Temporal free?
The open source version is, and you run it yourself, paying with server, database, and maintenance costs. The managed version starts at one hundred dollars a month on the Essentials plan, with a million actions and a storage allowance included, and you top up only for the overage. A new account receives one thousand dollars in credits valid for ninety days.
Why must a workflow be deterministic?
Because replay after a failure means running the function again with results substituted from history. Code that would take different steps given the same events diverges from the record. Nondeterministic things, time and external calls included, go into activities.
Does it suit agents with a language model?
Yes, and that is a growing use. An agent task consists of many calls, each can fail, and repeating everything from scratch costs tokens. Resuming from the interruption point saves money, and the signal mechanism handles pausing for a human decision before an irreversible action.
Which languages are supported?
TypeScript, Go, Java, Python, PHP, .NET, Ruby, and Rust, the last of those in public preview, with the same conceptual model in each. Processes written in different languages can call each other, which helps with a back end made of several services.
Documentation sits on the project site, and billing details on the cloud pricing page.