test(sdk): the pin was a permission, so give the premises a proof

The guard checked whether the installed SDK satisfied the pin. Nobody had
ever checked whether anyone had READ it. Those are different questions, and
the gap between them was a whole version range: pinned >=0.2.111,<0.3,
premises source-verified through 0.2.110, installed 0.2.120. Every build in
between was admissible and unexamined — `uv sync --upgrade` would have kept
806 tests green on an SDK no one had opened. Written red first: a guard
handed 0.2.140 returned it without complaint.

_VERIFIED_THROUGH is the ratchet. It records the newest build actually read
at source, and a newer one fails naming the five premises to re-check. The
pin is untouched and was never the defect — measurement dissolved the
premise that it needed lifting. It was not too narrow but too wide, and a
wider permission is not repaired by widening it further.

The premises themselves were prose the failure message recited. Nothing
tested them, so one that stopped being true would have surfaced on the one
live paid run (S10, D6). They are now a table introspected against the
installed package, with the printed prose derived from that same table so a
checked attribute cannot go unreported or a reported one unchecked. The
premise introspection structurally cannot see — that query() yields an
AssistantMessage then a closing ResultMessage — is named apart, and is the
honest reason the human reading still has to happen.

Value-proved, not merely named: disabling the ratchet reds 1 test, stubbing
the inventory to "no gaps" reds 3, re-hardcoding the prose reds 1, and
lowering _VERIFIED_THROUGH below the installed build reds the real
installed-version test rather than only a monkeypatched one.

0.2.139 read at source (0.2.120 -> 0.2.139, latest on PyPI today; STATE said
0.2.134, measured 08-09 and stale). The public query.py is byte-identical,
every premise field keeps its type and default, and the parser changes are
additive. One needed a look: 0.2.139 added a skills path defaulting
setting_sources to ["user", "project"], which would have undone the S10
isolation fix — it fires only on None, so the explicit [] is out of reach.
Prose carrying stale version claims moved with the reading, never ahead of
it: each was re-verified at 0.2.139 before being restated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014dKDjVG7qrBh9NkAAxutqN
This commit is contained in:
Kjell Tore Guttormsen 2026-08-18 16:57:57 +02:00
commit 90a41774fc
8 changed files with 271 additions and 38 deletions

View file

@ -7,6 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Changed
- **The SDK pin now has a proof behind it, not only a permission.** `claude-agent-sdk`
moved 0.2.120 → 0.2.139, and the version guard gained `_VERIFIED_THROUGH`: the newest
build whose source was actually read. The pin (`>=0.2.111,<0.3`, unchanged) says what
`uv` may resolve; it never said anyone had looked. Those are different questions, and
conflating them left 0.2.1110.2.999 admissible while the premises were source-verified
only through 0.2.110 — `uv sync --upgrade` would have kept the suite green on an
unexamined build. A build newer than the last one read now fails RED, naming the
premises to re-check. A third question — "is something newer upstream?" — is
deliberately NOT asked: it needs the network, and this suite stays offline.
- **The SDK premises are checked, not merely printed.** They had existed only as a
sentence the failure message recited; nothing verified them, so a premise that stopped
being true would have surfaced on the one live paid run. They are now a table
introspected against the installed package, with the operator-facing prose derived from
that same table so the two cannot drift. The one premise introspection structurally
cannot see — that `query()` yields an `AssistantMessage` then a closing `ResultMessage`
— is named separately, and is the honest reason a human reading is still required.
- Re-verified at 0.2.139 source level: the public `query.py` is byte-identical to 0.2.120,
every premise field keeps its type and default, and the parser changes are additive
(a new `origin` passthrough, a new `ConversationResetMessage` this client ignores).
0.2.139 also added a skills path that can default `setting_sources` to
`["user", "project"]`; it fires only on `None`, so the explicit `[]` that carries the
S10 isolation fix is out of its reach.
## [0.1.0] - 2026-08-17
### Added

View file

