configuration

package
v0.16.23 Latest Latest
Warning

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

Go to latest
Published: Jul 6, 2026 License: MIT Imports: 30 Imported by: 0

Documentation

Overview

Package configuration provides high-level configuration and credential resolution for providers, including support for custom providers and stored credentials.

Index

Constants

View Source
const (
	ConfigVersion   = "2.0"
	ConfigDirName   = ".sprout"
	ConfigFileName  = "config.json"
	APIKeysFileName = "api_keys.json"

	OutputVerbosityCompact = "compact"
	OutputVerbosityDefault = "default"
	OutputVerbosityVerbose = "verbose"
)
View Source
const ProvidersDirName = "providers"

Variables

This section is empty.

Functions

func AllowedSkillsPath

func AllowedSkillsPath(projectRoot string) string

AllowedSkillsPath returns the full path to the allowed_skills file for a given project root.

func BootstrapIsolatedConfig

func BootstrapIsolatedConfig(configDir string) error

BootstrapIsolatedConfig initializes an isolated config directory by cloning the user's main config on first use.

Behavior: - Creates configDir if missing. - If configDir/config.json already exists, does nothing. - Otherwise clones default config from the main config location (if present). - Removes command-history fields from the cloned config.

func CanonicalizeCustomProviderName

func CanonicalizeCustomProviderName(name string) (string, error)

func DebugPrintConfig

func DebugPrintConfig(config *Config, apiKeys *APIKeys)

DebugPrintConfig prints current configuration for debugging

func DeleteCustomProvider

func DeleteCustomProvider(name string) error

func EnsureProviderAPIKey

func EnsureProviderAPIKey(provider string, apiKeys *APIKeys) error

EnsureProviderAPIKey ensures the provider has an API key, prompting if needed

func GetAPIKeysPath

func GetAPIKeysPath() (string, error)

GetAPIKeysPath returns the full path to the API keys file

func GetAvailableProviders

func GetAvailableProviders() []string

GetAvailableProviders returns all supported providers

func GetConfigDir

func GetConfigDir() (string, error)

GetConfigDir returns the configuration directory path

func GetConfigPath

func GetConfigPath() (string, error)

GetConfigPath returns the full path to the config file

func GetCustomProviderPath

func GetCustomProviderPath(name string) (string, error)

func GetEnv

func GetEnv(sproutKey, legacyKey string) string

GetEnv checks for an environment variable using the Sprout (SPROUT_*) name first, falling back to the legacy Ledit (LEDIT_*) name.

Convention: sproutKey should be the SPROUT_* equivalent, legacyKey the LEDIT_* original.

func GetEnvSimple

func GetEnvSimple(suffix string) string

GetEnvSimple checks SPROUT_* then LEDIT_* for a variable name suffix. E.g., GetEnvSimple("CONFIG") checks SPROUT_CONFIG then LEDIT_CONFIG.

func GetProviderDisplayName added in v0.16.12

func GetProviderDisplayName(provider string) string

GetProviderDisplayName returns a user-friendly name for the provider. Lookup chain:

  1. Static display-name map (generated from embedded configs — fastest and the common case for built-ins).
  2. Runtime factory (covers remote-only providers published to GitHub Pages whose display_name isn't baked into the static map).
  3. CustomProviders (user-defined local providers in config.json).
  4. Raw provider ID as a last resort.

func GetProviderEnvVarName

func GetProviderEnvVarName(provider string) string

func GetProvidersDir

func GetProvidersDir() (string, error)

func GetWorkspaceConfigPath

func GetWorkspaceConfigPath(workspaceRoot string) string

GetWorkspaceConfigPath returns the path to workspace-level config

func HasProviderAuth

func HasProviderAuth(provider string) bool

HasProviderAuth checks whether a provider has a configured credential. For providers that don't require an API key (local providers), always returns true.

func Initialize

func Initialize() (*Config, *APIKeys, error)

Initialize loads or creates configuration with first-run setup

func IsConfigConflict

func IsConfigConflict(err error) bool

IsConfigConflict is a convenience predicate so callers don't have to type-assert by hand. Returns false for nil errors.

func IsCriticalOperation

func IsCriticalOperation(command string) bool

IsCriticalOperation reports whether a command matches a pattern that is NEVER allowed regardless of profile, persona, or interactive approval. Reserved for operations that can permanently destroy the system or leave it in an unrecoverable state.

This is the single source of truth for "critical" across every security gate: the static classifier (agent_tools.isCriticalSystemOperation delegates here) and the persona risk cascade (EvaluateOperationRisk) both consult it, so the two gates can't disagree on what's critical.

Covers:

  • rm -rf targeting root or the current dir (`/`, `/*`, `~`, `$HOME`, `.`, `*`)
  • Classic fork-bomb pattern `:(){:|:&};:`
  • Filesystem creation / raw disk overwrite (mkfs, dd to a block device)
  • Mass process kills (killall -9 / -KILL)
  • chmod 000 on a system root path
  • Overwriting critical auth/system files (/etc/shadow, /etc/passwd, …)

Tokenized matching matches invokesCommand semantics so a benign substring inside a path or argument can't trigger a false positive.

func IsValidRiskProfile

func IsValidRiskProfile(s string) bool

IsValidRiskProfile reports whether s names a known profile. User-defined profiles (added via Config.RiskProfiles) are NOT considered "valid" by this predicate — it only covers the baked-in names. Callers that need to accept user-defined profiles should check Config.RiskProfiles directly via ResolveRiskProfileRules.

func IsWorkspaceConfigPresent

func IsWorkspaceConfigPresent(workspaceRoot string) bool

IsWorkspaceConfigPresent checks if a workspace config file exists

func KnownProviderNames added in v0.16.12

func KnownProviderNames() []string

KnownProviderNames returns the union of the compile-time provider list and whatever the runtime factory has registered (which includes embedded + filesystem + remote configs once pkg/factory.init has wired SetProviderNamesLookup). Static entries keep their generated order; runtime-only additions are appended in sorted order so the result is deterministic across calls.

func LoadCustomProviders

func LoadCustomProviders() (map[string]CustomProviderConfig, error)

LoadCustomProviders loads all custom provider configs from the global providers directory (~/.config/sprout/providers/).

func LoadCustomProvidersFromDir

func LoadCustomProvidersFromDir(providersDir string) (map[string]CustomProviderConfig, error)

LoadCustomProvidersFromDir loads all custom provider JSON files from the given directory. Use this when SPROUT_CONFIG is temporarily overridden (e.g. inside NewManagerWithLayers) and custom providers must still be read from the true global location.

func LookupEnv

func LookupEnv(suffix string) (string, bool)

LookupEnv checks SPROUT_* first, then LEDIT_*. Returns the value and whether it was found.

func MapProviderStringToClientType

func MapProviderStringToClientType(cfg *Config, raw string) (api.ClientType, error)

MapProviderStringToClientType converts a provider string to ClientType, including built-in providers, custom providers from config, and factory-backed dynamic providers.

func MigrateConfig

func MigrateConfig(raw map[string]interface{}, targetVersion string) (map[string]interface{}, error)

MigrateConfig applies all necessary migration steps to bring raw config up to the target version. It takes a raw JSON map, determines the current version, and runs each step in order. Returns the migrated raw config or an error if a step fails or the chain cannot reach the target.

func MigrateConfigFileAPIKeys

func MigrateConfigFileAPIKeys(configPath string) error

MigrateConfigFileAPIKeys migrates any api_key values found in config.json's custom_providers entries into the unified credential store, then strips the keys from the config file. Called on every Load() but exits immediately if the migration marker exists.

This is necessary because the CustomProviderConfig struct no longer has an APIKey field, so json.Unmarshal would silently drop these values.

func MigrateEmbeddedAPIKeys

func MigrateEmbeddedAPIKeys(providers map[string]CustomProviderConfig) error

MigrateEmbeddedAPIKeys moves any api_key values found in custom provider JSON files into the unified credential store, then strips the key from the file. Called on every Load() but exits immediately if the migration marker exists.

func MigrateLegacyCustomProviders

func MigrateLegacyCustomProviders(cfg *Config) (map[string]CustomProviderConfig, error)

func PromptForAPIKey

func PromptForAPIKey(provider string) (string, error)

PromptForAPIKey prompts the user for an API key with helpful guidance

func ReadAllowedSkills

func ReadAllowedSkills(projectRoot string) map[string]bool

ReadAllowedSkills reads the .sprout/allowed_skills file from the given project root (cwd). Returns a set of allowed skill IDs. If the file does not exist, returns nil (meaning all skills are allowed).

func RegisterMigration deprecated

func RegisterMigration(from, to string, fn MigrationFunc)

RegisterMigration adds a migration step to the global registry. It panics if a migration from the same source version is already registered (at-most-one-step-per-source prevents ambiguous chains).

Deprecated: Use registerMigration (returns an error) for new code. This wrapper is retained for backward compatibility with external callers and tests that expect the panic behavior.

func RequiresAPIKey

func RequiresAPIKey(provider string) bool

RequiresAPIKey checks if a provider requires an API key.

func ResolveProviderModel

func ResolveProviderModel(cfg *Config, explicitProvider, explicitModel string) (api.ClientType, string, error)

ResolveProviderModel resolves provider and model using one canonical precedence path: 1) Explicit provider flag/arg 2) Explicit model in provider:model format (only when prefix is a valid provider) 3) SPROUT_PROVIDER env (with LEDIT_PROVIDER backward-compat) 4) SPROUT_MODEL env (provider:model format only when prefix is a valid provider, LEDIT_MODEL backward-compat) 5) Config last_used_provider 6) Auto-detected provider via DetermineProvider

Model precedence: 1) Explicit model (trimmed to model segment when provider:model format is recognized) 2) SPROUT_MODEL env (same parsing rule, LEDIT_MODEL backward-compat) 3) Config provider model default

func SaveAPIKeys

func SaveAPIKeys(keys *APIKeys) error

SaveAPIKeys saves API keys to the active backend. When keyring is active, each key is stored via SetToActiveBackend, keys that are no longer in the map are deleted from the keyring, and keys that are now in the keyring are cleaned from the encrypted file store. When file is active, it uses the existing file-based Save behavior.

