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
9 changes: 9 additions & 0 deletions cmd/main/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ ADVANCED OPTIONS:
--sandbox Sandbox config JSON for isolated execution
--coding Coding config JSON for OpenCode integration
--notify Enable notifications for this agent
--harness-config Agentic harness config JSON for multi-turn execution

EXAMPLES:
# Create a simple agent
Expand All @@ -84,6 +85,13 @@ EXAMPLES:
--prompt "Execute Python code in a sandbox." \
--sandbox '{"enabled":true,"image":"python:3.11"}'

# Create with agentic harness (Claude Agent SDK-like)
stn agent create coding-agent \
--description "Multi-turn coding assistant" \
--prompt "You are a coding assistant." \
--tools read,write,bash,glob,grep,edit \
--harness-config '{"max_steps":50,"sandbox":{"mode":"docker"}}'

# Create with structured output for CloudShip
stn agent create cost-analyzer \
--description "Analyzes cloud costs" \
Expand Down Expand Up @@ -121,6 +129,7 @@ ADVANCED OPTIONS:
--sandbox Update sandbox config JSON
--coding Update coding config JSON
--notify Update notification setting
--harness-config Update agentic harness config JSON

EXAMPLES:
# Update the prompt
Expand Down
75 changes: 75 additions & 0 deletions cmd/main/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -978,6 +978,70 @@ func runInit(cmd *cobra.Command, args []string) error {
}
}

// Set harness defaults (agentic execution harness)
if !viper.IsSet("harness.workspace.path") {
viper.Set("harness.workspace.path", "./workspace")
}
if !viper.IsSet("harness.workspace.mode") {
viper.Set("harness.workspace.mode", "host")
}
if !viper.IsSet("harness.compaction.enabled") {
viper.Set("harness.compaction.enabled", true)
}
if !viper.IsSet("harness.compaction.threshold") {
viper.Set("harness.compaction.threshold", "0.85")
}
if !viper.IsSet("harness.compaction.protect_tokens") {
viper.Set("harness.compaction.protect_tokens", 40000)
}
if !viper.IsSet("harness.git.auto_branch") {
viper.Set("harness.git.auto_branch", true)
}
if !viper.IsSet("harness.git.branch_prefix") {
viper.Set("harness.git.branch_prefix", "agent/")
}
if !viper.IsSet("harness.git.auto_commit") {
viper.Set("harness.git.auto_commit", false)
}
if !viper.IsSet("harness.git.require_approval") {
viper.Set("harness.git.require_approval", true)
}
if !viper.IsSet("harness.git.workflow_branch_strategy") {
viper.Set("harness.git.workflow_branch_strategy", "shared")
}
if !viper.IsSet("harness.nats.enabled") {
viper.Set("harness.nats.enabled", true)
}
if !viper.IsSet("harness.nats.kv_bucket") {
viper.Set("harness.nats.kv_bucket", "harness-state")
}
if !viper.IsSet("harness.nats.object_bucket") {
viper.Set("harness.nats.object_bucket", "harness-files")
}
if !viper.IsSet("harness.nats.max_file_size") {
viper.Set("harness.nats.max_file_size", "100MB")
}
if !viper.IsSet("harness.nats.ttl") {
viper.Set("harness.nats.ttl", "24h")
}
if !viper.IsSet("harness.permissions.external_directory") {
viper.Set("harness.permissions.external_directory", "deny")
}

// Set sandbox defaults (Docker-based code execution)
if !viper.IsSet("sandbox.enabled") {
viper.Set("sandbox.enabled", false)
}
if !viper.IsSet("sandbox.code_mode_enabled") {
viper.Set("sandbox.code_mode_enabled", false)
}
if !viper.IsSet("sandbox.docker_image") {
viper.Set("sandbox.docker_image", "ubuntu:22.04")
}
if !viper.IsSet("sandbox.idle_timeout_minutes") {
viper.Set("sandbox.idle_timeout_minutes", 30)
}

