Skip to content

Add core agent A2A adapter - #1456

Closed
Rajesh270712 wants to merge 4 commits into
iflytek:mainfrom
Rajesh270712:bountyops/BOU-3255-astron-a2a
Closed

Add core agent A2A adapter#1456
Rajesh270712 wants to merge 4 commits into
iflytek:mainfrom
Rajesh270712:bountyops/BOU-3255-astron-a2a

Conversation

@Rajesh270712

@Rajesh270712 Rajesh270712 commented Jun 26, 2026

Copy link
Copy Markdown

Summary

Adds a focused A2A adapter for the core agent service, including discovery, message sending, runtime task endpoints, and e2e-style task coverage.

Related Issue

Fixes #709.

Problem

Issue #709 asks for Astron Agent to expose an A2A-compatible core agent surface so external A2A clients can discover the agent and send messages/tasks. The existing service did not expose an A2A discovery card, message:send HTTP adapter, or task runtime endpoints for core/agent.

Change

  • Adds A2A Pydantic schemas for agent cards, messages, tasks, artifacts, security schemes, send-message requests/responses, task send params, and task status/artifact events.
  • Exposes /.well-known/agent-card.json and /agent/v1/a2a/agent-card.json with HTTP+JSON interface metadata, capabilities, skills, text I/O modes, and the existing x-consumer-username header auth shape.
  • Adds /agent/v1/a2a/message:send to map A2A text parts into the existing CustomChatCompletion runner and return A2A task envelopes for completed, submitted, and failed states.
  • Adds runtime task routes under /agent/v1/a2a: POST /tasks:send, GET /tasks/{task_id}, and GET /tasks/{task_id}/events.
  • Records completed/submitted task state and status/artifact events in an in-process runtime store for retrieval by A2A clients.
  • Declares the task event union as an explicit TypeAlias so the repo mypy quality check accepts the runtime task event annotations.
  • Adds tests for agent card shape, empty text rejection, completed responses, submitted-task behavior, and a FastAPI TestClient e2e-style send/get/events task flow.

Tests

Latest type-only CI fix validation:

  • cd core/agent && .venv/bin/python -m mypy --disallow-untyped-defs --disallow-incomplete-defs --check-untyped-defs --no-implicit-optional --ignore-missing-imports --explicit-package-bases . -> Success, no issues in 67 source files
  • cd core/agent && PYTHONPATH=.. .venv/bin/python -m pytest tests/test_a2a.py -q -> 9 passed, 3 warnings
  • flake8 on the touched file -> passed
  • isort --check-only --profile black on the touched file -> passed
  • black --check on the touched file -> passed
  • compileall -q api/v1/a2a.py -> passed
  • git diff --check HEAD~1..HEAD and git diff --check -> passed

Runtime task/e2e follow-up validation before the type-only CI fix:

  • PYTHONPATH=.. .venv/bin/python -m pytest tests/test_a2a.py -q -> 9 passed, 3 warnings
  • PYTHONPATH=.. .venv/bin/python -m pytest tests/test_router_and_schemas.py tests/test_main.py tests/test_a2a.py -q -> 22 passed, 5 warnings
  • PYTHONPATH=.. .venv/bin/python -m pytest tests -q -> 183 passed, 24 warnings
  • .venv/bin/python -m black --check api/v1/a2a.py api/schemas/a2a.py tests/test_a2a.py -> passed
  • .venv/bin/python -m isort --check-only --profile black api/v1/a2a.py api/schemas/a2a.py tests/test_a2a.py -> passed
  • .venv/bin/python -m flake8 --extend-ignore=E501 api/v1/a2a.py api/schemas/a2a.py tests/test_a2a.py -> passed
  • PYTHONPATH=.. .venv/bin/python -m compileall -q api/v1/a2a.py api/schemas/a2a.py tests/test_a2a.py -> passed

Risk Notes

  • This remains a text-only adapter slice.
  • The task runtime store is in-process memory; this PR does not add persistence or cross-worker synchronization.
  • The latest public CodeQL/CI workflow runs for head 0e0110f19786c0bb22c8afae03e21f377a8214e8 currently show action_required, so their jobs have not run yet.
  • I did not run a live Kagent or external A2A client integration test.
  • Runtime model/plugin config is still supplied through request metadata or the existing A2A_MODEL_* environment defaults.

