Repository navigation
temporal-claude-agent: durable Claude Agent SDK agents on Temporal #135
Description
Project link
https://github.com/osamastro7-droid/temporal-claude-agent
Language
Python
Short description (max 256 chars)
A Python package that runs Claude Agent SDK agents on Temporal. Every tool call becomes its own Activity: a crash resumes from the last finished step, a refund can't happen twice, and risky tools wait for a human approval (a validated Update).
Long Description
temporal-claude-agent runs agents built with Anthropic's Claude Agent SDK inside a Temporal Workflow, so they survive crashes and never repeat a finished tool call.
The problem. The Claude Agent SDK runs its own agent loop inside the Claude Code engine. If the machine dies mid-task, the agent loses its place, and a retry can repeat risky actions, like paying a refund twice.
How it works
- Durable tools are declared to Claude. A
PreToolUsehook answers "defer", so Claude stops at every tool call. The engine never runs the tool itself. - The Workflow runs each tool as its own Temporal Activity with a fixed ID (
tool-<tool_use_id>), after a human approval if the tool needs one. - The next Claude step resumes the session from a session store and receives the saved result as a normal
tool_result.
What you get
- Finished steps never run again, on any worker. A crash resumes from the last finished step.
- Every tool call gets a stable receipt number (its Activity ID) to pass on as an idempotency key. In the demo, a crash after the money moved gives 2 attempts and 1 refund.
- Risky tools wait for a human, for minutes or days, through a Temporal Update with a validator and an allowed-approvers list. Refused approvals never reach the history.
- Progress and cost through Queries, and clear Activity names in the Temporal UI.
Temporal features used: Activities with fixed IDs and retries, heartbeats on Claude steps, Updates with validators, Queries, wait_condition, and a worker plugin (ClaudeAgentPlugin).
Tested
- 12 automated tests, including worker crashes (SIGKILL) and the real Claude Code engine running on Temporal with a stand-in model.
- A real-Claude matrix (8 scenarios, 2 runs each) passed on Haiku 4.5, Sonnet 5, Opus 5.5 and Fable 5.1. The same scenarios also run in Amazon Bedrock mode.
- GitHub Actions runs everything on every push, and daily against the newest SDK and Claude Code release.
- Six engine behaviours I ran into, and the workaround for each, are written up in spike/FINDINGS.md.
See it interactively. A playable, pencil-drawn walkthrough of one refund: type your name, buy a teapot, ask for a refund, approve it as the manager, and pull the plug at almost any moment to watch the agent recover:
https://osamastro7-droid.github.io/temporal-claude-agent/map/
Status. v0.1, a tested prototype and a community project, not an official Temporal or Anthropic integration. It relies on how today's Claude Code engine handles "defer". Each Claude step starts a new engine process (about 1 to 1.5 s of overhead). Only tools declared as durable are durable. The included file session store is for development. The README lists every limitation.
Quickstart (Python 3.10+ and the Temporal CLI). The demo uses a scripted stand-in Claude by default, so no API key is needed:
git clone https://github.com/osamastro7-droid/temporal-claude-agent && cd temporal-claude-agent
pip install -e ".[dev]"
temporal server start-dev # window 1
cd examples && python -m refund_agent.worker # window 2
cd examples && python -m refund_agent.run_demo # window 3Author(s)
Osamah Al-Harazi — GitHub — headshot/avatar


Activity
github-actions commentedon Sep 24, 2026
ZiggyBot — automated pre-evaluation
Overall: Looks good ✅
Notes:
tool-<tool_use_id>), and the Activity ID is passed to external systems as an idempotency key. The refund Activity demonstrates this and enforces the amount cap in code rather than trusting model output.Suggested questions for submitter:
append/load) have a reference non-file implementation, or is that left entirely to the user?Notable Temporal pattern:
Automated AI pre-screen by ZiggyBot. Final decisions are made by the community team.
Reviewer: Check the box below to generate a Contentful-ready long description for this submission.
osamastro7-droid commentedon Sep 24, 2026
Thanks for the review! Here are the answers, with the evidence.
1. If a future Claude Code engine changes or removes "defer"
Pausing uses a documented feature. Anthropic's hooks docs describe the PreToolUse "defer" decision: the tool doesn't execute, the run ends with stop_reason "tool_deferred", and the SDK result carries deferred_tool_use. The docs describe one pause and resume; chaining several pauses in one session is not documented, and that is where the quirks in spike/FINDINGS.md come from. The docs also say "defer" is ignored when Claude makes several tool calls in one turn, which is why the package defers one call and asks Claude to repeat the others.
If an engine ever stops honoring "defer", the agent now fails closed:
Before this change, a simulated engine that ignores "defer" made the workflow complete "successfully" while the agent told the customer the order did not exist. Now the workflow stops. Tests: tests/test_fail_closed.py (the real Claude Code engine with a stand-in model).
CI also runs every day against the newest claude-agent-sdk and Claude Code. Today that is SDK 0.2.159 with Claude Code 2.1.281: 17/17 tests pass, and 8/8 scenarios in both Anthropic API mode and Amazon Bedrock mode (stand-in model).
2. Continue-As-New
Planned, not implemented yet. Measured with the real agent loop on Temporal Server 1.32 defaults:
The plan: at a step boundary with no approval pending, when workflow.info().is_continue_as_new_suggested() is true, continue as new with the session id, the tool result not yet delivered and the counters, then resume the same session. Until then, large tool results should be passed by reference (Temporal rejects any payload over 2 MiB by default).
3. Session store
The runner accepts any Claude Agent SDK SessionStore (only append and load are required), and Anthropic's SDK repo includes reference S3, Redis and Postgres adapters (examples/session_stores). I ran the full refund flow on the real engine with the Redis reference adapter (with fakeredis, not a live server), every step on a fresh machine with an empty Claude folder: it completed, and the engine ran no tool itself. FileSessionStore now passes the six required contracts of the SDK's run_session_store_conformance suite in CI (tests/test_session_store.py). A packaged production store is still on the roadmap; until then the README points to those adapters.
The numbers are also in the README limitations. Happy to answer anything else.