Skip to content
Merged
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
4,100 changes: 3,590 additions & 510 deletions go/api/config/crd/bases/kagent.dev_agents.yaml

Large diffs are not rendered by default.

4,726 changes: 3,903 additions & 823 deletions go/api/config/crd/bases/kagent.dev_sandboxagents.yaml

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions go/api/v1alpha2/agent_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,10 @@ type SharedDeploymentSpec struct {
// is created, and this config will be applied to it.
// +optional
ServiceAccountConfig *ServiceAccountConfig `json:"serviceAccountConfig,omitempty"`
// ExtraContainers is a list of additional containers to run alongside the main agent container.
// Useful for sidecars such as token proxies, log shippers, or security agents.
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

Exposing []corev1.Container directly in a CRD can significantly bloat the generated OpenAPI schema and can also prune unknown/newer container fields (apiserver schema-pruning) as Kubernetes evolves. To avoid CRD size/compatibility issues, consider marking this field as schemaless / preserve-unknown-fields (kubebuilder marker) or switching to a slimmer custom sidecar spec / raw JSON representation, depending on how strictly you want to validate user input.

Suggested change
// Useful for sidecars such as token proxies, log shippers, or security agents.
// Useful for sidecars such as token proxies, log shippers, or security agents.
// Preserve unknown/newer container fields in the CRD schema to avoid pruning as Kubernetes evolves.
// +kubebuilder:validation:Schemaless
// +kubebuilder:pruning:PreserveUnknownFields

Copilot uses AI. Check for mistakes.
// +optional
ExtraContainers []corev1.Container `json:"extraContainers,omitempty"`
}

type ServiceAccountConfig struct {
Expand Down
7 changes: 7 additions & 0 deletions go/api/v1alpha2/zz_generated.deepcopy.go

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

27 changes: 27 additions & 0 deletions go/core/internal/controller/translator/agent/deployments.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ type resolvedDeployment struct {
PodSecurityContext *corev1.PodSecurityContext
ServiceAccountName *string
ServiceAccountConfig *v1alpha2.ServiceAccountConfig
ExtraContainers []corev1.Container
}

// getDefaultResources sets default resource requirements if not specified
Expand Down Expand Up @@ -105,6 +106,22 @@ func getRuntimeImageRepository(runtime v1alpha2.DeclarativeRuntime) string {
}
}

// validateExtraContainers checks that none of the extra containers use the
// reserved name "kagent" and that no two containers share the same name.
func validateExtraContainers(containers []corev1.Container) error {
seen := make(map[string]bool)
for _, c := range containers {
if c.Name == "kagent" {
return fmt.Errorf("extraContainers: %q is a reserved container name", c.Name)
}
if seen[c.Name] {
return fmt.Errorf("extraContainers: duplicate container name %q", c.Name)
}
seen[c.Name] = true
}
return nil
}

func resolveInlineDeployment(agent v1alpha2.AgentObject, mdd *modelDeploymentData) (*resolvedDeployment, error) {
specRef := agent.GetAgentSpec()
// Defaults
Expand Down Expand Up @@ -161,6 +178,10 @@ func resolveInlineDeployment(agent v1alpha2.AgentObject, mdd *modelDeploymentDat
}
}

if err := validateExtraContainers(spec.ExtraContainers); err != nil {
return nil, err
}

