Skip to content

refactor(types): promote protocol types to agentex.protocol.*#371

Open
max-parke-scale wants to merge 5 commits into
nextfrom
maxparke/agx1-292-promote-protocol-types
Open

refactor(types): promote protocol types to agentex.protocol.*#371
max-parke-scale wants to merge 5 commits into
nextfrom
maxparke/agx1-292-promote-protocol-types

Conversation

@max-parke-scale
Copy link
Copy Markdown
Contributor

@max-parke-scale max-parke-scale commented May 27, 2026

Summary

Moves the JSON-RPC envelope types (JSONRPCRequest/Response/Error) and ACP method-param types (CreateTaskParams, SendMessageParams, SendEventParams, CancelTaskParams, RPCMethod) out of agentex.lib.types.* into a new canonical location at agentex.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:

  • This PR: refactor only — set up agentex.protocol.* as the canonical location for protocol types
  • Follow-up PR: introduces the slim agentex-sdk-client package (which will ship agentex.protocol.* but not agentex.lib.*), letting REST-only consumers like packages/egp-api-backend drop hand-rolled JSON-RPC dict literals in favor of these typed shapes

Motivation

egp-api-backend today hand-rolls a ~600-line JSON-RPC gateway at egp_api_backend/server/internal/interfaces/services/evaluation_task/evaluation_task_gateways/agentex_output_evaluation_task_gateway.py. Sample:

async with session.post(
    rpc_path,
    json={
        "jsonrpc": "2.0",
        "method": "task/create",
        "params": {
            "name": task_name,
            "params": {"description": ..., "is_eval": True},
        },
    },
) as resp:

These shapes map directly onto the types defined in agentex.lib.types.acp and agentex.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

File Change
src/agentex/protocol/__init__.py New — package docstring
src/agentex/protocol/acp.py Moved from src/agentex/lib/types/acp.py; no other change
src/agentex/protocol/json_rpc.py Moved from src/agentex/lib/types/json_rpc.py; swapped from agentex.lib.utils.model_utils import BaseModelfrom pydantic import BaseModel (behavior-preserving — these classes don't use any model_utils extensions like from_yaml/to_json/populate_by_name)
src/agentex/lib/types/acp.py Replaced with back-compat shim re-exporting from canonical path
src/agentex/lib/types/json_rpc.py Replaced with back-compat shim re-exporting from canonical path
9 files in src/agentex/lib/* Internal imports updated to canonical path
tests/test_header_forwarding.py Internal import updated to canonical path

Slim-safety of moved files

Both moved modules depend only on pydantic and agentex.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.py pulls temporal, converters.py pulls 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)

ruff check .
# All checks passed!

python -m build --wheel
unzip -l dist/agentex_sdk-0.11.4-py3-none-any.whl | grep -E "(protocol/|lib/types/(acp|json_rpc))"
# Both paths shipped: agentex/protocol/acp.py + agentex/protocol/json_rpc.py
#                     agentex/lib/types/acp.py (shim) + agentex/lib/types/json_rpc.py (shim)

# Identity check from a fresh install:
python -c "
from agentex.protocol.acp import RPCMethod as A
from agentex.lib.types.acp import RPCMethod as B
assert A is B  # same class object — shim re-exports, not duplicates
print('identity OK')
"
# identity OK

Test plan

  • CI passes (ruff, pyright, mypy, pytest).
  • Any consumer doing from agentex.lib.types.acp import CreateTaskParams continues to work without change.
  • from agentex.protocol.acp import CreateTaskParams works as the new canonical path.

Greptile Summary

This PR moves the JSON-RPC envelope types (JSONRPCRequest/Response/Error) and ACP method-param types out of agentex.lib.types.* into a new agentex.protocol.* package, the first step toward a slim REST-only client distribution. Back-compat shims at the old paths ensure existing imports continue to work unchanged.

  • New canonical package src/agentex/protocol/ contains acp.py and json_rpc.py; json_rpc.py explicitly restores from_attributes=True / populate_by_name=True via _PROTOCOL_MODEL_CONFIG to match the behavior inherited from the previous model_utils.BaseModel.
  • Shims at agentex.lib.types.acp and agentex.lib.types.json_rpc re-export all original symbols including RPC_SYNC_METHODS and PARAMS_MODEL_BY_METHOD, closing the regression flagged in the prior review pass.
  • tests/test_protocol_shims.py pins the back-compat contract: symbol parity, class object identity (preserving isinstance semantics for mixed import styles), and model_config preservation.

Confidence Score: 5/5

Safe to merge — pure refactor with full back-compat shims, no behavioral changes, and targeted tests covering the exact regressions raised in the prior review cycle.

Both concerns from the previous review round have been resolved: RPC_SYNC_METHODS/PARAMS_MODEL_BY_METHOD are now included in the shim, and from_attributes=True/populate_by_name=True are explicitly re-applied in the canonical json_rpc.py. The new test_protocol_shims.py locks in symbol parity, class identity, and model config preservation. No logic changed — only import paths moved.

No files require special attention.

Important Files Changed

Filename Overview
src/agentex/protocol/acp.py New canonical home for ACP param types; exact copy from the original lib/types/acp.py using the same plain pydantic.BaseModel — no behavior change.
src/agentex/protocol/json_rpc.py New canonical JSON-RPC types; explicitly restores from_attributes=True and populate_by_name=True via _PROTOCOL_MODEL_CONFIG to match legacy model_utils.BaseModel behavior.
src/agentex/lib/types/acp.py Replaced with back-compat shim; re-exports all original symbols including RPC_SYNC_METHODS and PARAMS_MODEL_BY_METHOD that were flagged as missing in a prior review pass.
src/agentex/lib/types/json_rpc.py Replaced with back-compat shim re-exporting JSONRPCError/Request/Response from the canonical path.
tests/test_protocol_shims.py New test file pinning the back-compat contract: symbol parity, class identity, and model_config preservation — directly validates the two concerns raised in the prior review.
src/agentex/lib/sdk/fastacp/base/base_acp_server.py Import updated from agentex.lib.types.* to agentex.protocol.* — straightforward one-line change per import group.

Reviews (5): Last reviewed commit: "fix(lint): sort imports in test_protocol..." | Re-trigger Greptile

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>
Comment thread src/agentex/lib/types/acp.py
Comment thread src/agentex/protocol/json_rpc.py Outdated
max-parke-scale added a commit that referenced this pull request May 27, 2026
…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>
@max-parke-scale
Copy link
Copy Markdown
Contributor Author

@greptile review

@declan-scale
Copy link
Copy Markdown
Contributor

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

@max-parke-scale
Copy link
Copy Markdown
Contributor Author

No, there's a shim at the old path.

max-parke-scale and others added 2 commits May 27, 2026 10:42
…,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>
Copy link
Copy Markdown
Contributor

@declan-scale declan-scale left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
@max-parke-scale
Copy link
Copy Markdown
Contributor Author

@declan-scale done — split across two PRs:

For sgp-docs: I grepped for any reference to the migrated modules (agentex.lib.types.acp / agentex.lib.types.json_rpc) and got zero hits. The only agentex.lib.types.* reference there is to tracing.SGPTracingProcessorConfig, which #371 didn't move (tracing isn't slim-safe — it brings in opentelemetry/ddtrace transitively). Let me know if I missed a doc surface there worth touching.

🤖 — posted via Claude Code

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.

2 participants