Skip to content

Commit 3335980

Browse files
Implement review comments:
update path in samples update controller test remove unused sample update CLAUDE.md undo changes to e2e_test.go
1 parent d49c9ee commit 3335980

File tree

8 files changed

+329
-59
lines changed

8 files changed

+329
-59
lines changed

CLAUDE.md

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,9 @@ make docker-push
9696
│ ├── webhook/ # Webhook configurations
9797
│ └── samples/ # Example Agent resources
9898
├── internal/
99-
│ ├── controller/ # Reconciliation logic and health check implementation
99+
│ ├── controller/ # Reconciliation logic
100100
│ └── webhook/ # Admission webhook handlers
101-
├── docs/
102-
│ └── examples/ # Health check implementation examples
101+
├── docs/ # Documentation
103102
└── test/
104103
├── e2e/ # End-to-end tests
105104
└── utils/ # Test utilities
@@ -165,14 +164,15 @@ spec:
165164

166165
## Health Check Architecture
167166

168-
The operator implements **protocol-aware health checking** that automatically generates appropriate Kubernetes readiness probes based on agent protocol specifications:
167+
The operator implements **protocol-aware health checking** that automatically generates appropriate Kubernetes readiness
168+
probes based on agent protocol specifications:
169169

170170
### Protocol-Based Health Checks
171171

172172
**A2A Agents (Agent-to-Agent Protocol):**
173173
- **Probe Type**: HTTP GET request
174174
- **Endpoint**: `/a2a/.well-known/agent-card.json`
175-
- **Port**: 8000 (default)
175+
- **Port**: 8000 (or specified protocol port)
176176
- **Purpose**: Validates that A2A framework is initialized and agent skills are loaded
177177
- **Benefits**: Meaningful readiness signal that confirms agent functionality, not just HTTP server availability
178178

