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..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 -p agentic-server --no-verify + 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/.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 17f5f09f..cc1e78ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,47 +2,87 @@ All notable changes to Agentic API are documented here. -## [Unreleased] +## [0.6.0] - 2026-09-09 + +### Added + +- 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 forwarding the model-generation + preference, bounded concurrent execution for gateway-executed built-in tools, batched web searches, stable output + ordering, and per-call failure isolation (#181, #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 an `agentic-llm-d` split-execution backend with authenticated hydrate and persist endpoints for the llm-d + coordinator (#216). +- Added deployment guides and replay coverage for NVIDIA Dynamo and llm-d Kubernetes upstreams, including persistent + PostgreSQL storage in the kind guide (#184, #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). +- Added client tool search support with typed tool discovery, deferred tool materialization, stateful continuation, and + recorded streaming, non-streaming, and WebSocket coverage (#186). +- Added concurrent Responses WebSocket multiplexing with per-request `stream_id` routing, FIFO ordering within each + stream, and bounded concurrency across streams (#240). +- Added compile-time OpenAPI 3.1 schema generation and checked-in schema validation for the HTTP API (#229). +- Added pinned SGLang conformance recordings, replay coverage, and launch and recording guidance (#267). ### Changed -- 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. +- 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). +- Documented executor streaming ownership and validation boundaries, with a repository review skill for enforcing the + architecture (#246). +- Updated the execution architecture documentation to match the current scheduler and llm-d backend (#270). +- Preserved the typed `ignore_eos` extension when forwarding Responses requests to vLLM (#268). ### Fixed -- Bounded WebSocket queues, generated response data, gateway tool results, and MCP discovery and HTTP/SSE payloads so - concurrent response streams cannot grow memory without limit. -- Preserved upstream error metadata (`retry-after`, request IDs, rate-limit headers) and the upstream content type on - the non-streaming Responses executor path, sharing one upstream-error adapter with the Messages handler (#250). -- Rejected split-execution responses with missing, reused, or unstable tool call IDs before persistence, keeping the - reserved response ID available for a corrected retry. +- 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). - Hardened split execution with atomic duplicate persistence, strict relayed-response validation, independent secret - validation, bounded hydrate and persist payloads, stable error envelopes, and graceful shutdown error propagation. + validation, bounded hydrate and persist payloads, stable error envelopes, and graceful shutdown error propagation + (#235). +- Rejected relayed responses with missing, reused, or unstable tool call IDs before persistence, while preserving the + reserved response ID for corrected retries (#237). +- Aligned relayed SSE validation with provider-compatible event shapes while continuing to reject inconsistent + lifecycles and terminal items (#236). - Forwarded Responses `text` generation settings through typed execution paths while preserving provider-specific - text formats on stateless proxy requests and JSON Schema property order. -- Replaced `WebSearchActionSearch::new` and `WebSearchCall::new` with fallible - `try_new(...)` constructors; callers now handle `WebSearchActionError` for - empty query lists instead of risking a panic. - -### Added - -- Added concurrent Responses WebSocket multiplexing with per-request `stream_id` routing, preserving FIFO ordering - within each stream while allowing independent streams to interleave on one connection. -- Documented running Agentic API in front of NVIDIA Dynamo and recorded Dynamo cassettes for stateful and - function-call flows. + stateless proxy payloads and JSON Schema property order (#231, #234). +- Bounded WebSocket queues, response data, gateway tool results, and MCP discovery and transport payloads so concurrent + response streams cannot grow memory without limit (#240). +- Enforced CLI readiness deadlines across probes and retry sleeps, including stalled and late-success cases (#265). +- Cleaned up model subprocesses when startup is interrupted or fails during readiness and database initialization + (#266). +- Preserved upstream error headers and content types on non-streaming Responses errors (#250, #262). +- Accepted upstream SSE `data:` fields with or without an optional separating space (#269). +- Rejected unsupported message file content on typed Responses paths instead of silently dropping it (#258). +- Excluded image bytes from compaction token estimates while continuing to count surrounding text (#255, #259). +- Treated negative upstream `sequence_number` sentinels as unspecified while preserving otherwise valid streaming + events (#267). +- 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. +- Strengthened multi-round cassette assertions for public stream ordering and stabilized Python readiness retry coverage + across supported interpreter versions (#242, #247). +- Added regression coverage for structured `input_text` items that omit an explicit message type (#150, #248). ## [0.5.0] - 2026-08-25 diff --git a/Cargo.lock b/Cargo.lock index 64b93a91..a8ad0af8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 4 [[package]] name = "agentic-llm-d" -version = "0.5.0" +version = "0.6.0" dependencies = [ "agentic-server-core", "axum", @@ -23,14 +23,14 @@ dependencies = [ [[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", @@ -67,7 +67,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 7db122dd..43b164a1 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 d3a5f15c..fec2ff26 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..38bcc09b 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,54 @@ 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_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) + 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 + ) + 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.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: release_workflow = RELEASE_WORKFLOW.read_text(encoding="utf-8") python_workflow = PYTHON_WORKFLOW.read_text(encoding="utf-8") @@ -70,6 +119,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 +145,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)