@ -15,7 +15,9 @@ alene; det er datert i README og skal ikke påstås om arbeid etter den datoen.
`docs/plan/`. Løpende state: `STATE.md` (local-only).
## Stack
Python ≥3.10. **Claude Agent SDK** (`claude-agent-sdk` ≥0.2, verifisert 0.2.110 per
Python ≥3.10. **Claude Agent SDK** (`claude-agent-sdk` ≥0.2, kildeverifisert 0.2.139 per
2026-08-18 — pinnen `>=0.2.111,<0.3` er en TILLATELSE, `_VERIFIED_THROUGH` i
`tests/test_sdk_version_guard.py` er beviset og går rødt på en ulest nyere build. Per
2026-07-03: MIT-lisens, bundler Claude Code CLI — ingen separat installasjon; import og
konstruksjon fungerer uten API-nøkkel, nøkkel trengs kun ved faktisk `query()`).
Pakkehåndtering: `uv`. Pydantic for kontrakts-validering.

View file

@ -21,7 +21,9 @@ Four offline checks (for a given backend profile, ``anthropic`` today):
refusal naming ``uv sync``, never an ImportError), the run-path symbols exist,
and the bundled Claude Code CLI is present on disk located offline via the
SDK package's own files, mirroring the SDK's ``_find_cli`` order (bundled
first, then a ``claude`` on PATH). Verified against installed 0.2.120.
first, then a ``claude`` on PATH). Verified against installed 0.2.139 by
reading its ``_find_cli`` (bundled at line 250, PATH at 256) the order is
unchanged from 0.2.120.
* **termination** the §8 stop contract constructs with positive round/token
caps and the per-call USD cap is positive (the budget belt is SET).

View file

@ -9,13 +9,18 @@ turn, a first-class USD cap (``ClaudeAgentOptions.max_budget_usd``) ON TOP of
the §8 token/round meter that the loop already charges, and NO filesystem
settings (``setting_sources=[]``).
Verified against claude-agent-sdk 0.2.110 at source level and release notes
through 0.2.120 pinned ``>=0.2.111,<0.3`` with a version guard
(``tests/test_sdk_version_guard.py``) that forces re-verification of these
premises before any widening: ``query()`` yields ``AssistantMessage`` (text
blocks + real model id) and a closing ``ResultMessage`` (provider-reported
``usage`` + ``total_cost_usd``). A reply without usage is passed through as
``None`` so the meter fails CLOSED (§8) this client never invents a count.
Verified against claude-agent-sdk 0.2.139 AT SOURCE LEVEL (2026-08-18; the
public ``query.py`` is byte-identical to 0.2.120 and every field below is
present with an unchanged type and default). Pinned ``>=0.2.111,<0.3``, but the
pin is only a permission ``tests/test_sdk_version_guard.py`` carries the
proof: it checks each attribute below against the INSTALLED package and goes
red on any build newer than the last one actually read. The premises:
``query()`` yields ``AssistantMessage`` (text blocks + real model id) and a
closing ``ResultMessage`` (provider-reported ``usage`` + ``total_cost_usd``).
Unknown message types are ignored by the loop below, so an SDK that adds one
(0.2.139 added ``ConversationResetMessage``) does not disturb this client.
A reply without usage is passed through as ``None`` so the meter fails CLOSED
(§8) this client never invents a count.
"""
from __future__ import annotations
@ -47,14 +52,16 @@ _USAGE_TOKEN_FIELDS = (
def build_call_options(model_id: str, *, max_budget_usd: float) -> ClaudeAgentOptions:
"""One bounded, ISOLATED completion call (§8 + S10 post-mortem).
``setting_sources=[]`` is the SDK's documented isolation mode (verified
against 0.2.110): the spawned CLI loads NO filesystem settings no
``setting_sources=[]`` is the SDK's documented isolation mode (re-read at
0.2.139): the spawned CLI loads NO filesystem settings no
session hooks, no CLAUDE.md, no operator instructions. The default
(``None``) loads ALL sources: in the S10 live run that injected the
operator's config into every call (~10-15k uncached tokens each) and
mandated a confirmation preamble that made pure-JSON replies impossible.
``system_prompt=None`` serializes to an EMPTY system prompt, not the
Claude Code preset.
Claude Code preset. 0.2.139 added a skills path that can DEFAULT
``setting_sources`` to ``["user", "project"]`` it fires only on ``None``,
so the explicit ``[]`` here is not reachable by it (read at source).
"""
return ClaudeAgentOptions(
model=model_id,

View file

@ -124,7 +124,7 @@ class TestPlaceholderDetector:
class TestSdkAndBundledCli:
"""Nøkkelantakelse (verified against installed claude-agent-sdk 0.2.120): the
"""Nøkkelantakelse (verified against installed claude-agent-sdk 0.2.139): the
SDK imports and its bundled CLI is present on disk checked OFFLINE via the
package's own files, mirroring the SDK's ``_find_cli`` order. Never a net call."""

