Skip to content

Agent deps are unbounded (>=X, no ceiling) — a breaking upstream major can silently break consumers and downstream CI gates #452

Description

@ling-senpeng13

Summary

Every agent-framework dependency is declared floor-only (>=X, no ceiling), so conductor-python[agents] accepts any future major version of langgraph, openai, anthropic, and friends. The e2e bundle's own test deps are fully unpinned. A breaking upstream release therefore reaches users — and downstream CI lanes — with no change to this repo, and presents as an SDK or server regression to whoever triages it.

Where it comes from

Two places, both in this repo:

1. pyproject.toml — the agents extra

langchain       = { version = ">=1.2.13", optional = true }
langchain-core  = { version = ">=1.2.20", optional = true }
langchain-openai= { version = ">=1.1.11", optional = true }
langgraph       = { version = ">=1.1.3",  optional = true }
google-adk      = { version = ">=1.27.1", optional = true }
openai          = { version = ">=2.28.0", optional = true }
openai-agents   = { version = ">=0.12.2", optional = true }
anthropic       = { version = ">=0.91.0", optional = true }
claude-code-sdk = { version = ">=0.0.25", optional = true }

2. scripts/package-e2e-bundle.sh — the heredoc that writes the bundle's requirements.txt

pytest
pytest-asyncio
pytest-xdist
pytest-rerunfailures
mcp-testkit

The bundle's run.sh does pip install -r requirements.txt at run time, so a consumer resolves "whatever is newest the day their lane happens to run."

Why this matters downstream

conductor-oss/conductor#1351 adds a gating CI lane that downloads the conductor-ai-e2e-python-* bundle and runs it against a from-source server. Because the bundle resolves its deps at run time, an unrelated upstream release can redden that gate. The failure looks like a server regression, which is the expensive part — the first hours go into looking for a Conductor bug that isn't there.

This is not hypothetical. A langgraph/pydantic interaction with typing.TypedDict already fails test_suite11_langgraph.py::test_messages_state_detection on Python < 3.12:

PydanticUserError: Please use `typing_extensions.TypedDict` instead of
`typing.TypedDict` on Python < 3.12

graph.get_input_jsonschema() raises, conductor/ai/agents/frameworks/langgraph.py swallows it in a bare except Exception, _input_is_messages is never set, and the assertion fails. Only the interpreter pin (3.12) is keeping that green today, not a dependency bound.

Proposed fix — bounds, not a lockfile

This is a library, so ranges are right and a committed lockfile would over-constrain consumers. Add next-major ceilings:

Package From To
langchain >=1.2.13 >=1.2.13,<2
langchain-core >=1.2.20 >=1.2.20,<2
langchain-openai >=1.1.11 >=1.1.11,<2
langgraph >=1.1.3 >=1.1.3,<2
google-adk >=1.27.1 >=1.27.1,<3
openai >=2.28.0 >=2.28.0,<3
openai-agents >=0.12.2 >=0.12.2,<0.13
anthropic >=0.91.0 >=0.91.0,<1
claude-code-sdk >=0.0.25 >=0.0.25,<0.1

and in the bundle heredoc: pytest>=8,<10, pytest-asyncio>=1,<2, pytest-xdist>=3,<4, pytest-rerunfailures>=16,<17, mcp-testkit>=1,<2.

Verified: 99 packages resolve cleanly under these bounds (uv pip compile --universal --python-version 3.12), and every capped package lands on the version already installed in a known-good environment — langgraph 1.2.10, anthropic 0.120.2, pytest 9.1.1, mcp-testkit 1.0.3. No cap excludes anything currently in use, so this is a no-op today and only takes effect on a future major.

Open questions for whoever picks this up

  1. 0.x policy. anthropic, openai-agents and claude-code-sdk are pre-1.0, where minors are breaking by convention. anthropic>=0.91.0,<1 is loose enough to be nearly no protection given their cadence; <0.121 would be real but needs frequent bumping. The table above is deliberately inconsistent on this point — worth settling it rather than copying.
  2. Cost of caps. Ceilings can block a consumer who legitimately needs a newer langgraph, and over-capping is a well-known source of resolution pain in Poetry libraries. <next-major is the mainstream compromise, but it is a trade.
  3. Alternative considered and rejected: generating a lock inside package-e2e-bundle.sh. That script documents being static — "no install, no network — the pinned version does not have to be on PyPI yet, so this can run before the publish job finishes" — and compiling a lock would need both, breaking release ordering.

Unrelated finding, same area

mcp-testkit requires Python >= 3.11, but the bundle's README.md and run.sh both advertise 3.10–3.13. Compiling the requirements at 3.10 is unsatisfiable:

The `requires-python` value (>=3.10) includes Python versions that are not
supported by your dependencies (e.g., all versions of mcp-testkit only
supports >=3.11)

Either the floor should say 3.11 or mcp-testkit should be conditional.


Happy to raise the PR if the direction and the 0.x policy are agreed — the change is ~14 lines across the two files.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions