Quick Start
1
Enable durable execution
Set
durable=True on ExecutionConfig, then capture agent.last_durable_run_id after the run and persist it alongside your business record.2
Resume after a crash
Set
resume_run_id to the saved id and call start() with the same prompt. Recorded tool results replay from the journal — side-effecting tools do not fire again.3
Enable in the gateway
Gateway agents journal their turns by default whenever the session store is durable (the shipped default) — no flag needed. Set the flag explicitly only when you want to pin the choice in See Gateway Durable Runs for the full operator surface, including the
gateway.yaml:durable_runs: false and reliability: "off" opt-outs.How It Works
The agent records one event per boundary. On resume the loop is re-driven from the top; journalled steps return their recorded payload instantly, and only un-journalled steps do real work.Run Outcomes
Each terminal outcome maps to an auto-finalize state and controls whetherresume_run_id clears itself.
A retryable failure leaves the run resumable. A non-retryable failure marks it
failed and non-resumable.
A per-tool NotSafelyResumable outcome does not by itself change the run status; the loop continues and the run finalizes normally on the next iteration.
Configuration Options
Three fields onExecutionConfig control durability. Agent.last_durable_run_id is the read-side companion.
Idempotency Contract for Tools
Any tool whose signature acceptsidempotency_key (or **kwargs) automatically receives a stable per-call key on every invocation. Non-declaring tools work unchanged.
"{run_id}:{seq}:{function_name}" — the same on resume, so a tool that dedups on it stays safe even before journal replay records its result.
restart_safe and idempotency_key are complementary: restart_safe=True says the tool may be replayed at all; the auto-injected idempotency_key gives it a stable per-call key to dedupe on when it is. See Restart-Safety Contract for Tools below.
Restart-Safety Contract for Tools
A tool can declare whether it is safe to re-run after a crash — on resume, an in-flight tool is only re-executed when this declaration says so, otherwise it surfaces a typed outcome for the operator to reconcile. Declare it on the decorator:True for read-only or idempotent tools, False for anything that sends, writes, charges, or deletes.
Pick the value by asking whether the tool has an external side effect that is not safe to repeat.
The class attribute form
BaseTool.restart_safe is the equivalent for subclass-style tools.
The same declaration also gates the ordinary tool-retry loops: a restart_safe=False tool is executed exactly once even when a transient error would normally be retried. See Tool Retry & Backoff → Retrying Non-Idempotent Tools.
When resume returns NotSafelyResumable
An effectful or undeclared tool that was in-flight at the crash is not re-run — the resumed turn records a typed outcome instead, so the operator can reconcile the external action.
restart_safe=True if it is genuinely idempotent.
Discovering Resumable Runs
When your process restarts and you don’t have therun_id handy, ask the journal directly for runs still running.
Advanced
Framework and tool authors integrating a custom executor can reach the durable context directly throughpraisonaiagents.agent.durable.
begin_durable_run, abegin_durable_run, end_durable_run, get_durable_run, and DurableRunContext for wiring the journal into a bespoke tool loop.
Common Patterns
Long-running scheduled job that survives a container restart:Best Practices
Persist last_durable_run_id with your business record
Persist last_durable_run_id with your business record
Capture
agent.last_durable_run_id right after start()/chat() and store it next to the order id or ticket id. That’s the value you set into resume_run_id later.Point journal_path at a persistent volume in containers
Point journal_path at a persistent volume in containers
The default
~/.praisonai/runs/journal.db is lost when a container is recreated. Pass an explicit path on a mounted volume so the journal survives restarts.Keep tools idempotent as belt-and-braces
Keep tools idempotent as belt-and-braces
Opt into the auto-injected
idempotency_key so a tool that crashes before recording its result still dedups on retry.Declare restart_safe on every tool used in a durable run
Declare restart_safe on every tool used in a durable run
True for read-only / idempotent tools; False for anything that sends, writes, charges, or deletes. Undeclared tools fall back to a name heuristic and fail closed on uncertainty — safe, but noisier than an explicit declaration.Handle NotSafelyResumable on resume
Handle NotSafelyResumable on resume
If a resumed turn returns
error_type: "NotSafelyResumable", the in-flight side effect was not re-driven. Reconcile the external action, then either mark the tool restart_safe=True (if truly idempotent) or continue the turn out of band.Distinguish retryable vs terminal failures
Distinguish retryable vs terminal failures
ToolExecutionError(is_retryable=True) leaves the run resumable; is_retryable=False marks it failed and non-resumable.Close streams cleanly
Close streams cleanly
Calling
stream.close() finalizes the run as cancelled, so aborting a stream never leaves a ghost running row.Related
Run-State Journal
The SQLite layer under durable runs.
Durable Approvals
Pause-for-approval that survives a restart.
Execution
Agent execution limits and configuration.
Gateway Session Persistence
Persist gateway sessions across restarts.
Enable durable runs for every agent in the WebSocket gateway.
Async Jobs
Persist async jobs and idempotency keys with SqliteJobStore.