@@ -195,18 +195,6 @@ The health check logic is implemented in `internal/controller/agent_controller.g
195195
- `hasOpenAIProtocol()`: Detects OpenAI protocol in agent specification
196196
- `probesEqual()`: Compares probe configurations to determine if deployment updates are needed
197197

198-
### Migration from Legacy Health Endpoints
199-
200-
**Previous Approach (Deprecated):**
201-
- Generic `/health` endpoints in each agent
202-
- Only validated HTTP server availability
203-
- Required manual implementation in every agent
204-
205-
**Current Approach (Recommended):**
206-
- Operator-managed, protocol-aware health checking
207-
- Validates actual agent functionality for A2A agents
208-
- Zero health code required in agents
209-
- Centralized management and consistent behavior
210198

211199
## Configuration
212200

config/samples/kustomization.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ resources:
33
- runtime_v1alpha1_agent.yaml
44
- runtime_v1alpha1_agent_template.yaml
55
- runtime_v1alpha1_agent_openai.yaml
6-
- runtime_v1alpha1_agent_mixed.yaml
76
- runtime_v1alpha1_agent_no_probe.yaml
87
- configmap.yaml
98
# +kubebuilder:scaffold:manifestskustomizesamples

config/samples/runtime_v1alpha1_agent.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@ metadata:
88
spec:
99
framework: google-adk
1010
image: ghcr.io/agentic-layer/weather-agent:0.3.0
11+
protocols:
12+
- type: A2A
13+
path: "/"
1114
replicas: 1
1215
env:
1316
- name: PORT
14-
value: "8000"
17+
value: "8080"
1518
- name: LOG_LEVEL
1619
value: "info"
1720
envFrom:

config/samples/runtime_v1alpha1_agent_mixed.yaml

Lines changed: 0 additions & 21 deletions
This file was deleted.

config/samples/runtime_v1alpha1_agent_template.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ spec:
99
framework: google-adk
1010
description: "A news agent that fetches and summarizes current articles"
1111
instruction: "You are a news agent that can fetch current news articles and provide summaries. Use the news fetcher tool to get articles and the summarizer agent to create concise summaries."
12-
model: "gemini/gemini-2.0-flash"
12+
model: "gemini/gemini-2.5-flash"
1313
subAgents:
1414
- name: summarizer_agent
1515
url: "https://agents.example.com/summarizer-agent.json"
@@ -18,6 +18,9 @@ spec:
1818
url: "https://news.mcpservers.org/fetch/mcp"
1919
- name: web_fetch
2020
url: "https://remote.mcpservers.org/fetch/mcp"
21+
protocols:
22+
- type: A2A
23+
path: "/"
2124
replicas: 1
2225
env:
2326
- name: LOG_LEVEL

internal/controller/agent_controller.go

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ import (
3838
)
3939

4040
const (
41-
agentContainerName = "agent"
41+
agentContainerName = "agent"
42+
a2aAgentCardEndpoint = "/.well-known/agent-card.json"
4243
)
4344

4445
// AgentReconciler reconciles a Agent object
@@ -376,16 +377,63 @@ func (r *AgentReconciler) hasOpenAIProtocol(agent *runtimev1alpha1.Agent) bool {
376377
return false
377378
}
378379

380+
// getA2AProtocol returns the first A2A protocol configuration found
381+
func (r *AgentReconciler) getA2AProtocol(agent *runtimev1alpha1.Agent) *runtimev1alpha1.AgentProtocol {
382+
for _, protocol := range agent.Spec.Protocols {
383+
if protocol.Type == "A2A" {
384+
return &protocol
385+
}
386+
}
387+
return nil
388+
}
389+
390+
// getOpenAIProtocol returns the first OpenAI protocol configuration found
391+
func (r *AgentReconciler) getOpenAIProtocol(agent *runtimev1alpha1.Agent) *runtimev1alpha1.AgentProtocol {
392+
for _, protocol := range agent.Spec.Protocols {
393+
if protocol.Type == "OpenAI" {
394+
return &protocol
395+
}
396+
}
397+
return nil
398+
}
399+
400+
// getProtocolPort returns the port from protocol or default if not specified
401+
func (r *AgentReconciler) getProtocolPort(protocol *runtimev1alpha1.AgentProtocol, defaultPort int32) int32 {
402+
if protocol != nil && protocol.Port != 0 {
403+
return protocol.Port
404+
}
405+
return defaultPort
406+
}
407+
408+
// getA2AHealthPath returns the A2A health check path based on protocol configuration
409+
func (r *AgentReconciler) getA2AHealthPath(protocol *runtimev1alpha1.AgentProtocol) string {
410+
basePath := a2aAgentCardEndpoint
411+
if protocol != nil && protocol.Path != "" {
412+
// If path is explicitly specified, use it
413+
// Special case: "/" means root path (no prefix)
414+
if protocol.Path == "/" {
415+
return basePath
416+
}
417+
return protocol.Path + basePath
418+
}
419+
// Default for agents without protocol specification or path
420+
return "/a2a" + basePath
421+
}
422+
379423
// generateReadinessProbe generates appropriate readiness probe based on agent protocols
380424
func (r *AgentReconciler) generateReadinessProbe(agent *runtimev1alpha1.Agent) *corev1.Probe {
381425
// Priority: A2A > OpenAI > None
382426
if r.hasA2AProtocol(agent) {
383427
// Use A2A agent card endpoint for health check
428+
a2aProtocol := r.getA2AProtocol(agent)
429+
healthPath := r.getA2AHealthPath(a2aProtocol)
430+
port := r.getProtocolPort(a2aProtocol, 8000)
431+
384432
return &corev1.Probe{
385433
ProbeHandler: corev1.ProbeHandler{
386434
HTTPGet: &corev1.HTTPGetAction{
387-
Path: "/a2a/.well-known/agent-card.json",
388-
Port: intstr.FromInt(8000),
435+
Path: healthPath,
436+
Port: intstr.FromInt(int(port)),
389437
},
390438
},
391439
InitialDelaySeconds: 10,
@@ -396,10 +444,13 @@ func (r *AgentReconciler) generateReadinessProbe(agent *runtimev1alpha1.Agent) *
396444
}
397445
} else if r.hasOpenAIProtocol(agent) {
398446
// Use TCP probe for OpenAI-only agents
447+
openaiProtocol := r.getOpenAIProtocol(agent)
448+
port := r.getProtocolPort(openaiProtocol, 8000)
449+
399450
return &corev1.Probe{
400451
ProbeHandler: corev1.ProbeHandler{
401452
TCPSocket: &corev1.TCPSocketAction{
402-
Port: intstr.FromInt(8000),
453+
Port: intstr.FromInt(int(port)),
403454
},
404455
},
405456
InitialDelaySeconds: 10,
@@ -813,8 +864,8 @@ func (r *AgentReconciler) buildA2AAgentCardUrl(agent *runtimev1alpha1.Agent) str
813864
// Find the A2A protocol
814865
for _, protocol := range agent.Spec.Protocols {
815866
if protocol.Type == "A2A" {
816-
return fmt.Sprintf("http://%s.%s.svc.cluster.local:%d/.well-known/agent-card.json",
817-
agent.Name, agent.Namespace, protocol.Port)
867+
return fmt.Sprintf("http://%s.%s.svc.cluster.local:%d%s",
868+
agent.Name, agent.Namespace, protocol.Port, a2aAgentCardEndpoint)
818869
}
819870
}
820871
return ""

0 commit comments

Comments
 (0)