dep := &resolvedDeployment{
Image: image,
Args: args,
Expand All @@ -181,6 +202,7 @@ func resolveInlineDeployment(agent v1alpha2.AgentObject, mdd *modelDeploymentDat
PodSecurityContext: spec.PodSecurityContext,
ServiceAccountName: spec.ServiceAccountName,
ServiceAccountConfig: spec.ServiceAccountConfig,
ExtraContainers: slices.Clone(spec.ExtraContainers),
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

There is no validation preventing extraContainers from using the reserved name kagent (or causing duplicate container names). If a user sets a sidecar name that duplicates the primary container (or another sidecar), the generated PodSpec will be rejected by Kubernetes and reconciliation will fail. Consider adding validation during resolve (or API validation) to reject ExtraContainers entries with Name == \"kagent\" and to enforce uniqueness across all container names.

Copilot uses AI. Check for mistakes.
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.

@mesutoezdil this needs to be solved

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in a71c636. Added validateExtraContainers which returns an error if any extra container uses the reserved name "kagent" or if two extra containers share the same name. The check runs in both resolveInlineDeployment and resolveByoDeployment before the slice is cloned. Five unit tests cover the cases: empty list, normal names, reserved name, duplicates, and mixed.

}

// Precedence: agent-level serviceAccountName > global default > auto-created SA (agent name)
Expand Down Expand Up @@ -241,6 +263,10 @@ func resolveByoDeployment(agent v1alpha2.AgentObject) (*resolvedDeployment, erro
replicas = new(int32(1))
}

if err := validateExtraContainers(spec.ExtraContainers); err != nil {
return nil, err
}

dep := &resolvedDeployment{
Image: image,
Cmd: cmd,
Expand All @@ -262,6 +288,7 @@ func resolveByoDeployment(agent v1alpha2.AgentObject) (*resolvedDeployment, erro
PodSecurityContext: spec.PodSecurityContext,
ServiceAccountName: spec.ServiceAccountName,
ServiceAccountConfig: spec.ServiceAccountConfig,
ExtraContainers: slices.Clone(spec.ExtraContainers),
}

// Precedence: agent-level serviceAccountName > global default > auto-created SA (agent name)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package agent

import (
"testing"

corev1 "k8s.io/api/core/v1"
)

func TestValidateExtraContainers(t *testing.T) {
t.Parallel()

tests := []struct {
name string
containers []corev1.Container
wantErr bool
}{
{
name: "empty list is fine",
containers: nil,
wantErr: false,
},
{
name: "normal sidecar names are fine",
containers: []corev1.Container{
{Name: "envoy"},
{Name: "log-shipper"},
},
wantErr: false,
},
{
name: "reserved name kagent is rejected",
containers: []corev1.Container{
{Name: "kagent"},
},
wantErr: true,
},
{
name: "duplicate sidecar names are rejected",
containers: []corev1.Container{
{Name: "proxy"},
{Name: "proxy"},
},
wantErr: true,
},
{
name: "kagent mixed with other containers is still rejected",
containers: []corev1.Container{
{Name: "envoy"},
{Name: "kagent"},
},
wantErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
err := validateExtraContainers(tt.containers)
if (err != nil) != tt.wantErr {
t.Errorf("validateExtraContainers() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,7 @@ func buildPodTemplate(
ImagePullSecrets: dep.ImagePullSecrets,
SecurityContext: dep.PodSecurityContext,
InitContainers: runtimeInputs.initContainers,
Containers: []corev1.Container{{
Containers: append([]corev1.Container{{
Name: "kagent",
Image: dep.Image,
ImagePullPolicy: dep.ImagePullPolicy,
Expand All @@ -482,7 +482,7 @@ func buildPodTemplate(
},
SecurityContext: runtimeInputs.securityContext,
VolumeMounts: runtimeInputs.volumeMounts,
}},
}}, dep.ExtraContainers...),
Volumes: runtimeInputs.volumes,
Tolerations: dep.Tolerations,
Affinity: dep.Affinity,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
operation: translateAgent
targetObject: agent-with-extra-containers
namespace: test
objects:
- apiVersion: v1
kind: Secret
metadata:
name: openai-secret
namespace: test
data:
api-key: c2stdGVzdC1hcGkta2V5 # base64 encoded "sk-test-api-key"
- apiVersion: kagent.dev/v1alpha2
kind: ModelConfig
metadata:
name: basic-model
namespace: test
spec:
provider: OpenAI
model: gpt-4o
apiKeySecret: openai-secret
apiKeySecretKey: api-key
- apiVersion: kagent.dev/v1alpha2
kind: Agent
metadata:
name: agent-with-extra-containers
namespace: test
spec:
type: Declarative
declarative:
description: A test agent with a sidecar container
systemMessage: You are a helpful assistant.
modelConfig: basic-model
deployment:
extraContainers:
- name: token-proxy
image: envoyproxy/envoy:v1.30.0
args:
- -c
- /etc/envoy/envoy.yaml
ports:
- containerPort: 9901
name: admin
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
tools: []
Loading
Loading