Human-assisted (RS-6). This is a runbook, not automation: every step here needs
rights a CI session doesn't have (pushing tags, pausing/resuming workflows, PyPI/npm
publish credentials). No step in this document has been executed by an agent — verify
the "current state" section against git tag, npm view, and PyPI before acting, since
it can drift the moment someone else runs a step.
The three language-binding release workflows (bindings-release.yml for
acdp-node, acdp-py-release.yml, acdp-wasm-release.yml) are tag-triggered:
pushing a tag matching acdp-node-v* / acdp-py-v* / acdp-wasm-v* runs the full
build-and-publish job unconditionally (if: github.event_name == 'push' || !inputs.dry_run
— the dry_run input only guards a manual workflow_dispatch, never a tag push). Some
past releases were published via manual workflow_dispatchwithout a corresponding
tag ever being pushed, so git tag now understates what's actually live on npm/PyPI.
⚠ H11: pushing a retroactive tag for a version that is already published will
refire the matching workflow and attempt to publish that exact version again — npm/PyPI
reject a republish of an existing version, so the run fails loudly (a red CI run, no
data corruption) rather than silently succeeding or overwriting anything. Loud-but-safe
is still not free: it wastes a CI run, pages whoever watches Actions, and (on the node
workflow specifically) sits before the acdp-released dispatch step to
acdp-control-plane, which only fires on the steps after a successful publish — so a
failed retroactive-tag run does not spuriously trigger a downstream auto-bump PR
there. Still, don't rely on "it'll just fail safely" as the plan — pick one of the three
options below deliberately.
release-plz.yml has a "Release SDK bindings at the acdp version" step that dispatches
all three binding release workflows (acdp-py-release.yml, bindings-release.yml,
acdp-wasm-release.yml) via workflow_dispatch -f dry_run=false whenever the core
acdp crate itself releases — that's the SDK cascade referenced throughout this doc.
As of 2026-08-30, each of those three workflows now pushes its own matching git tag
(acdp-py-v$VER / acdp-node-v$VER / acdp-wasm-v$VER) automatically, in a "Tag the
release" step that runs immediately after a successful workflow_dispatch-triggered
publish (gated on github.event_name == 'workflow_dispatch' && !inputs.dry_run, so it
never fires on an actual tag push — the tag already exists in that case — or on a dry
run — nothing was published). This closes the root cause behind the "Why this exists"
section above: a workflow_dispatch publish can no longer land without a tag.
That same "Tag the release" step also fails the job if the version input was left
blank on a manual dispatch — deliberately, since a blank version can't be turned into a
sane tag name. This runs after the publish itself, so a manual non-dry-run dispatch
with a blank version will now publish successfully and then fail the job at the tag
step (a previously-green pattern that is red now). Always pass an explicit
-f version=... on a manual non-dry-run dispatch.
This does not retire the manual tag-push instructions elsewhere in this document
(Steps 1–2 below, and the RS-8 addendum's Steps further down). Those remain exactly what
you want for the manual release path — a human deliberately dispatching a workflow
or pushing a tag themselves outside the automated cascade, e.g. testing a binding
release before the core crate is ready to cut, or recovering from a failed automated
cascade. The automated tagging above is simply no longer the only path that produces
a correctly-tagged release; it's the path the SDK cascade takes by default.
Two deliberate scope limits on this automation, so this doc doesn't imply broader
coverage than what actually shipped:
Consumer-bump notification stays manual-path-only. The new tag push does not
itself re-trigger the workflow that pushed it — a GITHUB_TOKEN-authored push does
not start new workflow runs. release-plz.yml's own comment notes the opposite case:
workflow_dispatch is exempt from that "no recursive workflow runs" rule, which is
why the SDK cascade's dispatched runs execute at all — but a plain tag push (like the
one the new "Tag the release" step performs here) gets no such exemption, so it does
not start a new run. That means the push-gated
consumer-notification dispatch steps (acdp-py-release.yml / bindings-release.yml,
gated if: github.event_name == 'push', which notify acdp-playground and
acdp-control-plane respectively) still only fire on an actual tag-push trigger,
never on the automated workflow_dispatch cascade. Downstream consumer-bump
notification remains a manual-path-only concern; this automation does not close that
separate gap.
A partial-failure recovery re-run correctly skips the tag step. The "Tag the
release" step uses a plain if: (carrying only its own gate above), which implicitly
requires success() on everything before it in the job. If an earlier step in that
publish job fails — including on a partial-failure recovery re-run — the tag step is
skipped. This is intentional, not a bug: an incomplete publish should not get tagged
as if it fully succeeded.
The sibling plan (agentcontextdistributionprotocol/plans/siblings/acdp-rs.md, RS-6)
was written 2026-08-28 and states PyPI is "still 0.7.0." That's now stale — live
PyPI/npm state as of this runbook is materially ahead of what the plan assumed:
0.7.0, 0.8.0 — the 0.8.0 workflow_dispatch at commit be72ca24 succeeded; a same-day follow-up attempt at 0.8.1 (commit c4c9be8d, run 29129059112) failed during cargo metadata on a getrandom version conflict (acdp-wasm required getrandom ^0.4 with feature js, which only exists on 0.2.x) — that bug was the dual-major getrandom pin mixup fixed later by PR #153 (commit c55eacd, 2026-08-28). 0.8.1 was never published for wasm(stale as of 2026-08-30 — a 0.8.1 retry did later succeed, tagged acdp-wasm-v0.8.1, and npm now holds 0.7.0 through 0.8.4; see the manifest column and re-run npm view/git tag before trusting this cell).
0.8.4 (bindings/acdp-wasm/Cargo.toml, bumped as of 2026-08-30 to match npm's actual latest published version, 0.8.4 — the bump is purely cosmetic per the workflow's re-stamp-at-build-time design and not itself a publish; see "Automated tag-on-publish" above)
Per "Automated tag-on-publish" above, any future workflow_dispatch publish (manual or
via the automated cascade) now tags itself automatically; no human backfill should be
needed going forward for new releases.
Re-run these checks before executing anything below — another session or the bot may
have moved the state again:
Target: all three bindings published and tagged at the crate family version,
0.8.1 — matching the accept criterion ("node/py/wasm published at the family version
with matching git tags"). Node and py content is already there (published, just
untagged); wasm genuinely needs one more publish.
Do this before pushing any tag in this runbook, retroactive or new — it's what makes
every following step safe regardless of which option below you pick for the retroactive
markers:
With the workflows paused, tags can be pushed safely — nothing will fire. Point each
tag at the exact commit that workflow run actually published from (not current HEAD),
so the tag is historically accurate:
git tag acdp-node-v0.8.0 c2a89032a5b706d95efd1e5e69e1f7df22aa6084git tag acdp-node-v0.8.1 c4c9be8d68dda2842b6a9b06efc2157d79090198git tag acdp-py-v0.8.0 c2a89032a5b706d95efd1e5e69e1f7df22aa6084git tag acdp-py-v0.8.1 c4c9be8d68dda2842b6a9b06efc2157d79090198git tag acdp-wasm-v0.8.0 be72ca24fe7d0246b1fe132ef10c0eaed7ecfc0bgit push origin acdp-node-v0.8.0 acdp-node-v0.8.1 acdp-py-v0.8.0 acdp-py-v0.8.1 acdp-wasm-v0.8.0
Do not tag acdp-wasm-v0.8.1 here — it was never actually published (see the table
above), so tagging it would be a false historical record. It gets a real, live tag in
Step 2 instead.
This is "tag with workflows paused" (H11 option 1) rather than non-triggering tag names:
it keeps the permanent tag names consistent with the live ones future releases will use
(acdp-node-v0.8.0, not some retroactive/… variant that git describe/tooling won't
recognize), at the cost of the two-step pause/resume — a fine trade for a one-time
backfill.
This is the one artifact that genuinely isn't at the family version yet.
Confirm the getrandom fix from PR #153 is present on the commit you're releasing
from (it is, as of main post-2026-08-28) — spot-check:
grep -n "getrandom" bindings/acdp-wasm/Cargo.toml# expect: getrandom = { version = "0.2", features = ["js"] }# getrandom_wasm = { package = "getrandom", version = "0.4", features = ["wasm_js"] }
Bumping bindings/acdp-wasm/Cargo.toml's version ahead of a release is optional,
low-priority cleanup, same as the equivalent node/py manifest bump in Step 3 below
— the workflow re-stamps this from the tag name at build time regardless, so the
committed value is never functionally required for a publish to succeed; it only
keeps the working tree honest for a human reader. (As of 2026-08-30, the manifest
already reads 0.8.4, ahead of any version this Step 2 was originally written
against — bump it again to whatever version you're actually releasing before
dispatching, or skip it and let the workflow's re-stamp handle it.)
Re-enable just the wasm workflow and do a dry run first, to catch any other drift
before spending a real publish attempt:
gh workflow enable acdp-wasm-release.ymlgh workflow run acdp-wasm-release.yml -f version=0.8.1 -f dry_run=true# watch it green, then:git tag acdp-wasm-v0.8.1 <the commit the dry run built from>git push origin acdp-wasm-v0.8.1
The pushed tag triggers the real (non-dry-run) publish.
Re-enable the other two workflows now that no more retroactive tags are pending:
bindings/acdp-node/package.json and bindings/acdp-py/Cargo.toml still declare
0.8.0 locally even though 0.8.1 is already published — harmless (the release
workflows stamp the version from the tag at build time, not from the committed
manifest) but confusing to a reader. Bump both to 0.8.1 in a normal PR, no tag
implications either way.
Once this runbook's tags exist, per 00-overview.md §3: PG-1 (PyPI→family version,
already effectively true, just needed the tag) and UI-1 (wasm bump + tagged release)
are unblocked, and CI-3 (tag-triggered publish = propagation head) can record this as
the policy going forward.
PR #175 merged to main as 510ca40 (2026-08-30). Both dry runs went green
(acdp-py-release.yml, bindings-release.yml, -f version=0.8.3), then real tags
acdp-py-v0.8.3 and acdp-node-v0.8.3 were pushed against 510ca40. Both release
workflows completed successfully — acdp0.8.3 is live on PyPI, and
@agentcontextdistributionprotocol/acdp0.8.3 plus all four platform packages
(acdp-darwin-arm64, acdp-darwin-x64, acdp-linux-x64-gnu, acdp-linux-arm64-gnu)
are live on npm. Verified via real pip install acdp==0.8.3 / npm install @agentcontextdistributionprotocol/acdp@0.8.3 installs plus an end-to-end
publish-request-with-anchors + verify_content_hash smoke test against each
installed package, not just the registry listing.
One transient hiccup, not a real failure: acdp-linux-x64-gnu@0.8.3's versioned
registry endpoint 404'd for about a minute after the CI publish step reported success
(the CI log showed the correct + @agentcontextdistributionprotocol/acdp-linux-x64-gnu@0.8.3
line) — the same npm read-replica propagation lag this runbook's own earlier section
already diagnosed for a different package. Resolved on its own; no republish needed.
acdp-py/acdp-node are now genuinely in lock-step at 0.8.3 (previously drifted,
0.8.1/0.8.2 respectively — see the superseded "Current state" section below, kept
for historical context on the drift pattern rather than deleted).
future release; re-check git tag / the registries fresh each time)
Artifact
Latest git tag
Actually live
Committed manifest
acdp (PyPI)
acdp-py-v0.8.1
0.8.2 (verified via pypi.org/pypi/acdp/json)
bindings/acdp-py/Cargo.toml: 0.8.0 (stale either way — irrelevant to publish, see below)
@agentcontextdistributionprotocol/acdp (npm)
acdp-node-v0.8.2
0.8.2 (verified via registry.npmjs.org)
bindings/acdp-node/package.json: 0.8.0 (same)
The git tag for acdp-py understates reality by one patch version — a 0.8.2 was
published to PyPI without a matching tag ever being pushed, the exact pattern this
runbook's "Why this exists" section describes. This doesn't block anything below; it
just means the target version is computed from the live registry, not from git tag.
Neither committed manifest (Cargo.toml/package.json) needs to change before
tagging — confirmed for both workflows, not assumed: bindings-release.yml's and
acdp-py-release.yml's "Stamp release version" steps both compute the published
version as ${{ inputs.version }} (dispatch) or ${GITHUB_REF_NAME#acdp-*-v} (tag
push), then overwrite the manifest file at build time — the committed value is never
read. Only the CHANGELOG.md entries need to already exist (they do — see the
## Unreleased sections in both bindings/acdp-py/CHANGELOG.md and
bindings/acdp-node/CHANGELOG.md).
Following the precedent set by RS-8's own core-crate release (0.8.1 → 0.8.2, a
patch bump for the same purely-additive, non-breaking anchors field): both
bindings go from their live 0.8.2 to 0.8.3. This happens to restore py/node
lock-step (both currently live at 0.8.2, matching for once) — a side effect, not a
guarantee this holds for the next release after this one.
Dry-run first, both workflows, to catch a build break before it's irreversible:
gh workflow run acdp-py-release.yml --ref feat/rs8-bindings-anchors -f dry_run=true -f version=0.8.3gh workflow run bindings-release.yml --ref feat/rs8-bindings-anchors -f dry_run=true -f version=0.8.3
Wait for both to go green (gh run watch <id> --exit-status or gh run list --workflow=<name> --limit 1) before continuing. If either fails, fix and re-dry-run
— do not proceed to a real tag push on a red dry run.
Note: dispatch this against feat/rs8-bindings-anchors only until it merges to
main; once merged, dispatch (and the real tag, below) should point at main.
Push the real tags, once merged to main and both dry runs are green:
git tag -a acdp-py-v0.8.3 -m "acdp-py v0.8.3" <main-HEAD-sha>git tag -a acdp-node-v0.8.3 -m "acdp-node v0.8.3" <main-HEAD-sha>git push origin acdp-py-v0.8.3 acdp-node-v0.8.3
Each push triggers its workflow for real (if: github.event_name == 'push' skips
the dry_run gate entirely — a tag push always publishes).
Verify live, the same way the earlier 0.8.2 npm incident was diagnosed in this
family's history — don't trust the packument/listing endpoint alone (it can lag);
check the versioned endpoint directly:
For npm specifically, also confirm the 4 platform packages
(acdp-darwin-arm64/acdp-darwin-x64/acdp-linux-x64-gnu/acdp-linux-arm64-gnu)
published at 0.8.3 too, not just the root loader package — the bindings-release.ymlpublish job runs napi pre-publish (platform packages) and npm publish (root) as
two separate steps; a partial success there is a known past failure mode for this
pipeline (napi-cli 3.x's --gh-release default caused exactly this on the 0.8.2
release — since fixed, but worth an explicit check here rather than assuming).
H11 reminder: if step 1's dry run or step 2's tag push needs a re-run for any
reason, do NOT re-push a tag for a version that already published successfully —
npm/PyPI reject the republish (harmless 403, per this family's own incident history)
but it's a wasted, alarming red CI run. Bump to the next patch instead if a real
do-over is needed.
This is a read, not an edit — docs/version-matrix.md lives in the spec repo
(agentcontextdistributionprotocol/agentcontextdistributionprotocol), not here; per this
family's cross-repo convention, the spec-repo session doing SPEC-11 should pull the
following facts from this runbook rather than this repo editing that file directly:
acdp (Rust, this repo): crate 0.8.1, tags acdp-v0.8.1 etc. — already current.
acdp-node / acdp-py / acdp-wasm: once this runbook's Steps 1–2 are executed,
all three sit at 0.8.1 with matching git tags (acdp-node-v0.8.1, acdp-py-v0.8.1,
acdp-wasm-v0.8.1) and their published registries (npm, npm, PyPI respectively).
The RFC coverage is identical across all four artifacts (RFC-ACDP-0001 through 0015,
0009 reserved) — acdp-wasm is verification-only (no producer/signing surface), the
other three implement both sides.
If Step 2 hasn't run yet when SPEC-11 executes, say so explicitly in the version
matrix rather than asserting 0.8.1 for wasm — check npm view @agentcontextdistributionprotocol/acdp-wasm versions first.