refactor(types): promote protocol types to agentex.protocol.*#371
Conversation
Moves the JSON-RPC envelope types and ACP method-param types out of the
hand-authored ADK overlay (agentex.lib.types.*) into a new canonical
location at agentex.protocol.*. Back-compat shims at the old paths
re-export the same classes, so existing imports continue to work.
Motivation:
The existing layout puts protocol-shape types under agentex.lib.types.
That means a REST-only consumer who just wants typed JSON-RPC envelopes
(e.g. egp-api-backend, which today hand-rolls a ~600-line gateway
constructing `{"jsonrpc": "2.0", "method": "task/create", ...}` dicts by
hand) can't import the types without pulling in the full heavy ADK
runtime (~31 deps including temporalio, fastapi, claude-agent-sdk, etc.).
This is the first step toward letting that consumer drop hand-rolled
dict literals in favor of typed CreateTaskParams / SendMessageParams /
SendEventParams / JSONRPCRequest / JSONRPCResponse models, ahead of the
forthcoming slim-package split (a separate PR).
Scope:
- Move src/agentex/lib/types/acp.py → src/agentex/protocol/acp.py
- Move src/agentex/lib/types/json_rpc.py → src/agentex/protocol/json_rpc.py
- json_rpc.py: switch from `agentex.lib.utils.model_utils.BaseModel`
(which transitively imports pyyaml) to `pydantic.BaseModel` directly.
These classes don't use any model_utils extensions (from_yaml, to_json,
populate_by_name) so the swap is behavior-preserving.
- Add re-export shims at the old paths so existing
`from agentex.lib.types.{acp,json_rpc} import ...` imports continue
to work. Identity check confirms the shimmed and canonical classes
are the same objects.
- Update all internal usages within agentex.lib.* to import from the
canonical path (9 files in src/agentex/lib + 1 in tests/).
Files moved use only pydantic + agentex.types.{Task,Agent,Event,
TaskMessageContent} — all slim-safe deps. Other agentex.lib.types.*
modules with heavier deps (fastacp pulls temporal, converters pulls
openai-agents) are left in place.
Verified locally:
- ruff check . → All checks passed
- Wheel build → ships both agentex/protocol/* and shims at
agentex/lib/types/{acp,json_rpc}.py
- Import test from a fresh editable install: both canonical and shim
paths resolve to the same class objects (identity-checked)
Zero install-time impact for existing consumers — same import paths
keep working.
Tracking: AGX1-292 (the slim/heavy split that motivates this refactor
lands as a follow-up PR).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…dk split Stacks on #371 (which moved protocol types to agentex.protocol.*). Together they enable REST-only consumers to install just the slim package and use typed protocol types without pulling the full ADK stack. Two-package layout: - agentex-sdk-client (slim, root pyproject.toml) * 6 deps: httpx, pydantic, typing-extensions, anyio, distro, sniffio * Ships agentex/{__init__.py, _*.py, _utils/, types/, resources/, protocol/, py.typed} * requires-python >= 3.11 * Stainless-managed * Slim wheel excludes src/agentex/lib/** - agentex-sdk (heavy, adk/pyproject.toml) * Depends on agentex-sdk-client>=0.11.4,<0.12 (lockstep, see comment) * Plus 31 ADK deps: temporalio, fastapi, redis, MCP, LLM providers, observability, CLI surface * Ships only agentex/lib/* via hatchling force-include from ../src/agentex/lib (lib/ stays at its historical location) * requires-python >= 3.12 (lib uses `from typing import override`, a 3.12+ stdlib API) * Hand-authored overlay; entire adk/ directory must be preserved across Stainless codegen via `keep_files: ["adk/**"]` Existing consumers (`pip install agentex-sdk`) see no change: heavy depends on slim, so the slim deps install transitively. Both packages contribute disjoint files to the agentex.* namespace. Release / publish wiring: - bin/publish-pypi publishes slim BEFORE heavy. Heavy depends on slim, so if the slim publish errors we abort before shipping a broken heavy that pins an unreleased slim. (Staff-engineer review fix.) - bin/check-release-environment validates BOTH PyPI tokens, with the legacy PYPI_TOKEN as a back-compat fallback. - .github/workflows/publish-pypi.yml passes AGENTEX_SDK_CLIENT_PYPI_TOKEN and AGENTEX_PYPI_TOKEN to the script. - release-please-config.json: two-package mode with components (agentex-sdk-client at root, agentex-sdk at adk/), and include-component-in-tag=true so tags become e.g. `agentex-sdk-client-v0.11.4` and `agentex-sdk-v0.11.4`. - .release-please-manifest.json: seeds adk/ at 0.11.4 so the first release produces matched versions. CI build job runs `rye build --wheel` for both packages; --wheel only (not sdist) because adk's wheel uses cross-directory force-include which can't resolve from inside an unpacked sdist tarball. Tag scheme breakage flagged with `!` in commit/PR title — downstream tooling that filters by raw `v*` tags will need updates. Required maintainer follow-ups before this can ship: - Stainless dashboard: add `adk/**` to keep_files; reduce dashboard dep-list to the 6 slim-base deps so codegen doesn't re-add the 31 ADK deps to root pyproject's `dependencies = [...]`. - PyPI: claim `agentex-sdk-client` package name; add AGENTEX_SDK_CLIENT_PYPI_TOKEN to repo secrets. - (Defer to a follow-up PR) post-codegen CI guardrail that asserts root pyproject's `dependencies = [...]` matches the 6-dep slim set. Verified locally: - Both wheels build cleanly via `rye build --wheel` - Slim ships agentex/protocol/* and excludes agentex/lib/* - Heavy ships only agentex/lib/* (incl. the back-compat shims from #371) - Dual install on Python 3.13: from agentex import Agentex AND from agentex.protocol.acp import RPCMethod AND from agentex.lib.utils.logging import make_logger AND from agentex.lib.types.acp import RPCMethod (shim) — all resolve; shim and canonical identity-check as the same class objects. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three issues from Greptile's review (4/5 confidence, all P1/P2):
1. Back-compat shim at `agentex.lib.types.acp` was missing two public
names from the original module: `RPC_SYNC_METHODS` and
`PARAMS_MODEL_BY_METHOD`. External consumers importing those from the
old path would have hit ImportError, breaking the PR's "zero install-
time impact" guarantee. Added both to the re-export list.
2. `agentex.protocol.json_rpc` silently dropped `from_attributes=True`
and `populate_by_name=True` config when switching from
`model_utils.BaseModel` to plain `pydantic.BaseModel`. Restored via
an explicit `model_config = ConfigDict(...)` on all three classes,
with a comment explaining why. The previous version inherited these
flags transitively; making them explicit + documented avoids drift.
3. All 10 CLI scaffolding templates (`agentex init`) generated
`from agentex.lib.types.acp import ...` — works via the shim but
immediately stale on the day this PR establishes
`agentex.protocol.acp` as canonical. Updated all templates to use
the new path so scaffolded code starts on the right foot.
Verified locally:
- ruff check . → All checks passed
- shim re-exports all 7 original symbols (5 classes + 2 module-level
constants), identity-checked vs canonical
- JSONRPCRequest/Response/Error all have
`model_config.get('from_attributes') is True` and
`populate_by_name is True`
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@greptile review |
|
This would be a breaking change right? Any consumer that imports these types (any agentex agent using them) will have to update their import paths |
|
No, there's a shim at the old path. |
…,json_rpc} Codifies the invariants Greptile flagged in PR review (and that the previous commit fixed): 1. Every symbol the original modules exported must be importable from the shim path — including the two module-level constants (RPC_SYNC_METHODS, PARAMS_MODEL_BY_METHOD) that an earlier shim iteration dropped. 2. The shim re-exports must be the *same* class objects as the canonical path (identity check, not just type equality). Different objects would silently break isinstance/match-case for any consumer that mixes import styles. 3. The pydantic ConfigDict (from_attributes=True, populate_by_name=True) that JSONRPCRequest/Response/Error inherited from model_utils.BaseModel before the refactor stays preserved on the canonical agentex.protocol.json_rpc classes. Verified locally: - All 5 tests pass against the current shim. - Simulated regression (removing RPC_SYNC_METHODS from the shim re-exports): 2 tests fail with the exact ImportError + AttributeError a downstream consumer would hit. Catches the contract violation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three I001 errors from `ruff check` on the new test file — length-sort ordering inside each `from X import (a, b, c)` block. Auto-fixed via `ruff check --fix`; test still passes after sort. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
declan-scale
left a comment
There was a problem hiding this comment.
Can we update the CLAUDE.md file to reflect the migration and also update the the docs in scale-agentex/agentex/docs and sgp-docs
…olicy
Per Declan's review on this PR — document the protocol-types migration
in CLAUDE.md so future contributors know:
- `src/agentex/protocol/` is the canonical home for slim-safe wire types
(acp.py + json_rpc.py); imports allowed from a future REST-only install.
- `src/agentex/lib/types/{acp,json_rpc}.py` are back-compat shims —
re-exporting from the canonical path. Existing user imports unaffected;
new code should target agentex.protocol.*.
- Other `lib/types/*` modules (tracing, agent_card, credentials, fastacp,
llm_messages, converters) stay in place because they have heavier
transitive deps that aren't slim-safe.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@declan-scale done — split across two PRs:
For 🤖 — posted via Claude Code |
|
Note that the docs PR shouldn't be merged until this lands in a release - it'll fail CI until then anyway. |
…dk split Stacks on #371 (which moved protocol types to agentex.protocol.*). Together they enable REST-only consumers to install just the slim package and use typed protocol types without pulling the full ADK stack. Two-package layout: - agentex-sdk-client (slim, root pyproject.toml) * 6 deps: httpx, pydantic, typing-extensions, anyio, distro, sniffio * Ships agentex/{__init__.py, _*.py, _utils/, types/, resources/, protocol/, py.typed} * requires-python >= 3.11 * Stainless-managed * Slim wheel excludes src/agentex/lib/** - agentex-sdk (heavy, adk/pyproject.toml) * Depends on agentex-sdk-client>=0.11.4,<0.12 (lockstep, see comment) * Plus 31 ADK deps: temporalio, fastapi, redis, MCP, LLM providers, observability, CLI surface * Ships only agentex/lib/* via hatchling force-include from ../src/agentex/lib (lib/ stays at its historical location) * requires-python >= 3.12 (lib uses `from typing import override`, a 3.12+ stdlib API) * Hand-authored overlay; entire adk/ directory must be preserved across Stainless codegen via `keep_files: ["adk/**"]` Existing consumers (`pip install agentex-sdk`) see no change: heavy depends on slim, so the slim deps install transitively. Both packages contribute disjoint files to the agentex.* namespace. Release / publish wiring: - bin/publish-pypi publishes slim BEFORE heavy. Heavy depends on slim, so if the slim publish errors we abort before shipping a broken heavy that pins an unreleased slim. (Staff-engineer review fix.) - bin/check-release-environment validates BOTH PyPI tokens, with the legacy PYPI_TOKEN as a back-compat fallback. - .github/workflows/publish-pypi.yml passes AGENTEX_SDK_CLIENT_PYPI_TOKEN and AGENTEX_PYPI_TOKEN to the script. - release-please-config.json: two-package mode with components (agentex-sdk-client at root, agentex-sdk at adk/), and include-component-in-tag=true so tags become e.g. `agentex-sdk-client-v0.11.4` and `agentex-sdk-v0.11.4`. - .release-please-manifest.json: seeds adk/ at 0.11.4 so the first release produces matched versions. CI build job runs `rye build --wheel` for both packages; --wheel only (not sdist) because adk's wheel uses cross-directory force-include which can't resolve from inside an unpacked sdist tarball. Tag scheme breakage flagged with `!` in commit/PR title — downstream tooling that filters by raw `v*` tags will need updates. Required maintainer follow-ups before this can ship: - Stainless dashboard: add `adk/**` to keep_files; reduce dashboard dep-list to the 6 slim-base deps so codegen doesn't re-add the 31 ADK deps to root pyproject's `dependencies = [...]`. - PyPI: claim `agentex-sdk-client` package name; add AGENTEX_SDK_CLIENT_PYPI_TOKEN to repo secrets. - (Defer to a follow-up PR) post-codegen CI guardrail that asserts root pyproject's `dependencies = [...]` matches the 6-dep slim set. Verified locally: - Both wheels build cleanly via `rye build --wheel` - Slim ships agentex/protocol/* and excludes agentex/lib/* - Heavy ships only agentex/lib/* (incl. the back-compat shims from #371) - Dual install on Python 3.13: from agentex import Agentex AND from agentex.protocol.acp import RPCMethod AND from agentex.lib.utils.logging import make_logger AND from agentex.lib.types.acp import RPCMethod (shim) — all resolve; shim and canonical identity-check as the same class objects. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Moves the JSON-RPC envelope types (
JSONRPCRequest/Response/Error) and ACP method-param types (CreateTaskParams,SendMessageParams,SendEventParams,CancelTaskParams,RPCMethod) out ofagentex.lib.types.*into a new canonical location atagentex.protocol.*. Back-compat shims at the old paths re-export the same classes — existing imports continue to work.Zero install-time impact for existing consumers. Same import paths keep working.
Tracking: AGX1-292. First of two PRs:
agentex.protocol.*as the canonical location for protocol typesagentex-sdk-clientpackage (which will shipagentex.protocol.*but notagentex.lib.*), letting REST-only consumers likepackages/egp-api-backenddrop hand-rolled JSON-RPC dict literals in favor of these typed shapesMotivation
egp-api-backendtoday hand-rolls a ~600-line JSON-RPC gateway ategp_api_backend/server/internal/interfaces/services/evaluation_task/evaluation_task_gateways/agentex_output_evaluation_task_gateway.py. Sample:These shapes map directly onto the types defined in
agentex.lib.types.acpandagentex.lib.types.json_rpc. They hand-roll because importing those types currently requires pulling in the full ADK runtime (~31 deps including temporalio, fastapi, claude-agent-sdk, etc.).Moving the protocol types out of
agentex.lib.*is the prerequisite for letting them ship in a future slim-package install.Changes
src/agentex/protocol/__init__.pysrc/agentex/protocol/acp.pysrc/agentex/lib/types/acp.py; no other changesrc/agentex/protocol/json_rpc.pysrc/agentex/lib/types/json_rpc.py; swappedfrom agentex.lib.utils.model_utils import BaseModel→from pydantic import BaseModel(behavior-preserving — these classes don't use anymodel_utilsextensions likefrom_yaml/to_json/populate_by_name)src/agentex/lib/types/acp.pysrc/agentex/lib/types/json_rpc.pysrc/agentex/lib/*tests/test_header_forwarding.pySlim-safety of moved files
Both moved modules depend only on
pydanticandagentex.types.{Task, Agent, Event, TaskMessageContent}— all already-slim-safe (Stainless-generated client surface + pydantic, which is in the bare client's 6 base deps).Other
agentex.lib.types.*modules have heavier deps (e.g.fastacp.pypulls temporal,converters.pypulls openai-agents) and are intentionally left in place. They can be promoted in follow-up PRs if/when other consumers need them slim-safe.Verification (local)
Test plan
from agentex.lib.types.acp import CreateTaskParamscontinues to work without change.from agentex.protocol.acp import CreateTaskParamsworks as the new canonical path.Greptile Summary
This PR promotes the JSON-RPC envelope types (
JSONRPCRequest/Response/Error) and ACP method-param types (CreateTaskParams,SendMessageParams, etc.) fromagentex.lib.types.*to a new canonical package atagentex.protocol.*. The old paths are converted to thin re-export shims so existing consumer imports continue to work without any code changes.agentex.protocolpackage (acp.py,json_rpc.py,__init__.py) holds the canonical definitions;json_rpc.pyexplicitly restores thefrom_attributes=True/populate_by_name=TrueConfigDictthat was previously inherited from the sharedmodel_utils.BaseModel.agentex.lib.types.{acp,json_rpc}re-export all original public symbols — includingRPC_SYNC_METHODSandPARAMS_MODEL_BY_METHOD— soisinstancechecks, type annotations, and existing imports remain identity-safe.tests/test_protocol_shims.pypins both the symbol-parity and class-identity invariants, and separately asserts theConfigDictpreservation.Confidence Score: 5/5
Safe to merge — this is a pure namespace reorganisation with no behavioural changes and fully-covered back-compat shims.
Both issues raised in the previous review round have been addressed in full: the shim now re-exports RPC_SYNC_METHODS and PARAMS_MODEL_BY_METHOD, and json_rpc.py explicitly restores from_attributes=True / populate_by_name=True via a shared ConfigDict. A dedicated test file pins both invariants. All internal imports and CLI templates have been mechanically updated to the canonical paths. No logic was changed anywhere.
No files require special attention.
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A[agentex.protocol.acp\nCANONICAL] -->|re-exported by| B[agentex.lib.types.acp\nback-compat shim] C[agentex.protocol.json_rpc\nCANONICAL] -->|re-exported by| D[agentex.lib.types.json_rpc\nback-compat shim] A -->|imported directly| E[fastacp/base_acp_server.py] A -->|imported directly| F[fastacp/impl/*.py] A -->|imported directly| G[temporal workflows & services] A -->|imported directly| H[CLI templates *.j2] C -->|imported directly| E B -->|still works for| I[external consumers] D -->|still works for| I subgraph "agentex.protocol package" A C J[__init__.py\ndocstring only] endReviews (6): Last reviewed commit: "docs(claude): document agentex.protocol/..." | Re-trigger Greptile