这是indexloc提供的服务,不要输入任何密码
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
.idea/
**/CLAUDE.local.md
*__debug_bin*

# macOS system files
.DS_Store
**/.DS_Store

4 changes: 4 additions & 0 deletions kubechain/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,7 @@ go.work
*.swp
*.swo
*~

# macOS system files
.DS_Store
**/.DS_Store
18 changes: 18 additions & 0 deletions kubechain/api/v1alpha1/agent_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ type AgentSpec struct {
// +optional
MCPServers []LocalObjectReference `json:"mcpServers,omitempty"`

// HumanContactChannels is a list of ContactChannel resources that can be used for human interactions
// +optional
HumanContactChannels []LocalObjectReference `json:"humanContactChannels,omitempty"`

// System is the system prompt for the agent
// +kubebuilder:validation:Required
// +kubebuilder:validation:MinLength=1
Expand Down Expand Up @@ -51,6 +55,10 @@ type AgentStatus struct {
// ValidMCPServers is the list of MCP servers that were successfully validated
// +optional
ValidMCPServers []ResolvedMCPServer `json:"validMCPServers,omitempty"`

// ValidHumanContactChannels is the list of human contact channels that were successfully validated
// +optional
ValidHumanContactChannels []ResolvedContactChannel `json:"validHumanContactChannels,omitempty"`
}

type ResolvedTool struct {
Expand All @@ -73,6 +81,16 @@ type ResolvedMCPServer struct {
Tools []string `json:"tools,omitempty"`
}

type ResolvedContactChannel struct {
// Name of the contact channel
// +kubebuilder:validation:Required
Name string `json:"name"`

// Type of the contact channel (e.g., "slack", "email")
// +kubebuilder:validation:Required
Type string `json:"type"`
}

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="Ready",type="boolean",JSONPath=".status.ready"
Expand Down
25 changes: 25 additions & 0 deletions kubechain/api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 31 additions & 0 deletions kubechain/config/crd/bases/kubechain.humanlayer.dev_agents.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,21 @@ spec:
spec:
description: AgentSpec defines the desired state of Agent
properties:
humanContactChannels:
description: HumanContactChannels is a list of ContactChannel resources
that can be used for human interactions
items:
description: LocalObjectReference contains enough information to
locate the referenced resource in the same namespace
properties:
name:
description: Name of the referent
minLength: 1
type: string
required:
- name
type: object
type: array
llmRef:
description: LLMRef references the LLM to use for this agent
properties:
Expand Down Expand Up @@ -114,6 +129,22 @@ spec:
description: StatusDetail provides additional details about the current
status
type: string
validHumanContactChannels:
description: ValidHumanContactChannels is the list of human contact
channels that were successfully validated
items:
properties:
name:
description: Name of the contact channel
type: string
type:
description: Type of the contact channel (e.g., "slack", "email")
type: string
required:
- name
- type
type: object
type: array
validMCPServers:
description: ValidMCPServers is the list of MCP servers that were
successfully validated
Expand Down
2 changes: 1 addition & 1 deletion kubechain/config/manager/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ kind: Kustomization
images:
- name: controller
newName: controller
newTag: "202504041032"
newTag: "202504041316"
58 changes: 56 additions & 2 deletions kubechain/internal/controller/agent/agent_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const (
// +kubebuilder:rbac:groups=kubechain.humanlayer.dev,resources=llms,verbs=get;list;watch
// +kubebuilder:rbac:groups=kubechain.humanlayer.dev,resources=tools,verbs=get;list;watch
// +kubebuilder:rbac:groups=kubechain.humanlayer.dev,resources=mcpservers,verbs=get;list;watch
// +kubebuilder:rbac:groups=kubechain.humanlayer.dev,resources=contactchannels,verbs=get;list;watch

// AgentReconciler reconciles a Agent object
type AgentReconciler struct {
Expand Down Expand Up @@ -121,6 +122,33 @@ func (r *AgentReconciler) validateMCPServers(ctx context.Context, agent *kubecha
return validMCPServers, nil
}

// validateHumanContactChannels checks if all referenced contact channels exist and are ready
func (r *AgentReconciler) validateHumanContactChannels(ctx context.Context, agent *kubechainv1alpha1.Agent) ([]kubechainv1alpha1.ResolvedContactChannel, error) {
validChannels := make([]kubechainv1alpha1.ResolvedContactChannel, 0, len(agent.Spec.HumanContactChannels))

for _, channelRef := range agent.Spec.HumanContactChannels {
channel := &kubechainv1alpha1.ContactChannel{}
err := r.Get(ctx, client.ObjectKey{
Namespace: agent.Namespace,
Name: channelRef.Name,
}, channel)
if err != nil {
return validChannels, fmt.Errorf("failed to get ContactChannel %q: %w", channelRef.Name, err)
}

if !channel.Status.Ready {
return validChannels, fmt.Errorf("ContactChannel %q is not ready", channelRef.Name)
}

validChannels = append(validChannels, kubechainv1alpha1.ResolvedContactChannel{
Name: channelRef.Name,
Type: string(channel.Spec.Type),
})
}

return validChannels, nil
}

// Reconcile validates the agent's LLM and Tool references
func (r *AgentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := log.FromContext(ctx)
Expand All @@ -142,9 +170,10 @@ func (r *AgentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
r.recorder.Event(&agent, corev1.EventTypeNormal, "Initializing", "Starting validation")
}

// Initialize empty valid tools and servers slices
// Initialize empty valid tools, servers, and human contact channels slices
validTools := make([]kubechainv1alpha1.ResolvedTool, 0)
validMCPServers := make([]kubechainv1alpha1.ResolvedMCPServer, 0)
validHumanContactChannels := make([]kubechainv1alpha1.ResolvedContactChannel, 0)

// Validate LLM reference
if err := r.validateLLM(ctx, &agent); err != nil {
Expand All @@ -154,6 +183,7 @@ func (r *AgentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
statusUpdate.Status.StatusDetail = err.Error()
statusUpdate.Status.ValidTools = validTools
statusUpdate.Status.ValidMCPServers = validMCPServers
statusUpdate.Status.ValidHumanContactChannels = validHumanContactChannels
r.recorder.Event(&agent, corev1.EventTypeWarning, "ValidationFailed", err.Error())
if updateErr := r.Status().Update(ctx, statusUpdate); updateErr != nil {
logger.Error(updateErr, "Failed to update Agent status")
Expand All @@ -171,6 +201,7 @@ func (r *AgentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
statusUpdate.Status.StatusDetail = err.Error()
statusUpdate.Status.ValidTools = validTools
statusUpdate.Status.ValidMCPServers = validMCPServers
statusUpdate.Status.ValidHumanContactChannels = validHumanContactChannels
r.recorder.Event(&agent, corev1.EventTypeWarning, "ValidationFailed", err.Error())
if updateErr := r.Status().Update(ctx, statusUpdate); updateErr != nil {
logger.Error(updateErr, "Failed to update Agent status")
Expand All @@ -189,6 +220,27 @@ func (r *AgentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
statusUpdate.Status.StatusDetail = err.Error()
statusUpdate.Status.ValidTools = validTools
Copy link
Contributor

Choose a reason for hiding this comment

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

weird to me that we set all these things every time we reconcile

statusUpdate.Status.ValidMCPServers = validMCPServers
statusUpdate.Status.ValidHumanContactChannels = validHumanContactChannels
r.recorder.Event(&agent, corev1.EventTypeWarning, "ValidationFailed", err.Error())
if updateErr := r.Status().Update(ctx, statusUpdate); updateErr != nil {
logger.Error(updateErr, "Failed to update Agent status")
return ctrl.Result{}, fmt.Errorf("failed to update agent status: %v", err)
}
return ctrl.Result{}, err // requeue
}
}

// Validate HumanContactChannel references, if any
if len(agent.Spec.HumanContactChannels) > 0 {
validHumanContactChannels, err = r.validateHumanContactChannels(ctx, &agent)
if err != nil {
logger.Error(err, "HumanContactChannel validation failed")
statusUpdate.Status.Ready = false
statusUpdate.Status.Status = StatusError
statusUpdate.Status.StatusDetail = err.Error()
statusUpdate.Status.ValidTools = validTools
statusUpdate.Status.ValidMCPServers = validMCPServers
Copy link
Contributor

Choose a reason for hiding this comment

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

weird that this falls through here - like why don't we set it where we compute it? Right now, if I'm reading this code, I have to go scroll up and see where validMCPServers came from in order to know what's happening here.

like is this nil in this case? is this computed?

statusUpdate.Status.ValidHumanContactChannels = validHumanContactChannels
r.recorder.Event(&agent, corev1.EventTypeWarning, "ValidationFailed", err.Error())
if updateErr := r.Status().Update(ctx, statusUpdate); updateErr != nil {
logger.Error(updateErr, "Failed to update Agent status")
Expand All @@ -204,6 +256,7 @@ func (r *AgentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
statusUpdate.Status.StatusDetail = "All dependencies validated successfully"
statusUpdate.Status.ValidTools = validTools
statusUpdate.Status.ValidMCPServers = validMCPServers
statusUpdate.Status.ValidHumanContactChannels = validHumanContactChannels
r.recorder.Event(&agent, corev1.EventTypeNormal, "ValidationSucceeded", "All dependencies validated successfully")

// Update status
Expand All @@ -216,7 +269,8 @@ func (r *AgentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
"name", agent.Name,
"ready", statusUpdate.Status.Ready,
"status", statusUpdate.Status.Status,
"validTools", statusUpdate.Status.ValidTools)
"validTools", statusUpdate.Status.ValidTools,
"validHumanContactChannels", statusUpdate.Status.ValidHumanContactChannels)
return ctrl.Result{}, nil
}

Expand Down
Loading