thunc()
Optional · new in 0.2.1

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.py

The 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")
# An agent's own tools (tools=) run at most once; name those that may run again after a crash:
# registry.agent_task("issues.triage", triage, version="1", workspace_id="repo", retry_safe_tools=["find_issue"])
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

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.

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/temporal

Integration 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.

Edit this page on GitHub