Durable agents with Temporal
Keep your task definitions, and run them on a separate runtime when execution needs to survive worker restarts. Each model turn and tool result is recorded in a Temporal workflow, so a restarted worker continues where it left off.
New in thunc 0.2.1. Durable agents are new; their API may change in a later release. Local thunc calls and agent.run() stay dependency-free and need no Temporal installation.
Start the service and worker
Install thunc with the Temporal extra and the official Temporal CLI. The service below is for local development and binds to loopback; production needs a properly operated Temporal service.
pip install "thunc[temporal,openai]"
mkdir -p .temporal-dev
temporal server start-dev --db-filename .temporal-dev/server.sqlite
# Another terminal, with provider credentials configured:
cd examples/temporal
python worker.py
# A third terminal in examples/temporal:
python client.pyThe example creates an exclusively worker-owned workspace. Set THUNC_WORKSPACE to select its path and THUNC_TEMPORAL_STATE for persistent journal and artifact storage outside that workspace. Keep the same volume and paths across worker restarts.
Register and submit
Register tasks on a worker, then submit them by stable identity from any process:
from thunc.temporal import Registry, Runtime, Worker
# Worker process; review_task is an existing @agent.task function.
registry = Registry(state_dir="/srv/thunc-state")
registry.agent_task("repo.review", review_task, version="1", workspace_id="repo")
worker = await Worker.connect("localhost:7233", task_queue="repo-v1", registry=registry)
# await worker.run() in the worker's async entry point
# Client process; a reconnectable handle survives this process exiting.
runtime = await Runtime.connect("localhost:7233", task_queue="repo-v1")
handle = await runtime.start(
"repo.review",
version="1",
workspace_id="repo",
inputs={},
returns=str,
request_id="review-123",
deadline_seconds=1800,
)
run = await handle.result()
print(run.value)Use registry.function(...) for typed @thunc.function definitions. runtime.get(handle.id, returns=str) reconnects; status() inspects and cancel() requests cooperative cancellation. Disconnecting a client does not cancel its run.
What recovers
- Recorded model turns and tool results replay without repeating completed provider calls. An unrecorded provider completion can still repeat and incur cost.
- Transient provider failures have a bounded three-attempt policy, separate from invalid-answer repair.
- File writes use intentions, before and after hashes, atomic replacement and completion receipts. Permission revocations narrow access on resume.
- Memory notes use stable operation identities; native reasoning, signatures and read hashes survive serialization.
- Runs against one workspace are serialized. A command with an uncertain outcome pauses the lane for operator reconciliation instead of running twice.
Resolve an uncertain effect
status = await handle.status()
await handle.resolve(
status["operation_id"],
"complete",
evidence="Verified the process exited and checked its effect",
output="Recovered output",
)Operators can complete, abort, or explicitly retry an uncertain command after checking the old process and its effects. Resolutions are deduplicated. Namespace authorization controls access; the evidence text is an audit record, not authentication. Termination or cancellation alone is not rollback.
Boundaries
Temporal history is not a workspace backup, and Temporal does not guarantee exactly-once external effects. Preserve the service database, worker journal, artifacts and workspace.
- The beta supports a persistent local filesystem and same-volume restart, not arbitrary multi-host failover.
- Agent permissions are not an OS sandbox. Local agent execution refuses durable-owned workspaces.
- Submissions and final results have a 256 KiB inline budget; immutable state snapshots have a 16 MiB ceiling. Represent larger final results with application-managed artifact references. Tombstones and artifacts are retained; there is no automatic garbage collection.
- Version task definitions and deployment queues, keep compatible workers until their runs drain, and replay saved histories before changing orchestration.
- A configured Temporal DataConverter or PayloadCodec protects service payloads as configured; local SQLite and artifacts need their own encrypted storage and filesystem permissions.
Compose and verify
examples/temporal/pipeline.py demonstrates classification, then agent analysis, then a typed summary, using native Temporal Workflows. thunc.temporal.adapters.execute_task submits and waits through stable identities; each agent still has separate model and tool Activities.
pip install -e '.[anthropic,openai,dev,temporal-test]'
pytest
THUNC_TEMPORAL_TESTS=1 pytest -c pytest-temporal.ini tests/temporalIntegration tests use the official local service and scripted providers, including real worker termination, replay, rollover and cancellation. They make no model calls. Temporal Python SDK 1.34.0 is the tested baseline.
The Temporal guide and runnable example in the repository covers service setup, typed functions, composition, retries, cancellation, permissions, storage, history replay and upgrades.