View file

@ -2,7 +2,7 @@
The S10 live run leaked the operator's Claude Code configuration into every
spawned CLI session: ``ClaudeAgentOptions.setting_sources`` defaults to
``None``, which loads ALL filesystem settings (verified against SDK 0.2.110)
``None``, which loads ALL filesystem settings (verified against SDK 0.2.139)
session-start hooks injected STATE.md into the model's context, every reply
opened with a mandated confirmation line (so a reply was NEVER pure JSON),
and each call paid ~10-15k uncached context tokens. ``[]`` is the SDK's
@ -49,11 +49,13 @@ class TestBuildCallOptions:
def test_the_system_prompt_is_not_the_claude_code_preset(self) -> None:
# Pins the OPTION value against the Claude Code preset. That None
# reaches the spawned CLI as --system-prompt "" was verified by
# READING subprocess_cli.py (0.2.1100.2.120) — this test does NOT
# READING subprocess_cli.py (0.2.1100.2.139, re-read at 0.2.139:
# `system_prompt is None` still serializes to `--system-prompt ""`)
# — this test does NOT
# bind that transport serialization; doing so would couple the suite
# to SDK-private API (the F11 fragility this repo retired).
#
# HONEST LIMIT (measured, 0.2.120): `system_prompt=None` is NOT
# HONEST LIMIT (measured, re-measured at 0.2.139): `system_prompt=None` is NOT
# distinguishable from leaving the field untouched — the SDK default
# is None too, so deleting `system_prompt=None` from build_call_options
# left the old `is None` assertion GREEN. It pinned the SDK's default,

View file

