Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down
21 changes: 11 additions & 10 deletions .github/workflows/release-crates.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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"
Expand All @@ -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 }}
Expand All @@ -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 }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down
100 changes: 70 additions & 30 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion docs/guides/python-installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
68 changes: 68 additions & 0 deletions tests/python/test_release_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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")
Expand All @@ -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] = []
Expand All @@ -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)