Uses GetStorageBackend() (not GetStorageMode()) for consistent resolution.

func SaveAPIKeysToDir

func SaveAPIKeysToDir(keys *APIKeys, configDir string) error

SaveAPIKeysToDir saves API keys to a specific config directory. This is like SaveAPIKeys() but routes file-backend saves through credentials.SaveToDir() instead of credentials.Save().

func SaveCustomProvider

func SaveCustomProvider(cfg CustomProviderConfig) error

func SelectProvider

func SelectProvider(currentProvider string, apiKeys *APIKeys) (string, error)

SelectProvider allows user to select a provider interactively

func SetEnv

func SetEnv(suffix, value string) error

SetEnv sets both the SPROUT_* and LEDIT_* versions of an env var for backward compatibility during the transition period.

func SetProviderConfigLookup added in v0.16.8

func SetProviderConfigLookup(fn ProviderConfigLookupFunc)

SetProviderConfigLookup registers a runtime config lookup. pkg/factory wires this to GlobalFactory().GetProviderConfig in its init() so that GetProviderAuthMetadata sees providers added by refreshFromRemote — not just embedded ones. The pattern mirrors credentials.SetProviderInfoFunc and exists to avoid a configuration → factory import cycle.

func SetProviderDisplayNameLookup added in v0.16.8

func SetProviderDisplayNameLookup(fn ProviderDisplayNameLookupFunc)

SetProviderDisplayNameLookup registers a runtime display-name lookup. pkg/factory wires this to GlobalFactory().GetProviderConfig in its init(); the callback returns cfg.DisplayName so providers published only to the remote registry surface their friendly label in the onboarding menu, model picker, and other UI surfaces.

func SetProviderNamesLookup added in v0.16.8

func SetProviderNamesLookup(fn ProviderNamesLookupFunc)

SetProviderNamesLookup registers a runtime provider-names lookup. pkg/factory wires this to GlobalFactory().GetAvailableProviders in its init() so onboarding / credential-enumeration loops surface providers added by refreshFromRemote.

func SetValidateAndSaveAPIKeyValidation added in v0.16.2

func SetValidateAndSaveAPIKeyValidation(skip bool)

SetValidateAndSaveAPIKeyValidation enables or disables the test-mode skip. Call with true in tests that need to store keys without network validation.

func ShowNextSteps

func ShowNextSteps(provider, configDir string)

ShowNextSteps displays helpful next steps after successful setup

func ShowWelcomeMessage

func ShowWelcomeMessage()

ShowWelcomeMessage displays a comprehensive welcome message for new users

func UnsetEnv

func UnsetEnv(suffix string)

UnsetEnv removes both SPROUT_* and LEDIT_* versions of an env var.

func ValidateAndSaveAPIKey

func ValidateAndSaveAPIKey(provider, key string) (int, error)

ValidateAndSaveAPIKey validates a new API key before storing it. If validation fails, the old key is preserved and an error is returned. Returns the number of models available if validation succeeds.

func WriteAllowedSkills

func WriteAllowedSkills(projectRoot string, ids []string) error

WriteAllowedSkills writes the .sprout/allowed_skills file in the given project root. IDs are sorted for deterministic output.

Types

type APIKeys

type APIKeys map[string]string

func LoadAPIKeys

func LoadAPIKeys() (*APIKeys, error)

LoadAPIKeys loads API keys from the active backend. When keyring is active, it loads from both keyring (tracked providers) and the file store (for backward compatibility with keys stored before keyring was enabled). When file is active, it uses the existing file-based Load behavior.

Uses GetStorageBackend() (not GetStorageMode()) to ensure consistent resolution on first run — the auto-detection logic runs exactly once and persists the mode, so subsequent Load/Save calls see the same backend.

func LoadAPIKeysFromDir

func LoadAPIKeysFromDir(configDir string) (*APIKeys, error)

LoadAPIKeysFromDir loads API keys from a specific config directory. This is like LoadAPIKeys() but takes an explicit config directory instead of reading from environment variables. It's useful for test environments and other scenarios where you want to load from a specific location without mutating process state.

When keyring is active, it loads from both keyring (tracked providers) and the file store at the specified configDir (for backward compatibility with keys stored before keyring was enabled). When file is active, it uses LoadFromDir.

func (APIKeys) Get

func (a APIKeys) Get(provider string) string

Optional helpers

func (*APIKeys) GetAPIKey

func (keys *APIKeys) GetAPIKey(provider string) string

GetAPIKey returns the API key for a provider

func (*APIKeys) HasAPIKey

func (keys *APIKeys) HasAPIKey(provider string) bool

HasAPIKey checks if a provider has an API key set. Checks the in-memory map first, then falls back to the active backend (keyring or file store) for credentials not in the map.

func (*APIKeys) PopulateFromEnvironment

func (keys *APIKeys) PopulateFromEnvironment() bool

PopulateFromEnvironment populates API keys from environment variables This is called on startup only to detect whether environment credentials are available.

func (*APIKeys) PopulateFromJSONEnv

func (keys *APIKeys) PopulateFromJSONEnv() bool

PopulateFromJSONEnv populates API keys from the SPROUT_API_KEYS_JSON environment variable. The value must be a JSON object mapping provider names to API key strings, e.g. {"openrouter":"sk-...","deepinfra":"di-..."}. This is designed for containerized/SaaS environments (e.g. Sprout Foundry) where keys are injected at runtime rather than stored in config files.

func (*APIKeys) Set

func (a *APIKeys) Set(provider, key string)

func (*APIKeys) SetAPIKey

func (keys *APIKeys) SetAPIKey(provider, key string)

SetAPIKey sets the API key for a provider

type APITimeoutConfig

type APITimeoutConfig struct {
	ConnectionTimeoutSec    int `json:"connection_timeout_sec,omitempty"`     // Time to establish connection (default: 300)
	FirstChunkTimeoutSec    int `json:"first_chunk_timeout_sec,omitempty"`    // Time to receive first response (default: 600)
	ChunkTimeoutSec         int `json:"chunk_timeout_sec,omitempty"`          // Max time between streaming chunks (default: 600)
	OverallTimeoutSec       int `json:"overall_timeout_sec,omitempty"`        // Total request timeout (default: 1800)
	CommitMessageTimeoutSec int `json:"commit_message_timeout_sec,omitempty"` // Timeout for commit message generation (default: 300)
}

APITimeoutConfig represents timeout settings for API calls

type AutoApproveRules

type AutoApproveRules struct {
	LowRiskOps    []string `json:"low_risk,omitempty"`        // Operations auto-approved by EA
	MediumRiskOps []string `json:"medium_risk,omitempty"`     // Operations the EA reasons about
	HighRiskNever []string `json:"high_risk_never,omitempty"` // Pattern names always gated (rm_recursive, force_flag, ...)
	// DefaultRisk is the level returned for operations that don't
	// match any of the above. Default (empty) is "medium" — the
	// classic EA behavior. Cautious profiles set this to "high"
	// so unrecognized commands route to a prompt. Permissive /
	// unrestricted set it to "low" so common operations auto-approve.
	DefaultRisk RiskLevel `json:"default_risk,omitempty"`
}

AutoApproveRules controls the EA's sliding risk cascade for operation approvals.

func AutoApproveRulesForProfile

func AutoApproveRulesForProfile(profile RiskProfile) AutoApproveRules

AutoApproveRulesForProfile returns the rules baked into each named profile. Unknown profiles fall back to RiskProfileDefault.

func DefaultAutoApproveRules

func DefaultAutoApproveRules() AutoApproveRules

DefaultAutoApproveRules returns the default risk cascade rules for the EA persona.

func ResolveRiskProfileRules

func ResolveRiskProfileRules(cfg *Config, profile RiskProfile) AutoApproveRules

ResolveRiskProfileRules returns the AutoApproveRules that should apply for the given profile name, honoring user overrides in Config.RiskProfiles before falling back to the baked-in defaults.

Resolution order:

  1. cfg.RiskProfiles[name] — user override (replaces builtins entirely; the user is the source of truth for any profile name they list, including the five named built-ins).
  2. AutoApproveRulesForProfile(name) — baked-in defaults for the known profile names. Unknown names fall through to the Default profile here.

cfg may be nil (no config loaded); in that case the baked-in rules are always returned.

type ChangeTrackingConfig

type ChangeTrackingConfig struct {
	// Enabled controls whether the change tracking subsystem is active
	// at all. When false, no file changes are recorded, no revision
	// history is written, and the rollback/recover/view_history tools
	// are no-ops.
	//
	// Defaults to true. The git-awareness guards (IsRevertSafe) now
	// prevent the subsystem from reverting committed work, so tracking
	// stays on by default. Set to false to disable the entire subsystem.
	Enabled *bool `json:"enabled,omitempty"`

	// ShellWalkEnabled controls whether the per-shell_command snapshot
	// walk runs at all. Disable for workspaces where the walk cost is
	// unacceptable (e.g., very-large monorepos with novel bloat
	// directories) or for users who don't care about recovering
	// shell-deleted untracked files. Direct file-tool tracking is
	// unaffected. Default: true. Only meaningful when Enabled is true.
	ShellWalkEnabled *bool `json:"shell_walk_enabled,omitempty"`

	// MaxFiles caps the number of files visited in a single walk.
	// Larger workspaces hit the cap and yield partial coverage with a
	// truncation log. Default: 50000.
	MaxFiles int `json:"max_files,omitempty"`

	// MaxTotalBytes caps cumulative content bytes captured per walk.
	// Files past this cap get path-only entries (still appear in
	// list_changes / FilesModified, but report recoverable=false).
	// Default: 32 MiB (33554432).
	MaxTotalBytes int64 `json:"max_total_bytes,omitempty"`

	// MaxDurationMs is the wall-clock budget for a single walk, in
	// milliseconds. Exceeding it aborts the walk with a partial-
	// coverage log. Default: 500 ms.
	MaxDurationMs int `json:"max_duration_ms,omitempty"`

	// AutoSkipFileCountThreshold is the per-directory immediate child
	// file count that triggers adaptive auto-skip. Default: 1500.
	AutoSkipFileCountThreshold int `json:"auto_skip_file_count_threshold,omitempty"`

	// RevisionRetention controls how the persistent revision store
	// (.sprout/revisions/ + .sprout/changes/) is compacted. Quantity-
	// based tiering: most recent N revisions are kept verbatim, next M
	// drop the conversation transcript, next K collapse to a one-line
	// summary, the rest are dropped. Position is by directory mtime,
	// so accessing an old revision via view_history / recover_file
	// promotes it back toward "hot" automatically.
	RevisionRetention *RevisionRetentionConfig `json:"revision_retention,omitempty"`
}

