workflow

package
v0.17.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 15, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Index

Constants

View Source
const (
	WorkflowWhenAlways    = "always"
	WorkflowWhenOnSuccess = "on_success"
	WorkflowWhenOnError   = "on_error"

	DefaultWorkflowOrchestrationStateFile  = ".sprout/workflow_state.json"
	DefaultWorkflowOrchestrationEventsFile = ".sprout/workflow_events.jsonl"
	DefaultWorkflowConversationSessionID   = "workflow"
)

Variables

This section is empty.

Functions

func ApplyWorkflowCommandOverrides

func ApplyWorkflowCommandOverrides(cfg *AgentWorkflowConfig, overrides *CLIOverrides)

func ApplyWorkflowInitialOverrides

func ApplyWorkflowInitialOverrides(chatAgent *agent.Agent, cfg *AgentWorkflowConfig, overrides *CLIOverrides) error

func ApplyWorkflowRuntimeOverrides

func ApplyWorkflowRuntimeOverrides(chatAgent *agent.Agent, runtime AgentWorkflowRuntime, overrides *CLIOverrides) error

func ApplyWorkflowSubagentOverrides

func ApplyWorkflowSubagentOverrides(subagentTypes map[string]configuration.SubagentType, overrides WorkflowSubagentOverrides)

ApplyWorkflowSubagentOverrides patches the SubagentTypes map entries matching the given overrides. No error is returned for unknown personas — they are skipped. Log lines are emitted for every skip and every successful apply so that silent divergence between the workflow JSON and the actual SubagentTypes is visible.

func AttachWorkflowBudget

func AttachWorkflowBudget(chatAgent *agent.Agent, cfg *AgentWorkflowConfig) (stop func())

AttachWorkflowBudget wires the workflow's USD budget and progress heartbeat onto the agent. Returns a stop function the caller MUST invoke before the agent shuts down — it unregisters callbacks and stops the heartbeat goroutine. If no budget is configured the returned stop is a no-op and no goroutines are started.

Heartbeat semantics:

  • Default cadence: 600s when a budget is configured, off otherwise.
  • cfg.Progress.HeartbeatSeconds > 0 overrides the cadence.
  • The heartbeat prints to stdout in a single line so it composes with existing console output without clobbering it.

func EmitWorkflowOrchestrationEvent

func EmitWorkflowOrchestrationEvent(cfg *AgentWorkflowConfig, eventType string, payload map[string]interface{}) error

func FindSubagentTypeMapKey

func FindSubagentTypeMapKey(subagentTypes map[string]configuration.SubagentType, normalizedID string) (string, bool)

FindSubagentTypeMapKey finds the original map key in SubagentTypes matching the given normalized persona ID. It mirrors the lookup logic in config.go GetSubagentType.

func IsValidWorkflowWhen

func IsValidWorkflowWhen(v string) bool

func LoadLoopCheckpoint

func LoadLoopCheckpoint(workDir string) (int, error)

LoadLoopCheckpoint reads the fallback checkpoint file and returns the line number. Returns (0, nil) if the file doesn't exist.

func LoopCheckpointFilePath

func LoopCheckpointFilePath(workDir string) string

LoopCheckpointFilePath returns the path to the lightweight fallback checkpoint file that stores just the TODO line number.

func NormalizeReasoningEffort

func NormalizeReasoningEffort(v string) string

func NormalizeWorkflowPaths

func NormalizeWorkflowPaths(paths []string) []string

func NormalizeWorkflowPersonaID

func NormalizeWorkflowPersonaID(raw string) string

NormalizeWorkflowPersonaID normalizes a persona ID the same way config.go does.

func NormalizeWorkflowWhen

func NormalizeWorkflowWhen(v string) string

func ParseBudgetWarnList

func ParseBudgetWarnList(s string) ([]float64, error)

ParseBudgetWarnList parses a comma-separated list of fractional thresholds (e.g. "0.5,0.8") into a sorted []float64. Each value must be in (0, 1].