Maintainer Attention

Please confirm whether the endpoint placement, auth declaration, A2A schema shape, and in-process task runtime approach match the direction you want for core/agent.

@CLAassistant

CLAassistant commented Jun 26, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces an A2A (Agent-to-Agent) protocol adapter for the core agent service, including Pydantic schemas, discovery and message-sending endpoints, and comprehensive unit tests. The review feedback focuses on improving robustness and error handling: specifically, wrapping SSE payload JSON parsing in a try-except block to prevent crashes, safely parsing the max_loop_count integer and providing a fallback UUID for uid, and breaking early from the SSE stream processing loop upon encountering an error.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread core/agent/api/v1/a2a.py
Comment on lines +248 to +256
def _parse_sse_payload(chunk: str) -> dict[str, Any] | None:
for line in chunk.splitlines():
if not line.startswith("data:"):
continue
payload = line.removeprefix("data:").strip()
if not payload or payload == "[DONE]":
return None
return json.loads(payload)
return None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

Parsing the SSE payload with json.loads without error handling can cause the request to crash with a 500 Internal Server Error if the stream contains malformed JSON or non-JSON data. Wrap the parsing logic in a try-except block to handle json.JSONDecodeError gracefully.

Suggested change
def _parse_sse_payload(chunk: str) -> dict[str, Any] | None:
for line in chunk.splitlines():
if not line.startswith("data:"):
continue
payload = line.removeprefix("data:").strip()
if not payload or payload == "[DONE]":
return None
return json.loads(payload)
return None
def _parse_sse_payload(chunk: str) -> dict[str, Any] | None:
for line in chunk.splitlines():
if not line.startswith("data:"):
continue
payload = line.removeprefix("data:").strip()
if not payload or payload == "[DONE]":
return None
try:
return json.loads(payload)
except json.JSONDecodeError:
return None
return None

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in f901f33: _parse_sse_payload now catches malformed JSON and ignores non-dict payloads instead of letting the request fail. Added regression coverage in test_collect_completion_text_ignores_malformed_sse_payload.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Follow-up after branch rewrite: the malformed SSE JSON fix is now in current public head 178eab5. _parse_sse_payload catches JSON decode errors and ignores non-dict payloads; regression coverage is test_collect_completion_text_ignores_malformed_sse_payload. Verified locally with PYTHONPATH=.. uv run --python 3.12 python -m pytest tests/test_a2a.py -q -> 8 passed.

Comment thread core/agent/api/v1/a2a.py Outdated
Comment on lines +164 to +186
max_loop_count = metadata.get("max_loop_count", os.getenv("A2A_MAX_LOOP_COUNT", 5))

