gwenflow.durability runs agents and flows on Temporal. A durable run survives worker crashes, deployments and restarts: it resumes where it stopped instead of starting over, and a model call or a tool call that already completed is never made again. Agents and flows stay the same objects: attaching a TemporalDurability makes them durable when they run inside a Temporal workflow, and changes nothing outside one.
import gwenflow does not need Temporal; only import gwenflow.durability does.
How it works
Temporal records every side effect of a workflow in its history. When a worker crashes, another one replays the workflow code and reuses the recorded results instead of executing the side effects again. Workflow code must therefore be deterministic, and all I/O must run in activities. With aTemporalDurability attached:
Identifiers generated during a run are deterministic too, so a replay rebuilds exactly the same history.
A durable agent
- Module-level agent, per-run copy. Inside a workflow,
agent.arun()runs on a copy of the agent created for the current workflow run, with its own history, and its model and tools proxied to activities. Everyarun()of the run reuses that copy, so a multi-turn conversation keeps its history, and a replay rebuilds it identically. The same agent object serves every concurrent run. - Name. The agent needs a
namemade of letters, digits,_or-. It is part of the activity names: do not rename an agent with runs in flight. - Async only. Use
await agent.arun()in a workflow;agent.run()and streaming raise aRuntimeError. - Context window.
context_window="auto"queries the model server, which workflow code cannot do: give an int, or leave it unset to use the built-in table. - An agent without durability raises an explicit error when it runs inside a workflow.
Tool approvals with AgentWorkflow
Tools declared with requires_approval=True pause the run (see Tool approvals). AgentWorkflow is an optional base class that waits durably for the decision, for hours or days if needed, and exposes it to the outside world:
timeout expires before every pending call is decided, run_agent raises asyncio.TimeoutError.
Activity options
start_to_close_timeout, heartbeat_timeout, retry_policy…). model_activity_config and the tool_activity_config entries are merged over activity_config. Defaults: 60 s for tools, 5 min with a 30 s heartbeat for model requests, and Temporal’s default retry policy. Retries belong to Temporal: inside its activity, a tool runs a single attempt.
Long conversations: continue_as_new
A Temporal history is limited in size and number of events. For an agent that runs for a very long time, pass continue_as_new: before each model request, when Temporal suggests it, it is called with the workflow-side agent and should restart the workflow with what it needs to resume.
Durable flows
- Independent branches run in parallel, as outside Temporal.
- Each node is a
gwenflow__flow_nodeactivity. Its timeout and retries come from the node’ssettings(timeoutin seconds,retries), then from the flow’ssettings(node_timeout,node_retries), then default to 300 s and 2 retries. Long nodes heartbeat every 10 s, so a dead worker is detected within 30 s. - Node
credentialsare resolved on the worker when the node runs: secrets never enter the workflow history. - A node that exhausts its retries raises
FlowNodeErrorin the workflow. Catch it, or let it fail the run:worker_options()declaresFlowErroras a workflow failure, so the run fails instead of retrying the workflow task forever.
DurableFlow: a ready-made workflow
DurableFlow takes a flow definition as input (flow.to_dict(), or the YAML loaded as a dict), so a run keeps the version of the flow it started with, whatever happens to the file afterwards. It returns every node’s result, keyed by node name, and reports progress through a query:
InvalidFlow application error, a node out of retries with a FlowNodeError one.
Scheduling
A durable flow is an ordinary workflow, so a Temporal schedule runs it on a timer:The worker: worker_options()
workflows, activities, workflow_runner and workflow_failure_exception_types arguments of a Temporal Worker:
- the workflow classes passed in;
- the flow node activity, and the activities of every durable agent passed in
agentsor declared in a workflow’s__gwenflow_agents__; - a sandbox that lets Gwenflow and the provider SDKs through. Import them in workflow modules under
workflow.unsafe.imports_passed_through(); FlowErroras a workflow failure.
Large payloads: ClaimCheckCodec
Temporal limits a payload to 2 MB. With ClaimCheckCodec, any payload above a threshold (128 KB by default) is stored in a blob store and the history keeps only its content hash: a 50 MB CSV node or a long agent history no longer hits the limit.
S3BlobStore works with any S3-compatible bucket (extra keyword arguments go to boto3.client). InMemoryBlobStore is for tests and single-process demos. Any object with async put(key, data) and get(key) methods can serve as a store. Blobs must live as long as the histories that reference them can be replayed, and the client and the workers must use the same codec.
Not supported yet
A durable agent cannot have a team or sub-agents, MCP servers, skills or long-term memory:TemporalDurability raises GwenflowDurabilityError when such an agent is built. Inside a workflow, streaming and the synchronous API raise the same error.
Examples
examples/agent_temporal.py: a durable agent, the simplest setup.examples/agent_temporal_human_approval.py: a durable agent whose tool waits for a human approval.examples/flow_temporal.py: a YAML flow run durably, then scheduled every day.