func PersistLoopCheckpoint

func PersistLoopCheckpoint(workDir string, lineNum int) error

PersistLoopCheckpoint writes just the line number to the fallback checkpoint file using an atomic write (temp file + rename).

func PersistWorkflowCheckpoint

func PersistWorkflowCheckpoint(cfg *AgentWorkflowConfig, state *WorkflowExecutionState, chatAgent *agent.Agent) error

func PersistWorkflowConversationState

func PersistWorkflowConversationState(chatAgent *agent.Agent, cfg *AgentWorkflowConfig) error

func PersistWorkflowExecutionState

func PersistWorkflowExecutionState(cfg *AgentWorkflowConfig, state *WorkflowExecutionState) error

func PrepareWorkflowRuntimeRestorer

func PrepareWorkflowRuntimeRestorer(chatAgent *agent.Agent, cfg *AgentWorkflowConfig, overrides *CLIOverrides) (func() error, error)

func RemoveLoopCheckpoint

func RemoveLoopCheckpoint(workDir string)

RemoveLoopCheckpoint deletes the fallback checkpoint file, ignoring not-found errors.

func ResolveStepPrompt

func ResolveStepPrompt(step AgentWorkflowStep) (string, error)

func ResolveWorkflowInitialPrompt

func ResolveWorkflowInitialPrompt(cliQuery string, cfg *AgentWorkflowConfig) (string, error)

func ResolveWorkflowTextOrFile

func ResolveWorkflowTextOrFile(text, filePath, label string) (string, error)

func RestoreWorkflowConversationState

func RestoreWorkflowConversationState(chatAgent *agent.Agent, cfg *AgentWorkflowConfig, state *WorkflowExecutionState) error

func RunAgentWorkflow

func RunAgentWorkflow(ctx context.Context, chatAgent *agent.Agent, eventBus *events.EventBus, cfg *AgentWorkflowConfig, state *WorkflowExecutionState, queryExecutor QueryExecutor, overrides *CLIOverrides) (bool, error)

func RunAgentWorkflowLoop

func RunAgentWorkflowLoop(ctx context.Context, chatAgent *agent.Agent, eventBus *events.EventBus, cfg *AgentWorkflowConfig, state *WorkflowExecutionState, queryExecutor QueryExecutor, overrides *CLIOverrides) (bool, error)

RunAgentWorkflowLoop iterates over unchecked TODO items, processing each with a fresh agent context. Between items, the conversation is cleared.

func ShouldRestoreWorkflowConversationState

func ShouldRestoreWorkflowConversationState(state *WorkflowExecutionState) bool

func ShouldRunWorkflowStep

func ShouldRunWorkflowStep(when string, hasError bool) bool

func ShouldYieldBeforeWorkflowStep

func ShouldYieldBeforeWorkflowStep(cfg *AgentWorkflowConfig, state *WorkflowExecutionState, nextStep AgentWorkflowStep, chatAgent *agent.Agent) bool

func StepFileTriggersSatisfied

func StepFileTriggersSatisfied(step AgentWorkflowStep) (bool, error)

func WorkflowEffectiveStepProvider

func WorkflowEffectiveStepProvider(chatAgent *agent.Agent, step AgentWorkflowStep) string

func WriteFileAtomic

func WriteFileAtomic(path string, data []byte, perm os.FileMode) error

WriteFileAtomic writes data to path atomically by writing to a temp file in the same directory and then renaming. This prevents partial/corrupt state files if the process crashes mid-write.

Types

type AgentWorkflowBudgetConfig

type AgentWorkflowBudgetConfig struct {
	// USD is the hard cap on cumulative cost across the workflow.
	// <= 0 means no cap.
	USD float64 `json:"usd,omitempty"`
	// WarnAt is a list of fractional thresholds (0.0–1.0). When the
	// cumulative spend first crosses each threshold, a single warning
	// is emitted to stdout and (when wired) the event bus.
	// Empty defaults to [0.50, 0.80].
	WarnAt []float64 `json:"warn_at,omitempty"`
	// OnExceed controls what happens when USD is reached.
	// "truncate" (default) sets the truncation flag so the run finishes
	// the current LLM response and stops gracefully. "stop" is reserved
	// for future hard-kill behavior; today it's treated like truncate.
	OnExceed string `json:"on_exceed,omitempty"`
}

AgentWorkflowBudgetConfig caps the total USD spend of a workflow run (primary agent + every subagent it spawns share the same budget).

USD-denominated rather than tokens because mixed-provider workflows route different personas to different price tiers — a token cap that covers an Opus orchestrator would let a DeepSeek coder consume 50× the work for the same budget, defeating the cap.

type AgentWorkflowConfig

type AgentWorkflowConfig struct {
	Description             string                            `json:"description,omitempty"`
	Initial                 *AgentWorkflowInitial             `json:"initial,omitempty"`
	Steps                   []AgentWorkflowStep               `json:"steps"`
	ContinueOnError         bool                              `json:"continue_on_error,omitempty"`
	PersistRuntimeOverrides *bool                             `json:"persist_runtime_overrides,omitempty"`
	Orchestration           *AgentWorkflowOrchestrationConfig `json:"orchestration,omitempty"`
	NoWebUI                 *bool                             `json:"no_web_ui,omitempty"`
	WebPort                 *int                              `json:"web_port,omitempty"`
	Daemon                  *bool                             `json:"daemon,omitempty"`
	Budget                  *AgentWorkflowBudgetConfig        `json:"budget,omitempty"`
	Progress                *AgentWorkflowProgressConfig      `json:"progress,omitempty"`

	// SubagentTimeoutSeconds overrides the per-run_subagent tool timeout
	// (default 1800 = 30 minutes). Set higher for very large refactors.
	SubagentTimeoutSeconds *int `json:"subagent_timeout_seconds,omitempty"`

	// RequiresApproval controls whether the run_automate agent tool must
	// surface an intent-confirmation prompt to the user before launching
	// this workflow. Pointer so we can distinguish "unset" (default: true)
	// from explicit false. Set to false for workflows that exist
	// specifically so an agent can invoke them mid-task — e.g. a
	// validation workflow referenced from AGENTS.md that the model must
	// run before considering work done. Anyone with workflow-file access
	// can flip this, so the security implication should be obvious to a
	// reader of the JSON.
	//
	// Only affects the agent tool path. The CLI (`sprout automate run`)
	// always prompts unless --yes is passed, because a human at the
	// keyboard might still fat-finger the wrong workflow.
	RequiresApproval *bool `json:"requires_approval,omitempty"`

	// Loop configures the workflow to iterate over a TODO file, processing
	// each unchecked item independently with a fresh agent context.
	// When Loop is set, Steps are ignored — the loop IS the execution plan.
	Loop *AgentWorkflowLoopConfig `json:"loop,omitempty"`
}

AgentWorkflowConfig defines non-interactive workflow orchestration.

func LoadAgentWorkflowConfig

func LoadAgentWorkflowConfig(path string) (*AgentWorkflowConfig, error)

func (*AgentWorkflowConfig) IsApprovalRequired

func (c *AgentWorkflowConfig) IsApprovalRequired() bool

IsApprovalRequired reports whether the run_automate tool path should surface an intent-confirmation prompt before launching this workflow. Defaults to true when unset.

func (*AgentWorkflowConfig) OrchestrationEnabled

func (c *AgentWorkflowConfig) OrchestrationEnabled() bool

func (*AgentWorkflowConfig) OrchestrationResumeEnabled

func (c *AgentWorkflowConfig) OrchestrationResumeEnabled() bool

func (*AgentWorkflowConfig) OrchestrationYieldOnProviderHandoff

func (c *AgentWorkflowConfig) OrchestrationYieldOnProviderHandoff() bool

func (*AgentWorkflowConfig) ShouldPersistRuntimeOverrides

