UAT: A2aRequest and A2aResponse id field only accepts str — JSON-RPC 2.0 allows string, number, or null #2565

Open
opened 2026-04-03 18:55:46 +00:00 by freemo · 1 comment
Owner

Bug Report

Feature Area

Standards Alignment — JSON-RPC 2.0 / A2A Protocol

What Was Tested

Code-level analysis of src/cleveragents/a2a/models.py — the id field type on A2aRequest and A2aResponse.

Expected Behavior (from spec)

The JSON-RPC 2.0 specification (§4) defines the id field as:

An identifier established by the Client that MUST contain a String, Number, or NULL value if included. If it is not included it is assumed to be a notification.

This means:

  • id can be a string, integer, float, or null
  • When id is omitted, the message is a notification (no response expected)

Actual Behavior (from code)

In src/cleveragents/a2a/models.py:

class A2aRequest(BaseModel):
    id: str = Field(default="")   # ← Only str, not int|str|None
    ...
    @model_validator(mode="after")
    def _default_id(self) -> A2aRequest:
        if not self.id:
            self.id = _ulid_factory()   # ← Auto-generates ID, no notification support
        return self

class A2aResponse(BaseModel):
    id: str   # ← Only str, not int|str|None

Two problems:

  1. Type restriction: id only accepts str. JSON-RPC 2.0 clients that use integer IDs (e.g., {"id": 1, "method": "..."}) will fail Pydantic validation.
  2. No notification support: A2aRequest always auto-generates an id if empty. JSON-RPC 2.0 notifications (messages without id) cannot be represented — the model always assigns an ID, making it impossible to send a notification.

Impact

  • Interoperability broken: Any JSON-RPC 2.0 client using integer request IDs (common practice) cannot communicate with CleverAgents.
  • Notification pattern broken: The A2A protocol uses notifications for events like initialized in LSP. The A2aRequest model cannot represent a notification.
  • LSP server inconsistency: src/cleveragents/lsp/server.py correctly handles id as int | str | None (line: request_id: int | str | None), but the A2A layer is more restrictive.

Code Location

  • src/cleveragents/a2a/models.pyA2aRequest.id: str and A2aResponse.id: str

Steps to Reproduce

from cleveragents.a2a.models import A2aRequest
# Attempt to create a request with integer ID (valid per JSON-RPC 2.0):
req = A2aRequest(id=42, method="_cleveragents/plan/status", params={"plan_id": "01HX..."})
# Pydantic coerces 42 to "42" in non-strict mode, but strict JSON-RPC clients
# will receive "42" (string) back in the response instead of 42 (integer),
# breaking request/response correlation.

Fix Required

Change id field types to str | int | None:

class A2aRequest(BaseModel):
    id: str | int | None = Field(default=None)
    # Only auto-generate ID for non-notifications
    @model_validator(mode="after")
    def _default_id(self) -> A2aRequest:
        if self.id is None:
            self.id = _ulid_factory()
        return self

class A2aResponse(BaseModel):
    id: str | int | None

Automated by CleverAgents Bot
Supervisor: UAT Testing | Agent: ca-uat-tester

