Repository navigation
ci: detect new framework releases and test each one in isolation against the PR lock #1066
Description
Part of #1065.
Summary
Add a daily framework-release-watch.yml workflow. It finds every framework package whose newest stable PyPI release is newer than the PR lock. For each one, it runs the existing framework checks with only that package moved and everything else held at the lock, then opens or updates one framework-release issue per (package, version) with the results and the exact next step. The issue closes itself when the lock catches up.
Motivation
See #1065. The nightly fresh resolve catches breakage but can't name which release caused it, and nothing prompts the lock refresh ADR 0007 rule 3 requires. BG §1.8.
Current behavior (verified)
-
Roster. The watch list comes from
ADAPTERS(integrations/init.py:197) and each extra's direct requirements (pyproject.toml:57-84). For example,langgraphalso pulls inlangchain-openaiandlangchain-core, andadkalso pulls inlitellm. -
Lock. Exact pins per extra live in
python/constraints/adapter-contract-<extra>-py3.12.txt, generated bycompile_constraints.py(_COMBOS, compile_constraints.py:91). -
Reusable checks:
verify_frameworks.py --only <fw> --require-installed --json(verify_frameworks.py:70, :372-374)- the whole suite with
DONKEY_CONTRACT_EXTRAarming the skip guard (conftest.py:47) scripts/smoke_example.py <fw>
framework-legsalready chains all three (nightly-matrix.yml:175-259), but on a fresh resolve. -
Reusable PyPI client.
fetch_project()andcanonicalize()are stdlib-only (check_new_dependencies.py:58, :116). -
Issue pattern. The open-or-update issue logic is in
alert(nightly-matrix.yml:261-299). -
Existing workflow guards:
- every job needs a bounded timeout and every matrix
fail-fast: false(test_workflow_bounds.py:45, :56) - an aggregate job needs every other job, and only
alertwrites issues (test_ci_workflows.py:38, :55). The new workflow must be registered in these parametrizations.
- every job needs a bounded timeout and every matrix
Proposal
1. python/scripts/check_framework_releases.py (stdlib only)
- Import, don't copy. Take
fetch_projectandcanonicalizefromcheck_new_dependencies.py. - Watch set. For each
ADAPTERS[k].extra, watch the direct requirements of that extra.- Locked version: the
==pin inadapter-contract-<extra>-py3.12.txt. - Latest version: the newest non-yanked, non-pre-release version on PyPI.
- Locked version: the
--json: emits a GitHub matrix of{framework, extra, package, locked, latest, bump: major|minor|patch, released_at, release_notes_url}, whererelease_notes_urlcomes frominfo.project_urls(Changelog / Release notes / Source). Prints[]when nothing is new.--constraints-for PKG==VER --out PATH: writes a copy of that extra's lock with that one pin replaced.--package NAME: limits the run to one package (forworkflow_dispatch).--stale-issues: reads the openframework-releaseissues (ghJSON on stdin) and prints the numbers of those whose version is now<=the lock.
2. .github/workflows/framework-release-watch.yml
Triggers:
schedule: "30 5 * * *", before the 06:00 nightlyworkflow_dispatchwith an optionalpackageinputpull_requestfiltered to the workflow and script paths, as a dry run: no issue writes, and the rendered issue bodies go to the job summary
Jobs:
-
detectruns the script and outputs the matrix. Later jobs are skipped when it is empty. -
evaluateruns once per release, withfail-fast: false,timeout-minutes: 30, Python 3.12, and matrix values bound throughenv::- Install with everything else locked:
pip install -e ".[llm,local,otel,$EXTRA]" --group dev -c <rewritten lock>. - If that doesn't resolve, the new release needs other packages moved too. Retry unconstrained and record the outcome as
needs co-bump. - Then run
verify_frameworks.py --only $FW --require-installed --json,pytest -qwithDONKEY_CONTRACT_EXTRA=$EXTRA, andsmoke_example.py $FW. - Collect outcomes. Each step uses
continue-on-errorand records its outcome, together with the installed version fromimportlib.metadata, into aresult.jsonartifact.
- Install with everything else locked:
-
alertruns withif: always(),needs:every job,issues: write. It is the only job that writes. For each release, it opens or updates one issue titledframework-release: <pkg> <locked> → <latest>, labelledframework-releaseanddependencies(creating the label withgh label create --force). The body contains:- versions, bump kind, release date and release-notes link
- a results table (signature / suite / smoke × isolated / co-bump) and the run link
- a placeholder for the impact summary, filled by ci: add an AI changelog impact review to the framework release watch #1067
- next action:
- All green: "safe to bump". From
python/, runpython scripts/compile_constraints.py, thenpython scripts/verify_frameworks.py --only <fw> --emit-verifiedto refresh the section 8 row indocs/verified-apis.md, then the PR gate. Reference this issue in the bump PR. - Anything red: "adapt the adapter or record a §8.1 incompatibility, never add a ceiling (§8.4)".
- All green: "safe to bump". From
alertalso closes the issues returned by--stale-issues, with a comment linking the lock commit. A re-run with unchanged results doesn't add a duplicate comment.
3. Docs
docs/ci.md: add a section for the new workflow.CONTRIBUTING.md: in the lock-refresh step, nameframework-releaseissues as the trigger.- No ADR change: rule 3 still holds, because the lock bump stays a maintainer PR.
Cross-surface impact
- core/llm/registry/tools: not affected. integrations: read-only.
ADAPTERSis the roster source, and adapter code doesn't change. - TypeScript parity (planned): not affected. When the TS port ships, its framework packages (npm) need the same watch. Note that in the Phase 5 TS epic.
- CLI: not affected.
- Nextra docs site: not affected (that's docs: publish a generated framework version-compatibility page #1068).
- docs/verified-apis.md: no row changes in this issue. The issue body tells the maintainer how to refresh the section 8 rows with
--emit-verified.
Out of scope
- The AI changelog review. That's ci: add an AI changelog impact review to the framework release watch #1067; this issue leaves a placeholder in the issue body for it.
- An automatic lock-bump PR. That's ci(deps): pin the dev and release toolchain with a lock, and make mypy independent of installed extras #763 and ci(security): harden the Actions supply chain — SHA pins, Dependabot, CODEOWNERS, least-privilege Pages #757.
- Pre-release watching (
aN/bN/rcN). Possible later behind a flag; not needed now.
Acceptance criteria
-
tests/unit/test_check_framework_releases.py, using a fakeFetch, covers: pre-releases and yanked versions are skipped; major/minor/patch classification; lock parsing; the single-pin constraint rewrite;--stale-issues. - A roster-drift test asserts the watched set equals
ADAPTERS× the extras' direct requirements, in the style oftest_nightly_framework_legs_match_adapters(test_adapter_roster.py:152). - The new workflow passes
test_workflow_bounds.pyandtest_ci_workflows.py, and is registered in both. - The PR's dry run lists today's real gaps (as of 2026-10-10: langchain-openai, langchain-core, litellm, strands-agents, agent-framework, anthropic, crewai) and renders their issue bodies in the job summary without creating issues.
- After merge,
workflow_dispatchwithpackage=crewaicreates exactly one issue. A second run updates it instead of duplicating it. Bumping the lock, then running the watch again, closes it. -
pytest -q && mypy && ruff check . && lint-importsare green.
Activity
ci: test each new framework release in isolation against the PR lock (#…