Skip to content

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

Merged
max-parke-scale merged 5 commits into
nextfrom
maxparke/agx1-292-promote-protocol-types
May 27, 2026
Merged

refactor(types): promote protocol types to agentex.protocol.*#371
max-parke-scale merged 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 promotes the JSON-RPC envelope types (JSONRPCRequest/Response/Error) and ACP method-param types (CreateTaskParams, SendMessageParams, etc.) from agentex.lib.types.* to a new canonical package at agentex.protocol.*. The old paths are converted to thin re-export shims so existing consumer imports continue to work without any code changes.

  • New agentex.protocol package (acp.py, json_rpc.py, __init__.py) holds the canonical definitions; json_rpc.py explicitly restores the from_attributes=True / populate_by_name=True ConfigDict that was previously inherited from the shared model_utils.BaseModel.
  • Back-compat shims at agentex.lib.types.{acp,json_rpc} re-export all original public symbols — including RPC_SYNC_METHODS and PARAMS_MODEL_BY_METHOD — so isinstance checks, type annotations, and existing imports remain identity-safe.
  • New test file tests/test_protocol_shims.py pins both the symbol-parity and class-identity invariants, and separately asserts the ConfigDict preservation.

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

Filename Overview
src/agentex/protocol/acp.py New canonical location for ACP types; identical content to the original lib/types/acp.py using plain pydantic.BaseModel (which the original already used). All seven public symbols present.
src/agentex/protocol/json_rpc.py New canonical location for JSON-RPC types; explicitly preserves from_attributes=True / populate_by_name=True via _PROTOCOL_MODEL_CONFIG, restoring the behavior previously inherited from model_utils.BaseModel.
src/agentex/lib/types/acp.py Replaced with back-compat shim; re-exports all seven original public symbols including RPC_SYNC_METHODS and PARAMS_MODEL_BY_METHOD.
src/agentex/lib/types/json_rpc.py Replaced with back-compat shim; re-exports all three JSON-RPC envelope classes from the canonical path.
tests/test_protocol_shims.py New test file pinning symbol-parity, class-identity, and ConfigDict preservation invariants across both shims; directly addresses the concerns raised in previous review threads.
src/agentex/lib/sdk/fastacp/base/base_acp_server.py Import updated to agentex.protocol.{acp,json_rpc}; no logic change.

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]
    end
Loading

Reviews (6): Last reviewed commit: "docs(claude): document agentex.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

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

Note that the docs PR shouldn't be merged until this lands in a release - it'll fail CI until then anyway.

@max-parke-scale max-parke-scale merged commit 6f1c14f into next May 27, 2026
37 checks passed
@max-parke-scale max-parke-scale deleted the maxparke/agx1-292-promote-protocol-types branch May 27, 2026 15:36
@stainless-app stainless-app Bot mentioned this pull request May 27, 2026
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>
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