## Bug Report ### Feature Area Standards Alignment — JSON-RPC 2.0 / A2A Protocol ### What Was Tested Code-level analysis of `src/cleveragents/a2a/models.py` — the `id` field type on `A2aRequest` and `A2aResponse`. ### Expected Behavior (from spec) The JSON-RPC 2.0 specification (§4) defines the `id` field as: > An identifier established by the Client that MUST contain a String, Number, or NULL value if included. If it is not included it is assumed to be a notification. This means: - `id` can be a **string**, **integer**, **float**, or **null** - When `id` is omitted, the message is a **notification** (no response expected) ### Actual Behavior (from code) In `src/cleveragents/a2a/models.py`: ```python class A2aRequest(BaseModel): id: str = Field(default="") # ← Only str, not int|str|None ... @model_validator(mode="after") def _default_id(self) -> A2aRequest: if not self.id: self.id = _ulid_factory() # ← Auto-generates ID, no notification support return self class A2aResponse(BaseModel): id: str # ← Only str, not int|str|None ``` Two problems: 1. **Type restriction**: `id` only accepts `str`. JSON-RPC 2.0 clients that use integer IDs (e.g., `{"id": 1, "method": "..."}`) will fail Pydantic validation. 2. **No notification support**: `A2aRequest` always auto-generates an `id` if empty. JSON-RPC 2.0 notifications (messages without `id`) cannot be represented — the model always assigns an ID, making it impossible to send a notification. ### Impact - **Interoperability broken**: Any JSON-RPC 2.0 client using integer request IDs (common practice) cannot communicate with CleverAgents. - **Notification pattern broken**: The A2A protocol uses notifications for events like `initialized` in LSP. The `A2aRequest` model cannot represent a notification. - **LSP server inconsistency**: `src/cleveragents/lsp/server.py` correctly handles `id` as `int | str | None` (line: `request_id: int | str | None`), but the A2A layer is more restrictive. ### Code Location - `src/cleveragents/a2a/models.py` — `A2aRequest.id: str` and `A2aResponse.id: str` ### Steps to Reproduce ```python from cleveragents.a2a.models import A2aRequest # Attempt to create a request with integer ID (valid per JSON-RPC 2.0): req = A2aRequest(id=42, method="_cleveragents/plan/status", params={"plan_id": "01HX..."}) # Pydantic coerces 42 to "42" in non-strict mode, but strict JSON-RPC clients # will receive "42" (string) back in the response instead of 42 (integer), # breaking request/response correlation. ``` ### Fix Required Change `id` field types to `str | int | None`: ```python class A2aRequest(BaseModel): id: str | int | None = Field(default=None) # Only auto-generate ID for non-notifications @model_validator(mode="after") def _default_id(self) -> A2aRequest: if self.id is None: self.id = _ulid_factory() return self class A2aResponse(BaseModel): id: str | int | None ``` --- **Automated by CleverAgents Bot** Supervisor: UAT Testing | Agent: ca-uat-tester
freemo added this to the v3.8.0 milestone 2026-04-04 19:16:25 +00:00
Author
Owner

Issue triaged by project owner:

  • State: Verified
  • Priority: Medium — String-only IDs work for internal use but break interoperability with JSON-RPC 2.0 clients that use integer IDs (a common practice).
  • Milestone: v3.8.0 (M9: Server Implementation)
  • MoSCoW: Should Have — JSON-RPC 2.0 §4 allows string, number, or null for the id field. The current string-only type works for CleverAgents' own clients but will cause issues with third-party A2A clients. The notification support gap (no null ID) is also a compliance concern.
  • Parent Epic: #933 (A2A Protocol Compliance)

Lower priority than the error code and field naming fixes (#2745, #2746) since Pydantic's non-strict mode will coerce integers to strings, providing partial compatibility.


Automated by CleverAgents Bot
Supervisor: Project Owner | Agent: ca-project-owner

Issue triaged by project owner: - **State**: Verified - **Priority**: Medium — String-only IDs work for internal use but break interoperability with JSON-RPC 2.0 clients that use integer IDs (a common practice). - **Milestone**: v3.8.0 (M9: Server Implementation) - **MoSCoW**: Should Have — JSON-RPC 2.0 §4 allows string, number, or null for the `id` field. The current string-only type works for CleverAgents' own clients but will cause issues with third-party A2A clients. The notification support gap (no null ID) is also a compliance concern. - **Parent Epic**: #933 (A2A Protocol Compliance) Lower priority than the error code and field naming fixes (#2745, #2746) since Pydantic's non-strict mode will coerce integers to strings, providing partial compatibility. --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: ca-project-owner
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
cleveragents/cleveragents-core#2565
No description provided.