Bump the mthds dependency
mthds is the MTHDS standard's Python client: the protocol wire models, the
runner abstracts, the package-manifest schema. This engine is built on it
directly — it subclasses its abstracts (StuffAbstract, ConceptAbstract,
WorkingMemoryAbstract, MTHDSProtocol) and re-exports its wire models as
part of pipelex's own public surface (pipelex/pipeline/input_form.py and
pipelex/pipeline/pipe_io_contracts.py list them in __all__).
That last part is what makes this bump different from bumping an ordinary dependency: an upstream rename is not an internal detail this repo absorbs, it is a breaking change to pipelex's own API, and it has to reach the changelog as one.
The job is to land the new pin in a state a human can read and commit: pin moved, lock regenerated, source adapted, checks green, changelog written, ledger squared. Stop before committing — the user stages and commits.
What the bump touches
| File | Why it moves |
|---|---|
pyproject.toml |
The pin itself, one line in [project].dependencies — mthds==X.Y.Z |
uv.lock |
Regenerated by make li |
pipelex/** |
Wherever the new release renamed, split, or tightened something this engine imports |
tests/** |
Same, plus any test asserting on a shape that moved |
CHANGELOG.md |
An entry under ## [Unreleased] — always at least the pin line |
.badges/tests.json, .test_durations |
Only if the adaptation added, removed or renamed tests |
Do not touch [project].version. That is this package's own version and it
moves only at release time, via the release skill.
The pin is exact, and it always tracks latest
The pin is mthds==X.Y.Z, not >=. That is deliberate and it is newer than it
looks — the dependency was a >= floor until v0.54.0, which moved it to an
exact pin and recorded the move as a breaking change. Never relax it back to
>= while bumping, and never widen it to get a resolve unstuck.
The reason is what this engine does with mthds: it re-exports mthds' wire models
as part of pipelex's own public surface, so a consumer resolving a different
mthds would be handed one version's classes through the __all__ of a pipelex
built against another — isinstance narrowing over a protocol union failing in
a way no version specifier explains. An exact pin makes the version this repo
tests against the version it demands, with no gap for a consumer to fall into.
The standing policy is to move the pin to the latest release, every time,
even when the new release carries nothing this engine uses. The cost is real and
worth naming when you report: everyone downstream — pipelex-sdk-python,
pipelex-server, every user install — is forced onto that exact mthds, and a
consumer that also depends on mthds directly must now move in step with
pipelex rather than resolving its own. Say so in the changelog when the bump is
otherwise uneventful, because for a consumer "pipelex now requires mthds
0.11.2" is the change.
Three numbers, none of which is the others
This is the single most common way to get confused here, and two of the three are visible in the same sentence of an upstream changelog.
| Number | Where it lives | What it means |
|---|---|---|
The mthds package version |
mthds-python/pyproject.toml, PyPI |
The Python client's own release number. This is what you are bumping. |
| The MTHDS standard version | MTHDS_STANDARD_VERSION in mthds/package/manifest/schema.py |
The version of the standard that client implements. Read it from the installed package rather than from memory — step 6 prints it. |
| The spec site's release number | the mthds/ repo's own CHANGELOG |
The documentation site's release, e.g. v0.9.0. Coincidentally close to the package number; unrelated to it. |
So "mthds 0.9.0 ships the artifacts the standard owns since MTHDS v0.9.0, which is standard version 1.0.0" is a coherent sentence. Read every version reference in the upstream notes against this table before repeating it in ours.
Workflow
1. Orient
Read the pin from pyproject.toml, not from the virtualenv:
grep -n '"mthds' pyproject.toml
.venv/bin/python -c "import importlib.metadata as m; print(m.version('mthds'))"
Under an exact pin these two should agree, in both directions — unlike a floor,
where a venv running ahead was normal. Any disagreement means the venv is stale:
someone edited pyproject.toml without re-running make li, or installed
something over it. Re-sync with make li before you measure anything against
the venv.
Check git status and note what was already dirty before you start.
Concurrent work in this worktree is normal, and at the end you need to separate
your changes from theirs and never stage something that isn't yours.
Then ask the ledger what it already knows. mthds-python files items here when
it lands something this engine will have to absorb, and those items usually
carry the exact diff — file, line, and the shape of the fix — which is faster
and more reliable than rediscovering it from a pyright error:
ledger inbound
ledger list --origin mthds-python --status open
The second query is the targeted one — every item mthds-python filed, wherever
it landed — and it is worth reading past the rows owned by this repo, because a
row owned by pipelex-sdk-python or by mthds is a piece of the same cascade
you will be filing into at step 10.
An item that describes the adaptation you are about to do should be claimed
(ledger claim <id>) before you start.
2. Resolve the target version
If the user named a version, use it. Otherwise ask PyPI:
curl -s https://pypi.org/pypi/mthds/json \
| python3 -c "import json,sys; print(json.load(sys.stdin)['info']['version'])"
info.version is the latest non-prerelease, which is what "latest" means for a
pin other people inherit exactly.
Ask PyPI, never the sibling checkout. mthds-python/pyproject.toml is a
working tree and is frequently ahead of what has been published — a version
sitting there under ## [Unreleased] is not installable, and a pin naming it
makes uv lock fail. If you notice the sibling is ahead, that is worth
mentioning in your report (it is a preview of the next bump) but it does not
change the target.
If the resolved version equals the current pin, say so and stop: there is
nothing to do, and manufacturing lock churn is worse than reporting "already
current". If the venv disagrees, mention that — make li is the real fix there.
3. Digest the upstream changes — before editing anything
For most dependencies you bump first and read the notes later. Not this one. Because this engine builds on mthds' types rather than merely calling them, the release notes are a work list, and reading them first is what turns a pyright cascade into a set of expected edits:
.venv/bin/python .claude/skills/bump-mthds/scripts/upstream_notes.py 0.9.0 0.10.0
The helper reads ../mthds-python/CHANGELOG.md and prints the released sections
strictly after the old pin up to and including the new one. It skips
## [Unreleased] — that section describes work that is not in the version you
are adopting, and this repo's changelog is read by people deciding whether an
upgrade will break them. If the checkout predates the target release the script
says so; fall back to:
gh release view v0.10.0 --repo mthds-ai/mthds-python
Read the notes for three things specifically: renamed or split types this repo
imports, parse-time tightening (mthds models are extra="forbid", so a shape
this engine constructs can start failing at construction), and any move of
MTHDS_STANDARD_VERSION.
4. Move the pin
One line in pyproject.toml. Make it a substring edit — replace ==0.11.1 with
==0.11.2 on that line, keeping the ==, and leave every other character
alone. Use your editor
rather than a shell one-liner: sed -i takes a separate empty argument on macOS
and an attached suffix on GNU/Linux, so no single invocation is portable, and
the release skill edits this same file the same way.
Confirm with grep -n '"mthds' pyproject.toml before moving on.
5. Re-lock and install
make li
That is make lock (uv lock) plus make install (uv sync --all-extras), so
it rewrites uv.lock and puts the new mthds in .venv, which everything
downstream of here depends on.
Do not reach for make update. That is uv lock --upgrade and it moves
every dependency in the tree at once, which turns a reviewable one-package bump
into a diff nobody can reason about. Moving the pin is already enough to make
plain uv lock re-resolve mthds.
Check the lock actually moved — a pin edit that didn't take is silent:
grep -A1 '^name = "mthds"' uv.lock
If uv lock reports the requirement is unsatisfiable, the version is almost
certainly not on PyPI yet. Re-run the query from step 2. If it genuinely isn't
published, stop and tell the user rather than inventing a git or path source:
adding one is a deliberate decision with a real cost (in uv, a source outranks
a version specifier, so the pin you wrote becomes decorative), not a workaround
to apply quietly. An exact pin can also be reported unsatisfiable because
another member of the resolve wants a different mthds — widening this pin is not
the fix, moving the other requirement is.
6. Check whether MTHDS_STANDARD_VERSION moved
.venv/bin/python -c "from mthds.package.manifest.schema import MTHDS_STANDARD_VERSION as v; print(v)"
Do this explicitly, because nothing in this repo pins it and the suite cannot
see it move. Every test compares the crate stamp against the imported constant
(assert crate.mthds_version == MTHDS_STANDARD_VERSION), and the one test that
hardcodes "1.0.0" mocks it. So the constant can change under a green suite.
Two things follow it, and one thing deliberately does not:
- The
mthds_versionstamp on every normalized crate this engine emits (normalize_crate(crate, mthds_version=MTHDS_STANDARD_VERSION)) — a visible wire field that changes for real users. - The manifest constraint warning in
LibraryManager._warn_if_mthds_version_unsatisfied— aMETHODS.tomldeclaringmthds_version = "^1.0.0"starts or stops warning depending on this constant. - Crate fingerprints do not move.
mthds_versionis excluded from the hashed payload by design (see the docstring oncompute_normalized_fingerprintinpipelex/libraries/library_crate.py), so a standard-version move does not invalidate anyone's fingerprint.
If it moved, that belongs in the changelog as a user-visible change even though no pipelex line changed to cause it.
7. Adapt the source
Run the type checker first — it is the fastest and most complete reader of a protocol break, and it costs seconds where the suite costs minutes:
make pyright
Work from the step-3 notes and any ledger item, not from guesswork. The adaptations that recur, in rough order of how often they bite:
- A renamed or split type this repo imports. Fix the import, the
__all__re-export, and everyisinstancenarrowing. Grep is the tool: a name that appears in__all__appears in at least three places. - A shape this engine constructs that got tighter. mthds' protocol models
are
extra="forbid", so a member that used to be tolerated now raises at construction. Watch formodel_copy(update={...})in particular — it does not validate, so it will happily leave a model in a state the class forbids, and the type checker is the only thing that notices. Rebuild the node as the right model rather than patching a field onto the wrong one. - A new abstract method on something this repo subclasses. Implement it;
don't
raise NotImplementedErrorto get green.
Failures here are the breaking change announcing itself, not incidental breakage. Adopt the new API. A bump whose failures were papered over is worse than no bump.
8. Run the checks
make agent-check
make agent-test
agent-check includes drift-check, which reads the git index, not the
working tree — so git add your changes before you trust its verdict. If it
reports an open contract, resolve it properly with the drift-review skill
rather than acking it blind.
If the adaptation added, removed or renamed tests, two committed artifacts go stale and neither is caught by a local aggregate:
make store-test-durations # .test_durations — keeps the CI shards balanced
make check-test-badge # .badges/tests.json — runs only in CI, so check it here
A MTHDS standard conformance red is a different animal: that workflow holds
this repo's pinned natives against the standard's own native-concepts.md from
an unpinned checkout of the mthds spec repo. A red there means the
standard moved and wants a dedicated catch-up change — bumping the package
neither causes nor fixes it. Say so rather than chasing it inside the bump.
9. Write the changelog entry
Add to CHANGELOG.md under ## [Unreleased], creating that heading right after
# Changelog if it isn't there. Work in progress accumulates there until a
release cuts it into a version heading — do not add a ## [vX.Y.Z] heading
yourself, and do not bump [project].version.
Every bump gets at least the pin line. Follow the house form, which names the release by what it carries:
- **`mthds` version**: The pin moves to `mthds==0.11.2`, <the release that
carries / the release fixing …>. Everyone downstream inherits that exact
version.
Anything that reached pipelex's own surface gets its own entry under
### Changed, marked breaking, written for an importer deciding whether this
upgrade will cost them work. Name the old symbol and the new one — a reader
hitting an ImportError searches for the name they had. Where a change is
breaking, write "breaking", not "pre-1.0 breaking".
If the bump is genuinely uneventful, say that in one sentence and stop. Padding a quiet bump with upstream detail that doesn't affect this repo makes the loud ones harder to spot.
10. Square the ledger
- Close what you actually landed, with evidence — the file and line you
changed, and the check that went green.
Closes <id>goes in the PR body when the user opens one. - File the cascade, and note that the exact pin makes it sharper.
pipelex-sdk-pythonpinsmthdsexactly too, andpipelex-serverpinspipelexexactly at several sites — so any bump here, breaking or not, puts a consumer that installs both this package andpipelex-sdk-pythoninto an unsatisfiable resolve until that repo moves to the same version. Check where it stands (grep mthds ../pipelex-sdk-python/pyproject.toml) and say so in your report. Neither repo is yours to edit from here: file it (ledger new --owner <repo> …) with the version that moved and, when there was one, the symbol. Theledgerskill has the full gesture. - File the reverse direction if you moved the wire.
mthds-pythoncommits protocol parity fixtures captured from this engine, so a change here that alters emitted output means those fixtures need recapturing — an item owned bymthds-python, naming the SHA to capture from. ledger validate, thenledger commit. Nothing else pushes the ledger.
11. Report and stop
Show the user:
- The pin move, old → new, and whether
uv.lockactually followed. - Whether
MTHDS_STANDARD_VERSIONmoved, and what that changes for users. - Every file you changed, separated from what was already dirty when you started.
- The check results, honestly — if
agent-testfailed, say so with the output rather than reporting a bump as done. - Whether the bump is breaking for pipelex's own importers, since that is what decides how the next release is written and who has to move after it.
- Anything left for a human: an upstream change whose adaptation is a judgment call, or a version the sibling checkout has that PyPI doesn't yet.
Then stop. Do not commit, branch, or push unless the user asks.
If they do ask, stage the files explicitly by path — never git add -A. A
working branch here is chore/<Topic> (spelled out, from the closed prefix set)
and PRs target dev.
Traps worth remembering
- Nothing in this repo enforces the
==. No check fails if the pin is relaxed to>=;pyproject.tomlis the only place the policy is written, and the changelog is where it was announced. A widened pin ships silently. - The pin is not the lock. Editing
pyproject.tomlwithout re-locking leavesuv.lockon the old version, which is what the venv and the suite actually use — a green run against the version you meant to leave behind. That is why step 5 verifies the lock actually moved. - The sibling checkout is routinely ahead of PyPI. Read
mthds-python's changelog for understanding, PyPI for the target version. A pin naming an unpublished version failsuv lock. ## [Unreleased]upstream is not in the version you adopted. Never quote it as part of the release. The helper script skips it for you.MTHDS_STANDARD_VERSIONmoves silently. Nothing pins it; the suite follows it. Print it (step 6).model_copy(update=…)does not validate. It is how a model ends up holding a value its class forbids, green at runtime, wrong on the type.make updateis notmake li. The former upgrades the whole tree.drift-checkreads the git index. Unstaged changes give a false green.check-test-badgeruns in no local aggregate. Green here, red in CI, on any commit that changes the test count.- A
MTHDS standard conformancered is about the spec repo, not this package. Different repo, different remedy. [project].versionis pipelex's own version. Bumping it is thereleaseskill's job, not this one's.