Skip to content
Open
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
14 changes: 14 additions & 0 deletions components/backend/handlers/sessions.go
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,20 @@ func CreateSession(c *gin.Context) {
session["spec"].(map[string]interface{})["autoPushOnComplete"] = *req.AutoPushOnComplete
}

// RunnerConfig for pluggable agents
if req.RunnerConfig != nil {
runnerConfig := make(map[string]interface{})
if req.RunnerConfig.Type != "" {
runnerConfig["type"] = req.RunnerConfig.Type
}
if req.RunnerConfig.Image != "" {
runnerConfig["image"] = req.RunnerConfig.Image
}
if len(runnerConfig) > 0 {
session["spec"].(map[string]interface{})["runnerConfig"] = runnerConfig
}
}

// Set multi-repo configuration on spec (simplified format)
{
spec := session["spec"].(map[string]interface{})
Expand Down
9 changes: 9 additions & 0 deletions components/backend/types/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ type AgenticSessionSpec struct {
Repos []SimpleRepo `json:"repos,omitempty"`
// Active workflow for dynamic workflow switching
ActiveWorkflow *WorkflowSelection `json:"activeWorkflow,omitempty"`
// Runner configuration for pluggable agent support
RunnerConfig *RunnerConfig `json:"runnerConfig,omitempty"`
}

// SimpleRepo represents a simplified repository configuration
Expand Down Expand Up @@ -58,6 +60,7 @@ type CreateAgenticSessionRequest struct {
EnvironmentVariables map[string]string `json:"environmentVariables,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
Annotations map[string]string `json:"annotations,omitempty"`
RunnerConfig *RunnerConfig `json:"runnerConfig,omitempty"`
}

type CloneSessionRequest struct {
Expand Down Expand Up @@ -86,6 +89,12 @@ type WorkflowSelection struct {
Path string `json:"path,omitempty"`
}

// RunnerConfig specifies which agent runner to use (enables pluggable agents)
type RunnerConfig struct {
Type string `json:"type,omitempty"` // Runner type: "claude-sdk", "langgraph", "crewai", "custom"
Image string `json:"image,omitempty"` // Optional custom container image override
}

// ReconciledRepo captures reconciliation state for a repository
type ReconciledRepo struct {
URL string `json:"url"`
Expand Down
16 changes: 16 additions & 0 deletions components/manifests/base/crds/agenticsessions-crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,22 @@ spec:
path:
type: string
description: "Optional path within repo (for repos with multiple workflows)"
runnerConfig:
type: object
description: "Configuration for the agent runner (enables pluggable agent support)"
properties:
type:
type: string
description: "Runner type identifier (e.g., 'claude-sdk', 'langgraph', 'crewai')"
default: "claude-sdk"
enum:
- "claude-sdk"
- "langgraph"
- "crewai"
- "custom"
image:
type: string
description: "Optional custom runner container image (overrides default for runner type)"
status:
type: object
properties:
Expand Down
7 changes: 7 additions & 0 deletions components/manifests/base/operator-deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ spec:
value: "quay.io/ambient_code/vteam_backend:latest"
- name: IMAGE_PULL_POLICY
value: "Always"
# Pluggable agent runner images (optional - if not set, defaults to AMBIENT_CODE_RUNNER_IMAGE)
- name: LANGGRAPH_RUNNER_IMAGE
value: "" # Example: "quay.io/ambient_code/vteam_langgraph_runner:latest"
- name: CREWAI_RUNNER_IMAGE
value: "" # Example: "quay.io/ambient_code/vteam_crewai_runner:latest"
- name: CUSTOM_RUNNER_IMAGE
value: "" # Example: "quay.io/your_org/custom_runner:latest"
# Vertex AI configuration from ConfigMap
- name: CLAUDE_CODE_USE_VERTEX
valueFrom:
Expand Down
7 changes: 7 additions & 0 deletions components/manifests/minikube/operator-deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ spec:
value: "localhost/vteam-backend:latest"
- name: IMAGE_PULL_POLICY
value: "Never"
# Pluggable agent runner images (optional - if not set, defaults to AMBIENT_CODE_RUNNER_IMAGE)
- name: LANGGRAPH_RUNNER_IMAGE
value: "" # Example: "localhost/vteam-langgraph-runner:latest"
- name: CREWAI_RUNNER_IMAGE
value: "" # Example: "localhost/vteam-crewai-runner:latest"
- name: CUSTOM_RUNNER_IMAGE
value: "" # Example: "localhost/custom-runner:latest"
envFrom:
- configMapRef:
name: operator-config
Expand Down
39 changes: 39 additions & 0 deletions components/operator/internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ type Config struct {
AmbientCodeRunnerImage string
ContentServiceImage string
ImagePullPolicy corev1.PullPolicy
// Runner type to image mappings for pluggable agents
RunnerImages map[string]string
}

// InitK8sClients initializes the Kubernetes clients
Expand Down Expand Up @@ -93,11 +95,48 @@ func LoadConfig() *Config {
}
imagePullPolicy := corev1.PullPolicy(imagePullPolicyStr)

// Initialize runner image mappings for pluggable agents
runnerImages := make(map[string]string)
runnerImages["claude-sdk"] = ambientCodeRunnerImage // Default Claude SDK runner

// Load additional runner images from environment variables
if langGraphImage := os.Getenv("LANGGRAPH_RUNNER_IMAGE"); langGraphImage != "" {
runnerImages["langgraph"] = langGraphImage
}
if crewAIImage := os.Getenv("CREWAI_RUNNER_IMAGE"); crewAIImage != "" {
runnerImages["crewai"] = crewAIImage
}
if customImage := os.Getenv("CUSTOM_RUNNER_IMAGE"); customImage != "" {
runnerImages["custom"] = customImage
}

return &Config{
Namespace: namespace,
BackendNamespace: backendNamespace,
AmbientCodeRunnerImage: ambientCodeRunnerImage,
ContentServiceImage: contentServiceImage,
ImagePullPolicy: imagePullPolicy,
RunnerImages: runnerImages,
}
}

// GetRunnerImage returns the appropriate runner image based on runnerConfig
// If custom image is specified, it takes precedence
// Otherwise, looks up the runner type in the image registry
// Falls back to default Claude SDK runner if not found
func (c *Config) GetRunnerImage(runnerType string, customImage string) string {
// Custom image override takes precedence
if customImage != "" {
return customImage
}

// Look up runner type in registry
if runnerType != "" {
if image, ok := c.RunnerImages[runnerType]; ok {
return image
}
}

// Default to Claude SDK runner
return c.AmbientCodeRunnerImage
}
11 changes: 10 additions & 1 deletion components/operator/internal/handlers/sessions.go
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,15 @@ func handleAgenticSessionEvent(obj *unstructured.Unstructured) error {
temperature, _, _ := unstructured.NestedFloat64(llmSettings, "temperature")
maxTokens, _, _ := unstructured.NestedInt64(llmSettings, "maxTokens")

// Extract runner configuration for pluggable agents
runnerConfig, _, _ := unstructured.NestedMap(spec, "runnerConfig")
runnerType, _, _ := unstructured.NestedString(runnerConfig, "type")
runnerImage, _, _ := unstructured.NestedString(runnerConfig, "image")
// Default to claude-sdk if not specified
if runnerType == "" {
runnerType = "claude-sdk"
}

// Hardcoded secret names (convention over configuration)
const runnerSecretsName = "ambient-runner-secrets" // ANTHROPIC_API_KEY only (ignored when Vertex enabled)
const integrationSecretsName = "ambient-non-vertex-integrations" // GIT_*, JIRA_*, custom keys (optional)
Expand Down Expand Up @@ -1021,7 +1030,7 @@ func handleAgenticSessionEvent(obj *unstructured.Unstructured) error {
},
{
Name: "ambient-code-runner",
Image: appConfig.AmbientCodeRunnerImage,
Image: appConfig.GetRunnerImage(runnerType, runnerImage),
ImagePullPolicy: appConfig.ImagePullPolicy,
// 🔒 Container-level security (SCC-compatible, no privileged capabilities)
SecurityContext: &corev1.SecurityContext{
Expand Down