[AAASM-4434] ⬆️ deps: Upgrade Python dependencies to latest stable, incl. LangGraph - #234
Conversation
… API No `langgraph` pin existed in pyproject.toml at all — the adapter (agent_assembly/adapters/langgraph/) reflectively loads `langgraph.graph.state.StateGraph` via importlib and structurally duck-types compiled-graph node maps, so it degrades gracefully when the framework isn't installed. That resilience meant the patch was never once exercised against a real langgraph install: every existing test drives it against a hand-rolled SimpleNamespace/FakeStateGraph double. Pin langgraph>=1.2.9,<2 (latest stable) as a dev/test dependency and add a real-package smoke test (test/integration/test_langgraph_real_package_smoke.py) that compiles and invokes an actual StateGraph. That test caught a genuine breaking API change: current LangGraph's CompiledStateGraph.nodes holds PregelNode wrapper objects whose own .invoke/.ainvoke are never called by the Pregel runtime during execution — it dispatches through the wrapped Runnable at PregelNode.bound instead. Wrapping PregelNode.invoke (what every existing mock effectively does) let patch.apply() return True and the graph run correctly, while every governance hook silently never fired. _wrap_node_entry() now detects the PregelNode shape (_is_pregel_node_wrapper) and wraps node_executor.bound.invoke/.ainvoke instead, verified against a real compiled graph for both the sync and async execution paths. ToolNode and compiled-subgraph detection are unaffected — their structural shape (tools_by_name / nodes+invoke) is unchanged in current LangGraph. The new smoke test resets the module-level _NODE_TRANSITION threading.local around each test (mirroring test_edge_emission.py's existing teardown) since it is process-global and otherwise leaks a stale node name into whichever edge-emission test runs next.
Most pyproject.toml ranges already permitted latest stable (the repo keeps wide compat ranges by convention), so `uv lock --upgrade` resolved nearly everything to current latest without a range edit — notably mypy 1.17->2.2, pytest 9.0->9.1, ruff 0.15.11->0.15.21, pre-commit 4.3->4.6, pylint 3.3->4.0, mkdocstrings[-python], openai-agents, smolagents, agno, and llama-index-core (0.14.22->0.14.23, keeping the documented AAASM-4169 nltk accepted-risk exception since no upstream fix exists). pydantic-ai's floor is the one exception: uv's solver resolved it down to 0.7.2 with the prior unbounded ">=0.3.0" floor (an unconstrained solve found a cheaper-to-satisfy graph at that older version) instead of latest. Raised to ">=2.0.0" to force resolution to 2.9.0, verified compatible. Also fixes fallout from those bumps so every check stays green: - pytest 9.0->9.1 changed monkeypatch.setattr(str, ...)'s internal string resolution to call the real importlib.import_module; two existing tests (test_langgraph_patch.py, test_registry.py) first monkeypatch importlib.import_module itself to always raise, which broke every subsequent string-targeted setattr in the same test. Patch via the already-imported module object instead of a dotted string. - mypy 1.17->2.2 reclassified "decorator returns Any" from the `misc` code to its own `untyped-decorator` code (test_microsoft_agent_framework_patch.py), tightened `type` vs `type[FrameworkAdapter]` attribute access (adapter_validator.py), and got smarter about the LangGraph Pregel.invoke overload set (dropped two now-unused type: ignore comments added for mypy 1.17 in the previous commit's real-package smoke test). - ruff 0.15.11 already produced 196 lint errors on unmodified master (mostly newly-stabilized ARG002/ARG005 unused-argument and UP045/UP007 PEP604 rules) — this was pre-existing, not introduced by this bump. Ran `ruff check --fix` for the 87 mechanically-fixable ones (Optional->X|None, Union->X|Y, deprecated typing imports, import sorting, etc.) and manually fixed the rest: contextlib.suppress instead of try/except/pass, a lambda assignment rewritten as def, and nested `with` statements combined. ruff.toml changes: exclude the generated agent_assembly/proto/ stubs (mirrors the existing mypy.ini ignore — must not be hand-edited to satisfy lint), scope ARG002/ARG005 exceptions to test/ (extending the same rationale as the existing repo-wide ARG001 ignore, but scoped so a genuinely-unused argument in agent_assembly/ still gets flagged), and add "test" to extra-standard-library (the pre-commit `isort` hook's bundled stdlib list includes CPython's own `test` package, which ruff's isort doesn't know about by default — without this the two tools disagreed on where `test.*` imports belong and repeatedly re-sorted each other's output). Not touched: native/ Rust crates (pinned by git SHA to the agent-assembly monorepo per ADR 0002 / AAASM-2559 — coordinated separately, out of scope here) and agent_assembly/proto/ generated stubs (committed verbatim; black wants to reformat them but there is no regeneration+diff CI check yet to tell us that's actually the intended committed form, so left as-is).
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Claude Code — PR reviewVerdict: Approve with one follow-up required before or shortly after merge. The flagship LangGraph fail-open fix is real, well-tested, and verified working against the actual installed package — but I found a second, narrower fail-open gap in the same fix that isn't covered by any test. Recommend either a small follow-up patch or an explicit tracked ticket before this ships to production governance paths that use subgraph-as-node. 1. CI checkRe-ran fresh: 22/22 green, no regressions. 2. Scope check (AAASM-4434)Compared ticket AC against the diff and PR description:
Ticket is otherwise fully covered. 3. Side-effect check — LangGraph fail-open fix (extra scrutiny, as requested)Fallback correctness — confirmed good. Test quality — confirmed good, not a "no-crash-only" test. assert recorder.events == [("start", "a"), ("end", "a"), ("start", "b"), ("end", "b")]for both the sync
Practical effect, verified against the real package:
Repro: # parent graph has add_node("sub_node", compiled_subgraph); patch applied with process_agent_id set
# -> on_graph_node_start/end DO fire for "sub_node"
# -> but _SPAWN_CTX.get() inside the subgraph's own nodes is None, not the expected SpawnContextThis is the same category of bug this PR was written to fix (a Suggested fix direction: in Ruff spot-check — confirmed style-only. Checked several hunks ( 4. Front-end checkThis repo's "front end" is the Summary: The core fail-open fix (plain function/callable nodes) is correct, well-reasoned, and backed by a real regression test I independently verified against the actual installed package in both directions (bug present pre-fix, bug fixed post-fix). The dependency sweep itself is complete and matches the ticket. The one thing I'd want addressed — either in this PR or a fast tracked follow-up — is the subgraph-as-node lineage gap in §3, since it's the same class of silent governance gap this PR exists to eliminate. |
Re-audited every dependency in this PR against real PyPI latest per user escalation (AAASM-4434). protobuf (6.33.6) and grpcio-tools (1.81.1) are the only two resolving below raw PyPI latest despite ranges that would otherwise permit newer versions. Investigated both and confirmed this is NOT the pydantic-ai "permissive floor, stale resolution" bug pattern — it's a real transitive ceiling from pydantic-ai's logfire extra (logfire caps opentelemetry-exporter-otlp-proto-http<1.43.0, which caps protobuf<7.0, which blocks grpcio-tools>=1.82.1's protobuf>=7.35.1 requirement). Verified via `uv lock --upgrade` (no-op) and by temporarily raising protobuf's floor to >=7.35.1, which reproduces a resolver conflict rooted at pydantic-ai. Documented with evidence so future re-audits don't re-litigate this. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017CSsRzBzAEHEP1hACy7DKW
Claude Code — dependency completeness re-audit (per user escalation)Per the user's broader complaint ("you missed openai-agents") I re-audited every dependency in this PR — not just the ones already flagged — comparing what's resolved in openai-agents — re-confirmed correct
Full verification table — all 33 direct dependencies (main deps + all
|
| Package | Constraint | Resolved (uv.lock) | PyPI latest | Match? |
|---|---|---|---|---|
| pydantic | >=2.0.0,<3.0.0 |
2.13.4 | 2.13.4 | Match |
| httpx | >=0.27.0,<1.0.0 |
0.28.1 | 0.28.1 | Match |
| typing-extensions | >=4.0.0 |
4.16.0 | 4.16.0 | Match |
| grpcio | >=1.66,<2 |
1.82.1 | 1.82.1 | Match |
| protobuf | >=5,<8 |
6.33.6 | 7.35.1 | See below — real ceiling, not a bug |
| coverage | ~=7.10 |
7.15.0 | 7.15.0 | Match |
| python-dotenv | >=1.0.1,<2 |
1.2.2 | 1.2.2 | Match |
| grpcio-tools | >=1.66,<2 |
1.81.1 | 1.82.1 | See below — real ceiling, not a bug |
| ruff | >=0.1.0 |
0.15.21 | 0.15.21 | Match |
| mypy | >=1.11,<3 |
2.2.0 | 2.2.0 | Match |
| pytest | >=8.1.1,<10 |
9.1.1 | 9.1.1 | Match |
| pytest-cov | >=5.0.0,<8 |
7.1.0 | 7.1.0 | Match |
| pytest-rerunfailures | >=14.0,<17 |
16.4 | 16.4 | Match |
| pytest-asyncio | >=0.23.0,<2 |
1.4.0 | 1.4.0 | Match |
| pytest-benchmark | >=4.0.0,<6 |
5.2.3 | 5.2.3 | Match |
| pydantic-ai | >=2.0.0 |
2.9.0 | 2.9.0 | Match (previously fixed this PR — floor raised 0.3.0 -> 2.0.0) |
| openai-agents | >=0.1.0 |
0.18.2 | 0.18.2 | Match — re-confirmed correct, proof for the user's original complaint |
| smolagents | >=1.0.0,<2.0.0 |
1.26.0 | 1.26.0 | Match (no 2.x exists yet on PyPI — cap not currently binding) |
| agno | >=2.0.0 |
2.7.2 | 2.7.2 | Match |
| llama-index-core | >=0.10.0 |
0.14.23 | 0.14.23 | Match |
| langgraph | >=1.2.9,<2 |
1.2.9 | 1.2.9 | Match (no 2.x exists yet on PyPI — cap not currently binding) |
| pre-commit | >=3.5.0,<5 |
4.6.0 | 4.6.0 | Match |
| pylint | >=3.1.0,<5 |
4.0.6 | 4.0.6 | Match |
| langchain-core | >=0.3.0,<1.5.0 |
1.4.9 | 1.4.9 | Match (1.5.0 not yet released — cap not currently binding) |
| mkdocs | >=1.6.0,<2 |
1.6.1 | 1.6.1 | Match |
| mkdocs-material | >=9.5.0,<10 |
9.7.6 | 9.7.6 | Match |
| mkdocstrings | >=0.24.0,<2 |
1.0.5 | 1.0.5 | Match |
| mkdocstrings-python | >=1.10.0,<3 |
2.0.5 | 2.0.5 | Match |
| mike | >=2.1.0,<3 |
2.2.0 | 2.2.0 | Match |
| mkdocs-autorefs | >=1.0.0,<2 |
1.4.4 | 1.4.4 | Match |
| mkdocs-git-revision-date-localized-plugin | >=1.2.0,<2 |
1.5.3 | 1.5.3 | Match |
| mkdocs-git-authors-plugin | >=0.9.0,<1 |
0.10.0 | 0.10.0 | Match |
| mkdocs-macros-plugin | >=1,<2 |
1.5.0 | 1.5.0 | Match |
31 of 33 dependencies resolve to exact PyPI latest. The 2 exceptions (protobuf, grpcio-tools) were individually investigated below — neither is a repeat of the "permissive floor -> stale resolution" bug found in pydantic-ai.
The 2 exceptions — investigated, confirmed NOT a repeat of the pydantic-ai bug
Both initially looked like the same pattern (resolved version behind what the constraint range permits). Investigation:
- Ran
uv lock --upgrade-package protobuf --upgrade-package grpcio-toolsand a fulluv lock --upgrade— both are no-ops,uv.lockbyte-identical afterward. This rules out "stale lock, resolver would pick newer if simply asked" (which was exactly pydantic-ai's problem). - Temporarily set
protobuf>=7.35.1,<8and re-ranuv lock— got a hard resolver conflict. Root cause, per uv's own conflict trace:pydantic-ai>=2.0.0depends onpydantic-ai-slim[...,logfire,...], andlogfire(latest 4.37.0) pinsopentelemetry-exporter-otlp-proto-http<1.43.0. Everyopentelemetry-exporter-otlp-proto-httprelease<=1.42.1depends on anopentelemetry-protothat capsprotobuf<7.0. So protobuf is genuinely ceilinged below 7.0 by a transitive dependency ofpydantic-ai's own bundled logfire extra — not by anything in this repo's own constraints. grpcio-tools1.82.1 (true PyPI latest) itself requiresprotobuf>=7.35.1,<8.0.0— which directly conflicts with the protobuf<7.0 ceiling above. So grpcio-tools is blocked from 1.82.1 for the same root cause, and stays at the newest version compatible with protobuf<7.0 (1.81.1).
This is a real, verified incompatibility, not a repeat of the pydantic-ai bug pattern — it traces back to an upstream package (logfire) not yet supporting newer opentelemetry/protobuf, not to a loose floor in this repo's own pyproject.toml. Fixed by documenting it, not by widening — widening the floor here was tested and shown to break the lock.
Commit d3206b3 (pushed to this branch) adds evidence-backed comments to pyproject.toml next to both protobuf and grpcio-tools, citing the exact conflict chain and the commands used to verify it, so a future re-audit doesn't have to re-derive this. Re-audit trigger noted inline: once logfire raises its opentelemetry-exporter-otlp-proto-http ceiling above 1.43.0, both should be re-checked.
Upper-bound caps (<2, <3, <10, etc.) — spot-checked for unjustified padding
Checked every capped dependency for whether the cap is currently binding (i.e., whether a newer major already exists on PyPI that the cap is suppressing):
langgraph<2andsmolagents<2.0.0: no 2.x release exists yet for either package on PyPI — the caps aren't blocking anything today, and match the existing AAASM-4434/AAASM-3539 rationale already in the file (protecting against an untested future major). Left as-is; nothing to widen since there's nothing beyond the cap to widen into yet.langchain-core<1.5.0: 1.5.0 hasn't been released yet either — same situation, cap not currently binding.- All other capped deps (pytest family, mkdocs family, mypy, pylint, pre-commit, pydantic, httpx, grpcio) resolve to exact PyPI latest within their existing caps — the caps aren't suppressing anything right now.
No cap was found to be silently truncating an already-resolvable newer version, so none required widening.
Validation after the fix
Comment-only change (pyproject.toml), uv.lock unaffected (byte-identical before/after uv lock). Re-ran the full suite in the worktree to confirm:
ruff check .— all checks passed.mypy agent_assembly— same 4 pre-existing errors as onmaster(native_coremodule not built in this env, missingtypes-grpciostubs); unrelated to this change, confirmed identical with/without the diff viagit stash.pytest— 784 passed, 16 skipped (all environment-gated: native core, optional frameworks not installed), 1 rerun. No regressions.
Bottom line
- 33/33 direct dependencies audited (main deps + dev/lint/test/pre-commit-ci/langchain-test/docs groups).
- openai-agents reconfirmed correct at genuine PyPI latest (0.18.2) — the specific claim in the original complaint does not hold for this PR.
- 2 additional gaps found (protobuf, grpcio-tools) that superficially matched the "stale resolution" bug pattern — both investigated and confirmed to be a real, upstream-caused transitive ceiling (via pydantic-ai's
logfireextra), not a repeat of the bug. Fixed by adding evidence-backed documentation in commitd3206b3(pushed) rather than widening, since widening was tested and shown to break the lock. - No other dependency — including all previously-unflagged ones (agno, smolagents, llama-index-core, langchain-core, langgraph, and the full docs/lint/pre-commit-ci groups) — shows the "permissive floor, stale resolution" pattern found in pydantic-ai.
…ions Many dependency floors in this PR were left at stale, wide-open values (e.g. openai-agents>=0.1.0) while uv.lock had already resolved to a much newer version (0.18.2). Raise 26 floors across [project].dependencies and the dependency-groups to the exact version uv.lock currently resolves, preserving every existing upper-bound cap. This is a manifest-honesty fix only — `uv lock` reproduces the identical 227-package resolution before and after, no functional or resolved-version changes.
Claude Code — manifest transparency fix (per user request)Addressing the feedback that many dependencies in this PR only had their lockfile ( Raised 26 floors to explicitly state the currently-resolved version as the new minimum. Every existing upper-bound cap (
A handful of floors were audited and left unchanged because the gap between the declared floor and the resolved version was trivial (1-2 minor versions on a mature package) and not meaningfully misleading: This makes no functional change. After editing Full check suite re-run after the change, all green:
Commit: |
|



Summary
Org-wide dependency-freshness sweep (epic AAASM-4430) for the Python SDK. Upgrades every dependency in
pyproject.tomlto latest stable, with LangGraph as the priority since it had no pin at all and a real breaking API change was hiding behind that gap.LangGraph: no pin ->
>=1.2.9,<2(latest stable)Investigation finding: there was no
langgraphpin anywhere inpyproject.tomlprior to this PR. The adapter (agent_assembly/adapters/langgraph/) reflectively loadslanggraph.graph.state.StateGraphviaimportliband structurally duck-types compiled-graph node maps, so it degrades gracefully when the framework isn't installed — but that same resilience meant the patch was never once exercised against a real langgraph install. Every existing test (unit and "integration") drove it against a hand-rolledSimpleNamespace/FakeStateGraphdouble.What migration was needed and why: pinning
langgraph>=1.2.9,<2and adding a real-package smoke test (test/integration/test_langgraph_real_package_smoke.py) that compiles and invokes an actualStateGraphcaught a genuine breaking API change: current LangGraph'sCompiledStateGraph.nodesholdsPregelNodewrapper objects whose own.invoke/.ainvokeare never called by the Pregel runtime during execution — it dispatches through the wrapped Runnable atPregelNode.boundinstead. WrappingPregelNode.invoke(what every existing mock effectively does) letpatch.apply()returnTrueand the graph run and return the correct result, while every governance hook silently never fired — a fail-open regression that would have shipped undetected.Fix:
_wrap_node_entry()now detects thePregelNodeshape (_is_pregel_node_wrapper) and wrapsnode_executor.bound.invoke/.ainvokeinstead, verified against a real compiled graph for both the sync (invoke) and async (ainvoke) execution paths.ToolNodeand compiled-subgraph detection are unaffected — their structural shape (tools_by_name/nodes+invoke) is unchanged in current LangGraph, verified directly against the real package.Other dependencies bumped to latest stable
Most
pyproject.tomlranges already permitted latest stable (the repo keeps wide compat ranges by convention), souv lock --upgraderesolved almost everything without a range edit:>=1.2.9,<2)>=0.3.0->>=2.0.0, see below)pydantic-ai exception: with the prior unbounded
>=0.3.0floor,uv lock --upgraderesolved pydantic-ai down to 0.7.2 instead of latest — an unconstrained solve found a cheaper-to-satisfy dependency graph at that older version. Raised the floor to>=2.0.0, which forces resolution to latest stable (2.9.0); verified compatible (protobuf still resolves within our existing>=5,<8range, just at 6.33.6 instead of 7.x).Fallout fixed to keep every check green
monkeypatch.setattr(str, ...)'s internal string-resolution to call the realimportlib.import_module. Two existing tests (test_langgraph_patch.py,test_registry.py) first monkeypatchimportlib.import_moduleitself to always raiseImportError(to simulate the framework being absent), which broke every subsequent string-targetedsetattrin the same test since that resolution machinery was now broken too. Fixed by patching via the already-imported module object instead of a dotted string.Any" from the genericmisccode to its ownuntyped-decoratorcode (test_microsoft_agent_framework_patch.py), tightenedtypevstype[FrameworkAdapter]attribute access (adapter_validator.py), and got smarter about LangGraph'sPregel.invokeoverload set (dropped twotype: ignorecomments that were needed under 1.17 but became "unused ignore" errors under 2.2, in the new smoke test).master(verified against a clean checkout) — mostly newly-stabilizedARG002/ARG005unused-argument andUP045/UP007PEP 604 rules. This was pre-existing, not introduced by this PR's bump, but it's one of this PR's required checks so it's fixed here:ruff check --fixfor the 87 mechanically-fixable ones, plus manual fixes forcontextlib.suppressinstead oftry/except/pass, a lambda assignment rewritten asdef, and nestedwithstatements combined into one.ruff.tomlchanges: exclude the generatedagent_assembly/proto/stubs (mirrors the existingmypy.iniignore for the same reason — committed verbatim, must not be hand-edited), scope theARG002/ARG005exception totest/(same rationale as the existing repo-wideARG001ignore, but scoped so a genuinely-unused argument inagent_assembly/still gets flagged), and add"test"toextra-standard-library(the pre-commitisorthook's bundled stdlib list includes CPython's owntestpackage; ruff's isort doesn't know that by default, so the two tools disagreed on wheretest.*imports belong and repeatedly re-sorted each other's output).agent_assembly.adapters.langgraph.patch._NODE_TRANSITION(a process-globalthreading.local()) around each test — it otherwise leaks a stale node name into whichever edge-emission test happens to run next, depending on collection order (mirrors the existing, same-shaped teardown intest_edge_emission.py).Documented exceptions (not touched)
native/Rust crates — pinned by git SHA to theagent-assemblymonorepo per ADR 0002 / AAASM-2559; that pin is coordinated separately across repos and is out of scope for this Python-dependency ticket.agent_assembly/proto/generated stubs — committed verbatim permypy.ini's existing ignore comment ("a CI drift check regenerates them and asserts no diff is left as a follow-up hygiene sub-task").black/isortwant to reformat them (pre-existing drift, not caused by this PR), but since no regeneration+diff CI check exists yet to confirm reformatted-and-committed is actually the intended form, they're left untouched rather than hand-edited.llama-index-core's transitivenltkHIGH-severity path-traversal (AAASM-4169) — unchanged accepted-risk: dev/test-only, never imported byagent_assembly/, no upstream fix exists (3.9.4/3.10.0 both carry it), no lock upgrade removes it.Checks run (all pass)
pytest test/— 779 passed, 16 skipped (skips are genuinely-absent optional frameworks: crewai, google-adk, haystack, agent-framework, native_corematurin build — same skip set as before this PR)ruff check .— cleanmypy(permypy.ini) — clean, 173 source filesmkdocs build --strict— cleanpre-commit run --all-files— clean except the pre-existing, documentedagent_assembly/proto/black/isort drift noted above (not touched)One pre-existing flaky benchmark (
test_detection_latency_under_50ms, a P99 timing-contract test) was observed to intermittently fail under heavy concurrent machine load and pass consistently in isolation (3/3) — confirmed unrelated to this PR's changes, not touched.Not merging — leaving open for review.