func (c *AgentWorkflowConfig) ShouldPersistRuntimeOverrides() bool

func (*AgentWorkflowConfig) Validate

func (c *AgentWorkflowConfig) Validate() error

type AgentWorkflowInitial

type AgentWorkflowInitial struct {
	Prompt     string `json:"prompt,omitempty"`
	PromptFile string `json:"prompt_file,omitempty"`
	AgentWorkflowRuntime
}

AgentWorkflowInitial is the first run definition (can replace CLI prompt).

type AgentWorkflowLoopConfig

type AgentWorkflowLoopConfig struct {
	// TodoFile is the markdown file to scan for [ ] items. Default: TODO.md.
	TodoFile string `json:"todo_file,omitempty"`
	// GatePromptFile is the system prompt for the gate LLM call that
	// parses each TODO section into a structured delegation prompt.
	// Required. The gate call uses the agent's existing client.
	GatePromptFile string `json:"gate_prompt_file,omitempty"`
	// MaxRetries is the number of retry attempts on build failure
	// before skipping an item. Default: 2.
	MaxRetries int `json:"max_retries,omitempty"`
	// MaxIterations caps the agent iterations per item. Default: 50.
	MaxIterations int `json:"max_iterations,omitempty"`
	// BuildCommand is run after each item to verify. Default: "go build ./...".
	BuildCommand string `json:"build_command,omitempty"`
}

AgentWorkflowLoopConfig configures the workflow to iterate over a TODO file, processing each unchecked item independently with a fresh agent context. When Loop is set, Steps are ignored — the loop IS the execution plan.

type AgentWorkflowOrchestrationConfig

type AgentWorkflowOrchestrationConfig struct {
	Enabled                bool   `json:"enabled,omitempty"`
	Resume                 *bool  `json:"resume,omitempty"`
	YieldOnProviderHandoff *bool  `json:"yield_on_provider_handoff,omitempty"`
	StateFile              string `json:"state_file,omitempty"`
	EventsFile             string `json:"events_file,omitempty"`
	ConversationSessionID  string `json:"conversation_session_id,omitempty"`
}

AgentWorkflowOrchestrationConfig enables external orchestration integration.

type AgentWorkflowProgressConfig

type AgentWorkflowProgressConfig struct {
	// HeartbeatSeconds is the interval at which the workflow prints a
	// progress line ([budget] $X of $Y · iter N · elapsed Tm).
	// <= 0 disables the heartbeat. Default 600 (10 min) when Budget is set.
	HeartbeatSeconds int `json:"heartbeat_seconds,omitempty"`
}

AgentWorkflowProgressConfig controls runtime visibility of the workflow.

type AgentWorkflowRuntime

type AgentWorkflowRuntime struct {
	SkipPrompt        *bool                     `json:"skip_prompt,omitempty"`
	Provider          string                    `json:"provider,omitempty"`
	Model             string                    `json:"model,omitempty"`
	Persona           string                    `json:"persona,omitempty"`
	DryRun            *bool                     `json:"dry_run,omitempty"`
	MaxIterations     *int                      `json:"max_iterations,omitempty"`
	NoStream          *bool                     `json:"no_stream,omitempty"`
	SystemPrompt      string                    `json:"system_prompt,omitempty"`
	SystemPromptFile  string                    `json:"system_prompt_file,omitempty"`
	Unsafe            *bool                     `json:"unsafe,omitempty"`
	NoSubagents       *bool                     `json:"no_subagents,omitempty"`
	ResourceDirectory string                    `json:"resource_directory,omitempty"`
	ReasoningEffort   string                    `json:"reasoning_effort,omitempty"`
	SubagentOverrides WorkflowSubagentOverrides `json:"subagent_overrides,omitempty"`
	// RiskProfile selects a named shell-command risk cascade preset
	// for this step / initial run (SP-058). One of: readonly,
	// cautious, default, permissive, unrestricted. Per-step values
	// override the workflow-level initial setting and the global
	// config. Unknown values fall through to the agent's default
	// resolution chain (override > config > "default").
	RiskProfile string `json:"risk_profile,omitempty"`
}