ChangeTrackingConfig gates and tunes the ChangeTracker. When Enabled is false (the default) the entire subsystem is dormant — no file changes are recorded, no revision history is written, and the rollback/recover/view_history tools are no-ops. When Enabled is true the per-shell_command snapshot walk runs (tuned by the remaining fields) and direct file-tool tracking (write_file, edit_file, patch_structured_file) records changes.

func (*ChangeTrackingConfig) Resolve

Resolve fills in defaults for any zero-value fields and returns a fully-populated config. Safe to call on nil — yields all-defaults.

type CombinedAssessment

type CombinedAssessment struct {
	ClassifierRisk     int    // SecurityRisk value (0=safe, 1=caution, 2=dangerous)
	ClassifierBlocked  bool   // ShouldBlock from the classifier
	ClassifierPrompt   bool   // ShouldPrompt from the classifier
	ClassifierCategory string // RiskCategory string
	PolicyAction       SecurityPolicyAction
	PolicyRule         *SecurityRule // the matched rule, if any
	PathAllowed        bool
	CommandDenied      bool
	OverrideAction     string // reason if policy overrides classifier
}

CombinedAssessment holds the result of combining the security classifier with the workspace security policy.

IMPORTANT: To avoid a circular import (configuration -> agent_tools -> configuration), the ClassifierResult field is a generic map instead of a typed reference to agent_tools.SecurityResult. Callers that need the typed result should use CombinedSecurityAssessmentWithClassifier below.

func CombinedSecurityAssessment

func CombinedSecurityAssessment(
	toolName string,
	args map[string]interface{},
	policy *SecurityPolicy,
	classifierRisk int,
	classifierBlocked bool,
	classifierPrompt bool,
	classifierCategory string,
) *CombinedAssessment

CombinedSecurityAssessment evaluates a tool call against the security policy.

classifierRisk is the risk level from the security classifier (0=safe, 1=caution, 2=dangerous). classifierBlocked indicates whether the classifier says the call should be blocked. classifierPrompt indicates whether the classifier says the call should prompt the user. classifierCategory is the risk category string from the classifier (e.g. "read-only", "file-write").

This interface avoids a circular import between configuration and agent_tools.

type ComputerUseConfig added in v0.16.12

type ComputerUseConfig struct {
	// Enabled is the master switch. When false (default) the computer_user
	// persona's mouse/keyboard/screenshot tools are never registered or
	// executed. Turning it on is a deliberate, one-time user choice.
	Enabled bool `json:"enabled,omitempty"`

	// MaxActionsPerMinute caps the action rate as a runaway-loop backstop.
	// Default: 60. Set to 0 to disable the cap (not recommended).
	MaxActionsPerMinute int `json:"max_actions_per_minute,omitempty"`

	// AuditLogDir is where per-session JSONL action logs are written.
	// Default: ~/.config/sprout/computer_use_log when empty.
	AuditLogDir string `json:"audit_log_dir,omitempty"`

	// WorkspaceAllowlist lists workspace roots where computer use is
	// auto-approved for the session without the per-session opt-in prompt.
	WorkspaceAllowlist []string `json:"workspace_allowlist,omitempty"`

	// PanicKeyChord is the key chord that triggers the panic key. Defaults
	// to "ctrl+shift+escape". Set to "disabled" to turn off the panic key
	// entirely.
	PanicKeyChord string `json:"panic_key_chord,omitempty"`

	// DestructiveAppGate controls whether the destructive-app denylist gate
	// is active. When true (default), actions targeting apps on the
	// denylist prompt the user for approval before proceeding.
	DestructiveAppGate bool `json:"destructive_app_gate,omitempty"`

	// OverrideFilePath is an optional override of the per-user denylist
	// override file location. When empty, the default path
	// (~/.config/sprout/computer_use_denylist_overrides.json) is used.
	OverrideFilePath string `json:"denylist_override_file,omitempty"`
}

ComputerUseConfig gates the computer_user persona's desktop-control tools (SP-063). The feature is categorically more dangerous than file edits — a click can send an email or empty the trash — so it is off by default and every field is a safety lever.

func (*ComputerUseConfig) Resolve added in v0.16.12

func (c *ComputerUseConfig) Resolve() ComputerUseConfig

Resolve returns a copy with defaults filled in for zero-value fields.

type Config

