Claude Agent SDK (subscription sign-in)¶
ClaudeAgentProvider and the claude extra shipped in 0.4.0 (2026-09-23).
See the [0.4.0] section of
CHANGELOG.md.
ClaudeAgentProvider runs ExecutionKit's provider-agnostic patterns against
Claude using a Claude subscription sign-in instead of an API key. It is the
only transport in the library that does not need ANTHROPIC_API_KEY or an
OpenAI-compatible endpoint.
See ADR-016 for why an SDK dependency is admitted here when ADR-004 keeps the base install dependency-free.
Install¶
The extra also needs the Claude Code CLI on PATH, signed in once:
That prerequisite is a machine-level one that pip cannot express. If the CLI is
missing, the provider raises PermanentError with the install instruction
rather than a transport error.
Credentials are resolved entirely by the CLI. ExecutionKit never reads, stores, or forwards them.
If your process already has an API key¶
The SDK spawns the CLI with {**os.environ, **options.env}, so a process that
has ANTHROPIC_API_KEY set — which any process using AnthropicBatchClient
does — would hand the child that key, and the CLI would authenticate with it.
That is a silent credential-class switch: the call bills the API account
instead of the subscription, and fails outright when the key is invalid or
unfunded.
ClaudeAgentProvider therefore blanks ANTHROPIC_API_KEY and
ANTHROPIC_AUTH_TOKEN in the CLI subprocess. They are blanked rather than
removed because options.env merges over os.environ and can only override
a key, never unset it; the CLI treats an empty value as absent.
To deliberately bill an API key instead, say so explicitly — the constructor's
env is applied over the scrub:
Use¶
import asyncio
from executionkit import ClaudeAgentProvider, consensus
provider = ClaudeAgentProvider(model="claude-opus-5", effort="high")
result = asyncio.run(
consensus(provider, "Is 1024 a power of two? Answer yes or no.", num_samples=3)
)
print(result.value)
ClaudeAgentProvider satisfies LLMProvider and StreamingProvider, so it
drops into consensus(), refine_loop(), map_reduce(), structured(),
pipe(), Kit, and the streaming helpers the same way Provider does.
What this transport cannot do¶
The Agent SDK is an agent harness, not a raw completions endpoint. Three controls have no equivalent, and the provider surfaces that rather than pretending otherwise.
| ExecutionKit input | Behaviour | Use instead |
|---|---|---|
temperature |
Warns; no sampling control exists | effort |
max_tokens |
Warns; the harness bounds by turns and spend | max_budget_usd |
tools |
Raises PermanentError |
Provider for react_loop() |
Two consequences worth planning around:
consensus()is weaker evidence here. Its sample diversity normally comes from temperature. Over this transport the samples vary only by the model's own nondeterminism, so a unanimous vote says less than the same vote overProvider.react_loop()is unavailable. The Agent SDK executes its own tools rather than returning tool calls for the caller to run, which is the inverse of whatreact_loop()needs. The provider does not setsupports_tools, soreact_loop()rejects it up front instead of failing mid-loop.
Harness controls¶
Constructor arguments map onto the SDK's own knobs:
| Argument | Effect |
|---|---|
model |
Claude model id (default claude-opus-5) |
system_prompt |
Prepended ahead of any system turn in messages |
effort |
low | medium | high | xhigh | max |
thinking |
Extended-thinking config, passed through verbatim |
max_budget_usd |
Hard per-call spend ceiling |
max_turns |
Turn ceiling; 1 keeps the harness to a single completion |
cwd |
Working directory handed to the CLI |
env |
Extra CLI-subprocess environment, applied over the API-key scrub |
extra_options |
Escape hatch merged into ClaudeAgentOptions |
Every call is issued with an empty tool set and an empty allow-list, so a completion has no filesystem, shell, or network side effects.
Message translation¶
ExecutionKit speaks OpenAI-style messages; the harness takes a prompt plus a
system prompt. system turns are hoisted into system_prompt. A lone user turn
is passed through verbatim; a genuine multi-turn history is rendered as a
labelled transcript, because the SDK's streaming-input mode accepts only user
messages and a prior assistant turn has no lossless representation.
Usage and cost¶
LLMResponse already understands Anthropic's input_tokens / output_tokens
spelling, so token counts pass through unchanged. The harness-reported
total_cost_usd is added to usage and set on the OpenTelemetry span when the
[otel] extra is installed.
Rate limits¶
Subscription rate-limit windows are a failure mode API keys do not have. A
rejected window raises RateLimitError with retry_after derived from the
reported reset time, so the standard retry configuration handles it. Warning-level
windows (allowed_warning) pass through without interrupting the call.