AgentWorkflowRuntime contains runtime options aligned with agent CLI flags.

func (*AgentWorkflowRuntime) Validate

func (r *AgentWorkflowRuntime) Validate(prefix string) error

type AgentWorkflowStep

type AgentWorkflowStep struct {
	Name          string   `json:"name,omitempty"`
	Prompt        string   `json:"prompt,omitempty"`
	PromptFile    string   `json:"prompt_file,omitempty"`
	Command       string   `json:"command,omitempty"`
	CommandFile   string   `json:"command_file,omitempty"`
	When          string   `json:"when,omitempty"`
	FileExists    []string `json:"file_exists,omitempty"`
	FileNotExists []string `json:"file_not_exists,omitempty"`
	AgentWorkflowRuntime
}

AgentWorkflowStep is a single step executed after the initial query.

A step is either an agent step (Prompt or PromptFile) or a shell step (Command or CommandFile). The two kinds are mutually exclusive — validation fails if both are set or neither is set.

Shell steps run the command via the user's $SHELL (or /bin/sh) with the workflow's working directory and inherit stdout/stderr. They do NOT trigger model inference; they are useful for cheap, deterministic steps like `make build`, `git status`, or invoking a custom script that prepares state for the next agent step.

func (AgentWorkflowStep) IsShellStep

func (s AgentWorkflowStep) IsShellStep() bool

IsShellStep reports whether the step is configured to run a shell command instead of triggering model inference.

type CLIOverrides

type CLIOverrides struct {
	SetWebUI    func(disabled bool)
	SetWebPort  func(port int)
	SetDaemon   func(enabled bool)
	SetNoStream func(enabled bool)
	GetNoStream func() bool

	// Budget/heartbeat CLI overrides. Zero values mean "inherit JSON".
	BudgetUSD        float64
	BudgetWarn       string
	HeartbeatSeconds int
}

CLIOverrides provides the callback functions that applyWorkflowCommandOverrides needs to mutate CLI-level global flags (web UI, port, daemon, streaming, budget, heartbeat). The cmd/ package constructs this with closures over its real flag variables.

type QueryExecutor

type QueryExecutor func(ctx context.Context, chatAgent *agent.Agent, eventBus *events.EventBus, query string) error

QueryExecutor is the signature of cmd.ProcessQuery. The runner and loop accept this as a dependency so the workflow package never imports cmd/.

type WorkflowExecutionState

type WorkflowExecutionState struct {
	Version            int    `json:"version"`
	InitialCompleted   bool   `json:"initial_completed"`
	NextStepIndex      int    `json:"next_step_index"`
	CurrentTodoLineNum int    `json:"current_todo_line_num,omitempty"`
	HasError           bool   `json:"has_error"`
	FirstError         string `json:"first_error,omitempty"`
	LastProvider       string `json:"last_provider,omitempty"`
	Complete           bool   `json:"complete"`
	UpdatedAt          string `json:"updated_at,omitempty"`
}

WorkflowExecutionState tracks workflow execution progress for checkpoint/resume support.

func LoadWorkflowExecutionState

func LoadWorkflowExecutionState(cfg *AgentWorkflowConfig) (*WorkflowExecutionState, error)

func NewWorkflowExecutionState

func NewWorkflowExecutionState() *WorkflowExecutionState

type WorkflowSubagentOverride

type WorkflowSubagentOverride struct {
	Provider string `json:"provider,omitempty"`
	Model    string `json:"model,omitempty"`
}

WorkflowSubagentOverride defines per-persona subagent provider/model overrides.

type WorkflowSubagentOverrides

type WorkflowSubagentOverrides map[string]WorkflowSubagentOverride

WorkflowSubagentOverrides maps persona IDs to their subagent routing overrides. Keys are normalized persona IDs (lowercase, hyphens→underscores). Values override provider/model for subagents with that persona.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL