We shipped a Claude Agent SDK executor in Dapr Agents with durable sessions and human approvals. Meaning: your Claude-built agent survives a crash mid-task, resumes with its session intact, and waits for an approval for as long as the approval takes – through restarts and deploys.
Getting started is rather easy:
pip install dapr-agents
dapr init
Then paste this into your agent definition:
from dapr_agents import DurableAgent
from dapr_agents.executors import ClaudeAgentExecutor
agent = DurableAgent(
name="reviewer",
executor=ClaudeAgentExecutor(session_store="statestore"),
require_approval=["merge_pr", "deploy"],
)
agent.start()
Now break it on purpose, because that's the whole point:
Give it a multi-step task.
kill -9 the process somewhere in the middle.
Start it again. The session resumes where it stopped and the steps already completed come back from the journal, not from re-running Claude.
Trigger an approval-gated tool, close your laptop, approve tomorrow. The wait survives because it lives in the workflow, not in memory.
All behavior was checked against dapr-agents main; the release notes have the exact versions: https://github.com/dapr/dapr-agents/releases
Same machinery works for LangChain Deep Agents. The adapter landed earlier this year in our diagrid python package, and deep agents with long tasks are exactly where durable sessions pay off.
If you want the guided version, the Dapr Agents track at Dapr University is free: https://www.diagrid.io/university/dapr-agents
It's that easy to get started. Give it a spin and lmk what you think 👀
Top comments (3)
Step 4 is the one I care about. My coding agents run headless Claude Code, one session per ticket, and when one needs my decision it asks on the ticket and ends its turn; the process exits instead of waiting. My answer comes back as an event and the orchestrator runs claude -p --resume on the same session id. When the approval lands, does the executor resume the original Claude session, or rebuild one from the journal?
Good question!
It resumes the original Claude session, not a rebuilt one. When a tool needs approval, the call is deferred and the Claude process exits, much like your setup. The wait then lives in a Dapr workflow, together with the session id. When the approval arrives, the executor resumes that same session id through the Agent SDK without sending a new prompt. Claude re-issues the deferred tool call, and the PreToolUse hook applies the decision to that exact tool call (it denies it if the arguments changed). The transcript is copied into a Dapr state store, so the resume works on any host, not only where
~/.claudelives. The workflow journal is only used for steps that already finished: those return their recorded results instead of calling Claude again. So yourclaude -p --resumepattern is the same idea; this adds a durable wait and a transcript that isn't tied to one machine.The transcript in the state store is the part I'd look at first. I run agents on a VM and a home PC that pull from the same tracker, so where a session's history lives matters to me. When the hook denies a re-issued call because the arguments changed, does Claude see that denial as a tool result and re-plan?