@ -1,17 +1,38 @@
"""SDK version guard (C2.5, closes C-N3) — LOAD-BEARING (§11).
The seam this file keeps alive: every SDK attribute premise in
``sdk_client.py`` was verified against a CONCRETE version range
(0.2.110 read at source level, release notes through 0.2.120; sdk-review
2026-07-16). The pin ``claude-agent-sdk>=0.2.111,<0.3`` freezes that range
this guard makes an upgrade outside it a RED test naming exactly which
premises must be re-verified, instead of a silent behaviour drift.
``sdk_client.py`` rests on a version whose source was actually READ.
Offline-safe: reads installed package metadata only no key, no network.
Three questions hide inside "is the SDK fine?", and conflating them is what
let a whole version range go unexamined (measured 2026-08-09: installed
0.2.120, pin ``>=0.2.111,<0.3``, premises source-verified only through
0.2.110 so 0.2.1110.2.999 satisfied the pin while nobody had read them):
(a) is the build inside the PIN? what ``uv`` is allowed to resolve
(b) is the build inside the VERIFIED? what a human has actually read
(c) is something newer upstream? REQUIRES NETWORK, never in pytest
A pin is a permission, not a proof, and it can reach further than the reading
that justified it. ``_VERIFIED_THROUGH`` is the ratchet for (b): a build newer
than the last one read at source goes RED naming the premises to re-verify,
even though the pin admits it. Question (c) is deliberately absent an
offline suite cannot answer it, and a test that reached the network to try
would trade a silent gap for a flaky one.
Two instruments, because they fail differently: ``TestTheSdkSurfaceInventory``
introspects the INSTALLED package, so a premise naming an attribute that no
longer exists goes red on ANY build but introspection only sees the names it
was told to look for, and cannot see a field whose MEANING changed underneath a
stable name. ``_VERIFIED_THROUGH`` covers exactly that blind spot by demanding
a human read. Neither subsumes the other.
Offline-safe: reads installed package metadata and already-imported classes
no key, no network.
"""
from __future__ import annotations
import dataclasses
import importlib.metadata
from pathlib import Path
@ -23,6 +44,14 @@ _PYPROJECT = Path(__file__).resolve().parents[1] / "pyproject.toml"
_VERIFIED_FLOOR = (0, 2, 111)
_VERIFIED_CEILING = (0, 3)
# The newest build whose SOURCE was read for the premises below — question (b).
# Raising this is a claim that someone opened the package and checked, so it
# moves ONLY together with that reading. 0.2.139 read 2026-08-18: the public
# ``query.py`` is byte-identical to 0.2.120, every premise field is present with
# an unchanged type and default, and the parser changes are purely additive
# (a new ``origin`` passthrough, a new ``ConversationResetMessage``).
_VERIFIED_THROUGH = (0, 2, 139)
def _pin_for(floor: tuple[int, ...], ceiling: tuple[int, ...]) -> str:
"""The pyproject requirement string the verified range implies.
@ -38,16 +67,39 @@ def _pin_for(floor: tuple[int, ...], ceiling: tuple[int, ...]) -> str:
_PIN = _pin_for(_VERIFIED_FLOOR, _VERIFIED_CEILING)
# The sdk_client.py attribute premises the verified range vouches for
# (sdk-review 2026-07-16, verified against package source through 0.2.120).
_SDK_PREMISES = (
"AssistantMessage.error/.model/.content",
"ResultMessage.usage/.total_cost_usd/.is_error/.subtype/.errors",
"ClaudeAgentOptions.max_budget_usd/.setting_sources/.system_prompt/.max_turns/.model/.tools",
"TextBlock.text",
"query() yields AssistantMessage then a closing ResultMessage",
# The sdk_client.py attribute premises, as a table the suite can CHECK rather
# than a sentence it can only print. Every entry is read by sdk_client.py; the
# inventory below asserts each one against the installed package, so a premise
# that quietly stopped being true fails HERE instead of on the one live run.
_SDK_SURFACE: tuple[tuple[str, tuple[str, ...]], ...] = (
("AssistantMessage", ("error", "model", "content")),
("ResultMessage", ("usage", "total_cost_usd", "is_error", "subtype", "errors")),
(
"ClaudeAgentOptions",
("max_budget_usd", "setting_sources", "system_prompt", "max_turns", "model", "tools"),
),
("TextBlock", ("text",)),
)
# The premise introspection is STRUCTURALLY unable to see: an ordering fact
# about a stream, not an attribute on a class. It is named separately rather
# than dropped, because the honest reason the version ratchet still exists is
# that this line can only be checked by a human reading the source.
_SEMANTIC_PREMISES = ("query() yields AssistantMessage then a closing ResultMessage",)
def _premise_text(surface: tuple[tuple[str, tuple[str, ...]], ...]) -> tuple[str, ...]:
"""The prose the failure message prints — DERIVED from the checked table.
Not a second hand-maintained copy: an attribute added to the table appears in
the operator-facing message for free, and one removed cannot linger there
claiming a premise nobody verifies any more (value-proof below).
"""
return tuple(f"{cls}." + "/.".join(attrs) for cls, attrs in surface) + _SEMANTIC_PREMISES
_SDK_PREMISES = _premise_text(_SDK_SURFACE)
def _parse(raw: str) -> tuple[int, ...]:
return tuple(int(part) for part in raw.split(".")[:3])
@ -83,6 +135,33 @@ def _pin_is_declared(text: str, pin: str) -> bool:
return f'"{pin}"' in body
def missing_sdk_attributes(
surface: tuple[tuple[str, tuple[str, ...]], ...] = _SDK_SURFACE,
) -> tuple[str, ...]:
"""Premises the INSTALLED build fails to satisfy — introspected, never assumed.
Fail-closed on three distinct absences, because each would otherwise read as
"no gaps found": the class is gone from the package, the class is no longer a
dataclass (so its fields are not what this check knows how to read), or the
attribute is missing. Returning () must mean "checked and clean", never
"could not look" that conflation is the whole defect class.
"""
import claude_agent_sdk
gaps: list[str] = []
for cls_name, attrs in surface:
cls = getattr(claude_agent_sdk, cls_name, None)
if cls is None:
gaps.append(f"{cls_name}: class absent from claude_agent_sdk")
continue
if not dataclasses.is_dataclass(cls):
gaps.append(f"{cls_name}: no longer a dataclass — fields unreadable")
continue
declared = {field.name for field in dataclasses.fields(cls)}
gaps.extend(f"{cls_name}.{attr}" for attr in attrs if attr not in declared)
return tuple(gaps)
def check_sdk_version() -> str:
"""Fail if the installed SDK is outside the verified range — naming the premises."""
raw = importlib.metadata.version("claude-agent-sdk")
@ -94,6 +173,19 @@ def check_sdk_version() -> str:
"BEFORE widening the pin (pyproject.toml + this guard together): "
+ "; ".join(_SDK_PREMISES)
)
if _parse(raw) > _VERIFIED_THROUGH:
# Question (b), the one the pin cannot answer: this build is PERMITTED
# and unread. Green here would mean `uv sync --upgrade` silently retires
# the verification that justified the pin in the first place.
through = ".".join(str(part) for part in _VERIFIED_THROUGH)
raise AssertionError(
f"claude-agent-sdk {raw} satisfies the pin "
f"{_PIN.removeprefix('claude-agent-sdk')} but is NEWER than the last build "
f"read at source ({through}). The pin permits it; nobody has verified it. "
"Read the new version's source, then raise _VERIFIED_THROUGH in the SAME "
"commit that installs it — these sdk_client.py premises are what to re-check: "
+ "; ".join(_SDK_PREMISES)
)
return raw
@ -122,11 +214,114 @@ class TestSdkVersionGuard:
with pytest.raises(AssertionError):
check_sdk_version()
def test_a_version_inside_the_pin_but_beyond_the_verified_trips_the_guard(
self, monkeypatch: pytest.MonkeyPatch
) -> None:
# THE HOLE THIS RATCHET CLOSES (measured 2026-08-09): 0.2.140 satisfies
# the pin, so `uv sync --upgrade` installs it without a word, and before
# _VERIFIED_THROUGH existed the whole suite stayed GREEN on a build whose
# premises nobody had read. A permission is not a proof.
monkeypatch.setattr(importlib.metadata, "version", lambda name: "0.2.140")
with pytest.raises(AssertionError) as err:
check_sdk_version()
message = str(err.value)
assert "sdk_client.py" in message
# The message must separate the two questions, or the operator re-reads
# the pin — the thing that was never wrong — instead of the source.
assert "satisfies the pin" in message
assert "NEWER than the last build read at source" in message
for premise in _SDK_PREMISES:
assert premise in message
def test_the_build_read_at_source_itself_passes(self, monkeypatch: pytest.MonkeyPatch) -> None:
# BOUNDARY CONTROL: a ratchet that rejected everything would pass the test
# above for the wrong reason. The verified build itself must be accepted.
through = ".".join(str(part) for part in _VERIFIED_THROUGH)
monkeypatch.setattr(importlib.metadata, "version", lambda name: through)
assert check_sdk_version() == through
def test_the_verified_reading_lies_inside_the_pin_it_justifies(self) -> None:
# A reading outside the installable range would vouch for a build uv can
# never resolve — the ratchet would be green and inert.
assert _VERIFIED_FLOOR <= _VERIFIED_THROUGH < _VERIFIED_CEILING
def test_the_pyproject_pin_matches_the_verified_range(self) -> None:
# The seam: what uv installs and what this guard vouches for are ONE range.
assert _pin_is_declared(_PYPROJECT.read_text(encoding="utf-8"), _PIN)
class TestTheSdkSurfaceInventory:
"""The premise table, checked against the INSTALLED package (§11).
Until now the premises existed only as a sentence printed on failure prose
the suite could recite but never test. Any of them could have stopped being
true and nothing would have gone red until the one live run (S10, D6) spent
real money to find out.
"""
def test_every_premise_holds_on_the_installed_build(self) -> None:
assert missing_sdk_attributes() == ()
def test_the_table_is_not_empty_of_the_things_it_claims_to_check(self) -> None:
# POSITIVE CONTROL, ahead of the negative: "no gaps" is also what an
# EMPTY table returns. Without this, the assertion above would hold just
# as well for a premise list somebody had quietly deleted.
assert len(_SDK_SURFACE) == 4
assert sum(len(attrs) for _, attrs in _SDK_SURFACE) == 15
def test_every_checked_attribute_is_named_in_the_operator_facing_message(self) -> None:
printed = "; ".join(_SDK_PREMISES)
for cls_name, attrs in _SDK_SURFACE:
for attr in attrs:
assert attr in printed, f"{cls_name}.{attr} is checked but never reported"
class TestTheInventoryGoesRedWhenTheSurfaceMoves:
"""LOAD-BEARING (§11): prove the inventory can SEE absence, on a mutated COPY.
A checker that reports no gaps is making a claim about the package; it is
indistinguishable from a checker that cannot look. Each proof below breaks the
surface deliberately and requires the specific gap to be named mirroring the
known-positive discipline the version ratchet above already follows.
"""
def test_red_when_a_premise_names_an_attribute_the_build_lacks(self) -> None:
gaps = missing_sdk_attributes((("ResultMessage", ("total_cost_usd", "no_such_field")),))
# Not merely "non-empty": the REAL attribute must survive as satisfied and
# only the fabricated one be reported, or a checker that flagged everything
# would pass this too.
assert gaps == ("ResultMessage.no_such_field",)
def test_red_when_the_class_disappears_from_the_package(self) -> None:
gaps = missing_sdk_attributes((("NoSuchMessage", ("text",)),))
assert gaps == ("NoSuchMessage: class absent from claude_agent_sdk",)
def test_red_when_the_checked_name_is_not_a_dataclass(self) -> None:
# `query` is a REAL export of the package — a genuine known-positive, not a
# fabricated stand-in. It is a function, so its fields are not readable the
# way this check reads fields, and the honest answer is a reported gap
# rather than a silent () from a `dataclasses.fields` TypeError.
import claude_agent_sdk
assert hasattr(claude_agent_sdk, "query"), "control is broken: the export is gone"
gaps = missing_sdk_attributes((("query", ("text",)),))
assert gaps == ("query: no longer a dataclass — fields unreadable",)
def test_red_when_the_prose_stops_covering_the_table(self) -> None:
# VALUE-proof on the derivation: an attribute added to the table appears in
# the operator-facing message WITHOUT a second edit. Were the prose a hand-
# maintained copy, this new attribute would be checked and never reported —
# the exact drift that made the old sentence outlive its verification.
widened = _premise_text((("ResultMessage", ("usage", "a_newly_relied_on_field")),))
assert "a_newly_relied_on_field" in "; ".join(widened)
assert "a_newly_relied_on_field" not in "; ".join(_SDK_PREMISES)
def test_the_semantic_premise_survives_derivation(self) -> None:
# The stream-ordering premise has no attribute to introspect, so nothing
# would go red if it silently dropped out of the derived prose.
assert _SEMANTIC_PREMISES[0] in "; ".join(_SDK_PREMISES)
class TestTheSeamGoesRedWhenDetached:
"""LOAD-BEARING (§11): the assertions above must FAIL on a detached pyproject.