type Config struct {
	Version string `json:"version"`

	// Provider and Model Configuration
	LastUsedProvider string            `json:"last_used_provider"`
	ProviderModels   map[string]string `json:"provider_models"`
	ProviderPriority []string          `json:"provider_priority"`

	// Language Server Override Configuration
	LanguageServers []LanguageServerOverride `json:"language_servers,omitempty"`

	// MCP Configuration
	MCP mcp.MCPConfig `json:"mcp"`

	// Preferences
	Preferences map[string]interface{} `json:"preferences,omitempty"`

	// DisableCoordinatorAutoActivate opts out of the automatic activation of the
	// coordinator persona (formerly Executive Assistant) when sprout starts in
	// the user's $HOME directory. When true, no persona is auto-activated and
	// the user must select one explicitly. Default false (auto-activate).
	DisableCoordinatorAutoActivate bool `json:"disable_coordinator_auto_activate,omitempty"`

	// AllowGitHistoryRewrite controls whether commands that can lose commit
	// history are accepted via shell_command without going through the git
	// tool's approval flow. Specifically: `git reset --hard <commit-ish>`,
	// `git rebase`, `git branch -D`, `git tag -d`.
	//
	// Working-tree-only destructive ops (`git checkout .`, `git restore`,
	// `git clean -fd`, `git reset --hard HEAD`, etc.) are always allowed
	// because the change tracker captures pre-mutation content and exposes
	// recover_file / recover_bulk for restoration. History rewrites can't
	// be recovered through the tracker — only via the reflog — so they
	// stay gated by default.
	//
	// Default: false (gated). Set true in environments where the agent
	// has tighter feedback loops (e.g. user-facing chat where every step
	// is confirmed) and the friction of going through the git tool isn't
	// worth it.
	AllowGitHistoryRewrite bool `json:"allow_git_history_rewrite,omitempty"`

	// UnifiedRiskResolver enables the unified risk resolver (SP-068 Phase 2).
	// When true (the default), gating decisions at call sites use a single
	// ResolveToolRisk assessment instead of the split Gate 1 (static
	// classifier) → Gate 2 (persona risk cascade) path. When false, the
	// legacy dual-gate code paths run (retained for compatibility). Set to
	// false explicitly to opt out of the unified resolver.
	UnifiedRiskResolver bool `json:"unified_risk_resolver,omitempty"`

	// ResourceDirectory stores captured web/vision resources relative to the current working directory.
	// This can be overridden at runtime with --resource-directory.
	ResourceDirectory string `json:"resource_directory,omitempty"`

	// ReasoningEffort sets a global default reasoning effort for chat requests.
	// Valid values: "low", "medium", "high". Empty means automatic selection.
	ReasoningEffort string `json:"reasoning_effort,omitempty"`

	// DisableThinking disables thinking/reasoning mode for thinking-capable models.
	// When true, models like Qwen3, Qwen3.5, GLM models, and Minimax models will
	// not use their thinking/reasoning mode. Note: GPT-OSS models do not support
	// disabling thinking (they use reasoning_effort instead).
	DisableThinking bool `json:"disable_thinking,omitempty"`

	// SystemPromptText overrides the main agent system prompt inline.
	// Empty means use the embedded default prompt.
	SystemPromptText string `json:"system_prompt_text,omitempty"`

	// SkipPrompt - for non-interactive mode
	SkipPrompt bool `json:"skip_prompt,omitempty"`

	// RiskProfile selects a named preset for the shell-command risk
	// cascade: readonly / cautious / default / permissive /
	// unrestricted. Empty or unrecognized values resolve to "default"
	// via AutoApproveRulesForProfile. Per-persona AutoApproveRules
	// always win over the profile. The CLI's --risk-profile flag and
	// a workflow step's "risk_profile" field both override this
	// value.
	RiskProfile string `json:"risk_profile,omitempty"`

	// RiskProfiles allows the user to override the baked-in rules
	// for any named profile. Keys are profile names (readonly,
	// cautious, default, permissive, unrestricted, or any
	// user-defined name); values replace the built-in rules entirely
	// for that name. Useful when the user wants a slightly different
	// definition of "cautious" or wants to add their own named
	// profile. See docs/SECURITY.md#risk-profiles.
	RiskProfiles map[string]AutoApproveRules `json:"risk_profiles,omitempty"`

	// ApprovedShellCommands is the user's persistent allowlist of
	// literal shell command strings that should auto-approve through
	// the high-risk cascade without prompting. Populated by the
	// "Always approve this command" choice on the approval dialog
	// (SP-058 follow-up). Stored as exact strings — matching is
	// command-literal equality, not pattern matching, so allow-listing
	// `rm -rf /tmp/build-cache` does NOT allow `rm -rf anything-else`.
	// The Critical tier still blocks regardless of this allowlist.
	// Users can edit this list directly in config.json to revoke an
	// entry, or remove all entries to reset.
	ApprovedShellCommands []string `json:"approved_shell_commands,omitempty"`

	// ApprovedShellCommandPatterns is the user's persistent allowlist of
	// glob patterns for shell commands that should auto-approve through
	// the high-risk cascade without prompting. Patterns use Go's path.Match
	// syntax: `*` matches any sequence of characters (but NOT `/`),
	// `?` matches any single character, `[abc]` matches a character class.
	// For example, `rm -rf /tmp/*` matches `rm -rf /tmp/build` but NOT
	// `rm -rf /home/x`. The Critical tier still blocks regardless of
	// pattern matches — patterns cannot override critical-tier gating,
	// which is enforced at the call site before this allowlist is consulted.
	// Users can edit this list directly in config.json to revoke an entry,
	// or remove all entries to reset.
	ApprovedShellCommandPatterns []string `json:"approved_shell_command_patterns,omitempty"`

	// DismissedPrompts tracks which one-time prompts the user has dismissed.
	DismissedPrompts map[string]bool `json:"dismissed_prompts,omitempty"`

	// API Timeout Configuration (in seconds)
	APITimeouts *APITimeoutConfig `json:"api_timeouts,omitempty"`

	// Custom Providers Configuration
	CustomProviders map[string]CustomProviderConfig `json:"custom_providers,omitempty"`

	// Command History Configuration
	CommandHistoryByPath map[string][]string `json:"command_history_by_path,omitempty"`
	HistoryIndexByPath   map[string]int      `json:"history_index_by_path,omitempty"`

	// Change History Configuration
	HistoryScope string `json:"history_scope,omitempty"` // "project" or "global"

	// Subagent Configuration
	SubagentProvider string `json:"subagent_provider,omitempty"` // Provider for subagents (defaults to LastUsedProvider)
	SubagentModel    string `json:"subagent_model,omitempty"`    // Model for subagents (defaults to provider's default model)
	// SubagentTypes is hydrated from the embedded catalog at config load time.
	// It is NOT persisted (json:"-"): personas are catalog-fixed and user
	// customization is intentionally not supported. Use DisabledPersonas to
	// hide specific personas from /persona list and from subagent spawning.
	SubagentTypes map[string]SubagentType `json:"-"`
	// DisabledPersonas holds canonical persona IDs the user has hidden via
	// `/persona <id> disable`. The catalog entries themselves are never
	// mutated; resolution checks this list and treats disabled IDs as absent.
	DisabledPersonas []string `json:"disabled_personas,omitempty"`
	// DefaultSubagentPersona is the persona ID used when run_subagent is called
	// without a persona argument. Defaults to "general" if unset. Setting this
	// lets users redirect default spawns without editing the catalog.
	DefaultSubagentPersona  string `json:"default_subagent_persona,omitempty"`
	SubagentMaxParallel     int    `json:"subagent_max_parallel,omitempty"`     // Maximum number of parallel subagents (default: 2)
	SubagentParallelEnabled *bool  `json:"subagent_parallel_enabled,omitempty"` // Enable/disable parallel subagent execution (default: true)
	SubagentMaxDepth        int    `json:"subagent_max_depth,omitempty"`        // Maximum subagent nesting depth (default: 2)

	// EAMode controls how the Executive Assistant persona operates.
	// "interactive" = standard chat interface (default)
	// "queue" = autonomous task processing, exits when queue is empty
	EAMode string `json:"ea_mode,omitempty"`

	// Commit Configuration
	CommitProvider string `json:"commit_provider,omitempty"` // Provider for commit message generation (defaults to LastUsedProvider)
	CommitModel    string `json:"commit_model,omitempty"`    // Model for commit message generation (defaults to provider's default model)

	// Review Configuration
	ReviewProvider string `json:"review_provider,omitempty"` // Provider for review commands (defaults to LastUsedProvider)
	ReviewModel    string `json:"review_model,omitempty"`    // Model for review commands (defaults to provider's default model)

	// Vision Fallback Configuration
	// VisionFallbackToOCR enables transparent fallback to the OCR model
	// when the primary vision model fails after retries. When true and
	// PDFOCRModel is configured, a single OCR attempt is made as a last
	// resort. Default: true (enabled). Controlled by VISION_FALLBACK_TO_OCR
	// env var (SPROUT_ / LEDIT_ prefixes).
	VisionFallbackToOCR bool `json:"vision_fallback_to_ocr,omitempty"`

	// PDF OCR Configuration
	PDFOCREnabled    bool   `json:"pdf_ocr_enabled,omitempty"`    // Enable PDF OCR processing
	PDFOCRProvider   string `json:"pdf_ocr_provider,omitempty"`   // Provider for PDF OCR (e.g., "ollama", "openai", "deepinfra")
	PDFOCRModel      string `json:"pdf_ocr_model,omitempty"`      // Model for PDF OCR (e.g., "glm-ocr", "llama3.2-vision")
	PDFOCRDownloaded bool   `json:"pdf_ocr_downloaded,omitempty"` // Whether the model has been downloaded

	// Embedding Index Configuration
	EmbeddingIndex *EmbeddingIndexConfig `json:"embedding_index,omitempty"`

	// Persistent Context Configuration
	PersistentContext *PersistentContextConfig `json:"persistent_context,omitempty"`

	// Computer Use Configuration (SP-063) — gates the computer_user persona's
	// mouse/keyboard/screenshot tools. Off by default; the tools are never
	// available unless this is explicitly enabled.
	ComputerUse *ComputerUseConfig `json:"computer_use,omitempty"`

	// Vision Configuration (SP-103-C3) — controls vision-pipeline runtime
	// behavior: parallel worker pool size, global concurrency cap, and
	// multi-image batching. All fields have safe defaults via Resolve().
	Vision *VisionConfig `json:"vision,omitempty"`

	// Change Tracking Configuration — controls the shell-mutation
	// snapshot pass. Direct file-tool hooks (write_file, edit_file,
	// patch_structured_file) are always tracked; this struct only
	// gates the walker that detects shell_command mutations.
	ChangeTracking *ChangeTrackingConfig `json:"change_tracking,omitempty"`

	// Skills Configuration
	Skills map[string]Skill `json:"skills,omitempty"` // Agent Skills that can be loaded into context

	// Zsh Command Execution
	EnableZshCommandDetection   bool `json:"enable_zsh_command_detection,omitempty"`   // Enable zsh-aware command detection (default: false)
	AutoExecuteDetectedCommands bool `json:"auto_execute_detected_commands,omitempty"` // Auto-execute detected commands without prompting (default: true)

	// Security Policy Configuration
	SecurityPolicy *SecurityPolicy `json:"security_policy,omitempty"`

	// Shell Configuration — user-configurable shell permission policy
	// (SP-049 Phase 2). Lets users define safe/dangerous command patterns
	// and a workspace-overlay mode.
	Shell ShellConfig `json:"shell,omitempty"`

	// MaxContextTokens caps the effective context window used when building
	// requests. When set, the agent acts as if the model has at most this
	// many tokens of context, limiting how large an input (and therefore
	// completion budget) a single request can claim. Useful as a cost-control
	// measure when using models with very large native context windows
	// (e.g. 1M-token models billed per input token). Nil or 0 means no cap.
	MaxContextTokens *int `json:"max_context_tokens,omitempty"`

	// Notifications Configuration (SP-070) — controls how the agent
	// notifies the user when long-running turns complete.
	Notifications *NotificationsConfig `json:"notifications,omitempty"`

	// Edit Approval Configuration (SP-072) — controls the per-hunk
	// diff approval gate for agent file writes.
	EditApproval *EditApprovalConfig `json:"edit_approval,omitempty"`

	// OutputVerbosity controls how much inter-tool-call narration and
	// streaming detail the UI shows. Valid values: "compact" (hide
	// interim model messages, show only tool results and final text),
	// "default" (show tool calls with results, show streaming final
	// text), "verbose" (show everything including interim narration).
	// Empty defaults to "default".
	OutputVerbosity string `json:"output_verbosity,omitempty"`

	// Wakeup controls auto-resume behavior for background task completions
	// (SP-108). When enabled, the daemon automatically processes pending
	// notifications by calling ProcessQueryWithContinuity so the agent can
	// act on completed background tasks without the user sending a manual
	// message. Budget controls prevent unattended token burn loops.
	Wakeup WakeupConfig `json:"wakeup,omitempty"`

	// Other flags
	FromAgent bool `json:"-"` // Internal flag, not persisted
	// contains filtered or unexported fields
}

Config represents the unified application configuration

func Load

func Load() (*Config, error)

Load loads the configuration from file

func LoadConfigWithLayers

func LoadConfigWithLayers(globalPath, workspacePath, sessionPath, globalDir string) (*Config, error)

LoadConfigWithLayers loads configuration from three layers: globalPath -> workspacePath -> sessionPath (each overrides previous) Each layer is optional; missing layers are skipped. globalDir is the directory for global providers (used when globalPath is empty but custom providers need loading).

func LoadOrInitConfig

func LoadOrInitConfig(skipPrompt bool) (*Config, error)

func MergeConfig

func MergeConfig(base, override *Config) *Config

MergeConfig merges two configs, with override taking precedence over base. The override config typically contains only changed fields (deltas). Returns a new config without modifying either input.

func NewConfig

func NewConfig() *Config

NewConfig creates a new configuration with sensible defaults

func RedactConfig

func RedactConfig(cfg *Config) Config

RedactConfig returns a copy of the configuration with all credential values redacted. The MCP server configs have their env vars and credentials maps redacted. This should be used for any display/export/diagnostic output where the config is shown to the user or logged.

func (*Config) GetAllEnabledSkills

func (c *Config) GetAllEnabledSkills() map[string]Skill

GetAllEnabledSkills returns all enabled skills

func (*Config) GetCommitModel

func (c *Config) GetCommitModel() string

GetCommitModel returns the configured model for commit message generation If not explicitly set, falls back to the provider's default model

func (*Config) GetCommitProvider

func (c *Config) GetCommitProvider() string

GetCommitProvider returns the configured provider for commit message generation. Returns an empty string if no explicit commit provider is set; callers should surface this and offer interactive provider selection.

func (*Config) GetEAMode

func (c *Config) GetEAMode() string

GetEAMode returns the current EA mode setting.

func (*Config) GetMCPTimeout

func (c *Config) GetMCPTimeout() time.Duration

GetMCPTimeout returns the MCP timeout as a time.Duration

func (*Config) GetModelForProvider

func (c *Config) GetModelForProvider(provider string) string

GetModelForProvider returns the configured model for a provider. Returns an empty string if no model is configured for the provider; callers handle this by running model selection against the live provider API.