return CustomCompletionInputs(
uid=(
_metadata_string(metadata, "uid")
or _metadata_string(request.message.metadata, "uid")
or request.message.context_id
or request.message.message_id
)[:64],
messages=[LLMMessage(role="user", content=text)],
stream=False,
meta_data={
"caller": "a2a_http_json",
"caller_sid": request.message.message_id,
"workflow_id": _metadata_string(metadata, "workflow_id"),
"run_id": request.message.task_id,
"node_id": _metadata_string(metadata, "node_id"),
},
model_config=model_config,
instruction=_metadata_mapping(metadata, "instruction"),
plugin=_metadata_mapping(metadata, "plugin"),
max_loop_count=int(max_loop_count),
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

Parsing max_loop_count directly using int() without validation can raise a ValueError or TypeError if the environment variable or metadata contains an invalid integer representation. Additionally, if uid is empty, it can cause tracing issues with Span. Robustly parse max_loop_count with a fallback and ensure uid has a fallback UUID. When using os.getenv, default values should be provided as strings to prevent type issues.

    try:
        max_loop_count = int(metadata.get("max_loop_count") or os.getenv("A2A_MAX_LOOP_COUNT", "5"))
    except (ValueError, TypeError):
        max_loop_count = 5

    return CustomCompletionInputs(
        uid=(
            _metadata_string(metadata, "uid")
            or _metadata_string(request.message.metadata, "uid")
            or request.message.context_id
            or request.message.message_id
            or str(uuid.uuid4())
        )[:64],
        messages=[LLMMessage(role="user", content=text)],
        stream=False,
        meta_data={
            "caller": "a2a_http_json",
            "caller_sid": request.message.message_id,
            "workflow_id": _metadata_string(metadata, "workflow_id"),
            "run_id": request.message.task_id,
            "node_id": _metadata_string(metadata, "node_id"),
        },
        model_config=model_config,
        instruction=_metadata_mapping(metadata, "instruction"),
        plugin=_metadata_mapping(metadata, "plugin"),
        max_loop_count=max_loop_count,
    )
References
  1. When using os.getenv in Python, default values should be provided as strings, not integers, to prevent TypeError if the environment variable is not set.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in f901f33: max loop count parsing now falls back safely when metadata/env values are invalid, and blank request identifiers now get a UUID fallback. Added regression coverage for invalid loop counts and missing IDs.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Follow-up after branch rewrite: the invalid max loop count and blank request ID fixes are now in current public head 178eab5. Invalid metadata/env loop counts fall back safely, and blank request identifiers get a UUID fallback; regression coverage covers invalid loop count and missing IDs. Verified locally with PYTHONPATH=.. uv run --python 3.12 python -m pytest tests/test_a2a.py -q -> 8 passed.

Comment thread core/agent/api/v1/a2a.py
Comment on lines +268 to +269
if payload.get("code", 0) != 0:
error_message = str(payload.get("message") or "A2A agent execution failed")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

When an error code is received in the SSE stream, the loop should break early to prevent unnecessary processing of subsequent chunks and potential secondary errors.

Suggested change
if payload.get("code", 0) != 0:
error_message = str(payload.get("message") or "A2A agent execution failed")
if payload.get("code", 0) != 0:
error_message = str(payload.get("message") or "A2A agent execution failed")
break

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in f901f33: stream collection now breaks immediately after an error payload, so later chunks are not processed after the error. Added regression coverage in test_collect_completion_text_stops_after_error_code.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Follow-up after branch rewrite: the stream error handling fix is now in current public head 178eab5. Stream collection breaks immediately after an error payload so later chunks are not processed; regression coverage is test_collect_completion_text_stops_after_error_code. Verified locally with PYTHONPATH=.. uv run --python 3.12 python -m pytest tests/test_a2a.py -q -> 8 passed.

@Rajesh270712
Rajesh270712 marked this pull request as draft June 26, 2026 15:50
@Rajesh270712
Rajesh270712 force-pushed the bountyops/BOU-3255-astron-a2a branch from 0f6143c to fcce485 Compare June 26, 2026 16:23
@Rajesh270712
Rajesh270712 marked this pull request as ready for review June 26, 2026 17:05
Signed-off-by: Rajesh Digambar Bagul <102693488+Rajesh270712@users.noreply.github.com>
Signed-off-by: Rajesh Digambar Bagul <102693488+Rajesh270712@users.noreply.github.com>

@dongjiang1989 dongjiang1989 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Good job. @Rajesh270712
We need tasks in runtime and add e2e test case

Comment thread core/agent/api/v1/a2a.py Outdated
organization="iFLYTEK",
url="https://github.com/iflytek/astron-agent",
),
version="0.1.0",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

(nit) We need keep version aligned with release version.

Comment thread core/agent/api/v1/a2a.py
"""Build public A2A discovery metadata for the core agent service."""

