Skip to content

feat(api): advertise contract version via X-Agentex-Version response header#322

Closed
max-parke-scale wants to merge 2 commits into
maxparke/add-release-pleasefrom
maxparke/agx1-367-server-version-header
Closed

feat(api): advertise contract version via X-Agentex-Version response header#322
max-parke-scale wants to merge 2 commits into
maxparke/add-release-pleasefrom
maxparke/agx1-367-server-version-header

Conversation

@max-parke-scale

@max-parke-scale max-parke-scale commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

What

Adds VersionHeaderMiddleware, which sets X-Agentex-Version: <__version__> on every response (sourced from src/_version.py, the release-managed version added in #321).

Why

Lets clients detect a server whose contract version is incompatible with their build. This is the server half of the runtime compat warning — consumed by the SDK's check (scale-agentex-python#410, AGX1-367), the runtime complement to the build-time compat suite. The SDK warning stays dormant until servers emit this header.

How

Pure-ASGI middleware (robust for streaming responses), registered in app.py alongside the existing middleware stack.

Test

tests/unit/api/test_version_header_middleware.py — a wrapped app sets X-Agentex-Version = __version__ (mirrors the existing health-interceptor unit-test pattern). ruff clean; test green.

Stacking

Stacked on #321 — needs src/_version.py (added there). PR base is the #321 branch so the diff shows only this change; will retarget to main once #321 merges.

Server half of AGX1-367.

🧑‍💻🤖 — posted via Claude Code

Greptile Summary

  • Adds VersionHeaderMiddleware to attach X-Agentex-Version from src._version.__version__ to API responses.
  • Registers the middleware in the FastAPI app and wires the OpenAPI app version to the same version source.
  • Adds unit coverage for the middleware behavior.

Confidence Score: 4/5

The version header integration needs fixes before merge because health endpoints and browser-accessible CORS responses do not fully support the advertised compatibility contract.

The changes are narrowly scoped and covered by a unit test, and focused runtime checks reproduced both contract gaps without indicating broader instability.

agentex/src/api/app.py needs attention for middleware ordering around health responses and CORS exposure of the version header.

T-Rex T-Rex Logs

What T-Rex did

  • Reproduced that health endpoints /healthz, /readyz, and /healthcheck respond with 200 OK but do not include the X-Agentex-Version header when requested from the exported ASGI app via Starlette TestClient.
  • Reproduced that a browser client cannot read the X-Agentex-Version header even when it is present in the response, because Access-Control-Expose-Headers is not configured to expose it.
  • Observed that after the code change, the x-agentex-version header appeared in the response (matching __version__ = 0.1.0) in the after-state logs.
  • Validated that the head artifact now exposes x-agentex-version in the response, with HTTP 200 OK and __version__=0.1.0, whereas the base artifact did not expose the header.

View all artifacts

T-Rex Ran code and verified through T-Rex

Comments Outside Diff (2)

  1. agentex/src/api/app.py, line 216 (link)

    P1 Health responses skip header. VersionHeaderMiddleware is installed inside fastapi_app, but the exported ASGI app is later wrapped with HealthCheckInterceptor. For /healthz, /readyz, and /healthcheck, that interceptor sends the response directly and never calls into the FastAPI middleware stack, so those production responses do not include X-Agentex-Version. Wrap the health interceptor inside the version middleware, or add the same header in the interceptor response helpers so health endpoints also advertise the contract version.

    Artifacts

    Repro: ASGI TestClient script requesting health endpoints from the exported app

    • Contains supporting evidence from the run (text/x-python; charset=utf-8).

    Repro: runtime request output showing HTTP status codes and missing X-Agentex-Version headers

    • Keeps the command output available without making the summary code-heavy.

    View artifacts

    T-Rex Ran code and verified through T-Rex

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: agentex/src/api/app.py
    Line: 216
    
    Comment:
    **Health responses skip header.** `VersionHeaderMiddleware` is installed inside `fastapi_app`, but the exported ASGI app is later wrapped with `HealthCheckInterceptor`. For `/healthz`, `/readyz`, and `/healthcheck`, that interceptor sends the response directly and never calls into the FastAPI middleware stack, so those production responses do not include `X-Agentex-Version`. Wrap the health interceptor inside the version middleware, or add the same header in the interceptor response helpers so health endpoints also advertise the contract version.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Fix in Cursor Fix in Claude Code Fix in Codex

  2. agentex/src/api/app.py, line 132-138 (link)

    P1 Browser clients cannot read header. X-Agentex-Version is a custom response header, but the CORS middleware does not expose it. For cross-origin browser requests, the response can include the header on the wire while response.headers.get('X-Agentex-Version') still returns nothing, so web clients cannot run the compatibility check. Add this header to expose_headers in the CORS config.

    Artifacts

    Repro: ASGI cross-origin request script for X-Agentex-Version exposure

    • Contains supporting evidence from the run (text/x-python; charset=utf-8).

    Stack trace captured during the T-Rex run

    • Keeps the raw stack trace available without making the summary code-heavy.

    View artifacts

    T-Rex Ran code and verified through T-Rex

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: agentex/src/api/app.py
    Line: 132-138
    
    Comment:
    **Browser clients cannot read header.** `X-Agentex-Version` is a custom response header, but the CORS middleware does not expose it. For cross-origin browser requests, the response can include the header on the wire while `response.headers.get('X-Agentex-Version')` still returns nothing, so web clients cannot run the compatibility check. Add this header to `expose_headers` in the CORS config.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

    Fix in Cursor Fix in Claude Code Fix in Codex

Fix All in Cursor Fix All in Claude Code Fix All in Codex

Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
agentex/src/api/app.py:216
**Health responses skip header.** `VersionHeaderMiddleware` is installed inside `fastapi_app`, but the exported ASGI app is later wrapped with `HealthCheckInterceptor`. For `/healthz`, `/readyz`, and `/healthcheck`, that interceptor sends the response directly and never calls into the FastAPI middleware stack, so those production responses do not include `X-Agentex-Version`. Wrap the health interceptor inside the version middleware, or add the same header in the interceptor response helpers so health endpoints also advertise the contract version.

### Issue 2 of 2
agentex/src/api/app.py:132-138
**Browser clients cannot read header.** `X-Agentex-Version` is a custom response header, but the CORS middleware does not expose it. For cross-origin browser requests, the response can include the header on the wire while `response.headers.get('X-Agentex-Version')` still returns nothing, so web clients cannot run the compatibility check. Add this header to `expose_headers` in the CORS config.

Reviews (1): Last reviewed commit: "feat(api): advertise contract version vi..." | Re-trigger Greptile

max-parke-scale and others added 2 commits June 17, 2026 13:39
scale-agentex has no version axis of its own — it floats at head, and the only
version concept comes from SGP's platform release. That leaves no way to name an
"oldest supported server contract" for downstream consumers.

Add release-please (release-type: python; conventional-commit PR titles already
enforced) so merges to main cut versioned releases: a vX.Y.Z git tag + GitHub
release + CHANGELOG.

Make the contract self-describe its version so the tag and spec never drift:
- src/_version.py is the single source; the FastAPI app reads it
  (version=__version__), so generate_openapi_spec.py emits it as info.version.
- release-please bumps src/_version.py, agentex/openapi.yaml (info.version),
  agentex/pyproject.toml, and the root pyproject together with the tag.

Each tag is thus an immutable, self-describing snapshot of agentex/openapi.yaml
the SDK's cross-version compat suite can pin as min-supported
(scaleapi/scale-agentex-python#407), replacing the placeholder commit SHA today.

Contract checkpoints, not a deploy gate: the server still floats at head for
deploys. Seeded at 0.1.0 with bootstrap-sha at current main HEAD so the
changelog starts fresh; the first feat/fix merge (or workflow_dispatch) cuts the
first release. Verified gen-openapi emits info.version from _version.py with no
spec drift.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…header

Adds VersionHeaderMiddleware, which sets X-Agentex-Version: <__version__> on
every response (sourced from src/_version.py, the release-managed version), so
clients can detect a server whose contract version is incompatible with their
build — consumed by the SDK's runtime check (scale-agentex-python#410, AGX1-367).

Pure-ASGI middleware (robust for streaming); registered in app.py alongside the
existing stack.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@max-parke-scale max-parke-scale requested a review from a team as a code owner June 17, 2026 18:34
@max-parke-scale

Copy link
Copy Markdown
Contributor Author

Closing as superseded by scaleapi/scale-agentex-python#408. That runtime version guard reads the backend's /openapi.json info.version rather than a response header, so this X-Agentex-Version middleware isn't needed — #321 already makes info.version self-describing, which is what #408 consumes. Consolidating onto #408 per discussion.

🧑‍💻🤖 — posted via Claude Code

@max-parke-scale max-parke-scale deleted the maxparke/agx1-367-server-version-header branch June 17, 2026 18:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant