Skip to content
Open
Show file tree
Hide file tree
Changes from 12 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
31 changes: 26 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,18 @@ SKILLS_INIT_IMAGE_NAME ?= skills-init
CONTROLLER_IMAGE_TAG ?= $(VERSION)
UI_IMAGE_TAG ?= $(VERSION)
APP_IMAGE_TAG ?= $(VERSION)
APP_FULL_IMAGE_TAG ?= $(VERSION)-full
Comment thread
jmhbh marked this conversation as resolved.
KAGENT_ADK_IMAGE_TAG ?= $(VERSION)
KAGENT_ADK_FULL_IMAGE_TAG ?= $(VERSION)-full
GOLANG_ADK_IMAGE_TAG ?= $(VERSION)
GOLANG_ADK_FULL_IMAGE_TAG ?= $(VERSION)-full
SKILLS_INIT_IMAGE_TAG ?= $(VERSION)
CONTROLLER_IMG ?= $(DOCKER_REGISTRY)/$(DOCKER_REPO)/$(CONTROLLER_IMAGE_NAME):$(CONTROLLER_IMAGE_TAG)
UI_IMG ?= $(DOCKER_REGISTRY)/$(DOCKER_REPO)/$(UI_IMAGE_NAME):$(UI_IMAGE_TAG)
APP_IMG ?= $(DOCKER_REGISTRY)/$(DOCKER_REPO)/$(APP_IMAGE_NAME):$(APP_IMAGE_TAG)
APP_FULL_IMG ?= $(DOCKER_REGISTRY)/$(DOCKER_REPO)/$(APP_IMAGE_NAME):$(APP_FULL_IMAGE_TAG)
KAGENT_ADK_IMG ?= $(DOCKER_REGISTRY)/$(DOCKER_REPO)/$(KAGENT_ADK_IMAGE_NAME):$(KAGENT_ADK_IMAGE_TAG)
KAGENT_ADK_FULL_IMG ?= $(DOCKER_REGISTRY)/$(DOCKER_REPO)/$(KAGENT_ADK_IMAGE_NAME):$(KAGENT_ADK_FULL_IMAGE_TAG)
GOLANG_ADK_IMG ?= $(DOCKER_REGISTRY)/$(DOCKER_REPO)/$(GOLANG_ADK_IMAGE_NAME):$(GOLANG_ADK_IMAGE_TAG)
GOLANG_ADK_FULL_IMG ?= $(DOCKER_REGISTRY)/$(DOCKER_REPO)/$(GOLANG_ADK_IMAGE_NAME):$(GOLANG_ADK_FULL_IMAGE_TAG)
SKILLS_INIT_IMG ?= $(DOCKER_REGISTRY)/$(DOCKER_REPO)/$(SKILLS_INIT_IMAGE_NAME):$(SKILLS_INIT_IMAGE_TAG)
Expand Down Expand Up @@ -201,12 +205,14 @@ build-all: buildx-create

.PHONY: build
build: ## Build and push all component images
build: buildx-create build-ui build-skills-init build-golang-adk build-golang-adk-full build-app build-controller
build: buildx-create build-ui build-skills-init build-golang-adk build-golang-adk-full build-app build-app-full build-controller
@echo "Build completed successfully."
@echo "Controller Image: $(CONTROLLER_IMG)"
@echo "UI Image: $(UI_IMG)"
@echo "App Image: $(APP_IMG)"
@echo "App Full Image: $(APP_FULL_IMG)"
@echo "Kagent ADK Image: $(KAGENT_ADK_IMG)"
@echo "Kagent ADK Full Image: $(KAGENT_ADK_FULL_IMG)"
@echo "Golang ADK Image: $(GOLANG_ADK_IMG)"
@echo "Golang ADK Full Image: $(GOLANG_ADK_FULL_IMG)"
@echo "Skills Init Image: $(SKILLS_INIT_IMG)"
Expand Down Expand Up @@ -234,7 +240,9 @@ build-img-versions: ## Print the fully-qualified image tags for all components
@echo controller=$(CONTROLLER_IMG)
@echo ui=$(UI_IMG)
@echo app=$(APP_IMG)
@echo app-full=$(APP_FULL_IMG)
@echo kagent-adk=$(KAGENT_ADK_IMG)
@echo kagent-adk-full=$(KAGENT_ADK_FULL_IMG)
@echo golang-adk=$(GOLANG_ADK_IMG)
@echo golang-adk-full=$(GOLANG_ADK_FULL_IMG)
@echo skills-init=$(SKILLS_INIT_IMG)
Expand All @@ -246,10 +254,11 @@ controller-manifests: ## Regenerate CRD manifests and copy them into the Helm ch

.PHONY: build-controller
build-controller: ## Build and push the controller image (embeds agent runtime digests via scripts/controller-digest-ldflags.sh)
build-controller: buildx-create controller-manifests build-app build-golang-adk build-golang-adk-full
build-controller: buildx-create controller-manifests build-app build-app-full build-golang-adk build-golang-adk-full
@set -e; \
DIGEST_LDFLAGS=$$(CONTAINER_RUNTIME=$(CONTAINER_RUNTIME) \
APP_IMG=$(APP_IMG) \
APP_FULL_IMG=$(APP_FULL_IMG) \
GOLANG_ADK_IMG=$(GOLANG_ADK_IMG) \
GOLANG_ADK_FULL_IMG=$(GOLANG_ADK_FULL_IMG) \
./scripts/controller-digest-ldflags.sh); \
Expand All @@ -272,11 +281,23 @@ build-kagent-adk: buildx-create
$(DOCKER_PUSH) $(KAGENT_ADK_IMG)

.PHONY: build-app
build-app: ## Build and push the app image (depends on kagent-adk)
build-app: ## Build and push the app image (distroless slim; depends on kagent-adk)
build-app: buildx-create build-kagent-adk
$(DOCKER_BUILDER) $(DOCKER_BUILD_ARGS) $(TOOLS_IMAGE_BUILD_ARGS) --build-arg KAGENT_ADK_VERSION=$(KAGENT_ADK_IMAGE_TAG) --build-arg DOCKER_REGISTRY=$(DOCKER_REGISTRY) -t $(APP_IMG) -f python/Dockerfile.app ./python
$(DOCKER_PUSH) $(APP_IMG)

.PHONY: build-kagent-adk-full
build-kagent-adk-full: ## Build and push the full Python kagent ADK image (includes sandbox runtime)
build-kagent-adk-full: buildx-create
$(DOCKER_BUILDER) $(DOCKER_BUILD_ARGS) $(TOOLS_IMAGE_BUILD_ARGS) -t $(KAGENT_ADK_FULL_IMG) -f python/Dockerfile.full ./python
$(DOCKER_PUSH) $(KAGENT_ADK_FULL_IMG)

.PHONY: build-app-full
build-app-full: ## Build and push the full app image (sandbox runtime; depends on kagent-adk-full)
build-app-full: buildx-create build-kagent-adk-full
$(DOCKER_BUILDER) $(DOCKER_BUILD_ARGS) $(TOOLS_IMAGE_BUILD_ARGS) --build-arg KAGENT_ADK_VERSION=$(KAGENT_ADK_FULL_IMAGE_TAG) --build-arg DOCKER_REGISTRY=$(DOCKER_REGISTRY) -t $(APP_FULL_IMG) -f python/Dockerfile.app ./python
$(DOCKER_PUSH) $(APP_FULL_IMG)

.PHONY: build-golang-adk
build-golang-adk: ## Build and push the Go ADK image
build-golang-adk: buildx-create
Expand Down Expand Up @@ -308,8 +329,8 @@ lint: ## Run linters for Go and Python
make -C python lint

.PHONY: push-test-agent
push-test-agent: buildx-create build-kagent-adk ## Build and push E2E test agent images to the local registry
echo "Building FROM DOCKER_REGISTRY=$(DOCKER_REGISTRY)/$(DOCKER_REPO)/kagent-adk:$(VERSION)"
push-test-agent: buildx-create build-kagent-adk build-kagent-adk-full ## Build and push E2E test agent images to the local registry
echo "Building FROM DOCKER_REGISTRY=$(DOCKER_REGISTRY)/$(DOCKER_REPO)/kagent-adk:$(VERSION)-full"
$(DOCKER_BUILDER) $(DOCKER_BUILD_ARGS) $(TOOLS_IMAGE_BUILD_ARGS) -t $(DOCKER_REGISTRY)/kebab:latest -f go/core/test/e2e/agents/kebab/Dockerfile ./go/core/test/e2e/agents/kebab
$(DOCKER_PUSH) $(DOCKER_REGISTRY)/kebab:latest
kubectl apply --namespace kagent --context kind-$(KIND_CLUSTER_NAME) -f go/core/test/e2e/agents/kebab/agent.yaml
Expand Down
2 changes: 0 additions & 2 deletions go/api/config/crd/bases/kagent.dev_sandboxagents.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11339,8 +11339,6 @@ spec:
rule: '!has(self.skills) || self.platform != ''substrate'''
- message: spec.substrate may only be set when spec.platform is substrate
rule: '!has(self.substrate) || self.platform == ''substrate'''
- message: BYO agents are not supported when spec.platform is substrate
rule: '!has(self.type) || self.type != ''BYO'' || self.platform != ''substrate'''
- message: type must be specified
rule: has(self.type)
- message: type must be either Declarative or BYO
Expand Down
60 changes: 30 additions & 30 deletions go/api/v1alpha2/agent_runtime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,36 @@ import (
"github.com/stretchr/testify/require"
)

func TestEffectiveDeclarativeRuntimeForAgent(t *testing.T) {
substrateSpec := AgentSpec{
Type: AgentType_Declarative,
Declarative: &DeclarativeAgentSpec{
Runtime: DeclarativeRuntime_Python,
func TestEffectiveDeclarativeRuntime(t *testing.T) {
tests := []struct {
name string
spec *AgentSpec
want DeclarativeRuntime
}{
{
name: "nil spec defaults to Python",
spec: nil,
want: DeclarativeRuntime_Python,
},
{
name: "unset runtime defaults to Python",
spec: &AgentSpec{Type: AgentType_Declarative, Declarative: &DeclarativeAgentSpec{}},
want: DeclarativeRuntime_Python,
},
{
name: "explicit Python runtime",
spec: &AgentSpec{Type: AgentType_Declarative, Declarative: &DeclarativeAgentSpec{Runtime: DeclarativeRuntime_Python}},
want: DeclarativeRuntime_Python,
},
{
name: "explicit Go runtime is honored",
spec: &AgentSpec{Type: AgentType_Declarative, Declarative: &DeclarativeAgentSpec{Runtime: DeclarativeRuntime_Go}},
want: DeclarativeRuntime_Go,
},
}

t.Run("regular Agent keeps configured runtime on substrate platform", func(t *testing.T) {
agent := &Agent{Spec: substrateSpec}
require.Equal(t, DeclarativeRuntime_Python, EffectiveDeclarativeRuntimeForAgent(agent))
})

t.Run("SandboxAgent on substrate uses Go", func(t *testing.T) {
sa := &SandboxAgent{Spec: SandboxAgentSpec{AgentSpec: substrateSpec, Platform: SandboxPlatformSubstrate}}
require.Equal(t, DeclarativeRuntime_Go, EffectiveDeclarativeRuntimeForAgent(sa))
})

t.Run("SandboxAgent on agent-sandbox keeps configured runtime", func(t *testing.T) {
sa := &SandboxAgent{Spec: SandboxAgentSpec{AgentSpec: substrateSpec, Platform: SandboxPlatformAgentSandbox}}
require.Equal(t, DeclarativeRuntime_Python, EffectiveDeclarativeRuntimeForAgent(sa))
})

t.Run("regular Agent honors Go runtime", func(t *testing.T) {
agent := &Agent{Spec: AgentSpec{
Type: AgentType_Declarative,
Declarative: &DeclarativeAgentSpec{
Runtime: DeclarativeRuntime_Go,
},
}}
require.Equal(t, DeclarativeRuntime_Go, EffectiveDeclarativeRuntimeForAgent(agent))
})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require.Equal(t, tt.want, EffectiveDeclarativeRuntime(tt.spec))
})
}
}
30 changes: 17 additions & 13 deletions go/api/v1alpha2/agent_spec_validation.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package v1alpha2

import "fmt"
import (
"fmt"
"strings"
)

const (
substrateSandboxSkillsUnsupportedMsg = "spec.skills is not supported when spec.platform is substrate"
substrateSandboxPythonRuntimeUnsupportedMsg = "spec.declarative.runtime must be \"go\" when spec.platform is substrate"
substrateSandboxBYOUnsupportedMsg = "BYO agents are not supported when spec.platform is substrate"
substrateSandboxSkillsUnsupportedMsg = "spec.skills is not supported when spec.platform is substrate"
substrateSandboxBYOMissingCommandMsg = "BYO agents on substrate must set spec.byo.deployment.cmd (substrate does not fall back to the image entrypoint)"
)

