Skip to content

Commit f75354a

Browse files
committed
Merge branch '1.0-dev' of https://github.com/a2aproject/a2a-python into reduce-boilerplate
2 parents 03366d9 + 3468180 commit f75354a

8 files changed

Lines changed: 360 additions & 28 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ coverage.xml
1212
spec.json
1313
docker-compose.yaml
1414
.geminiignore
15+
docs/ai/ai_learnings.md
1516

1617
# ITK Integration Test Artifacts
1718
itk/a2a-samples/

GEMINI.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,20 @@
2323
1. **Required Reading**: You MUST read the contents of @./docs/ai/coding_conventions.md and @./docs/ai/mandatory_checks.md at the very beginning of EVERY coding task.
2424
2. **Initial Checklist**: Every `task.md` you create MUST include a section for **Mandatory Checks** from @./docs/ai/mandatory_checks.md.
2525
3. **Verification Requirement**: You MUST run all mandatory checks before declaring any task finished.
26+
27+
## 5. Mistake Reflection Protocol
28+
29+
When you realise you have made a mistake — whether caught by the user,
30+
by a tool, or by your own reasoning — you MUST:
31+
32+
1. **Acknowledge the mistake explicitly** and explain what went wrong.
33+
2. **Reflect on the root cause**: was it a missing check, a false
34+
assumption, skipped verification, or a gap in the workflow?
35+
3. **Immediately append a new entry to @./docs/ai/ai_learnings.md**
36+
following the format defined in that file. This is not optional and
37+
does not require user confirmation. Do it before continuing. Update user
38+
about the changes to the workflow in the current chat.
39+
40+
The goal is to treat every mistake as a signal that the workflow is
41+
incomplete, and to improve it in place so the same mistake cannot
42+
happen again.

docs/ai/ai_learnings.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
> [!NOTE] for Users:
2+
> This document is meant to be read by an AI assistant (Gemini) in order to
3+
> learn from its mistakes and improve its behavior on this project. Use
4+
> its findings to improve GEMINI.md setup.
5+
6+
# AI Learnings
7+
8+
A living record of mistakes made during this project and the rules
9+
derived from them. Every entry must follow the format below.
10+
11+
---
12+
13+
## Entry format
14+
15+
**Mistake**: What went wrong.
16+
**Root cause**: Why it happened.
17+
**Rule**: The concrete rule added to prevent recurrence.
18+
19+
---

samples/cli.py

Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,41 +11,52 @@
1111

1212
from a2a.client import A2ACardResolver, ClientConfig, create_client
1313
from a2a.types import Message, Part, Role, SendMessageRequest, TaskState
14-
from a2a.utils.message import get_message_text
14+
from a2a.utils import get_artifact_text, get_message_text
15+
from a2a.utils.agent_card import display_agent_card
1516

1617

1718
async def _handle_stream(
1819
stream: Any, current_task_id: str | None
1920
) -> str | None:
2021
async for event in stream:
22+
if event.HasField('message'):
23+
print('Message:', get_message_text(event.message, delimiter=' '))
24+
return None
25+
2126
if not current_task_id:
22-
current_task_id = event.task.id
23-
if event:
24-
if event.HasField('status_update'):
25-
state_name = TaskState.Name(event.status_update.status.state)
26-
print(f'TaskStatusUpdate [state={state_name}]:', end=' ')
27-
if event.status_update.status.HasField('message'):
28-
message = event.status_update.status.message
29-
print(get_message_text(message, delimiter=' '))
30-
print()
31-
32-
if (
33-
event.status_update.status.state
34-
== TaskState.TASK_STATE_COMPLETED
35-
):
36-
current_task_id = None
37-
print('--- Task Completed ---')
38-
39-
elif event.HasField('artifact_update'):
40-
print(
41-
f'TaskArtifactUpdate [name={event.artifact_update.artifact.name}]:',
42-
end=' ',
27+
if event.HasField('task'):
28+
current_task_id = event.task.id
29+
print('--- Task Started ---')
30+
print(f'Task [state={TaskState.Name(event.task.status.state)}]')
31+
else:
32+
raise ValueError(f'Unexpected first event: {event}')
33+
34+
if event.HasField('status_update'):
35+
state_name = TaskState.Name(event.status_update.status.state)
36+
message_text = (
37+
': '
38+
+ get_message_text(
39+
event.status_update.status.message, delimiter=' '
4340
)
44-
for part in event.artifact_update.artifact.parts:
45-
if part.text:
46-
print(part.text, end=' ')
47-
print()
48-
41+
if event.status_update.status.HasField('message')
42+
else ''
43+
)
44+
print(f'TaskStatusUpdate [state={state_name}]{message_text}')
45+
if state_name in (
46+
'TASK_STATE_COMPLETED',
47+
'TASK_STATE_FAILED',
48+
'TASK_STATE_CANCELED',
49+
'TASK_STATE_REJECTED',
50+
):
51+
current_task_id = None
52+
print('--- Task Finished ---')
53+
elif event.HasField('artifact_update'):
54+
print(
55+
f'TaskArtifactUpdate [name={event.artifact_update.artifact.name}]:',
56+
get_artifact_text(
57+
event.artifact_update.artifact, delimiter=' '
58+
),
59+
)
4960
return current_task_id
5061

5162

@@ -76,7 +87,7 @@ async def main() -> None:
7687
resolver = A2ACardResolver(httpx_client, args.url)
7788
card = await resolver.get_agent_card()
7889
print('\n✓ Agent Card Found:')
79-
print(f' Name: {card.name}')
90+
display_agent_card(card)
8091

8192
client = await create_client(card, client_config=config)
8293

samples/hello_world_agent.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727
AgentProvider,
2828
AgentSkill,
2929
Part,
30+
Task,
31+
TaskState,
32+
TaskStatus,
3033
a2a_pb2_grpc,
3134
)
3235

@@ -75,6 +78,15 @@ async def execute(
7578
context_id,
7679
)
7780

81+
await event_queue.enqueue_event(
82+
Task(
83+
id=task_id,
84+
context_id=context_id,
85+
status=TaskStatus(state=TaskState.TASK_STATE_SUBMITTED),
86+
history=[user_message],
87+
)
88+
)
89+
7890
updater = TaskUpdater(
7991
event_queue=event_queue,
8092
task_id=task_id,

src/a2a/utils/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Utility functions for the A2A Python SDK."""
22

33
from a2a.utils import proto_utils
4+
from a2a.utils.agent_card import display_agent_card
45
from a2a.utils.artifact import (
56
get_artifact_text,
67
new_artifact,
@@ -44,6 +45,7 @@
4445
'build_text_artifact',
4546
'completed_task',
4647
'create_task_obj',
48+
'display_agent_card',
4749
'get_artifact_text',
4850
'get_data_parts',
4951
'get_file_parts',

src/a2a/utils/agent_card.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
"""Utility functions for inspecting AgentCard instances."""
2+
3+
from a2a.types.a2a_pb2 import AgentCard
4+
5+
6+
def display_agent_card(card: AgentCard) -> None:
7+
"""Print a human-readable summary of an AgentCard to stdout.
8+
9+
Args:
10+
card: The AgentCard proto message to display.
11+
"""
12+
width = 52
13+
sep = '=' * width
14+
thin = '-' * width
15+
16+
lines: list[str] = [sep, 'AgentCard'.center(width), sep]
17+
18+
lines += [
19+
'--- General ---',
20+
f'Name : {card.name}',
21+
f'Description : {card.description}',
22+
f'Version : {card.version}',
23+
]
24+
if card.documentation_url:
25+
lines.append(f'Docs URL : {card.documentation_url}')
26+
if card.icon_url:
27+
lines.append(f'Icon URL : {card.icon_url}')
28+
if card.HasField('provider'):
29+
url_suffix = f' ({card.provider.url})' if card.provider.url else ''
30+
lines.append(f'Provider : {card.provider.organization}{url_suffix}')
31+
32+
lines += ['', '--- Interfaces ---']
33+
for i, iface in enumerate(card.supported_interfaces):
34+
binding = f'{iface.protocol_binding} {iface.protocol_version}'.strip()
35+
parts = [
36+
p
37+
for p in [binding, f'tenant={iface.tenant}' if iface.tenant else '']
38+
if p
39+
]
40+
suffix = f' ({", ".join(parts)})' if parts else ''
41+
line = f' [{i}] {iface.url}{suffix}'
42+
lines.append(line)
43+
44+
lines += [
45+
'',
46+
'--- Capabilities ---',
47+
f'Streaming : {card.capabilities.streaming}',
48+
f'Push notifications : {card.capabilities.push_notifications}',
49+
f'Extended agent card : {card.capabilities.extended_agent_card}',
50+
]
51+
52+
lines += [
53+
'',
54+
'--- I/O Modes ---',
55+
f'Input : {", ".join(card.default_input_modes) or "(none)"}',
56+
f'Output : {", ".join(card.default_output_modes) or "(none)"}',
57+
]
58+
59+
lines += ['', '--- Skills ---']
60+
if card.skills:
61+
for skill in card.skills:
62+
lines += [
63+
thin,
64+
f' ID : {skill.id}',
65+
f' Name : {skill.name}',
66+
f' Description : {skill.description}',
67+
f' Tags : {", ".join(skill.tags) or "(none)"}',
68+
]
69+
if skill.examples:
70+
for ex in skill.examples:
71+
lines.append(f' Example : {ex}')
72+
else:
73+
lines.append(' (none)')
74+
75+
lines.append(sep)
76+
print('\n'.join(lines))

0 commit comments

Comments
 (0)