14
uv.lock generated
View file

@ -177,7 +177,7 @@ wheels = [
[[package]]
name = "claude-agent-sdk"
version = "0.2.120"
version = "0.2.139"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
@ -185,13 +185,13 @@ dependencies = [
{ name = "sniffio" },
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/eb/7f/7b69aed292a4edecae132e4dbe6b6decb4e88ec142fc91d117b19058c9e0/claude_agent_sdk-0.2.120.tar.gz", hash = "sha256:e428552f79a76e0d85789369eeb58249b33f350200124e5fc86b24168bd00805", size = 268639, upload-time = "2026-07-15T23:18:50.997Z" }
sdist = { url = "https://files.pythonhosted.org/packages/11/b6/cfcdefed1f866a8ba372ef3884c8020dd54338d15d8b45d5a1ff7432cea1/claude_agent_sdk-0.2.139.tar.gz", hash = "sha256:4395ed541cdd4c13aeb1213b3b414b7e8a94cc060a773137e961882e81c174a7", size = 319519, upload-time = "2026-08-14T22:34:48.038Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/80/85/5e8958704db0f8195e63f8ec4a80c5fb14756edc785bb3535e0dc5d91104/claude_agent_sdk-0.2.120-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c5ead9fb4bdaf70069978703ec6d74b30bd269e9632a4aea4dc8c4e999ac3a1b", size = 71110966, upload-time = "2026-07-15T23:18:54.743Z" },
{ url = "https://files.pythonhosted.org/packages/c1/53/c6cdad82ac100c8a45887999614e9fe206b77b43790dacc6de42b156ad4c/claude_agent_sdk-0.2.120-py3-none-macosx_11_0_x86_64.whl", hash = "sha256:1248591c7bffeb6e10e8cd169e0766854951fba8816e3e7d81a003f8bfca6f08", size = 76067995, upload-time = "2026-07-15T23:18:58.398Z" },
{ url = "https://files.pythonhosted.org/packages/bf/f8/248e3f58d0f0aa7d76bd34b11b18135cc124f5b9a9b55219cc1ca03d662a/claude_agent_sdk-0.2.120-py3-none-manylinux_2_17_aarch64.whl", hash = "sha256:abc73ccdf3decca566cd18084e74bc2f2d10b8b77cc1fd5ed4299d5c15e5078b", size = 81027434, upload-time = "2026-07-15T23:19:03.318Z" },
{ url = "https://files.pythonhosted.org/packages/11/59/6adb0c53534646f1d5ddc41226ff37b2adc413a472e2f87a9011548a137f/claude_agent_sdk-0.2.120-py3-none-manylinux_2_17_x86_64.whl", hash = "sha256:888070c246c92e102c52001d26532cd3646a700656d7c368f2f91a1d3c16b534", size = 82084704, upload-time = "2026-07-15T23:19:08.729Z" },
{ url = "https://files.pythonhosted.org/packages/2a/06/036b8dce1e86ecd5e2e1ddc281736cdb33bad6d24e748b9553e235b028fe/claude_agent_sdk-0.2.120-py3-none-win_amd64.whl", hash = "sha256:bc1441c94f60c9e7b4b8c641742fedf68451f573062ef69dd43d86a61b1fb219", size = 81958518, upload-time = "2026-07-15T23:19:13.082Z" },
{ url = "https://files.pythonhosted.org/packages/ea/7f/f04c33553cbc69bb96d045dc38a6266726fad72130f22f405dfe9eb54bf1/claude_agent_sdk-0.2.139-py3-none-macosx_11_0_arm64.whl", hash = "sha256:cbc50cc475ec633cabfa36347646097e9b1466d53130e4a04a87308ff830c87b", size = 88043656, upload-time = "2026-08-14T22:34:53.027Z" },
{ url = "https://files.pythonhosted.org/packages/85/d0/a17f5318ca0220479f20fdf83fa54a838a0a13ee203495ff67c72c3f43a7/claude_agent_sdk-0.2.139-py3-none-macosx_11_0_x86_64.whl", hash = "sha256:1c08206b1603444582cd365effaf95d2a8248661f1492281fb2d529b0887c047", size = 93000433, upload-time = "2026-08-14T22:34:58.225Z" },
{ url = "https://files.pythonhosted.org/packages/c5/2e/5bcec31700d76ad2d5b9fc28521a75a66f464063dac11373cf8d61446a4f/claude_agent_sdk-0.2.139-py3-none-manylinux_2_17_aarch64.whl", hash = "sha256:e69ae1a0b2af684c64839cc16e10b70800d9d2f57622b8c0d1739dd878cd7346", size = 97396659, upload-time = "2026-08-14T22:35:03.734Z" },
{ url = "https://files.pythonhosted.org/packages/c8/7f/582b3c1936c9f4ebc1bdc55a3923f1b680ef3c01928ffff1ea38eb84f637/claude_agent_sdk-0.2.139-py3-none-manylinux_2_17_x86_64.whl", hash = "sha256:34b289b3436fe24013f7b9cfe9f0a4e0806917a9ef8bbe829cda9a7b12d41a77", size = 98391889, upload-time = "2026-08-14T22:35:09.683Z" },
{ url = "https://files.pythonhosted.org/packages/56/54/d94af31d19b4e8d63d1b15002fd333ea77a040b7ab7a388044e511c8f9f6/claude_agent_sdk-0.2.139-py3-none-win_amd64.whl", hash = "sha256:9b76f0ffe216d6ca290d5f4f295ecb030dc496f101986ac99480a89d4abc6426", size = 100746507, upload-time = "2026-08-14T22:35:15.144Z" },
]
[[package]]