Release Kitaru
Use the current workflows as the source of truth:
- Core:
.github/workflows/release.yml - Plugins:
.github/workflows/release-plugins.yml - Python package inventory:
release/release-units.toml - TypeScript release set:
release/typescript.mdand.github/workflows/release-typescript.yml - Frontend:
zenml-io/zenml-frontend-monorepo/.github/workflows/release-kitaru-ui.yml - Follow-up skills release: skills-release
Read AGENTS.md. Read plugins/AGENTS.md and plugins/DEVELOPMENT.md for plugin changes. Read FRONTEND-TESTING.md for core or UI work.
Do not create a tag, dispatch a publishing workflow, approve an environment, or publish an artifact without explicit user confirmation. A request to prepare a release authorizes a release PR only.
Select the release shape
Ask only for choices that cannot be derived from the repository and registries.
- Core: Release
kitaru, the selected UI, public images, the managed image, and Helm from one core tag. - One plugin: Release one selected Python plugin distribution. Do not release the UI or core.
- All plugins: Inspect and prepare every plugin release unit independently.
- Coordinated: Release core with its selected UI, then publish plugins that depend on that core version.
There is no separate bundle tag. The core tag publishes the Python package and deployables in one workflow.
Confirm the selected distributions, versions, frontend tag, publication order, and release commit before editing.
Discover release impact
Fetch current remote state and load the release inventory:
git fetch origin --prune --tags
uv run --no-project --with packaging==26.2 \
python scripts/release_units.py list --format json
For every selected unit, resolve its latest published tag. Compare that tag with origin/develop and inspect merged PRs in the range with git log, git diff, and gh pr view. Derive directly changed core and plugin units from impact-paths in release/release-units.toml. Read requires:* labels as release follow-up metadata, together with Release context, linked work, and existing changelog fragments in changelog.d/. A directly changed unit does not need a matching label when it will be published in the next applicable release. If its publication is intentionally deferred past that release, attach its exact release-label and record the intended timing in Release context.
For one plugin, start at that plugin's previous tag. For all plugins, calculate a separate range for every plugin release unit. requires:plugins means every unit is expected; report a unit with no implementation change as a red flag and require an explanation in the release PR.
For core, collect requirements for frontend, plugins, skills, ZenML docs, website, examples, and additional context from source PRs. Read linked repositories with gh or existing local checkouts. Do not write to them.
Compare declared follow-ups with the actual diff and PR context. Report unknown, conflicting, or stale signals. The diff identifies direct units; labels record deferred publication or other work that the Kitaru diff cannot show. Repository state decides what can be released.
For core, collect every merged PR label in the range and use the latest published stable core version as the input to the deterministic version rule:
uv run --no-project --with packaging==26.2 \
python scripts/release_units.py propose-core-version \
--latest-version <latest-stable-version> \
--label <first-merged-pr-label> \
--label <second-merged-pr-label> ...
Pass every label occurrence; do not summarize or discard labels before running the command. A Breaking Change label advances a pre-1.0 core to the next minor version and a post-1.0 core to the next major version. Without that label, the command advances the patch version. The version in pyproject.toml may contain +dev; it is a development placeholder and is never the input or release proposal. PR prose about an expected dependency floor does not override the command result.
Before editing, rerun the command with --candidate <proposed-version> and require it to pass. Do not prepare a different core version.
Propose explicit versions and changelog entries after discovery. Check PyPI versions and Git tags before proposing a version, and never reuse a published version. Get the user's acceptance before editing release metadata.
Separate implementation from release preparation
Feature PRs leave existing plugin package versions and server default requirements/display versions unchanged. Put behavior changes in the package's Unreleased changelog section. The release-preparation PR selects package versions and updates all matching default-catalog metadata together. New packages require initial metadata, but becoming a server default remains an explicit release decision.
Follow the development dependency policy in plugins/DEVELOPMENT.md. Plugins compatible with published core keep their supported minimum. A plugin requiring unreleased core uses an exact dependency on the current root development version, such as kitaru==0.25.0+dev, and records the needed core change in Release context.
Before changing core to its selected release version, inspect every plugin's core dependency. Replace each resolved development pin with the selected compatible release floor, such as kitaru>=0.26.0, preserving extras and compatible upper bounds. Do not guess the future version in the feature PR. An unresolved development pin blocks release preparation; account for every affected plugin and its publication timing. A plugin-only release can replace the pin only when a published core contains the required change.
Regenerate plugins/uv.lock after these conversions. Verify the selected wheels' Requires-Dist metadata contains no development placeholder or local checkout dependency before handing over publication commands.
Inspect the TypeScript packages separately from the Python inventory. Include their lockstep release when their changes require publication. Inspect the Kitaru skills repository for a pending release and record its own version and compatibility requirements.
Apply version rules
- Use canonical PEP 440 for Python:
X.Y.ZorX.Y.ZrcN. - The core workflow converts
X.Y.ZrcNtoX.Y.Z-rc.Nfor Docker and Helm. - Use
vX.Y.ZorvX.Y.Z-rc.Nas the frontend workflow input. - The frontend release tag is
kitaru-ui-vX.Y.Zorkitaru-ui-vX.Y.Z-rc.N. - Never reuse a published version or move a published tag.
- Increment the RC number for another candidate of the same target.
- Remove the RC suffix to accept a candidate without changing
X.Y.Z.
For pre-1.0 plugins, use a patch when the public API and observable output stay compatible for existing supported inputs. Use a minor for new capabilities or breaking behavior. Importer output includes session grouping, IDs, node structure, field mappings, normalized values, and incomplete or failed trace handling.
Release or select the frontend
Perform this section before the core preparation PR when the core needs a new UI release.
- Inspect
zenml-io/zenml-frontend-monorepoand select an exact commit onmain. - Confirm its Kitaru UI checks pass.
- Select a stable frontend version input, such as
v0.4.0, for an official core release. - Ask for explicit confirmation before publishing.
- Emit minimal tag commands for the frontend repository, using publication-handoff.md. Use
HEADonly whenmainpoints to the reviewed commit:
git checkout main
git tag kitaru-ui-v0.4.0 HEAD
git push origin kitaru-ui-v0.4.0
The tag push starts release-kitaru-ui.yml.
- Capture and monitor the exact run:
gh run list --repo zenml-io/zenml-frontend-monorepo \
--workflow release-kitaru-ui.yml --limit 5
gh run view <run-id> --repo zenml-io/zenml-frontend-monorepo
- Verify the release tag and both assets:
gh release view kitaru-ui-v0.4.0 \
--repo zenml-io/zenml-frontend-monorepo
Require kitaru-ui.tar.gz and kitaru-ui.tar.gz.sha256. Verify the workflow ran at the reviewed commit and the release is stable. The frontend workflow derives its release channel from whether the selected commit is on main; official core releases require a stable bundle.
If the user asks only for a preparation PR, do not push the frontend tag or dispatch the frontend workflow. The user can explicitly select an expected frontend tag before it exists. Mark asset verification as pending and do not create the core tag until both assets exist.
Prepare a core release PR
Create a branch from current origin/develop. Preserve unrelated work in the active checkout.
- Set
[project].versioninpyproject.toml. - Add a concise release-specific fragment to
changelog.d/when needed. - Preview the release section with
uv run python scripts/changelog_fragments.py build --version <version> --draft, then run it without--draft. This moves every fragment into a newCHANGELOG.mdsection for the selected version and deletes the fragment files. - Create
releases/python/kitaru/<version>.toml:
schema-version = 1
kitaru-version = "<python-version>"
ui-tag = "<kitaru-ui-tag>"
- Resolve plugin development dependencies to the selected core version, then run
uv lockanduv lock --project plugins. Keep unrelatedexclude-newertimestamp churn out of the diff. - Run
uv run python scripts/generate_openapi.pyand commitopenapi/openapi.json. - If a default plugin version changes, update every matching server catalog requirement and display version. The release inventory validates these values against the package manifest.
Read default membership from release/release-units.toml. Do not copy a fixed plugin count or a retired plugin name into the skill.
Check the standalone quickstart dependency floor and lockfile under examples/python/pydantic_ai_ticket_resolver/. Its README uses uv sync --frozen, so changing only the dependency floor does not update the installed version. Refresh both files to a published compatible core version, retaining unrelated dependency pins:
uv add --project examples/python/pydantic_ai_ticket_resolver --no-sync \
--upgrade-package "kitaru==<published-version>" \
"kitaru[cli,mcp,server,worker]>=<published-version>"
With the local test PostgreSQL available, run uv sync --frozen and uv run --frozen python scripts/run_ci_e2e.py from the example directory before installing candidate wheels. The published-dependency run and the candidate-wheel run verify different installation paths. Preserve the existing lockfile cutoff when no unrelated dependency update is needed.
Do not put an unpublished version or a local wheel path into the public quickstart lockfile. If the example requires the upcoming release, record its dependency refresh and frozen end-to-end check as a post-publication follow-up; candidate-wheel success alone does not complete it.
The frontend declaration contains only the schema version, Kitaru version, and trusted frontend tag. The workflow downloads and verifies the published checksum.
Prepare a plugin-only release PR
Select the distribution from release/release-units.toml.
- Update only the selected package version:
uv version --project plugins --package <distribution> <version> --no-sync
- Update its Kitaru compatibility range when required.
- Update its package changelog.
- Run
uv lock --project plugins. - Leave every unselected package unchanged.
For a default-catalog plugin, update matching server requirements and display versions in this release-preparation PR. The current inventory validator requires them to match the selected package version. Feature PRs avoid that coupling by leaving both versions and default pins unchanged.
For a coordinated release, prepare core and plugin versions together. Tag core first. After the core publish-python job succeeds and the exact version is available on PyPI, tag dependent plugins. Core deployables and other jobs can continue while plugins publish. A default pin may reference a queued plugin version when the repository's pending-default behavior is available and verified. An independent plugin release uses an already-published compatible core.
Patch an existing plugin release line
Use the unit's maintenance branch from release/release-units.toml, named release/<plugin>/<major.minor>. Confirm the branch exists and the requested version is the next unused patch version. The plugin workflow accepts a tag whose commit belongs to develop or that exact maintenance branch.
When the same bug exists on develop, merge the implementation there first. Then cherry-pick only its implementation commit onto a fix branch based on the maintenance line:
git fetch origin --prune --tags
git switch --track origin/release/langfuse/0.4
git switch -c fix/langfuse-0.4.1
git cherry-pick <implementation-commit>
When the bug only affects a superseded line, create the fix branch from that maintenance line and implement the fix there. Forward-port it only if the same bug exists on develop.
On the fix branch:
- Bump only the selected plugin to the patch version.
- Update its changelog and
plugins/uv.lock. - For a default-catalog plugin, update matching bootstrap requirements and display versions only when that maintenance line uses the patched version.
- Run the selected plugin's focused tests, release inventory validation, and artifact smoke.
- Open the PR against the maintenance branch, not
develop.
After the maintenance PR merges, resolve its exact merge commit and prepare the namespaced tag command. Do not push the tag without explicit authorization:
Use HEAD only when the maintenance branch points to that reviewed commit; otherwise use its literal SHA:
git checkout release/langfuse/0.4
git tag python/kitaru-langfuse-importer/v0.4.1 HEAD
git push origin python/kitaru-langfuse-importer/v0.4.1
Leave a newer package version and default-catalog declaration on develop unchanged when patching a superseded line.
Validate the preparation
Always run:
git diff --check
just check
uv run --no-project --with packaging==26.2 python scripts/release_units.py validate
For core metadata and UI selection, run:
uv run python scripts/release_ui.py --version <core-version>
uv run pytest -q tests/scripts/test_release_ui.py tests/scripts/test_release_units.py
For plugin metadata, dependencies, or default pins, run:
uv run --project plugins ruff format --config plugins/pyproject.toml --check plugins
uv run --project plugins ruff check --config plugins/pyproject.toml plugins
uv run --project plugins ty check --project plugins
uv run --project plugins pytest -q -c plugins/pyproject.toml plugins/tests tests/server/test_default_plugins.py
just plugin-artifact-smoke
Review the final version, frontend tag, dependency ranges, exact default pins, changelog, file list, and validation results.
Open the preparation PR
Commit only the release files. Push the branch and open a draft PR to develop.
Include:
- release shape and versions
- source PRs included for each release unit
- frontend tag and asset status for core
- dependency and default-pin decisions
- plugin coverage and explanations for missing units in an all-plugin release
- linked skills, ZenML docs, website, examples, and other follow-ups
- validations run
- exact post-merge tag and follow-up order
## Reviewer Noteswith a concrete review path
Stop after the PR unless the user explicitly asks to publish.
After the PR merges, verify its exact merge commit, then emit the publication command handoff in the PR and final response exactly as publication-handoff.md specifies: grouped by repository and source branch, covering every selected Python plugin, the TypeScript release set when selected, any new frontend release, manual main promotion, and the Kitaru skills release handoff. Do not execute publication commands during preparation.
Rehearse before publication
Manual dispatch builds and validates without publishing. These are agent-side rehearsal commands, not part of the user-facing tag handoff.
Core:
gh workflow run release.yml --repo zenml-io/kitaru --ref <reviewed-branch-or-tag> \
-f package-tag=python/kitaru/v<python-version>
Plugin:
gh workflow run release-plugins.yml --repo zenml-io/kitaru --ref <reviewed-branch-or-tag> \
-f package-tag=python/<distribution>/v<python-version>
Use an existing branch or tag that resolves to the reviewed eligible commit. Confirm the run's headSha matches that commit, inspect its artifacts, and confirm no publishing job ran.
Publish a plugin
After the preparation PR merges, select its exact reachable develop commit.
Use the branch and commit checks in publication-handoff.md before emitting:
git checkout develop
git tag python/<distribution>/v<python-version> HEAD
git push origin python/<distribution>/v<python-version>
Approve the package's PyPI environment when required. Verify the wheel, source distribution, hashes, and immutable GitHub Release.
For a stable release, the workflow creates or fast-forwards the unit's maintenance branch after the GitHub Release exists. A maintenance-line patch uses the exact reviewed maintenance-branch commit prepared above instead of a develop commit.
Publish core and deployables
Before tagging, verify the selected stable frontend bundle exists and release metadata is complete. Coordinated plugin versions can remain queued when pending-default behavior has been verified.
Use the branch and commit checks in publication-handoff.md before emitting:
git checkout develop
git tag python/kitaru/v<python-version> HEAD
git push origin python/kitaru/v<python-version>
The tag starts .github/workflows/release.yml. The workflow:
- downloads and verifies the selected frontend
- builds and tests the wheel
- publishes Kitaru to PyPI
- builds and publishes client, worker, server, and managed images
- publishes the Helm chart
- moves public Docker
latestaliases only for a stable release - creates the immutable GitHub Release
- creates or fast-forwards the stable maintenance branch
- creates a draft post-release PR that sets core to
<version>+devand updates both lockfiles and the generated OpenAPI version
Dependent plugin tags can be pushed after step 3 succeeds and the exact core package is available on PyPI. They do not wait for the remaining jobs.
For a stable release, fast-forward main to the tagged release commit before merging the generated development-reset PR. The reset PR must leave main at the clean release version and change only pyproject.toml, uv.lock, plugins/uv.lock, and openapi/openapi.json on develop.
Approve required environments only after checking the candidate evidence. A managed-image failure is reported as a warning and does not block public deployables.
Verify each published surface independently. Report core PyPI availability, public artifacts, managed-image status, installer smoke, maintenance-branch state, and development-reset status separately. A reset-job failure can make the workflow red after successful artifact publication. Diagnose and report that follow-up failure without describing the already-published artifacts as unpublished.
After the required core and plugin versions are available on PyPI, complete any pending quickstart dependency refresh through a reviewed follow-up PR and rerun its frozen end-to-end test. Report which branch contains that update: the public README links to main, and merging a follow-up into develop alone does not update the public example. The development-reset PR does not currently refresh the quickstart lockfile.
The workflow does not update main. After the newest stable core's public artifacts and GitHub Release succeed, inspect any remaining workflow failure and emit the main fast-forward block from publication-handoff.md for the release owner to run. Do not execute it on their behalf. The fast-forward triggers the existing docs workflow. Skip this step for prereleases and older maintenance-line core releases.
After core and required plugins are available, hand off pending zenml-io/kitaru-skills changes to its skills-release skill. Read the current skill before emitting that repository's commands. It owns the independent skills version, develop release commit, tag, main promotion, and GitHub Release. Core publication does not itself authorize publishing skills.
Curate GitHub Release Notes
After every selected release workflow has succeeded and its published artifacts have been verified, curate the GitHub Release body for every release created in this release flow. This is a post-publication metadata step, not a replacement for the publication workflows: their autogenerated notes remain the fallback until curation is complete.
Tags, versions, uploaded assets, registry artifacts, release targets, and prerelease/latest state are immutable. A release body may be deliberately updated after publication only with explicit user confirmation. Do not curate releases that are outside the selected release set, and do not rewrite an older UI release merely because a core release references it.
Build the exact release set before drafting. It can include:
- the core tag
python/kitaru/v<version>; - each selected Python plugin tag from
release/release-units.toml; - the TypeScript release-set tag
typescript/kitaru/v<version>, which covers@zenml-io/kitaru,@zenml-io/kitaru-mastra, and@zenml-io/kitaru-vercel-aitogether; - a new
kitaru-ui-v<version>release inzenml-io/zenml-frontend-monorepoonly when this release flow published that UI release.
For every selected release, first capture its current state with gh release view <tag> --repo <repo> --json name,tagName,targetCommitish,isPrerelease,assets,body and record its latest state separately with gh release list --repo <repo> --json tagName,isLatest. Resolve the previous tag from the same tag family, never from GitHub's repository-wide previous-release selection. The comparison families are python/kitaru/v* for core, the selected unit's tag-prefix for a Python plugin, typescript/kitaru/v* for TypeScript, and kitaru-ui-v* for the UI. A stable release compares with the previous stable tag in its family, never its last release candidate. A prerelease compares with the nearest preceding prerelease in its release line, falling back to the latest stable tag in its family. For a maintenance-line plugin patch, choose a predecessor from the same <major>.<minor> release line whose tag is an ancestor of the current tag, and verify that ancestry with git merge-base --is-ancestor <previous-tag> <tag>. If no eligible predecessor exists, mark it as the first release in that comparison family rather than inventing one. This avoids cross-package and cross-maintenance-line comparison links.
Use reviewed release evidence, not PR titles alone:
- Core: the released
CHANGELOG.md, merged PR context, and the core-tag-to-core-tag diff. - Python plugin: that distribution's changelog, its
impact-paths, and the package-tag-to-package-tag diff. - TypeScript:
release/typescript.md, the three package manifests and changelog or PR context, distinguishing client, Mastra, and Vercel AI changes when they differ. - UI: the path-scoped source and existing note-generation contract in
zenml-frontend-monorepo, plus the UI-tag-to-UI-tag diff.
Replace the autogenerated PR list with a proportionate reader-facing body. Start with ## Highlights: one to three short paragraphs explaining observable effects relative to the previous release, or introduce the initial capability for a first release. A patch release can say that it is focused maintenance; a minor release should foreground the most consequential capability. Follow with nonempty ## Added, ## Changed, ## Fixed, or ## Infrastructure sections when they make the release clearer. For TypeScript, identify which of the three published packages each meaningful item affects. Use code formatting for identifiers, include the same-family Full Changelog comparison link only when an eligible predecessor exists, and omit empty sections.
Do not include site-only work, Dependabot-only bumps, internal refactors without an observable effect, reverted or no-op change pairs, unverified claims, or private operational details. Do not use --generate-notes for the curated body. Keep each paragraph and list item on one physical line, and do not use em dashes or en dashes.
Show every complete draft and its tag-to-previous-tag mapping to the user before editing GitHub. If four or more independent releases need curation, the release coordinator may delegate evidence gathering and drafts in small batches. The coordinator owns the exact release set, checks every draft against its source evidence, collects one approval, and performs all GitHub edits and verification. Delegates do not edit GitHub Releases.
Only after explicit approval, write each approved body to a temporary file and update only the body:
gh release edit <tag> --repo <repo> --notes-file <notes-file>
Do not pass title, asset, target, prerelease, or latest flags. Re-fetch each release with the same JSON fields, record its latest state again with gh release list --repo <repo> --json tagName,isLatest, and verify that its tag, target, assets, prerelease/latest state are unchanged and its body exactly matches the approved draft. Report every curated release and any release that remains pending or was intentionally excluded.
Recover a failed release
Do not delete or move a release tag. Do not reuse a version for different bytes.
- Inspect the failed step and completed external writes.
- Confirm existing artifacts match the immutable tag.
- Determine whether retrying the original job can succeed. A workflow fix on a newer branch does not change the tagged source.
- Ask for explicit confirmation before retrying publication.
- Rerun the failed jobs of the original tag-triggered run to preserve its event and immutable artifacts:
gh run rerun <original-run-id> --repo zenml-io/kitaru --failed
Manual dispatch is a non-publishing rehearsal and cannot finish a failed publication. If recovery needs different package bytes, prepare a new version through a reviewed PR.
Keep credentials, private endpoints, account IDs, and internal infrastructure names out of committed content and PR descriptions.