# Bump Mthds

> Move this repo's exact `mthds` dependency pin to the latest release on PyPI (or a version you name), re-lock, adapt the engine source to whatever the new release broke, run the checks, and write the CHANGELOG entry — stopping before the commit. Use whenever the user says "bump mthds", "update mthds", "upgrade mthds", "move to mthds 0.10.0", "get us on the latest mthds", "is our mthds pin stale", "is our mthds floor stale", "what's the latest mthds", or asks to build against a newly published `mthds` release. Also use when a failure looks like an mthds version mismatch — a pyright `reportAttributeAccessIssue` on something under `mthds.protocol`, an ImportError for a name that used to be there, a pydantic `Extra inputs are not permitted` from a protocol model, or a `uv lock` saying mthds is unsatisfiable. This is the **`mthds` PyPI package** (the MTHDS standard's Python client, from the sibling `mthds-python` repo) — not the MTHDS standard's spec pages, and not releasing pipelex itself, which is the `release` s

- Skill: `pipelex/bump-mthds` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add pipelex/bump-mthds`
- Raw SKILL.md: https://api.skillmd.com/api/skills/pipelex/bump-mthds/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML, Coding & Dev Tools
- Author: pipelex (https://skillmd.com/u/pipelex)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/pipelex/bump-mthds

---


# 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:

```bash
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:

```bash
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:

```bash
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:

```bash
.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:

```bash
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

```bash
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:

```bash
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

```bash
.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_version` **stamp** 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`
  — a `METHODS.toml` declaring `mthds_version = "^1.0.0"` starts or stops
  warning depending on this constant.
- **Crate fingerprints do not move.** `mthds_version` is excluded from the
  hashed payload by design (see the docstring on
  `compute_normalized_fingerprint` in `pipelex/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:

```bash
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 every `isinstance` narrowing. 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 for `model_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 NotImplementedError` to 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

```bash
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:

```bash
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*:

```markdown
- **`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-python` pins `mthds` exactly too, and `pipelex-server` pins
  `pipelex` exactly at several sites — so any bump here, breaking or not, puts
  a consumer that installs both this package and `pipelex-sdk-python` into 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. The `ledger` skill has the full gesture.
- **File the reverse direction if you moved the wire.** `mthds-python` commits
  protocol parity fixtures captured *from this engine*, so a change here that
  alters emitted output means those fixtures need recapturing — an item owned by
  `mthds-python`, naming the SHA to capture from.
- `ledger validate`, then `ledger commit`. Nothing else pushes the ledger.

### 11. Report and stop

Show the user:

- The pin move, old → new, and whether `uv.lock` actually followed.
- Whether `MTHDS_STANDARD_VERSION` moved, 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-test` failed, 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.toml` is 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.toml` without re-locking
  leaves `uv.lock` on 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 fails `uv 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_VERSION` moves 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 update` is not `make li`.** The former upgrades the whole tree.
- **`drift-check` reads the git index.** Unstaged changes give a false green.
- **`check-test-badge` runs in no local aggregate.** Green here, red in CI, on
  any commit that changes the test count.
- **A `MTHDS standard conformance` red is about the spec repo, not this
  package.** Different repo, different remedy.
- **`[project].version` is pipelex's own version.** Bumping it is the `release`
  skill's job, not this one's.

