Skip to content

Claude Agent SDK integration - #169

Merged
patbeqo merged 10 commits into
mainfrom
patbeqo/claude-agents-sdk
Oct 30, 2026
Merged

patbeqo merged 10 commits into
mainfrom
patbeqo/claude-agents-sdk

Conversation

@patbeqo

@patbeqo patbeqo commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Samples: temporalio/samples-python#403 — one runnable sample per feature, pinned to this branch.

In short: users keep plain Claude Agent SDK code (ClaudeSDKClient or query) in a Workflow
and pass it a TemporalTransport. Claude Code runs unmodified and owns the agent loop. Temporal
Activities carry its stdin and stdout, and approvals, hooks and custom tools run as Workflow code.

What was changed

Adds python/claude_agent_sdk: the temporalio-claude-agent-sdk package (Pre-release), with root
API temporalio.claude_agent_sdk. Requires temporalio>=1.35.0.

API What it is
TemporalTransport A Claude Agent SDK Transport. Pass it to your own ClaudeSDKClient or query in the Workflow. Its Activities are connect, write, end_input, read and close.
ClaudeAgentSDKPlugin Registers the transport's Activities on the Worker and runs them with a Connector (connector=, default SubprocessConnector).
SubprocessConnector The default connector: Claude Code in a subprocess of the Worker, run by the Claude Agent SDK's own SubprocessCLITransport.
Connector, ClaudeCodeProcess Implement these to run Claude Code somewhere else, such as a Docker Sandbox, VM or remote host. connect returns a process with write, end_input, read, close and terminate.
ConnectActivityInput, Output, TransportActivityOptions The input a connector gets, one item of Claude Code's output, and per-Activity options for the transport.

Design

Screenshot 2026-10-27 at 4 31 14 PM
  • Claude Code runs unmodified, with its whole agent loop. Activities carry its native
    stream-json protocol.
  • Callbacks run as Workflow code: approvals, hooks and @tool tools.
  • A Connector decides where Claude Code runs
    (Activity dependency injection),
    and its Activities stay on the Worker that holds it
    (Worker execution affinity).
  • Optional streaming and OpenTelemetry tracing.

Decision log