// AgentSpecHasSkills reports whether the spec configures any skill sources.
Expand All @@ -18,23 +20,25 @@ func AgentSpecHasSkills(spec *AgentSpec) bool {
}

// ValidateSubstrateSandboxAgentSpec rejects substrate sandbox configurations that kagent
// does not support yet (for example declarative skills on Agent Substrate).
// does not support yet (for example declarative skills on Agent Substrate). Declarative
// Python/Go and BYO (Go/Python) agents are supported; BYO agents must provide an explicit
// command because substrate copies the container Command verbatim with no image-entrypoint
// fallback.
func ValidateSubstrateSandboxAgentSpec(agent *SandboxAgent) error {
if agent == nil || AgentSandboxPlatform(agent) != SandboxPlatformSubstrate {
return nil
}
spec := agent.GetAgentSpec()
if spec.Type == AgentType_BYO {
return fmt.Errorf("%s", substrateSandboxBYOUnsupportedMsg)
}
if AgentSpecHasSkills(spec) {
return fmt.Errorf("%s", substrateSandboxSkillsUnsupportedMsg)
}
if spec.Type == AgentType_Declarative &&
spec.Declarative != nil &&
spec.Declarative.Runtime != "" &&
spec.Declarative.Runtime != DeclarativeRuntime_Go {
return fmt.Errorf("%s", substrateSandboxPythonRuntimeUnsupportedMsg)
if spec.Type == AgentType_BYO {
dep := spec.BYO
// Trim so a whitespace-only cmd is rejected like an empty one (substrate would treat it
// as no command, and the UI trims before validating — keep backend/UI aligned).
if dep == nil || dep.Deployment == nil || dep.Deployment.Cmd == nil || strings.TrimSpace(*dep.Deployment.Cmd) == "" {
return fmt.Errorf("%s", substrateSandboxBYOMissingCommandMsg)
}
Comment thread
jmhbh marked this conversation as resolved.
}
return nil
}
38 changes: 33 additions & 5 deletions go/api/v1alpha2/agent_spec_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func TestValidateSubstrateSandboxAgentSpec(t *testing.T) {
require.Contains(t, err.Error(), substrateSandboxSkillsUnsupportedMsg)
})