func (*Config) GetReviewModel

func (c *Config) GetReviewModel() string

GetReviewModel returns the configured model for review commands If not explicitly set, falls back to the provider's default model

func (*Config) GetReviewProvider

func (c *Config) GetReviewProvider() string

GetReviewProvider returns the configured provider for review commands. Returns an empty string if no explicit review provider is set; callers should surface this and offer interactive provider selection.

func (*Config) GetSkill

func (c *Config) GetSkill(id string) *Skill

GetSkill retrieves a skill configuration by ID Returns nil if the skill doesn't exist or is disabled

func (*Config) GetSkillPath

func (c *Config) GetSkillPath(id string) string

GetSkillPath returns the full path to a skill directory

func (*Config) GetSubagentMaxDepth

func (c *Config) GetSubagentMaxDepth() int

GetSubagentMaxDepth returns the maximum subagent nesting depth. Defaults to 2 if not configured or set to 0.

func (*Config) GetSubagentMaxParallel

func (c *Config) GetSubagentMaxParallel() int

GetSubagentMaxParallel returns the maximum number of parallel subagents Defaults to 2 if not configured or set to 0

func (*Config) GetSubagentModel

func (c *Config) GetSubagentModel() string

GetSubagentModel returns the configured model for subagents If not explicitly set, falls back to the provider's default model

func (*Config) GetSubagentParallelEnabled

func (c *Config) GetSubagentParallelEnabled() bool

GetSubagentParallelEnabled returns whether parallel subagent execution is enabled Defaults to true if not explicitly set (nil pointer)

func (*Config) GetSubagentProvider

func (c *Config) GetSubagentProvider() string

GetSubagentProvider returns the configured provider for subagents. Returns an empty string if no explicit subagent provider is set; callers inherit from the parent agent's provider or treat empty as a signal that no explicit subagent provider is configured.

func (*Config) GetSubagentType

func (c *Config) GetSubagentType(id string) *SubagentType