// Generate encryption key if not already present
if viper.GetString("encryption_key") == "" {
encryptionKey, err := auth.GenerateAPIKey()
Expand All @@ -997,6 +1061,14 @@ func runInit(cmd *cobra.Command, args []string) error {
return fmt.Errorf("failed to write config file: %w", err)
}

// Create harness workspace directory
harnessWorkspaceDir := filepath.Join(configDir, "workspace")
if err := os.MkdirAll(harnessWorkspaceDir, 0755); err != nil {
fmt.Printf(" Warning: Failed to create workspace directory: %v\n", err)
} else {
fmt.Printf(" Created harness workspace directory\n")
}

// Generate .gitignore for workspace if using custom config path
if configPath != "" {
gitignorePath := filepath.Join(configDir, ".gitignore")
Expand All @@ -1006,6 +1078,9 @@ station.db
station.db-shm
station.db-wal

# Harness workspace (agent execution artifacts)
workspace/

# Runtime variable resolution
vars/

Expand Down
26 changes: 22 additions & 4 deletions cmd/main/handlers/agent/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ func (h *AgentHandler) RunAgentCreate(cmd *cobra.Command, args []string) error {
sandboxConfig, _ := cmd.Flags().GetString("sandbox")
codingConfig, _ := cmd.Flags().GetString("coding")
notifyEnabled, _ := cmd.Flags().GetBool("notify")
harnessConfig, _ := cmd.Flags().GetString("harness-config")

if sandboxConfig != "" {
var sandboxMap map[string]interface{}
Expand All @@ -273,6 +274,12 @@ func (h *AgentHandler) RunAgentCreate(cmd *cobra.Command, args []string) error {
return fmt.Errorf("invalid --coding JSON: %v", err)
}
}
if harnessConfig != "" {
var harnessMap map[string]interface{}
if err := json.Unmarshal([]byte(harnessConfig), &harnessMap); err != nil {
return fmt.Errorf("invalid --harness-config JSON: %v", err)
}
}

cfg, err := config.Load()
if err != nil {
Expand Down Expand Up @@ -341,8 +348,8 @@ func (h *AgentHandler) RunAgentCreate(cmd *cobra.Command, args []string) error {
return fmt.Errorf("failed to create agent: %v", err)
}

if sandboxConfig != "" || codingConfig != "" || notifyEnabled {
if err := exportService.ExportAgentWithConfigs(createdAgent.ID, app, appType, sandboxConfig, codingConfig, notifyEnabled); err != nil {
if sandboxConfig != "" || codingConfig != "" || harnessConfig != "" || notifyEnabled {
if err := exportService.ExportAgentWithAllConfigs(createdAgent.ID, app, appType, sandboxConfig, codingConfig, harnessConfig, notifyEnabled); err != nil {
fmt.Printf("⚠️ Agent created but export failed: %v\n", err)
}
}
Expand Down Expand Up @@ -397,6 +404,7 @@ func (h *AgentHandler) RunAgentUpdate(cmd *cobra.Command, args []string) error {
sandboxConfig, _ := cmd.Flags().GetString("sandbox")
codingConfig, _ := cmd.Flags().GetString("coding")
notifyEnabled, _ := cmd.Flags().GetBool("notify")
harnessConfig, _ := cmd.Flags().GetString("harness-config")

if sandboxConfig != "" {
var sandboxMap map[string]interface{}
Expand All @@ -410,6 +418,12 @@ func (h *AgentHandler) RunAgentUpdate(cmd *cobra.Command, args []string) error {
return fmt.Errorf("invalid --coding JSON: %v", err)
}
}
if harnessConfig != "" {
var harnessMap map[string]interface{}
if err := json.Unmarshal([]byte(harnessConfig), &harnessMap); err != nil {
return fmt.Errorf("invalid --harness-config JSON: %v", err)
}
}

cfg, err := config.Load()
if err != nil {
Expand Down Expand Up @@ -537,13 +551,17 @@ func (h *AgentHandler) RunAgentUpdate(cmd *cobra.Command, args []string) error {
updates = append(updates, "notify")
needsExport = true
}
if cmd.Flags().Changed("harness-config") {
updates = append(updates, "harness-config")
needsExport = true
}

if len(updates) == 0 {
fmt.Printf("⚠️ No updates specified. Available flags:\n")
fmt.Printf(" --prompt, --description, --max-steps, --tools, --output-schema\n")
fmt.Printf(" --output-schema-preset, --app, --app-type\n")
fmt.Printf(" --memory-topic, --memory-max-tokens\n")
fmt.Printf(" --sandbox, --coding, --notify\n")
fmt.Printf(" --sandbox, --coding, --notify, --harness-config\n")
return nil
}

Expand All @@ -561,7 +579,7 @@ func (h *AgentHandler) RunAgentUpdate(cmd *cobra.Command, args []string) error {
if needsExport {
finalApp := agentConfig.App
finalAppType := agentConfig.AppType
if err := exportService.ExportAgentWithConfigs(updatedAgent.ID, finalApp, finalAppType, sandboxConfig, codingConfig, notifyEnabled); err != nil {
if err := exportService.ExportAgentWithAllConfigs(updatedAgent.ID, finalApp, finalAppType, sandboxConfig, codingConfig, harnessConfig, notifyEnabled); err != nil {
fmt.Printf("⚠️ Agent updated but export failed: %v\n", err)
}
}
Expand Down
Loading
Loading