Claude Agent SDK integration
Temporal's integration with the Claude Agent SDK gives you the same tools, agent loop, and context management that power Claude Code, programmable in a Temporal Workflow.
The Claude Agent SDK allows you to drive the Claude Code agent from your own application, in a process you operate. Temporal provides the durable execution of the hooks, tools, and prompts to steer the agent.
Code snippets on this page come from the Claude Agent SDK samples directory.
Get started
Prerequisites
- This guide assumes you are familiar with the Claude Agent SDK. If you are not, refer to the Claude Agent SDK documentation.
- If you are new to Temporal, read Understanding Temporal or take the Temporal 101 course.
- Set up your local development environment by following Set up your local development environment. Leave the Temporal development server running if you want to test your code locally.
- Use Python, the Temporal Python SDK, and the Claude Agent SDK versions that satisfy the integration's package requirements.
Install the Claude Agent SDK integration
Install the temporalio-claude-agent-sdk package.
uv add temporalio-claude-agent-sdk
Drive Claude Code from a Workflow
The Claude Agent SDK communicates with Claude Code through a
Transport. Its default transport starts a subprocess and
communicates with it over standard input and output (stdio). Those operations aren't safe to perform in a Workflow.
To drive Claude Code from a Workflow, use TemporalTransport. It performs the process communication through Activities,
so the Workflow can replay safely. Pass it as the transport argument to query() or the ClaudeSDKClient constructor,
using the same options for both the transport and the SDK call.
claude_agent_sdk_plugin/structured_output/workflow.py
transport = TemporalTransport(options)
async with ClaudeSDKClient(
options=options, transport=transport
) as client:
await client.query(query_prompt)
Don't pass secrets in ClaudeAgentOptions.env. Those values are recorded in Workflow Event History as Activity input.
Set secrets in the environment where Claude Code runs, or use a credential proxy to keep them out of the sandbox.
Register ClaudeAgentSDKPlugin on the Worker. The plugin registers the Activities that TemporalTransport uses.
claude_agent_sdk_plugin/tools/run_worker.py
worker = Worker(
client,
task_queue="claude-agent-sdk-tools",
workflows=[ToolsWorkflow],
activities=[fetch_weather],
plugins=[ClaudeAgentSDKPlugin()],
)
Import ClaudeAgentSDKPlugin, TemporalTransport, and the connector APIs from temporalio.claude_agent_sdk.
Provide custom tools
In addition to Claude Code's built-in tools, the Claude Agent SDK lets you define custom tools through an in-process MCP server. With the Temporal integration, custom tools you define in the Workflow execute in Workflow context and benefit from durable execution. Their functions must follow the Workflow determinism rules, so use an Activity or Child Workflow for non-deterministic work such as calling an external API.
In the tools sample, get_weather
calls the fetch_weather Activity, which returns a fixed response as a placeholder for an API call. Pass get_weather
and any other tools to create_sdk_mcp_server, then add the server to ClaudeAgentOptions.mcp_servers.
Claude Code needs permission to use the tools. In the sample, allowed_tools grants that permission using each tool's
fully qualified name.
Add guardrails to agent behavior
Intercept agent behavior with hooks
The Claude Agent SDK lets you intercept and customize agent behavior at key execution points with hooks. With the Temporal integration, hooks you define in the Workflow execute in Workflow context and benefit from durable execution. Their callbacks must follow the Workflow determinism rules, so use an Activity or Child Workflow for non-deterministic work such as writing to an external audit service. Temporal can retry Activities when those operations fail.
In the hooks sample, a PreToolUse
hook denies Bash commands containing rm and logs the command through an Activity. The sample registers the hook in
ClaudeAgentOptions.hooks with a HookMatcher that selects Bash tool calls.
Add human approval
The Claude Agent SDK lets you provide a can_use_tool
callback to decide whether Claude Code can use a tool. With the Temporal integration, the callback runs in the Workflow
and can wait durably for a person's approval through a Signal or Update. The callback must follow the
Workflow determinism rules, so use an Activity or Child Workflow for
non-deterministic work.
In the human-in-the-loop sample,
the can_use_tool callback waits for a person's decision and returns the corresponding permission result. The sample is
designed for one pending approval at a time. A Query shows the pending tool call, and an Update approves or denies it.
Stream output from a Workflow
The Claude Agent SDK can stream a reply as it's generated. The plugin reads each event from Claude Code in an Activity, and the Workflow publishes it to a topic of a Workflow Stream. Any client can subscribe to the topic by Workflow Id to show tokens as they arrive.
Create a topic when the Workflow starts. Set include_partial_messages=True in the options, as in the Claude Agent SDK,
and pass the topic to TemporalTransport as stream. The two go together. Setting one without the other raises
ValueError.
In the streaming sample,
a client subscribes to the topic and receives stream_event messages from Claude Code. For details on event delivery
and stream completion, see Workflow Streams.
Configure a custom connector
By default, Claude Code runs in a separate subprocess started by the Worker, with the Worker's user, environment, and files. The Claude
Agent SDK documents this as its
subprocess model. To run Claude Code somewhere
else, such as a container, a VM, or a remote host, provide a custom connector to the plugin. Your connector provides access to the Claude Code process, and TemporalTransport uses Activities to manage communication
between the Worker and the process.
A custom connector implements the Connector protocol. Its connect method starts Claude Code and returns a
ClaudeCodeProcess, which provides the methods for communicating with the process. The default
SubprocessConnector
implements this protocol for a local subprocess.
For example, the custom connector sample
runs Claude Code in a Docker Sandbox, providing an isolation boundary between
the Worker and Claude Code. Its connector sets up the sandbox and uses SubprocessConnector to run sbx as the local
process.
The Workflow continues to use TemporalTransport as before, while you pass your custom connector to
ClaudeAgentSDKPlugin on the Worker.
claude_agent_sdk_plugin/plugin_with_custom_transport/run_worker.py
plugin = ClaudeAgentSDKPlugin(
connector=SandboxConnector(uploads={PLUGIN_DIR: LOCAL_PLUGIN_DIR})
)
Copy files in. A sandbox starts with a clean filesystem, and paths in ClaudeAgentOptions, such as cwd and plugin directories, are
paths on Claude Code's host. Anything Claude Code needs must be copied there before it starts. The sample connector copies the configured directories from the Worker host to their paths in the sandbox.
Keep secrets out of the sandbox. Use the Docker Sandbox credential proxy to keep the real API key outside the sandbox. The sample connector registers the key with the proxy, which adds it to requests to Anthropic API hosts. Claude Code only sees a placeholder. See Manage credentials in the Docker Sandbox docs.
Manage the Worker connection to Claude Code
A connection to the Claude Code process can be held by only one Worker. Any eligible Worker can establish the
connection, but once established, that Worker handles all Activities that communicate with the process. If the connection
is lost, the Workflow can create a new ClaudeSDKClient and resume the Claude Code session. When a Worker fails, Temporal
replays the Workflow on another Worker, where it can establish a new connection.
To resume the session, Claude Code needs access to its session files. With the default connector, these files are stored
on the Worker host. Use shared storage or set activity_options={"connect": {"task_queue": ...}} on
TemporalTransport to route connections to a Task Queue polled by a Worker that can access the session files.
To resume the interrupted turn, configure these Claude Agent SDK options.
resumeselects the Claude Code session.resume_session_atresumes from the interrupted prompt.extra_args={"replay-user-messages": None}makes the prompt identifier available in the response stream.
The sample sets these options in ClaudeAgentOptions.
claude_agent_sdk_plugin/tools/workflow.py
extra_args={"replay-user-messages": None},
resume=session_id if prompt_id else None,
resume_session_at=prompt_id,
The following loop uses those options to resume the session after a lost connection.
claude_agent_sdk_plugin/tools/workflow.py
async def ask(self, prompt: str) -> str:
"""Answer the prompt, redoing the turn if the connection is lost."""
CONTINUE = "Continue from where you left off."
session_id = None # Claude Code's, once it started
prompt_id = None # The prompt's, once Claude Code saved it
while True:
try:
options = self.options(session_id, prompt_id)
transport = TemporalTransport(options)
async with ClaudeSDKClient(
options=options, transport=transport
) as client:
await client.query(CONTINUE if prompt_id else prompt)
async for message in client.receive_response():
if (
isinstance(message, SystemMessage)
and message.subtype == "init"
):
session_id = message.data["session_id"]
elif (
isinstance(message, UserMessage)
and message.uuid
and not prompt_id
):
prompt_id = message.uuid
elif isinstance(message, ResultMessage):
return message.result or "\n".join(message.errors or [])
raise RuntimeError("Claude Code ended the turn without a result")
except CLIConnectionError:
# Redo the turn from the prompt if Claude Code saved it, or start again.
pass
Claude Code repeats the interrupted turn, so tools and hooks may run again and previously approved tool calls may require approval again. Make your tools and hooks safe to run more than once.
The communication Activities use a 30-second schedule-to-start timeout by default. You can change it with
activity_options as shown in the configuration reference.
Reference and troubleshooting
Feature support
Other Claude Agent SDK features that need no changes for Temporal can be used as usual, subject to the limitations below.
| Feature | Support | Notes |
|---|---|---|
| Custom tools | Supported | In-process MCP tool functions run in the Workflow and must be deterministic. |
| Hooks | Supported | Hook callbacks run in the Workflow and must be deterministic. |
| Tool approvals | Supported | The can_use_tool callback runs in the Workflow and must be deterministic. |
| Streaming | Supported | Enable partial messages and pass a Workflow Stream topic to TemporalTransport. Retried turns can produce repeated output. |
| Session recovery | Supported | A new connection needs access to the Claude Code session files. Resuming an interrupted turn can repeat tool calls and approvals. |
| Custom connectors | Supported | write, end_input, and close must be safe to retry and call concurrently. |
| Remote connections | Supported | Use a custom connector. For OpenTelemetry, pass TRACEPARENT and TRACESTATE in the Claude Code process environment when starting it. |
| OpenTelemetry | Supported | Register OpenTelemetryPlugin before ClaudeAgentSDKPlugin and enable Claude Code tracing where it runs. See the sample. |
| Default Claude Agent SDK transport | Not supported in Workflows | Use TemporalTransport for Workflow-safe process communication. |
settings as a file path | Not supported | Pass a JSON object string instead of a file path. |
Composing with other plugins
You can register ClaudeAgentSDKPlugin alongside other Temporal plugins. Plugins configure the Worker in registration
order, so check each plugin's documentation for ordering requirements.
Replay safety
TemporalTransport runs communication with Claude Code through Activities. Temporal records their results in the
Workflow's Event History and uses those results during replay. Custom tool functions, hooks, and can_use_tool callbacks
run in the Workflow and must be deterministic.
Each turn adds to the Workflow's Event History. For a long conversation, finish the turn, disconnect, and
Continue-As-New with the session identifier. In the new Workflow run, set resume to that session identifier. See the agent chat sample.
Configuration
| API | Option | Default | Behavior |
|---|---|---|---|
ClaudeAgentSDKPlugin | connector | SubprocessConnector() | Starts and connects to Claude Code. Provide a custom connector to run it in another environment. |
TemporalTransport | options | Required | Claude Agent SDK options. Pass the same options to the transport and the client. |
TemporalTransport | activity_options | None | Configures Activity options for connect, write, read, end_input, or close. Use schedule_to_start_timeout to detect a lost Worker sooner. |
TemporalTransport | stream | None | A Workflow Stream topic for partial messages. Set include_partial_messages=True in the Claude Agent SDK options when providing a topic. |
Failure behavior
The transport raises Claude Agent SDK errors in the Workflow. The plugin treats these errors as Workflow failures if left unhandled. Other Activity errors are retried according to the Activity retry policy.
| Error | Meaning |
|---|---|
CLIConnectionError | The connection to Claude Code is unavailable. After a Worker is lost, create a new client and resume the session as described in Manage the Worker connection to Claude Code. |
CLINotFoundError | The Claude Code executable could not be found where the connector runs it. |
ProcessError | Claude Code exited with an error. |
CLIJSONDecodeError | Output from Claude Code could not be decoded as JSON. |
ValueError | The transport configuration is invalid. Set both include_partial_messages and stream, or neither, and pass settings as a JSON object string instead of a file path. |