t.Run("rejects python runtime on substrate platform", func(t *testing.T) {
t.Run("allows python runtime on substrate platform", func(t *testing.T) {
agent := &SandboxAgent{
Spec: SandboxAgentSpec{
Platform: SandboxPlatformSubstrate,
Expand All @@ -48,24 +48,52 @@ func TestValidateSubstrateSandboxAgentSpec(t *testing.T) {
},
},
}
require.NoError(t, ValidateSubstrateSandboxAgentSpec(agent))
})

t.Run("rejects BYO agents without an explicit command on substrate platform", func(t *testing.T) {
agent := &SandboxAgent{
Spec: SandboxAgentSpec{
Platform: SandboxPlatformSubstrate,
AgentSpec: AgentSpec{
Type: AgentType_BYO,
BYO: &BYOAgentSpec{Deployment: &ByoDeploymentSpec{Image: "example/agent:latest"}},
},
},
}
err := ValidateSubstrateSandboxAgentSpec(agent)
require.Error(t, err)
require.Contains(t, err.Error(), substrateSandboxPythonRuntimeUnsupportedMsg)
require.Contains(t, err.Error(), substrateSandboxBYOMissingCommandMsg)
})

t.Run("rejects BYO agents on substrate platform", func(t *testing.T) {
t.Run("rejects BYO agents with a whitespace-only command on substrate platform", func(t *testing.T) {
cmd := " "
agent := &SandboxAgent{
Spec: SandboxAgentSpec{
Platform: SandboxPlatformSubstrate,
AgentSpec: AgentSpec{
Type: AgentType_BYO,
BYO: &BYOAgentSpec{},
BYO: &BYOAgentSpec{Deployment: &ByoDeploymentSpec{Image: "example/agent:latest", Cmd: &cmd}},
},
},
}
err := ValidateSubstrateSandboxAgentSpec(agent)
require.Error(t, err)
require.Contains(t, err.Error(), substrateSandboxBYOUnsupportedMsg)
require.Contains(t, err.Error(), substrateSandboxBYOMissingCommandMsg)
})

t.Run("allows BYO agents with an explicit command on substrate platform", func(t *testing.T) {
cmd := "/app"
agent := &SandboxAgent{
Spec: SandboxAgentSpec{
Platform: SandboxPlatformSubstrate,
AgentSpec: AgentSpec{
Type: AgentType_BYO,
BYO: &BYOAgentSpec{Deployment: &ByoDeploymentSpec{Image: "example/agent:latest", Cmd: &cmd}},
},
},
}
require.NoError(t, ValidateSubstrateSandboxAgentSpec(agent))
})

t.Run("allows BYO agents on agent-sandbox platform", func(t *testing.T) {
Expand Down
14 changes: 1 addition & 13 deletions go/api/v1alpha2/agent_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ func AgentSandboxPlatform(agent AgentObject) SandboxPlatform {
}

// EffectiveDeclarativeRuntime returns the ADK runtime from spec fields (defaults to Python).
// All agents (including substrate SandboxAgents) honor spec.declarative.runtime.
func EffectiveDeclarativeRuntime(spec *AgentSpec) DeclarativeRuntime {
if spec == nil {
return DeclarativeRuntime_Python
Expand All @@ -281,19 +282,6 @@ func EffectiveDeclarativeRuntime(spec *AgentSpec) DeclarativeRuntime {
return runtime
}

// EffectiveDeclarativeRuntimeForAgent returns the runtime for a reconciled agent object.
// Substrate SandboxAgents always use Go; regular Agents honor spec.declarative.runtime.
func EffectiveDeclarativeRuntimeForAgent(agent AgentObject) DeclarativeRuntime {
spec := agent.GetAgentSpec()
if agent.GetWorkloadMode() == WorkloadModeSandbox &&
AgentSandboxPlatform(agent) == SandboxPlatformSubstrate &&
spec != nil &&
spec.Type == AgentType_Declarative {
return DeclarativeRuntime_Go
}
return EffectiveDeclarativeRuntime(spec)
}

// NetworkConfig configures outbound network access for sandboxed execution paths.
type NetworkConfig struct {
// AllowedDomains lists the domains that sandboxed execution may contact.
Expand Down
1 change: 0 additions & 1 deletion go/api/v1alpha2/sandboxagent_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ type SandboxAgent struct {

// +kubebuilder:validation:XValidation:rule="!has(self.skills) || self.platform != 'substrate'",message="spec.skills is not supported when spec.platform is substrate"
// +kubebuilder:validation:XValidation:rule="!has(self.substrate) || self.platform == 'substrate'",message="spec.substrate may only be set when spec.platform is substrate"
// +kubebuilder:validation:XValidation:rule="!has(self.type) || self.type != 'BYO' || self.platform != 'substrate'",message="BYO agents are not supported when spec.platform is substrate"
type SandboxAgentSpec struct {
AgentSpec `json:",inline"`

Expand Down
4 changes: 2 additions & 2 deletions go/core/internal/controller/sandboxagent_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func (r *SandboxAgentController) Reconcile(ctx context.Context, req ctrl.Request
return ctrl.Result{}, fmt.Errorf("get SandboxAgent: %w", err)
}

if sandboxAgentUsesSubstrate(&sa) && r.SubstrateLifecycle != nil {
if sandboxAgentUsesSubstrate(&sa) && r.substrateConfigured() {
if res, err := r.reconcileSubstrateSandboxAgent(ctx, &sa); err != nil || !res.IsZero() {
return res, err
}
Expand Down Expand Up @@ -109,7 +109,7 @@ func (r *SandboxAgentController) SetupWithManager(mgr ctrl.Manager) error {
if err != nil {
return err
}
if r.SubstrateLifecycle != nil {
if r.substrateConfigured() {

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.

Can we get rid of this r.substrateConfigured() in a follow-up. If substrate is not configured we shouldn't even run this reconciler at all, nothing will work. Right now it's scattered everywhere

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.

I think it makes sense to separate this into a separate substrate controller, I can work on that as a follow up.

build = build.Watches(
&atev1alpha1.ActorTemplate{},
handler.EnqueueRequestsFromMapFunc(r.enqueueSandboxAgentForSubstrateResource),
Expand Down
Loading
Loading