interface_url = f"{_public_base_url()}/agent/v1/a2a"
return A2AAgentCard(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Miss authentication struct. Authentication requirements for the agent.

Comment thread core/agent/api/v1/a2a.py
@@ -0,0 +1,342 @@
"""A2A protocol adapter for the core agent service."""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Miss task completion. About: Task, TaskStatus, TaskStatusUpdateEvent, TaskArtifactUpdateEvent, TaskSendParams etc.

Expose task-oriented A2A send/get/events routes backed by the core agent completion runtime.

Record completed task state and status/artifact events so A2A clients can retrieve runtime task output after execution.

Signed-off-by: Rajesh Digambar Bagul <102693488+Rajesh270712@users.noreply.github.com>
@Rajesh270712

Copy link
Copy Markdown
Author

Thanks for the review. I pushed 19e41d45f5c31f310deb360aa5e688f18d5bf43e to address the requested runtime tasks and e2e coverage.

Changes in that head:

  • Added runtime task routes: POST /agent/v1/a2a/tasks:send, GET /agent/v1/a2a/tasks/{task_id}, and GET /agent/v1/a2a/tasks/{task_id}/events.
  • Added task runtime schemas/events and in-process runtime task recording for completed/submitted A2A tasks.
  • Added test_tasks_send_get_and_events_e2e covering send/get/events through FastAPI TestClient.

Validation:

  • PYTHONPATH=.. .venv/bin/python -m pytest tests/test_a2a.py -q -> 9 passed, 3 warnings
  • PYTHONPATH=.. .venv/bin/python -m pytest tests/test_router_and_schemas.py tests/test_main.py tests/test_a2a.py -q -> 22 passed, 5 warnings
  • PYTHONPATH=.. .venv/bin/python -m pytest tests -q -> 183 passed, 24 warnings
  • black --check, isort --check-only, flake8 --extend-ignore=E501, compileall, and git diff --check passed on the touched files.

Note: the runtime task store is in-process memory for this adapter follow-up; I did not add persistence or cross-worker synchronization.

Signed-off-by: Rajesh Digambar Bagul <102693488+Rajesh270712@users.noreply.github.com>
@Rajesh270712

Copy link
Copy Markdown
Author

I pushed 0e0110f19786c0bb22c8afae03e21f377a8214e8 to address the post-push CI mypy failure from 19e41d45f5c31f310deb360aa5e688f18d5bf43e.

The change declares _TaskEvent as an explicit TypeAlias, which fixes the mypy valid-type errors for api/v1/a2a.py lines 49 and 302 without changing runtime behavior.

Validation:

  • cd core/agent && .venv/bin/python -m mypy --disallow-untyped-defs --disallow-incomplete-defs --check-untyped-defs --no-implicit-optional --ignore-missing-imports --explicit-package-bases . -> success, no issues in 67 source files
  • cd core/agent && PYTHONPATH=.. .venv/bin/python -m pytest tests/test_a2a.py -q -> 9 passed, 3 warnings
  • flake8, isort --check-only, black --check, compileall, and git diff --check passed for the touched file/diff.

Public CI note: GitHub currently reports the new CodeQL/CI workflow runs for 0e0110f19786c0bb22c8afae03e21f377a8214e8 as action_required, so I will keep this PR on the monitor path until those jobs run or maintainer feedback arrives.

@dongjiang1989

Copy link
Copy Markdown
Contributor

@Rajesh270712

Thanks a lot for your great contribution!

This pull request contains too many changes in one batch. Could you please split it into 3 separate smaller PRs as outlined below for easier review and incremental merge:

  • A2A schemas + Agent Card discovery & registration logic
  • A2A core task modules: Task, TaskStatus and TaskList related implementations
  • Remaining features including authentication logic, together with all E2E test cases

Let me know once you’ve split them out, I’ll start reviewing each one promptly.

@Rajesh270712

Copy link
Copy Markdown
Author

Thanks for the guidance. I split the work into the requested smaller pieces:

  1. A2A schemas + Agent Card discovery/registration: Add A2A agent card discovery #1480
  2. A2A core task modules/runtime: Add A2A task runtime store Rajesh270712/astron-agent#1
  3. Remaining auth/message execution + E2E tests: Add A2A message auth execution tests Rajesh270712/astron-agent#2

Part 2 and 3 are stacked review PRs in my fork so their diffs stay small. GitHub does not let me open an upstream PR against a base branch that only exists in my fork, so after #1480 lands I can retarget/open part 2 upstream, then do the same for part 3 after part 2.

Validation run locally with Python 3.12:

  • uv run pytest tests/test_a2a.py -q
  • uv run isort --profile black --check-only api/schemas/a2a.py api/v1/a2a.py tests/test_a2a.py
  • uv run black --check api/schemas/a2a.py api/v1/a2a.py tests/test_a2a.py
  • uv run flake8 --extend-ignore=E501 api/schemas/a2a.py api/v1/a2a.py tests/test_a2a.py

@FenjuFu

FenjuFu commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Closing in favor of the split stack starting with #1480 — we'll review the pieces there. Thanks for splitting it up.

@FenjuFu FenjuFu closed this Jul 17, 2026
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.

[FEATURE] Support A2A(agent2agent) Protocol

4 participants