From 67901ab6f8f2e29a7cc36beec51cfc23b1562557 Mon Sep 17 00:00:00 2001 From: Francisco Javier Arceo Date: Tue, 1 Sep 2026 23:57:47 -0400 Subject: [PATCH 1/3] chore: prepare release v0.6.0 Signed-off-by: Francisco Javier Arceo --- .github/workflows/python.yml | 2 + .github/workflows/release-crates.yml | 2 +- .github/workflows/release-python.yml | 2 +- CHANGELOG.md | 55 ++++++++++++++++++---------- Cargo.lock | 6 +-- Cargo.toml | 4 +- README.md | 4 +- docs/guides/python-installation.md | 2 +- tests/python/test_release_version.py | 44 ++++++++++++++++++++++ 9 files changed, 91 insertions(+), 30 deletions(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 4c9bbdfc..310b7b30 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -6,6 +6,7 @@ on: pull_request: paths: - ".github/workflows/python.yml" + - ".github/workflows/release-crates.yml" - ".github/workflows/release-python.yml" - "Cargo.lock" - "Cargo.toml" @@ -24,6 +25,7 @@ on: - main paths: - ".github/workflows/python.yml" + - ".github/workflows/release-crates.yml" - ".github/workflows/release-python.yml" - "Cargo.lock" - "Cargo.toml" diff --git a/.github/workflows/release-crates.yml b/.github/workflows/release-crates.yml index 75128273..8bb4749b 100644 --- a/.github/workflows/release-crates.yml +++ b/.github/workflows/release-crates.yml @@ -149,7 +149,7 @@ jobs: run: | set -euo pipefail cargo publish --dry-run -p agentic-server-core - cargo package -p agentic-server --no-verify + cargo package --list -p agentic-server echo "Would release commit ${{ steps.target.outputs.sha }}" echo "Would create tag ${{ steps.version.outputs.tag }}" echo "Would create GitHub release ${{ steps.version.outputs.tag }} with generated notes" diff --git a/.github/workflows/release-python.yml b/.github/workflows/release-python.yml index 3c57ce3c..ffd4b524 100644 --- a/.github/workflows/release-python.yml +++ b/.github/workflows/release-python.yml @@ -9,7 +9,7 @@ on: description: "Build-only Python release version already merged into main" required: true type: string - default: "0.5.0" + default: "0.6.0" concurrency: group: release-python-${{ inputs.version }} diff --git a/CHANGELOG.md b/CHANGELOG.md index d764accf..d9184554 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,35 +2,50 @@ All notable changes to Agentic API are documented here. -## [Unreleased] +## [0.6.0] - 2026-09-01 -### Changed +### Added -- Forwarded `parallel_tool_calls` as the model-generation preference for typed - Responses requests, including built-in-only and mixed tool declarations (#181). -- Added bounded, configurable parallel execution for Responses gateway rounds, - preserving model call order and applying per-handler same-tool safety. -- Preserved MCP list-tools records in continuation history for registry lifecycle - decisions while excluding them from model input, preventing repeated public - list-tools emission on later turns. -- Clarified Codex tool execution roles by replacing ambiguous ownership language - with the preferred client-executed and gateway-executed terminology. +- Added the build-only `agentic-api` Python distribution with `serve`, `doctor`, and version commands, packaged Rust + gateway binaries, local or remote vLLM launch modes, wheel validation, and Linux and macOS release artifacts (#201). +- Added end-to-end parallel tool calling for typed Responses requests, including bounded concurrent execution for + gateway-executed built-in tools, batched web searches, stable output ordering, and per-call failure isolation (#214). +- Added attached Claude Code and Codex workflows with isolated model and provider configuration and recorded CLI + coverage (#210). +- Added configurable streaming chunk timeouts for Responses and Messages streams, with a ten-minute default (#221, + #227). +- Added deployment guides and replay coverage for NVIDIA Dynamo and llm-d Kubernetes upstreams (#207, #212). +- Added a benchmark suite comparing WebSocket, HTTP/SSE, and HTTP/JSON Agentic API flows with direct vLLM across tool + loops, function selection, and stateful conversation workloads (#185). +- Added a repository-local pull request review skill with explicit wire-format and replay-cassette checks (#228). -### Fixed +### Changed -- Replaced `WebSearchActionSearch::new` and `WebSearchCall::new` with fallible - `try_new(...)` constructors; callers now handle `WebSearchActionError` for - empty query lists instead of risking a panic. +- Forwarded typed Responses reasoning configuration upstream and preserved complete streamed reasoning content, + summaries, and opaque state (#219, #225). +- Replayed persisted plaintext reasoning safely during continuation while rejecting opaque-only state that vLLM cannot + consume (#222). +- Preserved MCP list-tools records in item history for discovery lifecycle decisions while excluding them from model + input, preventing repeated public discovery items on later turns (#214). +- Improved Rust and container CI caching, test setup, and path filtering to shorten release validation (#205). +- Clarified client-executed and gateway-executed tool roles in Codex integration documentation (#230). -### Added +### Fixed -- Documented running Agentic API in front of NVIDIA Dynamo and recorded Dynamo cassettes for stateful and - function-call flows. +- Rejected continuations that omit required function call outputs instead of proceeding with unresolved call IDs + (#214). +- Preserved MCP and web-search public item types during mixed built-in tool rounds (#214). +- Removed connection-nominated hop-by-hop headers from proxied requests and responses as required by HTTP semantics + (#217). +- Required a healthy packaged gateway before `agentic-api doctor --mode local` reports success (#223). +- Rebuilt workspace crates after `cargo-chef` dependency cooking so container binaries carry current source and package + metadata (#208, #209). +- Made web-search action construction fallible so empty query lists return a typed error instead of panicking (#230). ### Testing -- Added Dynamo upstream replay tests, a generic cassette validator (`scripts/validate-cassettes.py`), and a dedicated - CI job for them. +- Added matched OpenAI and gateway cassettes for reasoning and parallel tool calling, replay tests for Dynamo, a generic + cassette validator, Python package and wheel test suites, and dedicated CI jobs for the new release paths. ## [0.5.0] - 2026-08-25 diff --git a/Cargo.lock b/Cargo.lock index 3bb2425a..ae5ec66c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,14 +4,14 @@ version = 4 [[package]] name = "agentic-praxis" -version = "0.5.0" +version = "0.6.0" dependencies = [ "agentic-server-core", ] [[package]] name = "agentic-server" -version = "0.5.0" +version = "0.6.0" dependencies = [ "agentic-server-core", "axum", @@ -44,7 +44,7 @@ dependencies = [ [[package]] name = "agentic-server-core" -version = "0.5.0" +version = "0.6.0" dependencies = [ "async-stream", "axum", diff --git a/Cargo.toml b/Cargo.toml index b4f812a8..3abf8fc1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = ["crates/*"] resolver = "3" [workspace.package] -version = "0.5.0" +version = "0.6.0" edition = "2024" license = "Apache-2.0" repository = "https://github.com/vllm-project/agentic-api" @@ -16,7 +16,7 @@ all = { level = "deny", priority = -1 } pedantic = { level = "warn", priority = -1 } [workspace.dependencies] -agentic-core = { package = "agentic-server-core", path = "crates/agentic-server-core", version = "0.5.0" } +agentic-core = { package = "agentic-server-core", path = "crates/agentic-server-core", version = "0.6.0" } async-stream = "0.3" axum = { version = "0.8", features = ["ws"] } either = "1" diff --git a/README.md b/README.md index b4d159db..561c43bc 100644 --- a/README.md +++ b/README.md @@ -123,8 +123,8 @@ permission checks and disables Codex approvals and sandboxing. ### Python distribution The `agentic-api` wheel packages the Rust gateway and a small Python launcher. This release produces wheel artifacts -for 0.5.0 as a build-only release: download the wheel for your platform from the release workflow, then install that local file. It is -not published on PyPI yet. +for 0.6.0 as a build-only release: download the wheel for your platform from the release workflow, then install that +local file. It is not published on PyPI yet. ```bash WHEEL_PATH=/absolute/path/to/agentic_api-PLATFORM.whl diff --git a/docs/guides/python-installation.md b/docs/guides/python-installation.md index c4573973..07b1c18e 100644 --- a/docs/guides/python-installation.md +++ b/docs/guides/python-installation.md @@ -8,7 +8,7 @@ The Rust-native `agentic` CLI remains supported for `run codex`, `run claude`, ` ## Install the release artifact -This release produces wheel artifacts for 0.5.0 on supported platforms but does not publish them to PyPI yet. +This release produces wheel artifacts for 0.6.0 on supported platforms but does not publish them to PyPI yet. Download the wheel for your platform from the release workflow and use its absolute path below: ```bash diff --git a/tests/python/test_release_version.py b/tests/python/test_release_version.py index 2c441f1f..2816ab20 100644 --- a/tests/python/test_release_version.py +++ b/tests/python/test_release_version.py @@ -9,6 +9,7 @@ REPO_ROOT = Path(__file__).resolve().parents[2] VALIDATOR = REPO_ROOT / "scripts" / "validate-python-release-version.sh" RELEASE_WORKFLOW = REPO_ROOT / ".github" / "workflows" / "release-python.yml" +CRATE_RELEASE_WORKFLOW = REPO_ROOT / ".github" / "workflows" / "release-crates.yml" PYTHON_WORKFLOW = REPO_ROOT / ".github" / "workflows" / "python.yml" BUILD_CONSTRAINTS = REPO_ROOT / "python-build-constraints.txt" WORKSPACE_VERSION = re.search( @@ -46,6 +47,30 @@ def test_release_workflow_keeps_dispatch_version_out_of_shell_source() -> None: assert all("${{ inputs.version }}" not in block for block in run_blocks) +def test_crate_release_dry_run_does_not_resolve_the_unpublished_core_version() -> None: + workflow = CRATE_RELEASE_WORKFLOW.read_text(encoding="utf-8") + dry_run_block = next(block for block in _workflow_run_blocks(workflow) if "Would release commit" in block) + cargo_commands = [line.strip() for line in dry_run_block.splitlines() if line.strip().startswith("cargo ")] + + assert "cargo package --list -p agentic-server" in cargo_commands + assert not any( + "agentic-server" in command + and "agentic-server-core" not in command + and command != "cargo package --list -p agentic-server" + for command in cargo_commands + ) + + package_list = subprocess.run( + ["cargo", "package", "--list", "--allow-dirty", "-p", "agentic-server"], + cwd=REPO_ROOT, + capture_output=True, + text=True, + check=False, + ) + assert package_list.returncode == 0, package_list.stderr + assert "Cargo.toml" in package_list.stdout.splitlines() + + def test_python_workflows_pin_build_tools_and_manylinux_artifact_contract() -> None: release_workflow = RELEASE_WORKFLOW.read_text(encoding="utf-8") python_workflow = PYTHON_WORKFLOW.read_text(encoding="utf-8") @@ -70,6 +95,14 @@ def test_python_workflows_pin_build_tools_and_manylinux_artifact_contract() -> N assert "hashFiles('Cargo.lock', 'python-build-constraints.txt')" in python_workflow +def test_python_workflow_validates_crate_release_workflow_changes() -> None: + workflow = PYTHON_WORKFLOW.read_text(encoding="utf-8") + + for trigger in ("pull_request", "push"): + section = _workflow_trigger_section(workflow, trigger) + assert section.count('".github/workflows/release-crates.yml"') == 1 + + def _workflow_run_blocks(workflow: str) -> list[str]: lines = workflow.splitlines() blocks: list[str] = [] @@ -88,3 +121,14 @@ def _workflow_run_blocks(workflow: str) -> list[str]: block.append(candidate) blocks.append("\n".join(block)) return blocks + + +def _workflow_trigger_section(workflow: str, trigger: str) -> str: + lines = workflow.splitlines() + start = lines.index(f" {trigger}:") + section: list[str] = [] + for line in lines[start + 1 :]: + if line.startswith(" ") and not line.startswith(" ") and line.strip(): + break + section.append(line) + return "\n".join(section) From 3610dc056bacd308f21e86b2ad66a709414e587c Mon Sep 17 00:00:00 2001 From: Francisco Javier Arceo Date: Wed, 9 Sep 2026 16:32:12 -0400 Subject: [PATCH 2/3] test: keep Python release default in sync Signed-off-by: Francisco Javier Arceo --- tests/python/test_release_version.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/python/test_release_version.py b/tests/python/test_release_version.py index 2816ab20..70b5c7f4 100644 --- a/tests/python/test_release_version.py +++ b/tests/python/test_release_version.py @@ -47,6 +47,14 @@ def test_release_workflow_keeps_dispatch_version_out_of_shell_source() -> None: assert all("${{ inputs.version }}" not in block for block in run_blocks) +def test_release_workflow_default_matches_workspace_version() -> None: + workflow = RELEASE_WORKFLOW.read_text(encoding="utf-8") + version_input = re.search(r'(?ms)^ version:\n.*?^ default: "([^"]+)"', workflow) + + assert version_input is not None + assert version_input.group(1) == WORKSPACE_VERSION + + def test_crate_release_dry_run_does_not_resolve_the_unpublished_core_version() -> None: workflow = CRATE_RELEASE_WORKFLOW.read_text(encoding="utf-8") dry_run_block = next(block for block in _workflow_run_blocks(workflow) if "Would release commit" in block) From 133bc6d0e6ffc10311dd3eaa9ff41b92a9a4a872 Mon Sep 17 00:00:00 2001 From: Francisco Javier Arceo Date: Wed, 9 Sep 2026 16:38:47 -0400 Subject: [PATCH 3/3] fix: validate release packages before publishing Signed-off-by: Francisco Javier Arceo --- .github/workflows/release-crates.yml | 21 ++++++------- tests/python/test_release_version.py | 44 +++++++++++++++++++--------- 2 files changed, 41 insertions(+), 24 deletions(-) diff --git a/.github/workflows/release-crates.yml b/.github/workflows/release-crates.yml index 8bb4749b..7cca80c1 100644 --- a/.github/workflows/release-crates.yml +++ b/.github/workflows/release-crates.yml @@ -56,8 +56,8 @@ jobs: toolchain: 1.98.0 components: clippy, rustfmt - - name: Refresh lockfile - run: cargo check --workspace + - name: Verify lockfile + run: cargo check --workspace --locked - name: Cache cargo registry and build uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v4 @@ -75,19 +75,19 @@ jobs: run: cargo fmt -- --check - name: Run clippy - run: cargo clippy --all-targets -- -D warnings + run: cargo clippy --all-targets --locked -- -D warnings - name: Run tests - run: cargo test + run: cargo test --locked - name: Read and verify package versions id: version run: | set -euo pipefail requested_version="${{ inputs.version }}" - core_version="$(cargo metadata --format-version 1 --no-deps \ + core_version="$(cargo metadata --locked --format-version 1 --no-deps \ | jq -r '.packages[] | select(.name == "agentic-server-core") | .version')" - server_version="$(cargo metadata --format-version 1 --no-deps \ + server_version="$(cargo metadata --locked --format-version 1 --no-deps \ | jq -r '.packages[] | select(.name == "agentic-server") | .version')" if [ -z "$core_version" ] || [ "$core_version" = "null" ]; then @@ -148,8 +148,9 @@ jobs: if: inputs.dry_run run: | set -euo pipefail - cargo publish --dry-run -p agentic-server-core - cargo package --list -p agentic-server + cargo publish --dry-run --locked -p agentic-server-core + cargo package --no-verify --locked -p agentic-server \ + --config 'patch.crates-io.agentic-server-core.path="crates/agentic-server-core"' echo "Would release commit ${{ steps.target.outputs.sha }}" echo "Would create tag ${{ steps.version.outputs.tag }}" echo "Would create GitHub release ${{ steps.version.outputs.tag }} with generated notes" @@ -158,7 +159,7 @@ jobs: if: ${{ !inputs.dry_run }} env: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} - run: cargo publish -p agentic-server-core + run: cargo publish --locked -p agentic-server-core - name: Wait for agentic-server-core index if: ${{ !inputs.dry_run }} @@ -183,7 +184,7 @@ jobs: if: ${{ !inputs.dry_run }} env: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} - run: cargo publish -p agentic-server + run: cargo publish --locked -p agentic-server - name: Create release refs if: ${{ !inputs.dry_run }} diff --git a/tests/python/test_release_version.py b/tests/python/test_release_version.py index 70b5c7f4..38bcc09b 100644 --- a/tests/python/test_release_version.py +++ b/tests/python/test_release_version.py @@ -55,28 +55,44 @@ def test_release_workflow_default_matches_workspace_version() -> None: assert version_input.group(1) == WORKSPACE_VERSION -def test_crate_release_dry_run_does_not_resolve_the_unpublished_core_version() -> None: +def test_crate_release_dry_run_packages_server_against_the_local_core() -> None: workflow = CRATE_RELEASE_WORKFLOW.read_text(encoding="utf-8") dry_run_block = next(block for block in _workflow_run_blocks(workflow) if "Would release commit" in block) - cargo_commands = [line.strip() for line in dry_run_block.splitlines() if line.strip().startswith("cargo ")] - - assert "cargo package --list -p agentic-server" in cargo_commands - assert not any( - "agentic-server" in command - and "agentic-server-core" not in command - and command != "cargo package --list -p agentic-server" - for command in cargo_commands + normalized_block = " ".join(dry_run_block.split()) + + for command in ( + "cargo check --workspace --locked", + "cargo clippy --all-targets --locked -- -D warnings", + "cargo test --locked", + "cargo publish --locked -p agentic-server-core", + "cargo publish --locked -p agentic-server", + ): + assert command in workflow + assert "cargo package --no-verify --locked -p agentic-server" in normalized_block + assert ( + "--config 'patch.crates-io.agentic-server-core.path=\"crates/agentic-server-core\"'" in normalized_block ) - - package_list = subprocess.run( - ["cargo", "package", "--list", "--allow-dirty", "-p", "agentic-server"], + assert "cargo package --list" not in normalized_block + + package = subprocess.run( + [ + "cargo", + "package", + "--no-verify", + "--locked", + "--allow-dirty", + "-p", + "agentic-server", + "--config", + 'patch.crates-io.agentic-server-core.path="crates/agentic-server-core"', + ], cwd=REPO_ROOT, capture_output=True, text=True, check=False, ) - assert package_list.returncode == 0, package_list.stderr - assert "Cargo.toml" in package_list.stdout.splitlines() + assert package.returncode == 0, package.stderr + assert (REPO_ROOT / "target" / "package" / f"agentic-server-{WORKSPACE_VERSION}.crate").is_file() def test_python_workflows_pin_build_tools_and_manylinux_artifact_contract() -> None: