From 98eeb47e1dc59f68e06a40588b1c0653842718b6 Mon Sep 17 00:00:00 2001 From: M3gA-Mind Date: Tue, 3 Nov 2026 15:16:02 +0530 Subject: [PATCH 1/3] ci(release): release from a tag on main instead of a bot commit The Release workflow bumped the version, committed "Release vX" as github-actions[bot] and pushed only the tag, because main's ruleset rejects direct pushes. Every release tag sat off main, main's version lagged the tags, and each new pin looked like a rewind downstream. The version bump now merges through a reviewed pull request, and a release is a vX.Y.Z tag on a commit on main. The workflow runs on the tag push (or a dispatch from main naming an existing tag), checks out the tag, refuses it unless its commit is on main and it matches the workspace version, then runs the same checks and crates.io publishing as before. It no longer commits, tags or pushes, and needs only read access. Replace the next-version script, which only the old flow used, with a test of the release workflow's guards. Bump the workspace to 3.1.4, above the v3.1.0 to v3.1.3 tags cut off main, and document the steps. --- .github/scripts/next-release-version.sh | 52 ------- .github/scripts/next-release-version.test.sh | 40 ----- .github/scripts/test-release-workflow.py | 152 +++++++++++++++++++ .github/workflows/ci.yml | 4 +- .github/workflows/release.yml | 126 ++++++--------- CHANGELOG.md | 9 ++ CONTRIBUTING.md | 16 ++ Cargo.lock | 24 +-- Cargo.toml | 2 +- crates/tinyagents-harness-tools/Cargo.toml | 6 +- crates/tinyagents-harness/Cargo.toml | 2 +- crates/tinyagents-hooks/Cargo.toml | 4 +- crates/tinyagents-live/Cargo.toml | 4 +- crates/tinyagents-orchestration/Cargo.toml | 14 +- crates/tinyagents-registry/Cargo.toml | 4 +- crates/tinyagents-runtime/Cargo.toml | 10 +- crates/tinyagents-session/Cargo.toml | 4 +- crates/tinyagents-tasks/Cargo.toml | 2 +- 18 files changed, 261 insertions(+), 214 deletions(-) delete mode 100755 .github/scripts/next-release-version.sh delete mode 100755 .github/scripts/next-release-version.test.sh create mode 100644 .github/scripts/test-release-workflow.py diff --git a/.github/scripts/next-release-version.sh b/.github/scripts/next-release-version.sh deleted file mode 100755 index f8530e473..000000000 --- a/.github/scripts/next-release-version.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env bash -# Computes the next release version for the Release workflow. -# -# usage: next-release-version.sh [tag ...] -# -# The base is the highest of the workspace version and every plain `vX.Y.Z` -# tag given. The workspace version alone is not a safe base: releases are -# published from a tag, and the version in Cargo.toml can lag the latest tag -# (it sat at 2.1.4 while v2.2.0 through v2.8.0 existed, so the workflow kept -# proposing a tag that was already taken). Prints ` `. -set -euo pipefail - -bump="${1:?bump (patch|minor|major) required}" -workspace_version="${2:?workspace version required}" -shift 2 - -semver='^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$' -if ! [[ "$workspace_version" =~ $semver ]]; then - echo "Workspace version '${workspace_version}' is not X.Y.Z" >&2 - exit 1 -fi - -base="$workspace_version" -for tag in "$@"; do - version="${tag#v}" - # Pre-release and other non-release tags never set the base. - if [[ "$tag" == v* && "$version" =~ $semver ]]; then - base="$(printf '%s\n%s\n' "$base" "$version" | sort -V | tail -n1)" - fi -done - -IFS=. read -r major minor patch <<< "$base" -case "$bump" in - major) - major=$((major + 1)) - minor=0 - patch=0 - ;; - minor) - minor=$((minor + 1)) - patch=0 - ;; - patch) - patch=$((patch + 1)) - ;; - *) - echo "Unsupported bump: ${bump}" >&2 - exit 1 - ;; -esac - -echo "${base} ${major}.${minor}.${patch}" diff --git a/.github/scripts/next-release-version.test.sh b/.github/scripts/next-release-version.test.sh deleted file mode 100755 index f3d75b917..000000000 --- a/.github/scripts/next-release-version.test.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env bash -# Tests for next-release-version.sh. Run: .github/scripts/next-release-version.test.sh -set -euo pipefail - -script="$(cd "$(dirname "$0")" && pwd)/next-release-version.sh" -failures=0 - -expect() { - local name="$1" want="$2" - shift 2 - local got - if got="$("$script" "$@" 2>/dev/null)"; then :; else got="error"; fi - if [[ "$got" != "$want" ]]; then - echo "FAIL ${name}: want '${want}', got '${got}'" >&2 - failures=$((failures + 1)) - fi -} - -# The regression: a stale workspace version must not hide newer tags. -expect "stale workspace minor" "2.8.0 2.9.0" minor 2.1.4 v2.1.4 v2.2.0 v2.7.0 v2.8.0 -expect "stale workspace patch" "2.8.0 2.8.1" patch 2.1.4 v2.8.0 v2.2.0 -expect "stale workspace major" "2.8.0 3.0.0" major 2.1.4 v2.8.0 -# Numeric, not lexical, ordering. -expect "numeric ordering" "2.10.0 2.11.0" minor 2.1.4 v2.9.0 v2.10.0 -# A workspace version ahead of every tag still wins. -expect "workspace ahead" "3.0.0 3.1.0" minor 3.0.0 v2.8.0 -expect "no tags" "2.1.4 2.1.5" patch 2.1.4 -# Pre-release and foreign tags never set the base. -expect "prerelease ignored" "2.8.0 2.9.0" minor 2.1.4 v2.8.0 v3.0.0-rc1 nightly 9.9.9 -# A leading-zero tag is not a release version (08 would be octal). -expect "leading zero ignored" "2.8.0 2.9.0" minor 2.1.4 v2.8.0 v2.08.0 -# Bad input fails. -expect "bad bump" "error" hotfix 2.1.4 -expect "bad workspace version" "error" minor 2.1 v2.8.0 - -if ((failures > 0)); then - echo "${failures} failure(s)" >&2 - exit 1 -fi -echo "next-release-version: ok" diff --git a/.github/scripts/test-release-workflow.py b/.github/scripts/test-release-workflow.py new file mode 100644 index 000000000..b5c667c00 --- /dev/null +++ b/.github/scripts/test-release-workflow.py @@ -0,0 +1,152 @@ +"""Exercise the Release workflow: a release is a tag on main, never a bot commit. + +Run: python3 -B .github/scripts/test-release-workflow.py -v +""" + +import os +from pathlib import Path +import re +import subprocess +import tempfile +import unittest + + +ROOT = Path(__file__).resolve().parents[2] +WORKFLOW = ROOT / ".github/workflows/release.yml" +VERIFY_STEP = "Verify the release tag is on main and matches the crate version" + +# Stands in for `cargo metadata`: reports the version in the checkout's VERSION file. +FAKE_CARGO = """#!/usr/bin/env bash +printf '{"packages":[{"name":"tinyagents-harness","version":"%s"}]}\\n' "$(cat VERSION)" +""" + + +def step_script(workflow, name): + match = re.search( + rf"(?ms)^ - name: {re.escape(name)}\n.*?^ run: \|\n((?:^ [^\n]*\n|^\n)+)", + workflow, + ) + if not match: + raise AssertionError(f"step {name!r} was not found") + return "\n".join( + line[10:] if line.startswith(" ") else "" for line in match[1].splitlines() + ) + + +def git(cwd, *args): + subprocess.run( + ["git", "-c", "user.name=test", "-c", "user.email=test@example.com", *args], + cwd=cwd, + check=True, + capture_output=True, + ) + + +class ReleaseWorkflowTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.workflow = WORKFLOW.read_text() + cls.script = step_script(cls.workflow, VERIFY_STEP) + + def test_release_never_commits_tags_or_pushes(self): + for forbidden in [ + "git commit", + "git tag", + "git push", + "cargo update", + "github-actions[bot]", + "user.name", + "next-release-version", + ]: + self.assertNotIn(forbidden, self.workflow) + self.assertIn("permissions:\n contents: read\n", self.workflow) + self.assertNotIn("contents: write", self.workflow) + + def test_release_is_triggered_by_a_tag(self): + self.assertIn(" push:\n tags: ['v*']\n", self.workflow) + dispatch = re.search(r"(?ms)^ workflow_dispatch:\n(?P.*?)(?=^\S|^concurrency:)", self.workflow) + self.assertIsNotNone(dispatch) + self.assertEqual(re.findall(r"^ ([a-z_-]+):$", dispatch["body"], re.MULTILINE), ["tag"]) + self.assertIn("ref: refs/tags/${{ env.RELEASE_TAG }}", self.workflow) + # The verification runs before anything is built or published. + self.assertLess(self.workflow.index(VERIFY_STEP), self.workflow.index("- name: Run tests")) + self.assertLess(self.workflow.index(VERIFY_STEP), self.workflow.index("cargo publish")) + + def setUp(self): + self.directory = tempfile.TemporaryDirectory() + self.addCleanup(self.directory.cleanup) + base = Path(self.directory.name) + bin_dir = base / "bin" + bin_dir.mkdir() + (bin_dir / "cargo").write_text(FAKE_CARGO) + (bin_dir / "cargo").chmod(0o755) + self.path = f"{bin_dir}{os.pathsep}{os.environ['PATH']}" + + # origin: main carries 3.1.4 (tagged v3.1.4, and v9.9.9 as a mismatch); + # a commit off main carries 3.1.5, tagged v3.1.5 like the old bot release. + work = base / "work" + work.mkdir() + git(work, "init", "-q", "-b", "main") + (work / "VERSION").write_text("3.1.4") + git(work, "add", "VERSION") + git(work, "commit", "-q", "-m", "bump to 3.1.4") + git(work, "tag", "v3.1.4") + git(work, "tag", "v9.9.9") + git(work, "tag", "v3.1.4-rc1") + git(work, "checkout", "-q", "-b", "off-main") + (work / "VERSION").write_text("3.1.5") + git(work, "commit", "-q", "-am", "Release v3.1.5") + git(work, "tag", "v3.1.5") + git(work, "checkout", "-q", "main") + git(work, "branch", "-q", "-D", "off-main") + self.origin = base / "origin.git" + git(base, "clone", "-q", "--bare", str(work), str(self.origin)) + + def verify(self, tag, event="push", ref=None): + checkout = Path(self.directory.name) / f"checkout-{tag}-{event}" + git(Path(self.directory.name), "clone", "-q", str(self.origin), str(checkout)) + git(checkout, "checkout", "-q", f"refs/tags/{tag}") + return subprocess.run( + ["bash", "-c", self.script], + cwd=checkout, + env={ + **os.environ, + "PATH": self.path, + "CRATE_NAME": "tinyagents-harness", + "RELEASE_TAG": tag, + "GITHUB_EVENT_NAME": event, + "GITHUB_REF": ref or f"refs/tags/{tag}", + }, + capture_output=True, + text=True, + ) + + def test_tag_on_main_matching_the_crate_version_is_released(self): + result = self.verify("v3.1.4") + self.assertEqual(result.returncode, 0, result.stderr) + result = self.verify("v3.1.4", event="workflow_dispatch", ref="refs/heads/main") + self.assertEqual(result.returncode, 0, result.stderr) + + def test_tag_off_main_is_refused(self): + result = self.verify("v3.1.5") + self.assertNotEqual(result.returncode, 0) + self.assertIn("is not on main", result.stderr) + + def test_tag_that_differs_from_the_crate_version_is_refused(self): + result = self.verify("v9.9.9") + self.assertNotEqual(result.returncode, 0) + self.assertIn("does not match tinyagents-harness version 3.1.4", result.stderr) + + def test_dispatch_from_another_branch_is_refused(self): + result = self.verify("v3.1.4", event="workflow_dispatch", ref="refs/heads/epic/release") + self.assertNotEqual(result.returncode, 0) + self.assertIn("dispatched from main", result.stderr) + + def test_non_release_tag_is_refused(self): + result = self.verify("v3.1.4-rc1") + self.assertNotEqual(result.returncode, 0) + self.assertIn("is not vX.Y.Z", result.stderr) + + +if __name__ == "__main__": + unittest.main() diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b9fc92310..40845da21 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,8 +64,8 @@ jobs: - name: Check formatting run: cargo fmt --all -- --check - - name: Release version script - run: .github/scripts/next-release-version.test.sh + - name: Release workflow + run: python3 -B .github/scripts/test-release-workflow.py -v - name: Ensure storage engines stay out of library trees run: bash .github/scripts/check-storage-engine-trees.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1556d1138..dcd92b6a3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,28 +1,28 @@ name: Release +# A release is a `vX.Y.Z` tag on a commit already on main. The version bump +# lands on main through a reviewed pull request first; this workflow never +# commits, tags, or pushes. Dispatch with an existing tag to retry a release. on: + push: + tags: ['v*'] workflow_dispatch: inputs: - bump: - description: Version bump to release - type: choice + tag: + description: Existing release tag (vX.Y.Z) on a commit on main + type: string required: true - options: - - patch - - minor - - major concurrency: - group: release-${{ github.ref_name }} + group: release-${{ github.event_name == 'push' && github.ref_name || inputs.tag }} cancel-in-progress: false permissions: - contents: write + contents: read jobs: publish-rust: name: Release Rust Crate - if: ${{ github.ref == 'refs/heads/main' }} runs-on: ubuntu-latest environment: Production env: @@ -33,10 +33,13 @@ jobs: # it via `tinyagents-harness`, which is always present and shares the # workspace-inherited version. CRATE_NAME: tinyagents-harness + RELEASE_TAG: ${{ github.event_name == 'push' && github.ref_name || inputs.tag }} steps: - uses: actions/checkout@v7 with: + ref: refs/tags/${{ env.RELEASE_TAG }} fetch-depth: 0 + persist-credentials: false # Required to resolve the `vendor/tinytools` path dependency. submodules: recursive @@ -48,6 +51,37 @@ jobs: with: workspaces: . + - name: Verify the release tag is on main and matches the crate version + shell: bash + run: | + set -euo pipefail + + if [[ "$GITHUB_EVENT_NAME" == "workflow_dispatch" && "$GITHUB_REF" != "refs/heads/main" ]]; then + echo "Release must be dispatched from main" >&2 + exit 1 + fi + if ! [[ "$RELEASE_TAG" =~ ^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then + echo "Release tag '${RELEASE_TAG}' is not vX.Y.Z" >&2 + exit 1 + fi + + tag_commit="$(git rev-parse "refs/tags/${RELEASE_TAG}^{commit}")" + if [[ "$(git rev-parse HEAD)" != "$tag_commit" ]]; then + echo "Checked-out commit is not ${RELEASE_TAG}" >&2 + exit 1 + fi + git fetch --no-tags origin main + if ! git merge-base --is-ancestor "$tag_commit" FETCH_HEAD; then + echo "${RELEASE_TAG} (${tag_commit}) is not on main; merge the version bump through a pull request and tag the merged commit" >&2 + exit 1 + fi + + crate_version="$(cargo metadata --format-version 1 --no-deps | jq -r --arg crate "$CRATE_NAME" '.packages[] | select(.name == $crate) | .version')" + if [[ "v${crate_version}" != "$RELEASE_TAG" ]]; then + echo "${RELEASE_TAG} does not match ${CRATE_NAME} version ${crate_version}" >&2 + exit 1 + fi + - name: Check formatting run: cargo fmt --all -- --check @@ -73,69 +107,6 @@ jobs: uses: bnjbvr/cargo-machete@main continue-on-error: true - - name: Compute next version - id: version - shell: bash - run: | - set -euo pipefail - - current_version="$(cargo metadata --format-version 1 --no-deps | jq -r --arg crate "$CRATE_NAME" '.packages[] | select(.name == $crate) | .version')" - if [[ -z "$current_version" ]]; then - echo "Could not resolve current version for crate $CRATE_NAME" >&2 - exit 1 - fi - - # The workspace version can lag the latest tag (releases are cut - # from tags), so the next version is derived from the highest of - # the two; see the script. - git fetch --tags origin - mapfile -t existing_tags < <(git tag --list 'v*') - versions="$(.github/scripts/next-release-version.sh "${{ inputs.bump }}" "$current_version" "${existing_tags[@]}")" - read -r base_version next_version <<< "$versions" - tag="v${next_version}" - - if git rev-parse --verify --quiet "refs/tags/${tag}"; then - echo "Tag ${tag} already exists" >&2 - exit 1 - fi - - { - echo "current_version=${current_version}" - echo "base_version=${base_version}" - echo "next_version=${next_version}" - echo "tag=${tag}" - } >> "$GITHUB_OUTPUT" - - - name: Update crate version - env: - CURRENT_VERSION: ${{ steps.version.outputs.current_version }} - NEXT_VERSION: ${{ steps.version.outputs.next_version }} - run: | - perl -0pi -e 's/(\[workspace\.package\][\s\S]*?\nversion = ")[^"]+(")/$1$ENV{NEXT_VERSION}$2/' Cargo.toml - - # Intra-workspace path dependencies (e.g. `tinyagents-tracing = - # { path = "...", version = "2.1.1" }`) pin the workspace version - # as a caret requirement. On a major bump the old requirement no - # longer matches the new version, so keep them in lockstep with - # the workspace bump or `cargo update` fails to resolve. - perl -pi -e 's/(tinyagents-[\w-]+\s*=\s*\{[^}]*version\s*=\s*")\Q$ENV{CURRENT_VERSION}\E(")/$1$ENV{NEXT_VERSION}$2/g' crates/*/Cargo.toml - - # Refresh every workspace member's Cargo.lock entry (not just - # $CRATE_NAME) so later `--locked` commands don't see a stale - # lockfile. - cargo update --workspace - - - name: Commit version bump and tag - env: - NEXT_VERSION: ${{ steps.version.outputs.next_version }} - RELEASE_TAG: ${{ steps.version.outputs.tag }} - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add Cargo.toml Cargo.lock crates/*/Cargo.toml - git commit -m "Release ${RELEASE_TAG}" - git tag -a "${RELEASE_TAG}" -m "Release ${RELEASE_TAG}" - - name: Check crates.io publishability id: publishable run: | @@ -153,15 +124,6 @@ jobs: if: steps.publishable.outputs.publishable == 'true' run: cargo package --locked -p "$CRATE_NAME" - # Only the tag is pushed. The "Protect main" ruleset requires a pull - # request for every change to main, so pushing the release commit there - # is always rejected (GH013). The tag points at the release commit, which - # stays off main; the next version is computed from the tags. - - name: Push release tag - env: - RELEASE_TAG: ${{ steps.version.outputs.tag }} - run: git push origin "refs/tags/${RELEASE_TAG}" - - name: Publish to crates.io if: steps.publishable.outputs.publishable == 'true' run: cargo publish --locked -p "$CRATE_NAME" diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d77a462b..a41c351dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 3.1.4 + +- Releases are cut from `main`: the version bump merges through a pull request + and the **Release** workflow runs on a `vX.Y.Z` tag of a commit on `main`, + checking that the tag matches the workspace version. The workflow no longer + commits a version bump or pushes a tag, so release tags stay on `main`. +- Bump the workspace crates to 3.1.4, above the v3.1.0 to v3.1.3 tags that + were cut off `main`. + ## 3.0.0 — 2026-10-22 **Breaking.** This release completes the TinyAgents slim-down: generic tool diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fb463eb4c..f1f3b5aaf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -109,6 +109,22 @@ Good issues include: Feature requests should explain the agent workflow they unlock, the public API shape they imply, and any safety or observability concerns. +## Releasing + +A release is a `vX.Y.Z` tag on a commit that is already on `main`. + +1. Open a pull request that sets `[workspace.package].version` in `Cargo.toml` + to the new version, updates the matching `version = "..."` requirements on + the `tinyagents-*` path dependencies in `crates/*/Cargo.toml` and the + workspace entries in `Cargo.lock`, and adds a `CHANGELOG.md` entry. Pick a + version above every existing `v*` tag. +2. After it merges, tag the merge commit on `main` and push the tag: + `git tag -s vX.Y.Z && git push origin vX.Y.Z`. +3. The **Release** workflow runs on the tag. It refuses a tag whose commit is + not on `main` or whose version differs from the workspace version, then + runs the checks and publishes. It never commits, tags, or pushes. To retry, + dispatch **Release** from `main` with the existing tag. + ## Security Do not report vulnerabilities through public issues. Use the process in diff --git a/Cargo.lock b/Cargo.lock index 434b4e54d..dd76c4820 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1980,7 +1980,7 @@ dependencies = [ [[package]] name = "tinyagents-definition" -version = "3.0.0" +version = "3.1.4" dependencies = [ "anyhow", "async-trait", @@ -1995,7 +1995,7 @@ dependencies = [ [[package]] name = "tinyagents-harness" -version = "3.0.0" +version = "3.1.4" dependencies = [ "anyhow", "async-trait", @@ -2041,7 +2041,7 @@ dependencies = [ [[package]] name = "tinyagents-harness-tools" -version = "3.0.0" +version = "3.1.4" dependencies = [ "anyhow", "async-trait", @@ -2075,7 +2075,7 @@ dependencies = [ [[package]] name = "tinyagents-hooks" -version = "3.0.0" +version = "3.1.4" dependencies = [ "anyhow", "async-trait", @@ -2094,7 +2094,7 @@ dependencies = [ [[package]] name = "tinyagents-integration-tests" -version = "3.0.0" +version = "3.1.4" dependencies = [ "anyhow", "async-trait", @@ -2125,7 +2125,7 @@ dependencies = [ [[package]] name = "tinyagents-live" -version = "3.0.0" +version = "3.1.4" dependencies = [ "anyhow", "async-trait", @@ -2138,7 +2138,7 @@ dependencies = [ [[package]] name = "tinyagents-orchestration" -version = "3.0.0" +version = "3.1.4" dependencies = [ "async-trait", "chrono", @@ -2169,7 +2169,7 @@ dependencies = [ [[package]] name = "tinyagents-registry" -version = "3.0.0" +version = "3.1.4" dependencies = [ "anyhow", "async-trait", @@ -2186,7 +2186,7 @@ dependencies = [ [[package]] name = "tinyagents-runtime" -version = "3.0.0" +version = "3.1.4" dependencies = [ "anyhow", "async-trait", @@ -2214,7 +2214,7 @@ dependencies = [ [[package]] name = "tinyagents-session" -version = "3.0.0" +version = "3.1.4" dependencies = [ "anyhow", "async-trait", @@ -2243,7 +2243,7 @@ dependencies = [ [[package]] name = "tinyagents-storage-kit" -version = "3.0.0" +version = "3.1.4" dependencies = [ "data-encoding", "serde", @@ -2259,7 +2259,7 @@ dependencies = [ [[package]] name = "tinyagents-tasks" -version = "3.0.0" +version = "3.1.4" dependencies = [ "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 57ff340da..f77d02137 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ exclude = ["vendor", "worktrees", "integration-proofs/run-ledger-mongodb"] resolver = "3" [workspace.package] -version = "3.0.0" +version = "3.1.4" edition = "2024" license = "GPL-3.0-only" repository = "https://github.com/tinyhumansai/tinyagents" diff --git a/crates/tinyagents-harness-tools/Cargo.toml b/crates/tinyagents-harness-tools/Cargo.toml index e7c9a1f82..be58ff7f6 100644 --- a/crates/tinyagents-harness-tools/Cargo.toml +++ b/crates/tinyagents-harness-tools/Cargo.toml @@ -25,11 +25,11 @@ sha2 = { version = "0.11", optional = true } serde = { workspace = true } serde_json = { workspace = true } schemars = "1.2.3" -tinyagents-harness = { path = "../tinyagents-harness", version = "3.0.0", default-features = false } -tinyagents-tasks = { path = "../tinyagents-tasks", version = "3.0.0", default-features = false, optional = true } +tinyagents-harness = { path = "../tinyagents-harness", version = "3.1.4", default-features = false } +tinyagents-tasks = { path = "../tinyagents-tasks", version = "3.1.4", default-features = false, optional = true } tinyinference-llm = { path = "../../vendor/tinyinference/crates/tinyinference-llm", version = "0.5.0", optional = true } tinytools = { path = "../../vendor/tinytools/crates/tinytools", version = "0.6.0" } -tinyagents-orchestration = { path = "../tinyagents-orchestration", version = "3.0.0", default-features = false, optional = true } +tinyagents-orchestration = { path = "../tinyagents-orchestration", version = "3.1.4", default-features = false, optional = true } tokio = { workspace = true, features = ["sync", "time", "macros", "rt", "rt-multi-thread"] } tracing = { workspace = true } uuid = { workspace = true } diff --git a/crates/tinyagents-harness/Cargo.toml b/crates/tinyagents-harness/Cargo.toml index cdbe20fe4..0b32778dd 100644 --- a/crates/tinyagents-harness/Cargo.toml +++ b/crates/tinyagents-harness/Cargo.toml @@ -30,7 +30,7 @@ schemars = "1.2.3" sha2 = "0.11" thiserror = "2" tracing = { workspace = true } -tinyagents-definition = { path = "../tinyagents-definition", version = "3.0.0" } +tinyagents-definition = { path = "../tinyagents-definition", version = "3.1.4" } tinytools-agent = { path = "../../vendor/tinytools/crates/tinytools-agent", version = "0.6.0", default-features = false } tinyinference-llm = { path = "../../vendor/tinyinference/crates/tinyinference-llm", version = "0.5.0" } tinyinference-providers = { path = "../../vendor/tinyinference/crates/tinyinference-providers", version = "0.5.0" } diff --git a/crates/tinyagents-hooks/Cargo.toml b/crates/tinyagents-hooks/Cargo.toml index de0bbe472..31ec92185 100644 --- a/crates/tinyagents-hooks/Cargo.toml +++ b/crates/tinyagents-hooks/Cargo.toml @@ -13,8 +13,8 @@ async-trait = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } regex = "1.10" -tinyagents-harness = { path = "../tinyagents-harness", version = "3.0.0", default-features = false } -tinyagents-harness-tools = { path = "../tinyagents-harness-tools", version = "3.0.0", default-features = false, optional = true } +tinyagents-harness = { path = "../tinyagents-harness", version = "3.1.4", default-features = false } +tinyagents-harness-tools = { path = "../tinyagents-harness-tools", version = "3.1.4", default-features = false, optional = true } tinyinference-llm = { path = "../../vendor/tinyinference/crates/tinyinference-llm", version = "0.5.0" } tinyinference-providers = { path = "../../vendor/tinyinference/crates/tinyinference-providers", version = "0.5.0" } tinytools = { path = "../../vendor/tinytools/crates/tinytools", version = "0.6.0" } diff --git a/crates/tinyagents-live/Cargo.toml b/crates/tinyagents-live/Cargo.toml index ae1394552..14df2ad06 100644 --- a/crates/tinyagents-live/Cargo.toml +++ b/crates/tinyagents-live/Cargo.toml @@ -13,7 +13,7 @@ description = "Runs live (realtime voice) agent sessions with harness tools and tinyliveagents = { path = "../../vendor/tinyliveagents/crates/tinyliveagents", version = "0.1.0" } # Tool calls run through the harness's own admission / middleware / fold # pipeline (`agent_loop::phases::execute_tool_batch`). -tinyagents-harness = { path = "../tinyagents-harness", version = "3.0.0" } +tinyagents-harness = { path = "../tinyagents-harness", version = "3.1.4" } serde_json = { workspace = true } tokio = { workspace = true, features = ["sync", "time"] } tracing = { workspace = true } @@ -23,7 +23,7 @@ async-trait = { workspace = true } anyhow = { workspace = true } tokio = { workspace = true, features = ["sync", "time", "macros", "rt-multi-thread"] } # The example builds a harness with the testkit's FakeTool. -tinyagents-harness = { path = "../tinyagents-harness", version = "3.0.0" } +tinyagents-harness = { path = "../tinyagents-harness", version = "3.1.4" } [lints] workspace = true diff --git a/crates/tinyagents-orchestration/Cargo.toml b/crates/tinyagents-orchestration/Cargo.toml index 1733c8594..ff7519678 100644 --- a/crates/tinyagents-orchestration/Cargo.toml +++ b/crates/tinyagents-orchestration/Cargo.toml @@ -9,7 +9,7 @@ rust-version.workspace = true description = "Host-neutral subagent invocation and lifecycle orchestration." [dependencies] -tinyagents-definition = { path = "../tinyagents-definition", version = "3.0.0" } +tinyagents-definition = { path = "../tinyagents-definition", version = "3.1.4" } async-trait = { workspace = true } chrono = { workspace = true } serde = { workspace = true } @@ -17,9 +17,9 @@ serde_json = { workspace = true } sha2 = "0.11" thiserror = "2" parking_lot = "0.12" -tinyagents-tasks = { path = "../tinyagents-tasks", version = "3.0.0", default-features = false } -tinyagents-harness = { path = "../tinyagents-harness", version = "3.0.0", default-features = false } -tinyagents-session = { path = "../tinyagents-session", version = "3.0.0", default-features = false } +tinyagents-tasks = { path = "../tinyagents-tasks", version = "3.1.4", default-features = false } +tinyagents-harness = { path = "../tinyagents-harness", version = "3.1.4", default-features = false } +tinyagents-session = { path = "../tinyagents-session", version = "3.1.4", default-features = false } tinyinference-llm = { path = "../../vendor/tinyinference/crates/tinyinference-llm", version = "0.5.0" } tinytools = { path = "../../vendor/tinytools/crates/tinytools", version = "0.6.0" } tokio = { workspace = true, features = ["sync", "rt", "macros", "time"] } @@ -44,9 +44,9 @@ tinystoragedrivers-document = { workspace = true } dotenvy = "0.15" futures = { workspace = true } tempfile = "3" -tinyagents-definition = { path = "../tinyagents-definition", version = "3.0.0" } -tinyagents-registry = { path = "../tinyagents-registry", version = "3.0.0" } -tinyagents-runtime = { path = "../tinyagents-runtime", version = "3.0.0" } +tinyagents-definition = { path = "../tinyagents-definition", version = "3.1.4" } +tinyagents-registry = { path = "../tinyagents-registry", version = "3.1.4" } +tinyagents-runtime = { path = "../tinyagents-runtime", version = "3.1.4" } tokio = { workspace = true, default-features = true, features = ["macros", "rt-multi-thread", "time", "test-util"] } [lints] diff --git a/crates/tinyagents-registry/Cargo.toml b/crates/tinyagents-registry/Cargo.toml index a4622b1d8..4dc635338 100644 --- a/crates/tinyagents-registry/Cargo.toml +++ b/crates/tinyagents-registry/Cargo.toml @@ -13,8 +13,8 @@ anyhow = { workspace = true } chrono = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } -tinyagents-definition = { path = "../tinyagents-definition", version = "3.0.0" } -tinyagents-harness = { path = "../tinyagents-harness", version = "3.0.0", default-features = false } +tinyagents-definition = { path = "../tinyagents-definition", version = "3.1.4" } +tinyagents-harness = { path = "../tinyagents-harness", version = "3.1.4", default-features = false } tinyinference-llm = { path = "../../vendor/tinyinference/crates/tinyinference-llm", version = "0.5.0" } tinyinference-providers = { path = "../../vendor/tinyinference/crates/tinyinference-providers", version = "0.5.0" } tinytools = { path = "../../vendor/tinytools/crates/tinytools", version = "0.6.0" } diff --git a/crates/tinyagents-runtime/Cargo.toml b/crates/tinyagents-runtime/Cargo.toml index 4b714e7ff..f54baa343 100644 --- a/crates/tinyagents-runtime/Cargo.toml +++ b/crates/tinyagents-runtime/Cargo.toml @@ -15,10 +15,10 @@ chrono = { workspace = true } serde_json = { workspace = true } sha2 = "0.11" thiserror = "2" -tinyagents-harness = { path = "../tinyagents-harness", version = "3.0.0" } -tinyagents-hooks = { path = "../tinyagents-hooks", version = "3.0.0" } -tinyagents-registry = { path = "../tinyagents-registry", version = "3.0.0" } -tinyagents-session = { path = "../tinyagents-session", version = "3.0.0" } +tinyagents-harness = { path = "../tinyagents-harness", version = "3.1.4" } +tinyagents-hooks = { path = "../tinyagents-hooks", version = "3.1.4" } +tinyagents-registry = { path = "../tinyagents-registry", version = "3.1.4" } +tinyagents-session = { path = "../tinyagents-session", version = "3.1.4" } tinyinference-llm = { path = "../../vendor/tinyinference/crates/tinyinference-llm", version = "0.5.0" } tinytools = { path = "../../vendor/tinytools/crates/tinytools", version = "0.6.0" } tokio = { workspace = true, features = ["io-util", "macros", "process", "rt", "sync", "time"] } @@ -27,7 +27,7 @@ tracing = { workspace = true } [dev-dependencies] anyhow = { workspace = true } tinyagents-storage-kit = { workspace = true } -tinyagents-session = { path = "../tinyagents-session", version = "3.0.0", features = ["testkit"] } +tinyagents-session = { path = "../tinyagents-session", version = "3.1.4", features = ["testkit"] } tinystoragedrivers-api = { workspace = true } tinystoragedrivers = { workspace = true } tinystoragedrivers-connect = { workspace = true } diff --git a/crates/tinyagents-session/Cargo.toml b/crates/tinyagents-session/Cargo.toml index 28225181b..a51650289 100644 --- a/crates/tinyagents-session/Cargo.toml +++ b/crates/tinyagents-session/Cargo.toml @@ -16,10 +16,10 @@ thiserror = "2" serde = { workspace = true } serde_json = { workspace = true } schemars = { version = "1.2.3", features = ["chrono04"] } -tinyagents-harness = { path = "../tinyagents-harness", version = "3.0.0", default-features = false, features = ["sqlite"] } +tinyagents-harness = { path = "../tinyagents-harness", version = "3.1.4", default-features = false, features = ["sqlite"] } # `OrchestrationTaskStatus` is the canonical lifecycle status the run-ledger and # transcript-view statuses convert to and from. -tinyagents-tasks = { path = "../tinyagents-tasks", version = "3.0.0", default-features = false } +tinyagents-tasks = { path = "../tinyagents-tasks", version = "3.1.4", default-features = false } tinytools-agent = { path = "../../vendor/tinytools/crates/tinytools-agent", version = "0.6.0", default-features = false } hex = "0.4" sha2 = "0.11" diff --git a/crates/tinyagents-tasks/Cargo.toml b/crates/tinyagents-tasks/Cargo.toml index e249aa031..914884570 100644 --- a/crates/tinyagents-tasks/Cargo.toml +++ b/crates/tinyagents-tasks/Cargo.toml @@ -11,7 +11,7 @@ description = "Graph-independent detached-task registry, task stores and orchest [dependencies] serde = { workspace = true } serde_json = { workspace = true } -tinyagents-harness = { path = "../tinyagents-harness", version = "3.0.0", default-features = false } +tinyagents-harness = { path = "../tinyagents-harness", version = "3.1.4", default-features = false } tinyinference-llm = { path = "../../vendor/tinyinference/crates/tinyinference-llm", version = "0.5.0" } tokio = { workspace = true, features = ["sync", "time", "rt", "rt-multi-thread"] } tracing = { workspace = true } From b52a79501d78f13c4b9ddfe4e18d7120f8658f9d Mon Sep 17 00:00:00 2001 From: M3gA-Mind Date: Tue, 3 Nov 2026 16:43:22 +0530 Subject: [PATCH 2/3] chore(deps): bump the MongoDB proof lockfile's workspace crates to 3.1.4 The nested integration-proofs/run-ledger-mongodb lockfile still recorded five path crates at 3.0.0, so its --locked conformance run would have had to rewrite it. --- integration-proofs/run-ledger-mongodb/Cargo.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/integration-proofs/run-ledger-mongodb/Cargo.lock b/integration-proofs/run-ledger-mongodb/Cargo.lock index c6ca7ada0..e4c849491 100644 --- a/integration-proofs/run-ledger-mongodb/Cargo.lock +++ b/integration-proofs/run-ledger-mongodb/Cargo.lock @@ -2387,7 +2387,7 @@ dependencies = [ [[package]] name = "tinyagents-definition" -version = "3.0.0" +version = "3.1.4" dependencies = [ "anyhow", "async-trait", @@ -2400,7 +2400,7 @@ dependencies = [ [[package]] name = "tinyagents-harness" -version = "3.0.0" +version = "3.1.4" dependencies = [ "anyhow", "async-trait", @@ -2451,7 +2451,7 @@ dependencies = [ [[package]] name = "tinyagents-session" -version = "3.0.0" +version = "3.1.4" dependencies = [ "anyhow", "async-trait", @@ -2476,7 +2476,7 @@ dependencies = [ [[package]] name = "tinyagents-storage-kit" -version = "3.0.0" +version = "3.1.4" dependencies = [ "data-encoding", "serde", @@ -2490,7 +2490,7 @@ dependencies = [ [[package]] name = "tinyagents-tasks" -version = "3.0.0" +version = "3.1.4" dependencies = [ "serde", "serde_json", From ecc3c262746e07f9c8631d2e28ee8bf873d17508 Mon Sep 17 00:00:00 2001 From: M3gA-Mind Date: Tue, 3 Nov 2026 16:43:22 +0530 Subject: [PATCH 3/3] ci(release): skip publishing a version crates.io already has A dispatched retry after a run that uploaded the crate but then failed would stop at cargo publish. Treat a version already on crates.io like publish = false and skip the package and publish steps. --- .github/scripts/test-release-workflow.py | 5 +++++ .github/workflows/release.yml | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/.github/scripts/test-release-workflow.py b/.github/scripts/test-release-workflow.py index b5c667c00..5e1ba4f31 100644 --- a/.github/scripts/test-release-workflow.py +++ b/.github/scripts/test-release-workflow.py @@ -71,6 +71,11 @@ def test_release_is_triggered_by_a_tag(self): # The verification runs before anything is built or published. self.assertLess(self.workflow.index(VERIFY_STEP), self.workflow.index("- name: Run tests")) self.assertLess(self.workflow.index(VERIFY_STEP), self.workflow.index("cargo publish")) + # A retry skips publishing a version crates.io already has. + self.assertIn('"https://crates.io/api/v1/crates/${CRATE_NAME}/${RELEASE_TAG#v}"', self.workflow) + self.assertLess( + self.workflow.index("api/v1/crates"), self.workflow.index("- name: Publish to crates.io") + ) def setUp(self): self.directory = tempfile.TemporaryDirectory() diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dcd92b6a3..433db2e70 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -116,6 +116,12 @@ jobs: if [[ "$publish" == "[]" ]]; then echo "publishable=false" >> "$GITHUB_OUTPUT" echo "::notice::${CRATE_NAME} has publish = false; skipping crates.io package/publish steps." + # A retried release whose earlier run already uploaded this version + # would fail `cargo publish`; skip it instead. + elif curl -fsS -o /dev/null -A "tinyagents-release (https://github.com/tinyhumansai/tinyagents)" \ + "https://crates.io/api/v1/crates/${CRATE_NAME}/${RELEASE_TAG#v}"; then + echo "publishable=false" >> "$GITHUB_OUTPUT" + echo "::notice::${CRATE_NAME} ${RELEASE_TAG#v} is already on crates.io; skipping crates.io package/publish steps." else echo "publishable=true" >> "$GITHUB_OUTPUT" fi