GetSubagentType retrieves a subagent type configuration by ID or alias. Personas are catalog-fixed (loaded from pkg/personas/configs/*.json at startup) — there is no user-override merge path. Returns nil if the persona does not exist or has been disabled via Config.DisabledPersonas.

func (*Config) GetSubagentTypeModel

func (c *Config) GetSubagentTypeModel(id string) string

GetSubagentTypeModel returns the model for a specific subagent type Falls back to the general subagent model if not specified

func (*Config) GetSubagentTypeProvider

func (c *Config) GetSubagentTypeProvider(id string) string

GetSubagentTypeProvider returns the provider for a specific subagent type Falls back to the general subagent provider if not specified

func (*Config) IsPersonaDisabled added in v0.16.4

func (c *Config) IsPersonaDisabled(id string) bool

IsPersonaDisabled reports whether the given persona ID has been disabled by the user (via /persona <id> disable). The canonical ID after alias resolution is matched; a disabled persona is returned as nil by GetSubagentType and filtered from GetAvailablePersonaIDs.

func (*Config) Save

func (c *Config) Save() error

Save saves the configuration to file

func (*Config) SaveToDir

func (c *Config) SaveToDir(dir string) error

SaveToDir saves the configuration to a specific directory, bypassing GetConfigPath() (which reads the SPROUT_CONFIG/LEDIT_CONFIG env vars). Use this when a Manager has an explicit configDir so that saves go to the correct location even after the env var has been restored.

func (*Config) SetCommitModel

func (c *Config) SetCommitModel(model string)

SetCommitModel sets the model for commit message generation

func (*Config) SetCommitProvider

func (c *Config) SetCommitProvider(provider string)

SetCommitProvider sets the provider for commit message generation

func (*Config) SetModelForProvider

func (c *Config) SetModelForProvider(provider, model string)

SetModelForProvider sets the model for a specific provider. The test provider is silently rejected to prevent it from leaking into the persisted config via direct Config access.

func (*Config) SetPersonaDisabled added in v0.16.4

func (c *Config) SetPersonaDisabled(id string, disabled bool)

SetPersonaDisabled adds or removes a persona ID from DisabledPersonas. Idempotent; aliases are normalized to the canonical form before storage.

func (*Config) SetReviewModel

func (c *Config) SetReviewModel(model string)

SetReviewModel sets the model for review commands

func (*Config) SetReviewProvider

func (c *Config) SetReviewProvider(provider string)

SetReviewProvider sets the provider for review commands

func (*Config) SetSubagentModel

func (c *Config) SetSubagentModel(model string)

SetSubagentModel sets the model for subagents

func (*Config) SetSubagentProvider

func (c *Config) SetSubagentProvider(provider string)

SetSubagentProvider sets the provider for subagents

func (*Config) Validate

func (c *Config) Validate() error

Validate checks the configuration for consistency and returns an error if any invalid settings are found. Returns the first error encountered.

type ConfigConflictError

type ConfigConflictError struct {
	// Path is the absolute path of the config file.
	Path string
	// LoadedModTime / LoadedSize is what the in-memory Config remembers
	// from when it was last loaded.
	LoadedModTime time.Time
	LoadedSize    int64
	// CurrentModTime / CurrentSize is what's on disk right now.
	CurrentModTime time.Time
	CurrentSize    int64
}

ConfigConflictError is returned from Config.Save when the on-disk config file has been modified since the in-memory Config was last loaded. Indicates another writer (another agent process, another webui tab, a hand-edit) changed the file out from under us.

SP-034-4b. Callers should surface this to the user with a "Settings changed on disk" prompt and offer to reload before retrying.

func (*ConfigConflictError) Error

func (e *ConfigConflictError) Error() string

Error satisfies the error interface. Format is stable and parsed by the webui's `config_conflict` error mapper — don't reorganize fields without coordinating with `pkg/webui/websocket_message_handlers.go`.

type CustomProviderConfig

type CustomProviderConfig struct {
	Name                   string                      `json:"name"`
	Endpoint               string                      `json:"endpoint"`
	ModelName              string                      `json:"model_name"`
	ContextSize            int                         `json:"context_size"`                  // Default context size for provider
	ModelContextSizes      map[string]int              `json:"model_context_sizes,omitempty"` // Per-model context sizes (e.g., "my-model": 131072)
	ReasoningEffort        string                      `json:"reasoning_effort,omitempty"`    // Optional provider-specific reasoning effort override
	Temperature            *float64                    `json:"temperature,omitempty"`         // Optional default temperature
	TopP                   *float64                    `json:"top_p,omitempty"`               // Optional default top_p
	Parameters             map[string]interface{}      `json:"parameters,omitempty"`          // Optional provider-specific default parameters
	RequiresAPIKey         bool                        `json:"requires_api_key"`
	ToolCalls              []string                    `json:"tool_calls,omitempty"`               // Optional explicit tool allowlist; when set, only these tools are exposed
	EnvVar                 string                      `json:"env_var,omitempty"`                  // Environment variable name for API key
	ChunkTimeoutMs         int                         `json:"chunk_timeout_ms,omitempty"`         // Streaming chunk timeout in milliseconds
	Conversion             providers.MessageConversion `json:"message_conversion,omitempty"`       // Message conversion configuration
	SupportsVision         bool                        `json:"supports_vision,omitempty"`          // Whether this provider supports vision requests
	VisionModel            string                      `json:"vision_model,omitempty"`             // Vision-capable model for this provider
	VisionFallbackProvider string                      `json:"vision_fallback_provider,omitempty"` // Optional fallback provider for vision
	VisionFallbackModel    string                      `json:"vision_fallback_model,omitempty"`    // Optional fallback model for vision provider
	BillingType            string                      `json:"billing_type,omitempty"`             // Billing model: pay_per_token (default), subscription, free
}

CustomProviderConfig represents a custom model provider configuration

func NormalizeCustomProviderConfig

func NormalizeCustomProviderConfig(cfg CustomProviderConfig) (CustomProviderConfig, error)

func (CustomProviderConfig) ModelsEndpoint

func (c CustomProviderConfig) ModelsEndpoint() string

func (CustomProviderConfig) ToProviderConfig

func (c CustomProviderConfig) ToProviderConfig() (*providers.ProviderConfig, error)

type EditApprovalConfig added in v0.16.12

type EditApprovalConfig struct {
	Mode  string   `json:"mode,omitempty"`
	Paths []string `json:"paths,omitempty"`

	// ShellCommand enables per-part shell approval prompts (SP-093-2).
	// When true, a multi-part shell command is split and each part is
	// approved individually via Agent.RequestShellApproval. Default: false,
	// which preserves the existing 4-option prompt for the whole command.
	ShellCommand bool `json:"shell_command,omitempty" yaml:"shell_command,omitempty"`
}

EditApprovalConfig controls the per-hunk diff approval gate (SP-072).

func (*EditApprovalConfig) Resolve added in v0.16.12

func (*EditApprovalConfig) ShouldGate added in v0.16.12

func (c *EditApprovalConfig) ShouldGate(path string) bool

type EmbeddingIndexConfig

type EmbeddingIndexConfig struct {
	// Enabled controls whether the embedding index is active.
	Enabled bool `json:"enabled,omitempty"`

	// IndexDir is the directory where the embedding index JSONL files are stored.
	// If empty, uses ~/.config/sprout/embeddings/
	IndexDir string `json:"index_dir,omitempty"`

	// SimilarityThreshold is the cosine similarity threshold for duplicate detection.
	// Range: 0.0 to 1.0. Default: 0.90
	SimilarityThreshold float32 `json:"similarity_threshold,omitempty"`

	// MaxResults is the maximum number of duplicate candidates to return.
	// Default: 3
	MaxResults int `json:"max_results,omitempty"`

	// AutoIndex controls whether the index is built automatically on first use.
	// Default: true
	AutoIndex bool `json:"auto_index,omitempty"`

	// ExcludePaths is a list of additional paths to exclude from indexing.
	ExcludePaths []string `json:"exclude_paths,omitempty"`
}

EmbeddingIndexConfig configures the embedding-based duplicate detection and semantic search.

type LanguageServerOverride

type LanguageServerOverride struct {
	ID          string   `json:"id" yaml:"id"`                                         // Unique server ID (e.g. "go", "typescript")
	Binary      string   `json:"binary" yaml:"binary"`                                 // Path to the binary (e.g. "gopls", "typescript-language-server")
	Args        []string `json:"args,omitempty" yaml:"args,omitempty"`                 // Command-line arguments (e.g. ["--stdio"])
	LanguageIDs []string `json:"language_ids,omitempty" yaml:"language_ids,omitempty"` // Language IDs this server handles (e.g. ["go"])
	InstallHint string   `json:"install_hint,omitempty" yaml:"install_hint,omitempty"` // Installation instructions
}

LanguageServerOverride allows users to customize or add language server configurations beyond the built-in defaults. When a matching ID exists in the default set, this override replaces it entirely. New IDs are appended to the merged list.

type Manager

type Manager struct {
	// contains filtered or unexported fields
}

Manager manages configuration state with safe concurrent access.

func NewManager

func NewManager() (*Manager, error)

NewManager creates a new configuration manager

func NewManagerSilent

func NewManagerSilent() (*Manager, error)

NewManagerSilent creates a new configuration manager without showing welcome messages

func NewManagerWithConfig

func NewManagerWithConfig(cfg *Config, apiKeys *APIKeys) *Manager

NewManagerWithConfig creates a new configuration manager from an explicit Config and optional API key set. The manager will persist saves to the same location that config.Save()/Load() would use for the current env (when configDir is empty) or to configDir (when non-empty). Pass nil for apiKeys to skip key loading.

func NewManagerWithDir

func NewManagerWithDir(configDir string) (*Manager, error)

NewManagerWithDir creates a configuration Manager fully backed by configDir. If no config file exists in configDir a fresh default one is written so that subsequent Load/Save calls operate deterministically.

This is intended for tests and tooling that need a hermetic config environment without touching the caller's real ~/.config/sprout.

func NewManagerWithLayers

func NewManagerWithLayers(globalDir, workspaceDir string) (*Manager, error)

NewManagerWithLayers creates a configuration manager using layered config. globalDir is the directory containing global config (~/.config/sprout/). workspaceDir is the directory containing workspace config ({workspace}/.sprout/). Each layer is optional - missing layers are skipped. Settings writes go to the workspace dir if provided, otherwise global.

func NewTestManager

func NewTestManager(t *testing.T) (*Manager, func())

NewTestManager builds a configuration Manager backed by an isolated temp directory so that tests never read, modify, or create files in the caller's real ~/.config/sprout config. Sets SPROUT_CONFIG + LEDIT_CONFIG to that temp dir via t.Setenv so any code path that uses GetConfigPath() (rather than the Manager) lands in the temp dir too.

Returns the Manager and a cleanup func (no-op today; reserved for future hooks like Layer 5 detection). Callers should defer cleanup unconditionally to keep the contract stable as the helper grows.

Usage:

mgr, cleanup := configuration.NewTestManager(t)
defer cleanup()

This helper lives in a non-_test.go file so packages outside `configuration` can import it. Inside the configuration package it is treated like any other helper.

func (*Manager) AddMCPServer

func (m *Manager) AddMCPServer(name string, server mcp.MCPServerConfig) error

AddMCPServer adds an MCP server configuration

func (*Manager) EnrichCustomProviders

func (m *Manager) EnrichCustomProviders()

EnrichCustomProviders loads custom provider files from the global providers directory into the config. This is needed before provider name lookups because config.json never stores custom providers directly.

func (*Manager) EnsureAPIKey

func (m *Manager) EnsureAPIKey(clientType api.ClientType) error

EnsureAPIKey ensures a provider has an API key, prompting if needed

func (*Manager) GetAPIKeyForProvider

func (m *Manager) GetAPIKeyForProvider(clientType api.ClientType) string

GetAPIKeyForProvider returns the API key for a provider

func (*Manager) GetAPIKeys

func (m *Manager) GetAPIKeys() *APIKeys

GetAPIKeys returns the current API keys

func (*Manager) GetAvailableProviders

func (m *Manager) GetAvailableProviders() []api.ClientType

GetAvailableProviders returns all providers that can be used

func (*Manager) GetConfig

func (m *Manager) GetConfig() *Config

GetConfig returns the current configuration

func (*Manager) GetConfigDir

func (m *Manager) GetConfigDir() string

GetConfigDir returns the stored config directory for this manager. Returns empty string if the manager uses the default (env-based) location.

func (*Manager) GetMCPConfig

func (m *Manager) GetMCPConfig() mcp.MCPConfig

GetMCPConfig returns the MCP configuration

func (*Manager) GetModelForProvider

func (m *Manager) GetModelForProvider(clientType api.ClientType) string

GetModelForProvider returns the model for the given provider

func (*Manager) GetProvider

func (m *Manager) GetProvider() (api.ClientType, error)

GetProvider returns the currently selected provider as ClientType

func (*Manager) HasAPIKey

func (m *Manager) HasAPIKey(clientType api.ClientType) bool

HasAPIKey checks if a provider has an API key

func (*Manager) MapStringToClientType

func (m *Manager) MapStringToClientType(s string) (api.ClientType, error)

MapStringToClientType converts string to ClientType, handling custom providers

func (*Manager) RefreshAPIKeys

func (m *Manager) RefreshAPIKeys() error

RefreshAPIKeys reloads API keys from the backend into the in-memory cache. This must be called after any external mutation of the credential backend (e.g., ValidateAndSaveAPIKey) to keep the Manager's in-memory map in sync.

func (*Manager) Reload

func (m *Manager) Reload() error

Reload re-reads the on-disk configuration and API keys into the in-memory cache. It is intended to be called from a SIGHUP handler so that config changes made externally (e.g., editing config.yaml) take effect without restarting the daemon. Running agents and tools are NOT affected — only subsequent queries will see the new configuration.

func (*Manager) ResolveProviderModel

func (m *Manager) ResolveProviderModel(explicitProvider, explicitModel string) (api.ClientType, string, error)

ResolveProviderModel resolves provider+model selection using canonical precedence.

func (*Manager) SaveAPIKeys deprecated

func (m *Manager) SaveAPIKeys() error

SaveAPIKeys saves the API keys to disk.

Deprecated: This performs a blind write with no validation. Use ValidateAndSaveAPIKey instead, which validates the key before saving and preserves the old key on validation failure. This method is retained for backward compatibility only.

func (*Manager) SaveConfig

func (m *Manager) SaveConfig() error

SaveConfig saves the configuration to disk

func (*Manager) SelectNewProvider

func (m *Manager) SelectNewProvider() (api.ClientType, error)

SelectNewProvider allows interactive provider selection

func (*Manager) SetMCPEnabled

func (m *Manager) SetMCPEnabled(enabled bool) error

SetMCPEnabled enables or disables MCP

func (*Manager) SetModelForProvider

func (m *Manager) SetModelForProvider(clientType api.ClientType, model string) error

SetModelForProvider sets the model for a provider

func (*Manager) SetProvider

func (m *Manager) SetProvider(clientType api.ClientType) error

SetProvider sets the current provider

func (*Manager) UpdateConfig

func (m *Manager) UpdateConfig(mutator func(*Config) error) error

UpdateConfig mutates the live config under lock and persists it to disk.

func (*Manager) UpdateConfigNoSave

func (m *Manager) UpdateConfigNoSave(mutator func(*Config) error) error

UpdateConfigNoSave mutates the live config under lock without persisting it.

type MigrationFunc

type MigrationFunc func(raw map[string]interface{}) error

MigrationFunc transforms a raw JSON config from one version to the next. It receives and returns a map[string]interface{} representing the parsed config JSON. The function should set "version" to the target version on success.

type NotificationsConfig added in v0.16.12

type NotificationsConfig struct {
	// CLIBell emits a terminal bell (\a) on completion.
	CLIBell bool `json:"cli_bell,omitempty"`
	// OSNotify fires an OS-level desktop notification on completion.
	OSNotify bool `json:"os_notify,omitempty"`
	// Browser fires a browser notification (used by WebUI, SP-070-4).
	Browser bool `json:"browser,omitempty"`
	// MinSeconds is the minimum turn duration (in seconds) before a
	// notification is sent.  Default: 10.0.  Turns completing in less
	// than this are considered too brief to warrant a notification.
	MinSeconds float64 `json:"min_seconds,omitempty"`
}

NotificationsConfig controls how the agent notifies the user when long-running turns complete (SP-070).

func (*NotificationsConfig) Resolve added in v0.16.12

Resolve returns a copy with defaults filled in for zero-value fields.

type PersistentContextConfig

type PersistentContextConfig struct {
	// ProactiveContextEnabled controls whether the system primes new sessions with
	// relevant past work via semantic retrieval from conversation history.
	// Default: true
	ProactiveContextEnabled bool `json:"proactiveContextEnabled,omitempty"`

	// MaxContextualResults is the maximum number of past turns to retrieve for context.
	// Default: 5
	MaxContextualResults int `json:"maxContextualResults,omitempty"`

	// MinRelevanceScore is the minimum time-decayed cosine similarity score for retrieval.
	// Range: 0.0 to 1.0. Results below this score are filtered out.
	// Default: 0.50
	MinRelevanceScore float64 `json:"minRelevanceScore,omitempty"`

	// MaxContextChars is the hard cap on total injected character count for context.
	// Default: 4000
	MaxContextChars int `json:"maxContextChars,omitempty"`

	// WorkspaceScopedRetrieval restricts retrieval to turns from the current workspace only.
	// When false (default), retrieval searches across all workspaces.
	// Default: false
	WorkspaceScopedRetrieval bool `json:"workspaceScopedRetrieval,omitempty"`

	// DriftDetectionEnabled controls whether conversational drift detection is active.
	// When enabled, the system checks if the conversation has drifted from its original intent.
	// Default: true
	DriftDetectionEnabled bool `json:"driftDetectionEnabled,omitempty"`

	// DriftThreshold is the cosine similarity threshold below which drift is flagged.
	// Range: 0.0 to 1.0. Lower values require more divergence before flagging.
	// Default: 0.60
	DriftThreshold float64 `json:"driftThreshold,omitempty"`

	// DriftCheckInterval is the number of turns between drift checks.
	// For example, 5 means drift is checked on turns 5, 10, 15, etc.
	// Default: 5
	DriftCheckInterval int `json:"driftCheckInterval,omitempty"`

	// RetentionDays controls how many days to keep persistent context entries.
	// Default: 0 (never expire). Set to a positive value to automatically clean
	// up entries older than the specified number of days at agent startup.
	RetentionDays int `json:"retentionDays,omitempty"`
}

PersistentContextConfig configures persistent conversational context and memory retrieval.

func (*PersistentContextConfig) Resolve

Resolve returns a copy of the config with default values filled in for any zero-value fields. Use this after loading from disk to ensure sensible defaults. If the receiver is nil, returns a fully-defaulted config.

type ProviderAuthMetadata

type ProviderAuthMetadata struct {
	Provider       string
	DisplayName    string
	RequiresAPIKey bool
	EnvVar         string
	AuthType       string
}

func GetProviderAuthMetadata

func GetProviderAuthMetadata(provider string) (ProviderAuthMetadata, error)

type ProviderConfigLookupFunc added in v0.16.8

type ProviderConfigLookupFunc func(name string) (envVar, authType string, ok bool)

ProviderConfigLookupFunc returns the env-var and auth-type for a runtime provider config — typically backed by the global provider factory, which merges embedded, filesystem, and remote (GitHub Pages) sources. Returns ok=false when the provider is not present in the runtime view.

type ProviderDiscoveryModel

type ProviderDiscoveryModel struct {
	ID            string   `json:"id"`
	Name          string   `json:"name,omitempty"`
	Description   string   `json:"description,omitempty"`
	ContextLength int      `json:"context_length,omitempty"`
	Tags          []string `json:"tags,omitempty"`
}

func DiscoverCustomProviderModels

func DiscoverCustomProviderModels(cfg CustomProviderConfig) ([]ProviderDiscoveryModel, error)

type ProviderDisplayNameLookupFunc added in v0.16.8

type ProviderDisplayNameLookupFunc func(name string) (displayName string, ok bool)

ProviderDisplayNameLookupFunc returns the user-facing label for a provider — sourced from the runtime config's display_name field. Returns ok=false when the provider is unknown to the runtime view or when its display_name is blank (so callers fall through to the static map / raw-id fallback).

type ProviderNamesLookupFunc added in v0.16.8

type ProviderNamesLookupFunc func() []string

ProviderNamesLookupFunc returns the full set of provider names known to the runtime — typically the global factory's view, which includes embedded, filesystem, and remote (GitHub Pages) registrations.

type RevisionRetentionConfig

type RevisionRetentionConfig struct {
	// HotCount: most recent N revisions kept verbatim (full
	// conversation.json + instructions + llm_response + all change
	// payloads). Fast view_history + full recovery. Default: 200.
	HotCount int `json:"hot_count,omitempty"`

	// WarmCount: next M revisions after the hot tier. conversation.json
	// dropped; instructions + response + change payloads kept. Recovery
	// still works; conversation context lost. Default: 500.
	WarmCount int `json:"warm_count,omitempty"`

	// MaxDirBytes is the long-stop cap on total revisions+changes
	// disk usage per workspace. If the count-based tiering still
	// leaves the directory over this size, trim oldest warm entries
	// until under cap. Default: 1 GiB (1073741824).
	MaxDirBytes int64 `json:"max_dir_bytes,omitempty"`

	// ArchiveFrozen: if true, dropped revisions are moved to
	// .sprout/revisions/_frozen/ instead of being deleted outright.
	// Opt-in safety net for users who want a recoverable record of
	// long-tail history at the cost of unbounded growth. Default: false.
	ArchiveFrozen bool `json:"archive_frozen,omitempty"`

	// MaxChangesPerRevision caps the number of change records kept per
	// revision in .sprout/changes/. A single runaway session (e.g. an
	// agent that `cd`'d into $HOME so a shell walk classified
	// pre-existing files as creates) can produce tens of thousands of
	// records; without this cap, count bloat persists even when total
	// bytes are under MaxDirBytes. Default: 10000.
	MaxChangesPerRevision int `json:"max_changes_per_revision,omitempty"`

	// MaxChangesAgeDays drops change records older than this number of
	// days regardless of their parent revision's tier. Belt-and-
	// suspenders against changes/ accumulating inside the hot window.
	// Default: 30. Set to a negative value to disable.
	MaxChangesAgeDays int `json:"max_changes_age_days,omitempty"`
}

RevisionRetentionConfig controls the quantity-based compaction of the persistent revision history. Two retained tiers plus a drop threshold:

  • hot: the most recent N revisions kept verbatim
  • warm: the next M revisions with conversation.json dropped
  • drop: anything older is removed entirely

The ChangeTracker is a short-horizon stop-gap (recover from a bad sed -i, undo a hasty rm), not a long-term audit log — that's what git is for. Once a revision falls out of warm, the user has either committed the work or wasn't going to recover it anyway, and the disk space is better spent on hot data.

See AGENTS.md "Change Tracking" for context.

func (*RevisionRetentionConfig) Resolve

Resolve fills in defaults for any zero-value fields and returns a fully-populated retention config. Safe to call on nil — yields all-defaults.

type RiskLevel

type RiskLevel string

RiskLevel represents the risk classification of an operation for the EA approval cascade.

const (
	RiskLevelLow      RiskLevel = "low"      // Auto-approve (git status, read operations)
	RiskLevelMedium   RiskLevel = "medium"   // Reason and decide (git commit, git push)
	RiskLevelHigh     RiskLevel = "high"     // Prompt the user when interactive; reject when not
	RiskLevelCritical RiskLevel = "critical" // Never approvable: rm -rf root, fork bombs
)

func MoreRestrictiveRiskLevel added in v0.16.7

func MoreRestrictiveRiskLevel(a, b RiskLevel) RiskLevel

MoreRestrictiveRiskLevel returns whichever of a or b gates harder. Ties return a. This is the combinator the unified resolver uses to fold multiple risk sources (classifier, persona cascade, git/fs/workspace gates) into one verdict.

func (RiskLevel) IsAtLeast added in v0.16.7

func (r RiskLevel) IsAtLeast(other RiskLevel) bool

IsAtLeast reports whether r is at least as severe as other.

func (RiskLevel) Rank added in v0.16.7

func (r RiskLevel) Rank() int

Rank returns the severity ordering of a risk level:

Low(0) < Medium(1) < High(2) < Critical(3)

An empty or unrecognized level ranks as Medium — the same safe default the persona cascade already applies to unmatched operations — so an unknown value can never silently sort below a known one.

type RiskProfile

type RiskProfile string

RiskProfile names a preset risk-cascade configuration. The active profile resolves to an AutoApproveRules via AutoApproveRulesForProfile. Persona-specified rules always take precedence over the profile.

const (
	// RiskProfileReadonly — strictest. ONLY read operations (git
	// status / log / diff, read_file) are permitted. Every write,
	// edit, shell command, or destructive op is BLOCKED outright
	// (no prompt path) by promoting to the Critical tier. Use for
	// audits, code review, or sandboxed inspection where the agent
	// should never mutate anything.
	RiskProfileReadonly RiskProfile = "readonly"

	// RiskProfileCautious — most operations prompt the user. Suitable
	// for sensitive workspaces or unfamiliar agents. Low-risk reads
	// auto-approve; everything else gets routed to a prompt.
	RiskProfileCautious RiskProfile = "cautious"

	// RiskProfileDefault — sane defaults matching the historical EA
	// cascade. Reads auto-approve, common edits/commits auto-approve,
	// destructive operations (force flags, rm -rf, lossy git) prompt.
	RiskProfileDefault RiskProfile = "default"

	// RiskProfilePermissive — high trust. Almost everything passes
	// without prompting; only truly destructive patterns route to a
	// prompt. Use when the agent is well-trusted and the workspace
	// is recoverable (clean checkout, throwaway dir).
	RiskProfilePermissive RiskProfile = "permissive"

	// RiskProfileUnrestricted — no risk cascade gating at all. Only
	// the Critical tier (rm -rf root, fork bombs) blocks. Use with
	// extreme care; intended for sandboxed / disposable environments.
	RiskProfileUnrestricted RiskProfile = "unrestricted"
)

type SecurityPolicy

type SecurityPolicy struct {
	DefaultAction  string         `json:"default_action,omitempty"`
	Rules          []SecurityRule `json:"rules,omitempty"`
	AllowedPaths   []string       `json:"allowed_paths,omitempty"`
	DeniedPaths    []string       `json:"denied_paths,omitempty"`
	DeniedCommands []string       `json:"denied_commands,omitempty"`
	MaxRiskLevel   string         `json:"max_risk_level,omitempty"`
}

SecurityPolicy defines workspace-level security rules

func DefaultSecurityPolicy

func DefaultSecurityPolicy() *SecurityPolicy

DefaultSecurityPolicy returns a conservative default policy

func LoadSecurityPolicy

func LoadSecurityPolicy(workspaceRoot string) (*SecurityPolicy, error)

LoadSecurityPolicy reads .sprout/security-policy.json from workspaceRoot. If the file doesn't exist, returns nil, nil (no error). Validates that actions and risk levels are recognized values, and normalizes AllowedPaths and DeniedPaths via filepath.Clean.

func (*SecurityPolicy) Evaluate

func (p *SecurityPolicy) Evaluate(command string) SecurityPolicyAction

Evaluate checks the command against rules in order. For each rule, it first tries matching the full command string, then the base command (first word). First matching rule wins. This means a rule like {Pattern: "git", Action: "deny"} will match "git commit" via base matching before a later rule {Pattern: "git commit*", Action: "allow"} gets evaluated.

Place more specific patterns before broader ones to get the intended behavior. If no rule matches, returns DefaultAction converted to SecurityPolicyAction. If DefaultAction is empty, defaults to "prompt".

func (*SecurityPolicy) IsCommandDenied

func (p *SecurityPolicy) IsCommandDenied(command string) bool

IsCommandDenied checks if the base command (first word) matches any entry in DeniedCommands (case-insensitive).

func (*SecurityPolicy) IsPathAllowed

func (p *SecurityPolicy) IsPathAllowed(path string) bool

IsPathAllowed checks whether the given path is allowed by this policy. If AllowedPaths is empty, returns true (no restrictions). Otherwise checks if the cleaned path starts with any of the allowed paths. Also ensures the path is not in DeniedPaths.

NOTE: This uses filepath.Clean for normalization but does NOT resolve symlinks. A symlink from an allowed path to a system directory could bypass this check.

func (*SecurityPolicy) IsPathDenied

func (p *SecurityPolicy) IsPathDenied(path string) bool

IsPathDenied checks if the cleaned path starts with any of the denied paths. Handles parent traversal: if "/etc" is denied, "/etc/passwd" is also denied.

NOTE: This uses filepath.Clean for normalization but does NOT resolve symlinks. A symlink from an allowed path to a system directory could bypass this check.

func (*SecurityPolicy) MaxAllowedRisk

func (p *SecurityPolicy) MaxAllowedRisk() int

MaxAllowedRisk converts MaxRiskLevel string to SecurityRisk int equivalent. "safe"=0, "caution"=1, "dangerous"=2. Defaults to 0 if empty or unrecognized.

type SecurityPolicyAction

type SecurityPolicyAction string

SecurityPolicyAction represents the action to take when a rule matches

const (
	PolicyAllow  SecurityPolicyAction = "allow"
	PolicyDeny   SecurityPolicyAction = "deny"
	PolicyPrompt SecurityPolicyAction = "prompt"
)

func (SecurityPolicyAction) IsValid

func (a SecurityPolicyAction) IsValid() bool

IsValid returns true if the action is a recognized SecurityPolicyAction value: "allow", "deny", "prompt", or empty string.

type SecurityRule

type SecurityRule struct {
	Pattern string `json:"pattern"`
	Action  string `json:"action"`
	Reason  string `json:"reason,omitempty"`
}

SecurityRule defines a pattern-based rule for command evaluation

type ShellConfig added in v0.16.12

type ShellConfig struct {
	UserSafePatterns      []ShellPattern         `json:"user_safe_patterns,omitempty"`
	UserDangerousPatterns []ShellPattern         `json:"user_dangerous_patterns,omitempty"`
	WorkspaceOverlay      WorkspaceOverlayConfig `json:"workspace_overlay,omitempty"`
}

ShellConfig holds user-configurable shell permission policy.

func (*ShellConfig) Validate added in v0.16.12

func (sc *ShellConfig) Validate() error

Validate checks the ShellConfig values and normalizes invalid ones. Returns an error for values that can't be normalized.

type ShellPattern added in v0.16.12

type ShellPattern struct {
	Match  string `json:"match"`            // prefix string or regex pattern
	Kind   string `json:"kind"`             // "prefix" or "regex"
	Reason string `json:"reason,omitempty"` // optional human-readable note
}

ShellPattern is a single user-defined shell classification pattern.

type Skill

type Skill struct {
	ID           string            `json:"id"`            // Unique identifier (e.g., "go-best-practices")
	Name         string            `json:"name"`          // Human-readable name
	Description  string            `json:"description"`   // What this skill provides and when to use it
	Path         string            `json:"path"`          // Relative path to skill directory
	Enabled      bool              `json:"enabled"`       // Whether this skill is available
	Metadata     map[string]string `json:"metadata"`      // Optional metadata (author, version, etc.)
	AllowedTools string            `json:"allowed_tools"` // Optional space-delimited list of pre-approved tools
}

Skill defines an Agent Skill that can be loaded into context

type SubagentType

type SubagentType struct {
	ID                 string            `json:"id"`                             // Unique identifier (e.g., "coder", "tester", "debugger")
	Name               string            `json:"name"`                           // Human-readable name (e.g., "Coder", "Tester")
	Description        string            `json:"description"`                    // What this subagent specializes in
	Provider           string            `json:"provider"`                       // Provider for this subagent type (optional, falls back to SubagentProvider)
	Model              string            `json:"model"`                          // Model for this subagent type (optional, falls back to SubagentModel)
	SystemPrompt       string            `json:"system_prompt"`                  // Relative path to system prompt file (e.g., "subagent_prompts/coder.md")
	SystemPromptText   string            `json:"system_prompt_text,omitempty"`   // Optional inline system prompt text (replaces base prompt entirely)
	SystemPromptAppend string            `json:"system_prompt_append,omitempty"` // Optional inline text appended to the base or loaded system prompt (for composition)
	AllowedTools       []string          `json:"allowed_tools,omitempty"`        // Optional explicit tool allowlist for focused persona behavior
	Aliases            []string          `json:"aliases,omitempty"`              // Optional aliases (e.g., "web-scraper")
	Enabled            bool              `json:"enabled"`                        // Catalog-only: every shipped persona sets this true. Runtime "is this persona usable?" is determined by Config.DisabledPersonas (user) + LocalOnly (env). Kept for catalog hygiene + defense-in-depth in case a future variant ships with a deliberately-disabled entry.
	LocalOnly          bool              `json:"local_only,omitempty"`           // Only available in local mode (not cloud)
	Delegatable        bool              `json:"delegatable,omitempty"`          // Whether this persona can be used as a subagent (default: true for worker personas, false for orchestrator personas)
	AutoApproveRules   *AutoApproveRules `json:"auto_approve_rules,omitempty"`   // Risk cascade rules for the runtime auto-approve check
	// Capabilities is an explicit list of agency grants this persona holds
	// (e.g. "git_write"). Replaces sniffing AutoApproveRules to infer what a
	// persona is allowed to do. Use HasCapability to query.
	Capabilities []string `json:"capabilities,omitempty"`
	// CanSpawnNonDelegatable lists otherwise-undelegatable persona IDs that
	// this persona may spawn. Replaces the hardcoded EA-spawn-authority
	// special-case. The coordinator carries ["orchestrator"] to enable the
	// canonical coordinator→orchestrator→specialist chain.
	CanSpawnNonDelegatable []string `json:"can_spawn_non_delegatable,omitempty"`
}

SubagentType defines a specialized subagent persona with its own configuration

func (*SubagentType) EvaluateOperationRisk

func (st *SubagentType) EvaluateOperationRisk(command string) RiskLevel

EvaluateOperationRisk determines the risk level of a shell operation based on the persona's auto-approve rules. Returns RiskLevelCritical for absolute-block patterns, otherwise RiskLevelLow, RiskLevelMedium, or RiskLevelHigh per the rules.

func (*SubagentType) GetAutoApproveRules

func (st *SubagentType) GetAutoApproveRules() AutoApproveRules

GetAutoApproveRules returns the auto-approve rules for this persona, falling back to defaults if none are configured. Callers MUST NOT modify the returned struct's slice fields, as they may share backing arrays with the original config.

func (*SubagentType) HasCapability added in v0.16.4

func (st *SubagentType) HasCapability(name string) bool

HasCapability reports whether the persona declares the given capability name. Comparison is case-insensitive and whitespace-tolerant.

type VisionConfig added in v0.16.19

type VisionConfig struct {
	// ParallelWorkers caps concurrent in-flight vision requests per
	// session. Vision requests are heavyweight (large payloads, slow
	// provider round-trips) so this defaults to 3 — independent of the
	// generic request_parallelism setting.
	//
	// Range: 1..32. Values outside this range are clamped in Resolve().
	// Set to 0 to fall back to the default (3).
	ParallelWorkers int `json:"parallel_workers,omitempty"`

	// MaxParallelRequests caps the global number of in-flight vision
	// API calls across the entire process. This is independent of
	// ParallelWorkers (which is per-session). Default: 8.
	MaxParallelRequests int `json:"max_parallel_requests,omitempty"`

	// EnableBatchProcessing toggles the multi-image batching layer
	// (VISION-4). Default: true.
	EnableBatchProcessing bool `json:"enable_batch_processing,omitempty"`

	// MaxBatchSize is the maximum number of images sent to the provider
	// in a single batched call. Default: 4. Range: 1..8.
	MaxBatchSize int `json:"max_batch_size,omitempty"`
}

VisionConfig controls vision-pipeline runtime behavior (SP-103-C3).

All fields are zero-valued by default; Resolve() fills in safe defaults. VisionConfig is consulted at runtime by the parallel OCR worker pool (pkg/agent_tools/vision_parallel.go::getVisionParallelWorkers).

func GetVisionConfig added in v0.16.19

func GetVisionConfig() VisionConfig

GetVisionConfig returns the raw VisionConfig from the on-disk configuration file. If the config file can't be loaded or the Vision section is absent, returns a zero-valued VisionConfig (all fields are zero). Callers should use Resolve() on the result to fill in defaults, or check individual fields directly (zero means "not set") for precedence-based lookups.

This is a convenience accessor for callers that don't have a Manager instance (e.g., vision_parallel.go::getVisionParallelWorkers).

func (*VisionConfig) Resolve added in v0.16.19

func (c *VisionConfig) Resolve() VisionConfig

Resolve returns a copy with defaults filled in for zero-value fields.

type WakeupConfig added in v0.16.19

type WakeupConfig struct {
	Enabled              bool `json:"enabled"`                 // Master switch; default false
	MaxTokensPerSession  int  `json:"max_tokens_per_session"`  // Hard cap on auto-resume token spend; default 5000
	MaxResumesPerSession int  `json:"max_resumes_per_session"` // Max auto-resumes before requiring user input; default 10
}

WakeupConfig controls auto-resume behavior for background task completions (SP-108). Stored in config.json under the "wakeup" key.

func DefaultWakeupConfig added in v0.16.19

func DefaultWakeupConfig() WakeupConfig

DefaultWakeupConfig returns conservative defaults.

type WorkspaceOverlayConfig added in v0.16.12

type WorkspaceOverlayConfig struct {
	// Mode: "tighten_only" (default — only user_dangerous_patterns honored),
	// "trusted" (full overlay honored after `sprout policy trust`), or
	// "ignore" (workspace policy never loaded).
	Mode string `json:"mode,omitempty"`
}

WorkspaceOverlayConfig controls how workspace-rooted policy files are loaded.

Jump to

Keyboard shortcuts

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