@intentsolutionsio/jeremy-adk-orchestrator 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +23 -0
- package/LICENSE +21 -0
- package/README.md +776 -0
- package/agents/a2a-protocol-manager.md +411 -0
- package/package.json +44 -0
- package/skills/adk-deployment-specialist/SKILL.md +54 -0
- package/skills/adk-deployment-specialist/references/ARD.md +71 -0
- package/skills/adk-deployment-specialist/references/PRD.md +67 -0
- package/skills/adk-deployment-specialist/references/errors.md +106 -0
- package/skills/adk-deployment-specialist/references/examples.md +89 -0
- package/skills/adk-deployment-specialist/references/how-it-works.md +191 -0
- package/skills/adk-deployment-specialist/references/workflow-examples.md +167 -0
- package/skills/adk-deployment-specialist/scripts/deploy-agent.sh +157 -0
- package/skills/adk-deployment-specialist/scripts/test-a2a-protocol.py +277 -0
package/README.md
ADDED
|
@@ -0,0 +1,776 @@
|
|
|
1
|
+
# Jeremy ADK Orchestrator
|
|
2
|
+
|
|
3
|
+
**🎯 VERTEX AI AGENT ENGINE + ADK DEPLOYMENT ONLY**
|
|
4
|
+
|
|
5
|
+
Expert Agent-to-Agent (A2A) protocol manager for communicating with **Vertex AI Agent Development Kit (ADK)** agents deployed on **Agent Engine**.
|
|
6
|
+
|
|
7
|
+
## ⚠️ Important: What This Plugin Is For
|
|
8
|
+
|
|
9
|
+
**✅ THIS PLUGIN IS FOR:**
|
|
10
|
+
- **ADK agents** deployed to **Vertex AI Agent Engine** (fully-managed runtime)
|
|
11
|
+
- **A2A Protocol** communication between Claude Code and ADK agents
|
|
12
|
+
- **Multi-agent orchestration** with ADK supervisory agents
|
|
13
|
+
- **Python, Java, and Go ADK agents** on Agent Engine
|
|
14
|
+
- Agent Engine features: Code Execution Sandbox, Memory Bank, Sessions
|
|
15
|
+
|
|
16
|
+
**❌ THIS PLUGIN IS NOT FOR:**
|
|
17
|
+
- LangChain agents (use LangSmith)
|
|
18
|
+
- LlamaIndex agents (not ADK compatible)
|
|
19
|
+
- Cloud Run deployments (use `jeremy-genkit-terraform` with `--cloud-run`)
|
|
20
|
+
- Self-hosted agent infrastructure
|
|
21
|
+
- Non-ADK agent frameworks
|
|
22
|
+
|
|
23
|
+
## Overview
|
|
24
|
+
|
|
25
|
+
This plugin enables Claude Code to communicate with ADK agents deployed on Vertex AI Agent Engine using the standardized A2A (Agent-to-Agent) Protocol. It handles task submission, status checking, session management, and AgentCard discovery for building multi-agent systems.
|
|
26
|
+
|
|
27
|
+
**Key Capabilities:**
|
|
28
|
+
- AgentCard discovery and capability inspection
|
|
29
|
+
- Task submission with structured inputs
|
|
30
|
+
- Session management for Memory Bank persistence
|
|
31
|
+
- Status polling and result retrieval
|
|
32
|
+
- Streaming responses for long-running tasks
|
|
33
|
+
- Multi-agent orchestration with supervisory patterns
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
/plugin install jeremy-adk-orchestrator@claude-code-plugins-plus
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Prerequisites & Dependencies
|
|
42
|
+
|
|
43
|
+
### Required Google Cloud Setup
|
|
44
|
+
|
|
45
|
+
**1. Google Cloud Project with APIs Enabled:**
|
|
46
|
+
```bash
|
|
47
|
+
# Enable required APIs
|
|
48
|
+
gcloud services enable aiplatform.googleapis.com \
|
|
49
|
+
discoveryengine.googleapis.com \
|
|
50
|
+
logging.googleapis.com \
|
|
51
|
+
monitoring.googleapis.com \
|
|
52
|
+
cloudtrace.googleapis.com \
|
|
53
|
+
--project=YOUR_PROJECT_ID
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**2. Authentication:**
|
|
57
|
+
```bash
|
|
58
|
+
# Application Default Credentials
|
|
59
|
+
gcloud auth application-default login
|
|
60
|
+
|
|
61
|
+
# Or use service account
|
|
62
|
+
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account-key.json"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**3. Required IAM Permissions:**
|
|
66
|
+
```yaml
|
|
67
|
+
# Minimum required roles:
|
|
68
|
+
- roles/aiplatform.user # Query Agent Engine resources
|
|
69
|
+
- roles/discoveryengine.admin # Manage agents and sessions
|
|
70
|
+
- roles/logging.viewer # Read agent logs
|
|
71
|
+
- roles/monitoring.viewer # Access metrics
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Required Python Packages
|
|
75
|
+
|
|
76
|
+
**Install via pip:**
|
|
77
|
+
```bash
|
|
78
|
+
# Core ADK SDK (required for agent development)
|
|
79
|
+
pip install google-adk>=1.15.1
|
|
80
|
+
|
|
81
|
+
# Vertex AI SDK with Agent Engine support
|
|
82
|
+
pip install google-cloud-aiplatform[agent_engines]>=1.120.0
|
|
83
|
+
|
|
84
|
+
# A2A Protocol SDK (for protocol-level communication)
|
|
85
|
+
pip install a2a-sdk>=0.3.4
|
|
86
|
+
|
|
87
|
+
# HTTP client for REST API calls
|
|
88
|
+
pip install requests>=2.31.0
|
|
89
|
+
|
|
90
|
+
# Observability & Monitoring
|
|
91
|
+
pip install google-cloud-logging>=3.10.0
|
|
92
|
+
pip install google-cloud-monitoring>=2.21.0
|
|
93
|
+
pip install google-cloud-trace>=1.13.0
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**All dependencies at once:**
|
|
97
|
+
```bash
|
|
98
|
+
pip install --upgrade \
|
|
99
|
+
'google-adk>=1.15.1' \
|
|
100
|
+
'google-cloud-aiplatform[agent_engines]>=1.120.0' \
|
|
101
|
+
'a2a-sdk>=0.3.4' \
|
|
102
|
+
'requests>=2.31.0' \
|
|
103
|
+
'google-cloud-logging>=3.10.0' \
|
|
104
|
+
'google-cloud-monitoring>=2.21.0' \
|
|
105
|
+
'google-cloud-trace>=1.13.0'
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Agent Engine Management (Python SDK Only)
|
|
109
|
+
|
|
110
|
+
**There is no `gcloud` CLI for Agent Engine.** All management is done via the Python SDK:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
import vertexai
|
|
114
|
+
|
|
115
|
+
client = vertexai.Client(project="YOUR_PROJECT_ID", location="us-central1")
|
|
116
|
+
|
|
117
|
+
# List all deployed agents (reasoning engines)
|
|
118
|
+
for agent in client.agent_engines.list():
|
|
119
|
+
print(f"{agent.display_name}: {agent.resource_name}")
|
|
120
|
+
|
|
121
|
+
# Get a specific agent
|
|
122
|
+
agent = client.agent_engines.get(
|
|
123
|
+
name="projects/YOUR_PROJECT/locations/us-central1/reasoningEngines/12345"
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# Delete an agent
|
|
127
|
+
# client.agent_engines.delete(name=agent.resource_name)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Verify SDK Installation:**
|
|
131
|
+
```bash
|
|
132
|
+
python3 -c "import vertexai; print('Vertex AI SDK ready')"
|
|
133
|
+
python3 -c "import google.adk; print(f'ADK SDK version: {google.adk.__version__}')"
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### ADK Agent Deployment Methods
|
|
137
|
+
|
|
138
|
+
**This plugin works with ADK agents deployed via:**
|
|
139
|
+
|
|
140
|
+
1. **ADK CLI Deployment:**
|
|
141
|
+
```bash
|
|
142
|
+
# Install ADK CLI
|
|
143
|
+
pip install google-adk
|
|
144
|
+
|
|
145
|
+
# Deploy agent to Agent Engine (interactive — prompts for project/location)
|
|
146
|
+
adk deploy cloud_run # Deploy to Cloud Run
|
|
147
|
+
# Or deploy via the Python SDK (see method 2 below) for Agent Engine
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
2. **Python SDK Deployment:**
|
|
151
|
+
```python
|
|
152
|
+
from google.adk.agents import Agent
|
|
153
|
+
import vertexai
|
|
154
|
+
|
|
155
|
+
# Define ADK agent
|
|
156
|
+
agent = Agent(
|
|
157
|
+
name="my-adk-agent",
|
|
158
|
+
model="gemini-2.5-flash",
|
|
159
|
+
instruction="Production ADK agent for deployment tasks.",
|
|
160
|
+
tools=[my_tool_function],
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# Deploy to Agent Engine
|
|
164
|
+
client = vertexai.Client(project=PROJECT_ID, location=LOCATION)
|
|
165
|
+
remote_agent = client.agent_engines.create(
|
|
166
|
+
agent_engine=agent,
|
|
167
|
+
requirements=["google-adk>=1.15.1"],
|
|
168
|
+
display_name="my-adk-agent",
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
print(f"Agent deployed: {remote_agent.resource_name}")
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
3. **Terraform Deployment:**
|
|
175
|
+
```hcl
|
|
176
|
+
resource "google_vertex_ai_reasoning_engine" "adk_agent" {
|
|
177
|
+
display_name = "my-adk-agent"
|
|
178
|
+
region = "us-central1"
|
|
179
|
+
|
|
180
|
+
spec {
|
|
181
|
+
agent_framework = "google-adk" # ← Must specify ADK
|
|
182
|
+
|
|
183
|
+
package_spec {
|
|
184
|
+
pickle_object_gcs_uri = "gs://bucket/agent.pkl"
|
|
185
|
+
python_version = "3.12"
|
|
186
|
+
requirements_gcs_uri = "gs://bucket/requirements.txt"
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
# Agent Engine features
|
|
190
|
+
runtime_config {
|
|
191
|
+
code_execution_config {
|
|
192
|
+
enabled = true
|
|
193
|
+
}
|
|
194
|
+
memory_bank_config {
|
|
195
|
+
enabled = true
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### ❌ NOT Compatible With
|
|
203
|
+
|
|
204
|
+
- **LangChain agents** (different framework, not ADK)
|
|
205
|
+
- **LlamaIndex agents** (not ADK compatible)
|
|
206
|
+
- **Cloud Run deployments** (use `jeremy-genkit-terraform`)
|
|
207
|
+
- **Cloud Functions** (not Agent Engine)
|
|
208
|
+
- **Self-hosted agent infrastructure** (requires Agent Engine runtime)
|
|
209
|
+
- **Non-Google agent frameworks** (Autogen, CrewAI, etc.)
|
|
210
|
+
|
|
211
|
+
## Features
|
|
212
|
+
|
|
213
|
+
✅ **AgentCard Discovery**: Automatic capability detection for ADK agents
|
|
214
|
+
✅ **A2A Protocol Communication**: Standardized task submission and retrieval
|
|
215
|
+
✅ **Session Management**: Persistent sessions with Memory Bank
|
|
216
|
+
✅ **Status Polling**: Real-time task status monitoring
|
|
217
|
+
✅ **Streaming Responses**: Handle long-running agent tasks
|
|
218
|
+
✅ **Multi-Agent Orchestration**: Supervisory agent patterns
|
|
219
|
+
✅ **Error Handling**: Retry logic and graceful degradation
|
|
220
|
+
✅ **Observability**: Integrated logging and tracing
|
|
221
|
+
|
|
222
|
+
## Components
|
|
223
|
+
|
|
224
|
+
### Agent
|
|
225
|
+
- **a2a-protocol-manager**: A2A protocol expert with task orchestration capabilities
|
|
226
|
+
|
|
227
|
+
### Skills (Auto-Activating)
|
|
228
|
+
- **a2a-protocol-manager**: Triggers on "communicate with ADK agent", "orchestrate agents", "send task to agent"
|
|
229
|
+
- **Tool Permissions**: Read, Bash, Write, Grep (for agent communication)
|
|
230
|
+
- **Version**: 1.0.0 (2026 schema compliant)
|
|
231
|
+
|
|
232
|
+
## Quick Start
|
|
233
|
+
|
|
234
|
+
### Natural Language Activation
|
|
235
|
+
|
|
236
|
+
Simply mention what you need:
|
|
237
|
+
|
|
238
|
+
```
|
|
239
|
+
"Communicate with the ADK agent at [endpoint]"
|
|
240
|
+
"Send a task to the sentiment-analysis agent"
|
|
241
|
+
"Orchestrate multiple ADK agents for this workflow"
|
|
242
|
+
"Check status of task ID abc-123"
|
|
243
|
+
"Discover capabilities of the agent at [endpoint]"
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
The skill auto-activates and handles A2A protocol communication.
|
|
247
|
+
|
|
248
|
+
## A2A Protocol Architecture
|
|
249
|
+
|
|
250
|
+
### Communication Flow
|
|
251
|
+
|
|
252
|
+
```
|
|
253
|
+
Claude Code Plugin
|
|
254
|
+
↓
|
|
255
|
+
AgentCard Discovery
|
|
256
|
+
↓ GET /.well-known/agent-card
|
|
257
|
+
Agent Metadata (capabilities, skills, schemas)
|
|
258
|
+
↓
|
|
259
|
+
Task Submission (A2A JSON-RPC 2.0)
|
|
260
|
+
↓ POST / (method: "tasks/send")
|
|
261
|
+
Task Created (task id, status)
|
|
262
|
+
↓
|
|
263
|
+
Task Status
|
|
264
|
+
↓ POST / (method: "tasks/get")
|
|
265
|
+
Task State (submitted, working, completed, failed)
|
|
266
|
+
↓
|
|
267
|
+
Result in task artifacts
|
|
268
|
+
↓ parts[].text / parts[].data
|
|
269
|
+
Agent Output
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### AgentCard Discovery
|
|
273
|
+
|
|
274
|
+
**Discover agent capabilities before invocation:**
|
|
275
|
+
|
|
276
|
+
```python
|
|
277
|
+
import requests
|
|
278
|
+
|
|
279
|
+
def discover_agent_capabilities(agent_endpoint):
|
|
280
|
+
"""
|
|
281
|
+
Fetch AgentCard to understand agent's tools and capabilities.
|
|
282
|
+
|
|
283
|
+
AgentCard contains:
|
|
284
|
+
- name: Agent identifier
|
|
285
|
+
- description: What the agent does
|
|
286
|
+
- tools: Available tools the agent can use
|
|
287
|
+
- input_schema: Expected input format
|
|
288
|
+
- output_schema: Expected output format
|
|
289
|
+
"""
|
|
290
|
+
response = requests.get(f"{agent_endpoint}/.well-known/agent-card")
|
|
291
|
+
agent_card = response.json()
|
|
292
|
+
|
|
293
|
+
print(f"Agent: {agent_card['name']}")
|
|
294
|
+
print(f"Description: {agent_card['description']}")
|
|
295
|
+
print(f"Available tools: {[tool['name'] for tool in agent_card['tools']]}")
|
|
296
|
+
|
|
297
|
+
return agent_card
|
|
298
|
+
|
|
299
|
+
# Example
|
|
300
|
+
agent_card = discover_agent_capabilities(
|
|
301
|
+
"https://us-central1-aiplatform.googleapis.com/v1/projects/my-project/locations/us-central1/reasoningEngines/my-agent"
|
|
302
|
+
)
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### Task Submission
|
|
306
|
+
|
|
307
|
+
**Submit a task to an ADK agent via A2A JSON-RPC:**
|
|
308
|
+
|
|
309
|
+
```python
|
|
310
|
+
import requests
|
|
311
|
+
import json
|
|
312
|
+
|
|
313
|
+
def submit_task(agent_endpoint, message_text, task_id=None):
|
|
314
|
+
"""
|
|
315
|
+
Submit a task to an ADK agent via A2A protocol (JSON-RPC 2.0).
|
|
316
|
+
|
|
317
|
+
Args:
|
|
318
|
+
agent_endpoint: A2A-compliant agent URL
|
|
319
|
+
message_text: Natural language instruction
|
|
320
|
+
task_id: Optional task ID (generated if not provided)
|
|
321
|
+
|
|
322
|
+
Returns:
|
|
323
|
+
task_id: Unique identifier for tracking task status
|
|
324
|
+
"""
|
|
325
|
+
import uuid
|
|
326
|
+
task_id = task_id or str(uuid.uuid4())
|
|
327
|
+
|
|
328
|
+
payload = {
|
|
329
|
+
"jsonrpc": "2.0",
|
|
330
|
+
"method": "tasks/send",
|
|
331
|
+
"params": {
|
|
332
|
+
"id": task_id,
|
|
333
|
+
"message": {
|
|
334
|
+
"role": "user",
|
|
335
|
+
"parts": [{"text": message_text}],
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
"id": f"req-{task_id}",
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
response = requests.post(
|
|
342
|
+
agent_endpoint,
|
|
343
|
+
json=payload,
|
|
344
|
+
headers={
|
|
345
|
+
"Content-Type": "application/json",
|
|
346
|
+
"Authorization": f"Bearer {get_access_token()}",
|
|
347
|
+
}
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
result = response.json()
|
|
351
|
+
task_status = result.get("result", {}).get("status", {}).get("state")
|
|
352
|
+
|
|
353
|
+
print(f"Task submitted: {task_id}")
|
|
354
|
+
print(f"Status: {task_status}")
|
|
355
|
+
|
|
356
|
+
return task_id
|
|
357
|
+
|
|
358
|
+
# Example
|
|
359
|
+
task_id = submit_task(
|
|
360
|
+
agent_endpoint="https://my-agent.example.com",
|
|
361
|
+
message_text="Analyze sentiment of customer reviews",
|
|
362
|
+
)
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### Status Polling
|
|
366
|
+
|
|
367
|
+
**Monitor task execution via A2A JSON-RPC:**
|
|
368
|
+
|
|
369
|
+
```python
|
|
370
|
+
import time
|
|
371
|
+
|
|
372
|
+
def poll_task_status(agent_endpoint, task_id, timeout=300):
|
|
373
|
+
"""
|
|
374
|
+
Poll task status until completion or timeout.
|
|
375
|
+
|
|
376
|
+
A2A task states:
|
|
377
|
+
- submitted: Task queued
|
|
378
|
+
- working: Agent is processing
|
|
379
|
+
- input-required: Agent needs more info
|
|
380
|
+
- completed: Task finished successfully
|
|
381
|
+
- failed: Task encountered error
|
|
382
|
+
- canceled: Task was canceled
|
|
383
|
+
"""
|
|
384
|
+
start_time = time.time()
|
|
385
|
+
|
|
386
|
+
while time.time() - start_time < timeout:
|
|
387
|
+
payload = {
|
|
388
|
+
"jsonrpc": "2.0",
|
|
389
|
+
"method": "tasks/get",
|
|
390
|
+
"params": {"id": task_id},
|
|
391
|
+
"id": f"poll-{int(time.time())}",
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
response = requests.post(
|
|
395
|
+
agent_endpoint,
|
|
396
|
+
json=payload,
|
|
397
|
+
headers={
|
|
398
|
+
"Content-Type": "application/json",
|
|
399
|
+
"Authorization": f"Bearer {get_access_token()}",
|
|
400
|
+
}
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
result = response.json().get("result", {})
|
|
404
|
+
state = result.get("status", {}).get("state", "unknown")
|
|
405
|
+
print(f"Status: {state}")
|
|
406
|
+
|
|
407
|
+
if state == "completed":
|
|
408
|
+
return result
|
|
409
|
+
elif state == "failed":
|
|
410
|
+
return result
|
|
411
|
+
|
|
412
|
+
time.sleep(5) # Poll every 5 seconds
|
|
413
|
+
|
|
414
|
+
raise TimeoutError(f"Task {task_id} did not complete within {timeout}s")
|
|
415
|
+
|
|
416
|
+
# Example
|
|
417
|
+
result = poll_task_status(agent_endpoint, task_id)
|
|
418
|
+
|
|
419
|
+
state = result.get("status", {}).get("state")
|
|
420
|
+
if state == "completed":
|
|
421
|
+
print("Task completed successfully!")
|
|
422
|
+
artifacts = result.get("artifacts", [])
|
|
423
|
+
for artifact in artifacts:
|
|
424
|
+
for part in artifact.get("parts", []):
|
|
425
|
+
print(f"Output: {part.get('text', '')}")
|
|
426
|
+
else:
|
|
427
|
+
print(f"Task failed: {result.get('status', {}).get('message')}")
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
### Result Retrieval
|
|
431
|
+
|
|
432
|
+
**Get agent output (included in tasks/get response):**
|
|
433
|
+
|
|
434
|
+
In A2A, results are returned as `artifacts` in the `tasks/get` response -- there is no separate result endpoint. Each artifact contains `parts` (text, data, or file).
|
|
435
|
+
|
|
436
|
+
```python
|
|
437
|
+
def get_task_result(agent_endpoint, task_id):
|
|
438
|
+
"""
|
|
439
|
+
Retrieve completed task output via tasks/get.
|
|
440
|
+
|
|
441
|
+
Results are in the 'artifacts' field of the task response.
|
|
442
|
+
Each artifact has 'parts' with text or structured data.
|
|
443
|
+
"""
|
|
444
|
+
payload = {
|
|
445
|
+
"jsonrpc": "2.0",
|
|
446
|
+
"method": "tasks/get",
|
|
447
|
+
"params": {"id": task_id},
|
|
448
|
+
"id": f"result-{task_id}",
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
response = requests.post(
|
|
452
|
+
agent_endpoint,
|
|
453
|
+
json=payload,
|
|
454
|
+
headers={
|
|
455
|
+
"Content-Type": "application/json",
|
|
456
|
+
"Authorization": f"Bearer {get_access_token()}",
|
|
457
|
+
}
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
task = response.json().get("result", {})
|
|
461
|
+
artifacts = task.get("artifacts", [])
|
|
462
|
+
|
|
463
|
+
print("Agent Output:")
|
|
464
|
+
for artifact in artifacts:
|
|
465
|
+
for part in artifact.get("parts", []):
|
|
466
|
+
if "text" in part:
|
|
467
|
+
print(part["text"])
|
|
468
|
+
elif "data" in part:
|
|
469
|
+
print(json.dumps(part["data"], indent=2))
|
|
470
|
+
|
|
471
|
+
return artifacts
|
|
472
|
+
|
|
473
|
+
# Example
|
|
474
|
+
artifacts = get_task_result(agent_endpoint, task_id)
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
## Multi-Agent Orchestration
|
|
478
|
+
|
|
479
|
+
### Supervisory Agent Pattern
|
|
480
|
+
|
|
481
|
+
**Orchestrate multiple ADK agents:**
|
|
482
|
+
|
|
483
|
+
```python
|
|
484
|
+
class SupervisoryOrchestrator:
|
|
485
|
+
"""
|
|
486
|
+
Coordinate multiple ADK agents for complex workflows.
|
|
487
|
+
|
|
488
|
+
Pattern: Supervisor delegates tasks to specialized agents.
|
|
489
|
+
"""
|
|
490
|
+
|
|
491
|
+
def __init__(self, agents_config):
|
|
492
|
+
self.agents = {
|
|
493
|
+
name: agent_config
|
|
494
|
+
for name, agent_config in agents_config.items()
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
def orchestrate(self, workflow_input):
|
|
498
|
+
"""
|
|
499
|
+
Execute multi-step workflow across agents.
|
|
500
|
+
"""
|
|
501
|
+
results = {}
|
|
502
|
+
session_id = None # Shared session for Memory Bank
|
|
503
|
+
|
|
504
|
+
# Step 1: Data extraction agent
|
|
505
|
+
task_id, session_id = submit_task(
|
|
506
|
+
self.agents['extractor']['endpoint'],
|
|
507
|
+
{"input": workflow_input},
|
|
508
|
+
session_id=session_id
|
|
509
|
+
)
|
|
510
|
+
status = poll_task_status(self.agents['extractor']['endpoint'], task_id)
|
|
511
|
+
results['extracted_data'] = get_task_result(
|
|
512
|
+
self.agents['extractor']['endpoint'],
|
|
513
|
+
task_id
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
# Step 2: Analysis agent (uses extracted data)
|
|
517
|
+
task_id, session_id = submit_task(
|
|
518
|
+
self.agents['analyzer']['endpoint'],
|
|
519
|
+
{"data": results['extracted_data']},
|
|
520
|
+
session_id=session_id # Continue same session
|
|
521
|
+
)
|
|
522
|
+
status = poll_task_status(self.agents['analyzer']['endpoint'], task_id)
|
|
523
|
+
results['analysis'] = get_task_result(
|
|
524
|
+
self.agents['analyzer']['endpoint'],
|
|
525
|
+
task_id
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
# Step 3: Synthesis agent (combines results)
|
|
529
|
+
task_id, session_id = submit_task(
|
|
530
|
+
self.agents['synthesizer']['endpoint'],
|
|
531
|
+
{
|
|
532
|
+
"extracted": results['extracted_data'],
|
|
533
|
+
"analyzed": results['analysis']
|
|
534
|
+
},
|
|
535
|
+
session_id=session_id
|
|
536
|
+
)
|
|
537
|
+
status = poll_task_status(self.agents['synthesizer']['endpoint'], task_id)
|
|
538
|
+
results['final_output'] = get_task_result(
|
|
539
|
+
self.agents['synthesizer']['endpoint'],
|
|
540
|
+
task_id
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
return results
|
|
544
|
+
|
|
545
|
+
# Usage
|
|
546
|
+
orchestrator = SupervisoryOrchestrator({
|
|
547
|
+
'extractor': {'endpoint': 'https://...'},
|
|
548
|
+
'analyzer': {'endpoint': 'https://...'},
|
|
549
|
+
'synthesizer': {'endpoint': 'https://...'}
|
|
550
|
+
})
|
|
551
|
+
|
|
552
|
+
workflow_results = orchestrator.orchestrate({
|
|
553
|
+
"document": "Customer feedback report...",
|
|
554
|
+
"analysis_type": "sentiment_and_topics"
|
|
555
|
+
})
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
## Observability & Monitoring
|
|
559
|
+
|
|
560
|
+
### Cloud Trace Integration
|
|
561
|
+
|
|
562
|
+
**Enable distributed tracing for A2A calls:**
|
|
563
|
+
|
|
564
|
+
```python
|
|
565
|
+
from opentelemetry import trace
|
|
566
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
567
|
+
from opentelemetry.exporter.cloud_trace import CloudTraceSpanExporter
|
|
568
|
+
|
|
569
|
+
# Configure Cloud Trace
|
|
570
|
+
trace.set_tracer_provider(TracerProvider())
|
|
571
|
+
cloud_trace_exporter = CloudTraceSpanExporter()
|
|
572
|
+
tracer = trace.get_tracer(__name__)
|
|
573
|
+
|
|
574
|
+
# Instrument A2A protocol calls
|
|
575
|
+
with tracer.start_as_current_span("a2a_task_submission") as span:
|
|
576
|
+
span.set_attribute("agent.endpoint", agent_endpoint)
|
|
577
|
+
span.set_attribute("task.type", "sentiment_analysis")
|
|
578
|
+
|
|
579
|
+
task_id, session_id = submit_task(agent_endpoint, task_input)
|
|
580
|
+
|
|
581
|
+
span.set_attribute("task.id", task_id)
|
|
582
|
+
span.set_attribute("session.id", session_id)
|
|
583
|
+
|
|
584
|
+
with tracer.start_as_current_span("a2a_task_polling") as span:
|
|
585
|
+
status = poll_task_status(agent_endpoint, task_id)
|
|
586
|
+
|
|
587
|
+
span.set_attribute("task.status", status['state'])
|
|
588
|
+
span.set_attribute("task.latency_ms", status.get('latency'))
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
### Cloud Logging
|
|
592
|
+
|
|
593
|
+
**Query orchestration logs:**
|
|
594
|
+
|
|
595
|
+
```bash
|
|
596
|
+
# View all A2A protocol calls
|
|
597
|
+
gcloud logging read "jsonPayload.component=a2a_protocol AND resource.type=aiplatform.googleapis.com/Agent" \
|
|
598
|
+
--project=YOUR_PROJECT_ID \
|
|
599
|
+
--limit=100 \
|
|
600
|
+
--format=json
|
|
601
|
+
|
|
602
|
+
# Filter by agent endpoint
|
|
603
|
+
gcloud logging read "jsonPayload.agent_endpoint=~'my-agent' AND severity>=WARNING" \
|
|
604
|
+
--project=YOUR_PROJECT_ID \
|
|
605
|
+
--limit=50
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
### Custom Metrics
|
|
609
|
+
|
|
610
|
+
**Track orchestration performance:**
|
|
611
|
+
|
|
612
|
+
```python
|
|
613
|
+
from google.cloud import monitoring_v3
|
|
614
|
+
|
|
615
|
+
def record_orchestration_metrics(
|
|
616
|
+
task_id: str,
|
|
617
|
+
latency_ms: float,
|
|
618
|
+
success: bool
|
|
619
|
+
):
|
|
620
|
+
"""Record custom metrics for A2A orchestration."""
|
|
621
|
+
client = monitoring_v3.MetricServiceClient()
|
|
622
|
+
project_name = f"projects/{PROJECT_ID}"
|
|
623
|
+
|
|
624
|
+
# Record task latency
|
|
625
|
+
series = monitoring_v3.TimeSeries()
|
|
626
|
+
series.metric.type = "custom.googleapis.com/adk/orchestration/latency"
|
|
627
|
+
series.metric.labels['task_id'] = task_id
|
|
628
|
+
|
|
629
|
+
point = monitoring_v3.Point()
|
|
630
|
+
point.value.double_value = latency_ms
|
|
631
|
+
point.interval.end_time.seconds = int(time.time())
|
|
632
|
+
series.points = [point]
|
|
633
|
+
|
|
634
|
+
client.create_time_series(name=project_name, time_series=[series])
|
|
635
|
+
|
|
636
|
+
# Record success/failure
|
|
637
|
+
series = monitoring_v3.TimeSeries()
|
|
638
|
+
series.metric.type = "custom.googleapis.com/adk/orchestration/success_rate"
|
|
639
|
+
|
|
640
|
+
point = monitoring_v3.Point()
|
|
641
|
+
point.value.int64_value = 1 if success else 0
|
|
642
|
+
point.interval.end_time.seconds = int(time.time())
|
|
643
|
+
series.points = [point]
|
|
644
|
+
|
|
645
|
+
client.create_time_series(name=project_name, time_series=[series])
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
## Storage Integration
|
|
649
|
+
|
|
650
|
+
### BigQuery Export
|
|
651
|
+
|
|
652
|
+
**Export orchestration logs to BigQuery:**
|
|
653
|
+
|
|
654
|
+
```python
|
|
655
|
+
from google.cloud import bigquery
|
|
656
|
+
|
|
657
|
+
def export_orchestration_history():
|
|
658
|
+
"""Export A2A protocol calls to BigQuery for analysis."""
|
|
659
|
+
client = bigquery.Client(project=PROJECT_ID)
|
|
660
|
+
|
|
661
|
+
# Create table for orchestration logs
|
|
662
|
+
schema = [
|
|
663
|
+
bigquery.SchemaField("timestamp", "TIMESTAMP"),
|
|
664
|
+
bigquery.SchemaField("task_id", "STRING"),
|
|
665
|
+
bigquery.SchemaField("session_id", "STRING"),
|
|
666
|
+
bigquery.SchemaField("agent_endpoint", "STRING"),
|
|
667
|
+
bigquery.SchemaField("status", "STRING"),
|
|
668
|
+
bigquery.SchemaField("latency_ms", "FLOAT"),
|
|
669
|
+
bigquery.SchemaField("input_tokens", "INTEGER"),
|
|
670
|
+
bigquery.SchemaField("output_tokens", "INTEGER"),
|
|
671
|
+
bigquery.SchemaField("error_message", "STRING"),
|
|
672
|
+
]
|
|
673
|
+
|
|
674
|
+
table_ref = client.dataset("agent_analytics").table("orchestration_logs")
|
|
675
|
+
table = bigquery.Table(table_ref, schema=schema)
|
|
676
|
+
table = client.create_table(table, exists_ok=True)
|
|
677
|
+
|
|
678
|
+
print(f"Created table: {table.project}.{table.dataset_id}.{table.table_id}")
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
**Query orchestration patterns:**
|
|
682
|
+
|
|
683
|
+
```sql
|
|
684
|
+
-- Most commonly orchestrated agents
|
|
685
|
+
SELECT
|
|
686
|
+
agent_endpoint,
|
|
687
|
+
COUNT(*) as total_calls,
|
|
688
|
+
AVG(latency_ms) as avg_latency,
|
|
689
|
+
SUM(CASE WHEN status = 'COMPLETED' THEN 1 ELSE 0 END) / COUNT(*) as success_rate
|
|
690
|
+
FROM `project.agent_analytics.orchestration_logs`
|
|
691
|
+
WHERE timestamp >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 7 DAY)
|
|
692
|
+
GROUP BY agent_endpoint
|
|
693
|
+
ORDER BY total_calls DESC;
|
|
694
|
+
|
|
695
|
+
-- Multi-agent workflow analysis
|
|
696
|
+
SELECT
|
|
697
|
+
session_id,
|
|
698
|
+
COUNT(DISTINCT agent_endpoint) as num_agents,
|
|
699
|
+
SUM(latency_ms) as total_latency,
|
|
700
|
+
ARRAY_AGG(agent_endpoint ORDER BY timestamp) as agent_sequence
|
|
701
|
+
FROM `project.agent_analytics.orchestration_logs`
|
|
702
|
+
WHERE session_id IS NOT NULL
|
|
703
|
+
AND timestamp >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 DAY)
|
|
704
|
+
GROUP BY session_id
|
|
705
|
+
HAVING num_agents > 1
|
|
706
|
+
ORDER BY total_latency DESC;
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
## Use Cases
|
|
710
|
+
|
|
711
|
+
### Single Agent Communication
|
|
712
|
+
```
|
|
713
|
+
"Communicate with the sentiment-analysis ADK agent at [endpoint]"
|
|
714
|
+
"Send customer reviews to the analysis agent"
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
### Multi-Agent Workflows
|
|
718
|
+
```
|
|
719
|
+
"Orchestrate data extraction, analysis, and synthesis agents"
|
|
720
|
+
"Run a multi-step workflow across these ADK agents: [list]"
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
### Session Management
|
|
724
|
+
```
|
|
725
|
+
"Continue the conversation with session ID abc-123"
|
|
726
|
+
"Create a new session with Memory Bank persistence"
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
### Status Monitoring
|
|
730
|
+
```
|
|
731
|
+
"Check status of task ID xyz-456"
|
|
732
|
+
"Monitor the long-running analysis task"
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
### Capability Discovery
|
|
736
|
+
```
|
|
737
|
+
"Discover capabilities of the agent at [endpoint]"
|
|
738
|
+
"What tools does this ADK agent support?"
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
## Integration with Other Plugins
|
|
742
|
+
|
|
743
|
+
### jeremy-vertex-engine
|
|
744
|
+
- Orchestrator invokes agents → Inspector validates health
|
|
745
|
+
- A2A protocol calls → Performance monitoring
|
|
746
|
+
|
|
747
|
+
### jeremy-vertex-validator
|
|
748
|
+
- Validator checks agent code → Orchestrator deploys and tests
|
|
749
|
+
- Pre-deployment validation → Runtime orchestration
|
|
750
|
+
|
|
751
|
+
### jeremy-adk-terraform
|
|
752
|
+
- Terraform provisions agents → Orchestrator manages communication
|
|
753
|
+
- Infrastructure deployment → Runtime coordination
|
|
754
|
+
|
|
755
|
+
## Requirements
|
|
756
|
+
|
|
757
|
+
- Google Cloud Project with Vertex AI enabled
|
|
758
|
+
- ADK agents deployed on Agent Engine (NOT Cloud Run)
|
|
759
|
+
- Appropriate IAM permissions for A2A protocol
|
|
760
|
+
- Python 3.10+ (for ADK SDK compatibility)
|
|
761
|
+
- Cloud Logging enabled (for observability features)
|
|
762
|
+
- Cloud Monitoring enabled (for custom metrics)
|
|
763
|
+
- BigQuery dataset (for analytics integration - optional)
|
|
764
|
+
|
|
765
|
+
## License
|
|
766
|
+
|
|
767
|
+
MIT
|
|
768
|
+
|
|
769
|
+
## Support
|
|
770
|
+
|
|
771
|
+
- Issues: https://github.com/jeremylongshore/claude-code-plugins/issues
|
|
772
|
+
- Discussions: https://github.com/jeremylongshore/claude-code-plugins/discussions
|
|
773
|
+
|
|
774
|
+
## Version
|
|
775
|
+
|
|
776
|
+
2.1.0 (2026) - SDK accuracy fixes, expanded error/example references, corrected imports
|