Approaches considered, and why this design won
Approach What it does Decision
Claude Code mod replay Replays recorded model steps and tool results into a new CLI Not chosen: re-implements Claude Code's agent loop; about 20 replay gaps, some unfixable
Workflow as the session store Keep the transcript in Workflow history as a durable log, and write it back into a new Claude Code's host on recovery Not chosen: the whole transcript goes through history, about 1 MB per turn with single entries up to 300 KB, so long sessions hit payload and history limits and need external storage anyway. Claude Code's own session files already hold it
External session store (#103) Resume a lost attempt from a store the plugin manages Not built in. A connector can load the session from a store before starting Claude Code; options.session_store doesn't work through a custom transport
Snapshots Restores the sandbox to the last turn's end Left to the user: the plugin doesn't require snapshots. A connector can restore one if its provider has them
Sandbox interface + named factories Plugin-owned abstraction over sandbox providers Not chosen: the plugin would require one abstraction and define recovery for every provider, through an interface the Claude Agent SDK doesn't have. A Connector leaves both to the user
Four custom transport Activities sh scripts that kept Claude Code's output in files, a watch Activity that Signalled each line, heartbeated byte offsets Replaced by the Connector and Worker affinity: Temporal's own patterns instead of plugin plumbing
Drain on shutdown On Worker shutdown, wait for Claude Code to exit and Signal the Workflow any output it hadn't read Not chosen: moves output outside Activities, and still loses output on a crash. Fail-fast shutdown keeps the Claude Agent SDK's behavior, and the Workflow redoes the turn
Workflow-picked session IDs The Workflow picks the session ID up front with workflow.uuid4() Replaced by taking it from Claude Code's init, as Claude Agent SDK apps do
CLAUDE_CODE_RESUME_INTERRUPTED_TURN Resume the session and let Claude Code continue the cut-off turn Replaced by resuming at the turn's prompt with resume_session_at: when the Worker dies, Claude Code records a pending approval as failed, so the turn no longer looks cut off

Checklist

  1. Related: Proposal: Claude Agent SDK plugin (python/claude_agent_sdk) #31 (proposal), Add the Claude Agent SDK plugin (python/claude_agent_sdk) #33, Add the Claude Agent SDK plugin #103. Samples: Add Claude Agent SDK samples samples-python#403

  2. How was this tested:
    64 unit and integration tests on a real dev server, plus a Worker-restart run over every sample
    in Add Claude Agent SDK samples samples-python#403: each resumed with the correct result.

  3. Any docs updates needed?
    The README covers setup, connectors (default and custom), recovery and Worker shutdown, long
    conversations, secrets, callbacks, progress, observability and hardening. A docs.temporal.io page
    is a follow-up.

@patbeqo patbeqo changed the title Add the Claude Agent SDK plugin (native transport) Claude Agent SDK integration Oct 23, 2026
Comment thread python/claude_agent_sdk/README.md Resolved
Comment thread python/claude_agent_sdk/README.md Resolved
Comment thread python/claude_agent_sdk/README.md Resolved
@patbeqo
patbeqo force-pushed the patbeqo/claude-agents-sdk branch 4 times, most recently from 641971c to 649d785 Compare October 29, 2026 00:23
@patbeqo
patbeqo force-pushed the patbeqo/claude-agents-sdk branch from 649d785 to 177fb62 Compare October 29, 2026 00:54
@patbeqo
patbeqo marked this pull request as ready for review October 29, 2026 00:56
@patbeqo
patbeqo requested a review from a team as a code owner October 29, 2026 00:56
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-29T01:02:57.322883Z 177fb62 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 177fb623f8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@osamastro7-droid

Copy link
Copy Markdown

@patbeqo thanks for the decision log, it made the design easy to test. I crash-tested how it behaves when a Worker dies. I haven't run this PR's own code, so I tested what it is built on: claude-agent-sdk 0.2.163 with its bundled Claude Code 2.1.286 (this PR's lock), the SDK's SubprocessCLITransport (what SubprocessConnector runs), a scripted model, and the resume from the README (options.resume with CLAUDE_CODE_RESUME_INTERRUPTED_TURN=1, one config folder for both Workers), on Linux. Script, no API key needed (python crash_repro.py worker-kill --steps 2 shows case 1): https://gist.github.com/osamastro7-droid/50d3c766262e71e1f42076e1b9fbb649

1. A Worker killed with SIGKILL leaves Claude Code running until its turn ends. After a SIGKILL of only the Worker process (for example kill -9, or a crash of its Python process), Claude Code finished the running command and called the model again (8 of 8 runs). When the model then asked for another pre-approved command, Claude Code ran it with no Worker connected (5 of 5). With can_use_tool, that next call failed with "Tool permission request failed: AbortError: Stream closed", and the turn still finished (2 of 2). The README says "Claude Code stops when the Worker process does", which holds for a shutdown, not for a kill. (Where the Worker is a container's PID 1, the container ends Claude Code too.)

2. Then the README's resume can wait forever, or run next to the orphan. The README notes that a resumed session has nothing to finish if the turn's result was lost. After a kill that is the usual case, since the orphan finishes the turn: the new Claude Code sent no messages and waited for input, so the loop as written would never get a ResultMessage (6 of 6, watched for 60 s). If the orphan was still working (a 40 s command), the new Claude Code resumed the same turn, the scripted model asked for the same command, and it ran twice at the same time (2 of 2).

3. A command cut off mid-run can run again, and the model isn't told. When the whole machine dies during a Bash call (simulated by SIGKILL of the Worker, Claude Code and the command at once; 6 of 6), or the Worker shuts down (the plugin's SIGTERM) and Claude Code exits before it writes the result to the session (6 of 8 shutdowns), the session has the tool_use but no result. On resume, Claude Code leaves that call out of its request and sends the prompt again with "Continue from where you left off.", so the model sees no sign that the command started. The scripted model asked for it again, and it ran a second time. In the other 2 shutdowns, Claude Code wrote "Exit code 137" to the session first, and the model saw that.

On Claude Code 2.1.273 (claude-agent-sdk 0.2.153) and 2.1.294 (claude-agent-sdk 0.2.165, the newest this PR allows), one run each with pre-approved commands, the Worker kill (1, and the endless wait in 2) and the machine crash (3) gave the same results.

What I can help with, as small follow-ups:

  • End Claude Code with its Worker in SubprocessConnector: on Linux a launcher that sets PR_SET_PDEATHSIG and then execs Claude Code, on macOS a small supervisor, on Windows a job object. I have this with tests in my Add the Claude Agent SDK plugin (python/claude_agent_sdk) #33 (d8492cd).
  • Resume only what is unfinished: as the README suggests for a lost result, read the session first (get_session_messages in an Activity), and if its last turn finished, take the result from there instead of waiting.
  • Tell the model what may have run: this PR already runs hooks as Workflow code, so a PreToolUse callback can record each call before it runs and a PostToolUse callback when it is done. After a resume, the Workflow knows which calls were cut off, and can tell the model they may have run or hold an exact repeat for a person. Add the Claude Agent SDK plugin (python/claude_agent_sdk) #33 tells the model this today.

Happy to open any of these after this lands, or to turn the cases into tests in your harness.

@patbeqo

patbeqo commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor Author

@osamastro7-droid Thanks, this was very useful.

  1. Fixed the README in 542f192: Claude Code stops when the Worker shuts down, and finishes its turn if the Worker is killed.
  2. Fixed in 0f8943a and c2ffc09. There's no transcript check now: the Workflow resumes the session at the turn's prompt with resume_session_at, and Claude Code redoes the turn. Resuming still needs the session on a shared volume or in a session store.
  3. Redoing the turn runs its tool calls again. The README now says to make tool calls and callbacks idempotent, as Activities already are.

Claude Code treats the end of its input as the end of the session and fails a
pending tool approval, so resuming the session after a Worker crash finished
the turn as if the approval were denied. Recover by resuming the session at the
prompt with resume_session_at and telling Claude Code to continue, which asks
about each tool call again however the old Claude Code stopped.

- Drop ClaudeCodeProcess.terminate. Worker shutdown now closes Claude Code.
- Pass control responses for requests the SDK never made through to the SDK,
  which ignores them, as replay-user-messages echoes the SDK's own answers.
- Show the recovery in the README and the restart test.

@brianstrauch brianstrauch left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! Can you add this plugin to the README? You could also consider bumping the version of claude-agent-sdk to latest.

@patbeqo
patbeqo merged commit b09ddf4 into main Oct 30, 2026
15 checks passed
@patbeqo
patbeqo deleted the patbeqo/claude-agents-sdk branch October 30, 2026 21:08
@brianstrauch brianstrauch mentioned this pull request Nov 2, 2026
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants