Choosing an execution model¶
Once a kit ships a Temporal integration, the failure mode flips. A team reaches for a workflow to classify one support ticket, inherits a worker fleet, a namespace and replay constraints they never needed, and concludes the kit is heavy. It is not: they climbed two rungs they had not earned.
Durability is a ladder with a plain in-process default. This page says which rung a design belongs on, what earns the next one, and what is not a reason to climb at all.
The ladder¶
Rung 1 — in-process¶
The default. Runner drives the loop in the process that received the request, and a
crash loses the run. Nothing is installed beyond the base package.
Ship here unless the table below says otherwise. Most agents are a few model calls and a handful of reads, and a lost run is a retried request.
Rung 2 — checkpointed¶
CheckpointStore writes a frontier at each unambiguous boundary, and a later process
resumes from it. Still one process at a time, still no worker fleet, still no engine — a
store and a lease. See checkpointing.md.
Earned by a run that will outlive the process that started it: an hours-long human gate, a queue that will be drained tomorrow, an expensive prefix nobody wants to pay twice.
Rung 3 — durable workflow¶
AgentWorkflow drives the same loop with every model and tool call as an activity, and the
deployment binds those activities to Temporal. See durable-runs.md.
Earned by the combination the lower rungs cannot hold: side effects that are irreversible or financially meaningful, applied across more than one system, over a run long enough that a restart is likely rather than possible. That is where a replayable history and a saga (compensation.md) stop being paperwork.
The decision table¶
Five questions. Take the highest rung any row demands, not the average.
| Question | Answer | Rung |
|---|---|---|
| How long does the run take? | under five minutes | Rung 1 |
| minutes to hours | Rung 2 | |
| hours to days | Rung 3 | |
| Is there a human gate? | none | Rung 1 |
| one, waiting hours or days, no transactions | Rung 2 | |
| a gate on money or on an irreversible action | Rung 3 | |
| Are the side effects irreversible? | reads only | Rung 1 |
| writes to one system, replayable by hand | Rung 2 | |
| irreversible or financially meaningful, across systems | Rung 3 | |
| How wide does it fan out? | a handful of branches | Rung 1 |
| wide, but cheap and restartable | Rung 1, with concurrency control | |
| wide, and each branch applies something | Rung 3 | |
| What does a restart cost? | a retried request | Rung 1 |
| an expensive prefix paid twice | Rung 2 | |
| work applied twice in someone else's system | Rung 3 |
A cheap run that fans out very wide is a concurrency problem, not a durability one. Cap it
with fan_out and a shared AttemptBudget (activity-policies.md)
and stay on Rung 1.
Not a reason to reach for a workflow¶
- observability — spans, a ledger and cost attribution are on every rung. See ledger.md and cost-attribution.md.
- retries — an activity policy retries on every rung, and a retry budget caps the storm. See activity-policies.md.
- an approval gate — approval is a grant, not a scheduler. See tool-approval.md.
- an autonomy grant — a ceiling is enforced by the run, not by the engine underneath it. See autonomy.md.
The rule a review cites: durability makes an action resumable, never authorised. A workflow that moves money without an approval gate and a matching autonomy grant is rejected however durable it is, and a model may not initiate that action in the first place.
Moving up a rung¶
No agent, tool or guardrail code changes. The loop is the same loop.
- Rung 1 → 2: hand the runner a
CheckpointStoreand give the run a stablerun_id. - Rung 2 → 3: construct
AgentWorkflowinstead ofRunner, hand it aJournal, and bind the two activity payloads in your worker. Installtesserix-adk[temporal]there — and only there.
The kit imports temporalio nowhere at package import time, so Rung 1 and Rung 2 never
install it and CI runs the whole suite without a Temporal server. That is not a convention:
it is asserted in tests/test_extras.py, which fails if any non-optional module reaches an
extra.
A consumer who already runs Temporal can call the kit as a plain library inside their own activity. That is supported: the runtime is deterministic-friendly and performs no I/O of its own beyond the activities you hand it.
Review checklist¶
Copy this into the design review.
- [ ] The chosen rung is named, and the table row that demanded it is quoted.
- [ ] Nothing on the design climbs a rung for observability, retries, approval or autonomy.
- [ ] Every irreversible or financially meaningful action has an approval gate and an autonomy grant, independent of the rung.
- [ ] Every applied step on Rung 3 has a paired reversal, or is declared irreversible.
- [ ] The run has a stable
run_idand a budget ceiling. - [ ] Local development and CI run the design with no Temporal server available.
- [ ] Rung 3 only: the worker fleet, namespace and retention are owned by a named team.
This is guidance, not a gate. A team with a legitimate unusual case may ship against the table — record the reason in the product's own ADR, with an owner, so the next reviewer reads a decision rather than an accident.