configuration

package
v0.17.17 Latest Latest
Warning

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

Go to latest
Published: Aug 18, 2026 License: MIT Imports: 30 Imported by: 0

Documentation

Overview

Package configuration: command categorization and force-flag detection. (split from config_risk_subagent.go)

Package configuration: git-command classification and risk-pattern matching. (split from config_risk_subagent.go)

Package configuration: heredoc / quoted-string stripping for risk classifiers. (split from config_risk_subagent.go)

Package configuration: risk-profile resolution and rule factory. (split from config_risk_subagent.go)

Package configuration: core risk-level and risk-profile types. (split from config_risk_subagent.go)

Package configuration: SubagentType struct, IsCriticalOperation, and risk evaluation. (split from config_risk_subagent.go)

Package configuration: low-context mode (LCM) abstraction. ContextProfile is the resolved shape every downstream call site reads.

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"

	// WorkspaceConfigFileName is the per-workspace config file. Deliberately
	// different from ConfigFileName to avoid a collision when the workspace
	// root is $HOME (both layers would otherwise share the same directory).
	WorkspaceConfigFileName = "workspace.json"

	// ConfigLocalFileName is the user-scope machine-local override file.
	// Same schema as ConfigFileName, higher precedence, never committed.
	// Lives in the config dir alongside config.json.
	ConfigLocalFileName = "config.local.json"

	// WorkspaceLocalFileName is the workspace-scope personal override file.
	// Same schema as WorkspaceConfigFileName, higher precedence within the
	// workspace layer, gitignored.
	WorkspaceLocalFileName = "workspace.local.json"

	APIKeysFileName = "api_keys.json"

	OutputVerbosityCompact = "compact"
	OutputVerbosityDefault = "default"
	OutputVerbosityVerbose = "verbose"
)
View Source
const ContextFloor = 8_000

ContextFloor is the minimum context window at which sprout will start. Below this, the agent is unusable.

View Source
const CredentialsDirName = "credentials"

CredentialsDirName is the subdirectory under the config dir that holds sensitive files (api_keys.json, key.age, mode files). Created 0700.

View Source
const EffectiveContextCapMinimum = 1024

EffectiveContextCapMinimum is the minimum cap a user may set via Config.MaxContextTokens.

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.

func CanonicalizeCustomProviderName

func CanonicalizeCustomProviderName(name string) (string, error)

CanonicalizeCustomProviderName lowercases and trims the input, then rejects anything outside the [a-z0-9_-] character set. Empty input is also rejected.

func CredentialsDir added in v0.17.17

func CredentialsDir() (string, error)

CredentialsDir returns the credentials subdirectory under the config root, creating it with mode 0700 if missing.

func CredentialsDirFromDir added in v0.17.17

func CredentialsDirFromDir(base string) (string, error)

CredentialsDirFromDir returns the credentials subdirectory under an explicit base, creating it with mode 0700 if missing.

func DebugPrintConfig

func DebugPrintConfig(config *Config, apiKeys *APIKeys)

DebugPrintConfig prints current configuration for debugging

func DeleteCustomProvider

func DeleteCustomProvider(name string) error

func EffectiveContextCapErrorf added in v0.17.7

func EffectiveContextCapErrorf(got int) error

EffectiveContextCapErrorf builds the error message returned when a user-configured cap falls below the minimum.

func EnsureProviderAPIKey

func EnsureProviderAPIKey(provider string, apiKeys *APIKeys) error

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

func EnsureWorkspaceConfigDir added in v0.17.17

func EnsureWorkspaceConfigDir(workspaceRoot string) error

EnsureWorkspaceConfigDir creates the workspace .sprout/ directory if missing and ships a .gitignore covering personal overrides and state directories. Idempotent — safe to call on every startup.

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 GetConfigLocalPath added in v0.17.17

func GetConfigLocalPath() (string, error)

GetConfigLocalPath returns the path to the user-scope local override file (config.local.json). The file may not exist; callers should stat before reading.

func GetConfigPath

func GetConfigPath() (string, error)

GetConfigPath returns the full path to the config file

func GetCustomProviderPath

func GetCustomProviderPath(name string) (string, error)

GetCustomProviderPath returns the path where a custom provider JSON file is stored. Always resolves to the global providers directory (~/.config/sprout/providers/) so that save, load, and delete all agree on the same location regardless of SPROUT_CONFIG.

func GetEnvSimple

func GetEnvSimple(suffix string) string

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

func GetGlobalProvidersDir added in v0.17.11

func GetGlobalProvidersDir() (string, error)

GetGlobalProvidersDir returns the global providers directory (~/.config/sprout/providers/). Custom providers are always stored here regardless of SPROUT_CONFIG — they are user-global resources, not project-scoped. This prevents a split-brain where a provider saved from a scoped session is invisible to another scope and can't be deleted from a different scope.

Uses getDefaultConfigDir (HOME-based, ignores SPROUT_CONFIG) so the location is stable across all sessions.

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)

GetProvidersDir returns the global providers directory. Kept as an alias for GetGlobalProvidersDir for backward compatibility with callers that display or reference the providers directory (e.g. cmd/diag.go).

func GetWorkspaceConfigPath

func GetWorkspaceConfigPath(workspaceRoot string) string

GetWorkspaceConfigPath returns the workspace-level config file to READ, or "" when there is no workspace layer (empty root, or $HOME).

Resolution: workspace.json if present, else the legacy config.json, else the workspace.json path (so callers can stat it and find nothing). Existing workspaces keep working untouched; nothing is moved or rewritten.

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 IsValidRiskProfileWithConfig added in v0.17.17

func IsValidRiskProfileWithConfig(s string, cfg *Config) bool

IsValidRiskProfileWithConfig reports whether s names a profile that is usable in the given config context: a baked-in profile name OR a user-defined name present in cfg.RiskProfiles. cfg may be nil, in which case only the built-in names are accepted.

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. It merges providers from the SPROUT_CONFIG-resolved directory and the global home directory (~/.config/sprout/providers/), with the global home directory winning on name conflicts.

This matches the layered manager's effective behavior (which only reads from global) and ensures the factory's /provider switch path resolves the same providers the /provider listing shows. Without the merge, a user running sprout from a project workspace with SPROUT_CONFIG overridden sees custom providers listed (via the layered manager) but receives a "not registered as a custom provider" error when trying to switch to one (via LoadCustomProviders, which would otherwise only see the SPROUT_CONFIG-resolved dir).

func LoadCustomProvidersFromDir

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

LoadCustomProvidersFromDir loads all custom provider JSON files from the given directory. Used by LoadCustomProviders for both the SPROUT_CONFIG-resolved dir and the global home dir (see the merge behavior in LoadCustomProviders for context).

func LookupEnv

func LookupEnv(suffix string) (string, bool)

LookupEnv checks SPROUT_*. 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.

Inputs accepted in priority order:

  1. Built-in ClientType IDs ("openai", "ollama-local", "zai-coding", ...)
  2. Custom providers from cfg.CustomProviders
  3. Factory-loaded embedded provider configs
  4. Display names ("OpenAI", "Ollama (Local)", ...) — reverse lookup against api.GetProviderName output. Provided for backward compatibility with sessions that persisted display names before the SP-034-fix.

func MigrateCommandPolicies added in v0.17.5

func MigrateCommandPolicies(cfg *Config)

MigrateCommandPolicies converts legacy approved_shell_commands and approved_shell_command_patterns fields into the unified CommandPolicies format. It is a no-op when cfg.CommandPolicies is already non-nil.

After migration, the old fields remain in the config for backward compatibility but are no longer consulted by the policy engine.

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)

MigrateLegacyCustomProviders copies any custom_providers entries that lived inline in config.json into the new file-per-provider format under ~/.config/sprout/providers/. Existing files win — the inline entry is only promoted when no file with the same name already exists.

func NeedsMigration added in v0.17.17

func NeedsMigration() bool

NeedsMigration returns true when a legacy ~/.sprout directory exists and no migration marker is present in the state dir.

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 ResolveEffectiveContextCap added in v0.17.7

func ResolveEffectiveContextCap(cfg *Config, nativeContextWindow int) (int, error)

ResolveEffectiveContextCap returns the user's effective context cap: the smaller of the model's native window and the configured MaxContextTokens. Nil/zero cap means "no cap" (returns native window).

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 SPROUT_PROVIDER backward-compat) 4) SPROUT_MODEL env (provider:model format only when prefix is a valid provider, SPROUT_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, SPROUT_MODEL backward-compat) 3) Config provider model default

func ResolveWorkspaceConfigFile added in v0.17.17

func ResolveWorkspaceConfigFile(dir string, dirIsHome bool) string

ResolveWorkspaceConfigFile picks the workspace config file inside dir.

dirIsHome disables the legacy fallback. This is load-bearing: `.sprout` is the per-directory sprout folder AND ~/.sprout is the user-level state directory, so at $HOME the legacy config.json is the user's global config, not a workspace config. Falling back to it there is precisely the aliasing this split exists to remove — and every existing install has that file, so without this guard the split would fix nothing for current users.

A user who deliberately runs with $HOME as the workspace still gets a real workspace layer; it just has to be an explicit ~/.sprout/workspace.json.

func RunMigration added in v0.17.17

func RunMigration() error

RunMigration moves each category of data from the legacy ~/.sprout directory to its new root. It is idempotent: already-moved files are skipped, and a marker prevents re-entry.

The legacy directory is left in place (empty of moved content) so a failed migration is diagnosable.

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 the SPROUT_* version of an env var.

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 the SPROUT_* version 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 ValidateCustomProviderEndpoint added in v0.16.25

func ValidateCustomProviderEndpoint(raw string) error

ValidateCustomProviderEndpoint checks that raw is a syntactically valid http or https URL with a host. It runs before NormalizeCustomProviderConfig (which auto-appends /v1/chat/completions), so it catches typos that would otherwise produce a config that silently fails model discovery.

Returns nil for empty input — that's allowed at this layer so the wizard can detect "user pressed enter on an empty prompt" separately from "user typed garbage".

func WorkspaceConfigDir added in v0.17.17

func WorkspaceConfigDir(workspaceRoot string) string

WorkspaceConfigDir returns the .sprout directory for a workspace root, or "" when the workspace root is the user's home directory.

$HOME never gets a workspace layer. It is not a project, and the global config already *is* the config for a user working out of their home — a second layer there adds no expressiveness and one large footgun: NewManagerWithLayers makes the workspace dir the SAVE target, so a daemon running with workspace=$HOME (which is exactly what `sprout service install` produces) writes the full merged config to ~/.sprout/workspace.json on the first settings save. On the next start that machine-written file is read back as a deliberate per-workspace opt-in — which is how a global "embeddings: on" preference turned into indexing the entire home directory.

Returning "" here makes every caller degrade to "no workspace layer", which NewManagerWithLayers already handles by saving to the global dir instead.

func WorkspaceConfigWritePath added in v0.17.17

func WorkspaceConfigWritePath(workspaceRoot string) string

WorkspaceConfigWritePath returns where workspace-level config is written, or "" when there is no workspace layer (empty root, or $HOME — see WorkspaceConfigDir). Always the new filename — legacy files are read but never written back to.

func WorkspaceEmbeddingIndexEnabled added in v0.17.17

func WorkspaceEmbeddingIndexEnabled(workspaceRoot string) bool

WorkspaceEmbeddingIndexEnabled reports whether the workspace's stored config explicitly opts into the embedding index (enabled && experimental), decoded tolerantly like RestoreEmbeddingIndex: missing, unreadable, or malformed config is not enabled. SPROUT_EXPERIMENTAL_EMBEDDINGS default-on is deliberately NOT consulted here — the env var is an operator escape hatch for daemon-side restore/socket hosting, while explicit tool operations must require the workspace's own opt-in.

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. Default: true.
	Enabled *bool `json:"enabled,omitempty"`

	// ShellWalkEnabled controls whether the per-shell_command snapshot walk runs. Default: true.
	ShellWalkEnabled *bool `json:"shell_walk_enabled,omitempty"`

	// MaxFiles caps the number of files visited in a single walk. Default: 50000.
	MaxFiles int `json:"max_files,omitempty"`

	// MaxTotalBytes caps cumulative content bytes captured per walk. Default: 32 MiB.
	MaxTotalBytes int64 `json:"max_total_bytes,omitempty"`

	// MaxDurationMs is the wall-clock budget for a single walk. Default: 500ms.
	MaxDurationMs int `json:"max_duration_ms,omitempty"`

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

	// RevisionRetention controls how the persistent revision store is compacted.
	RevisionRetention *RevisionRetentionConfig `json:"revision_retention,omitempty"`
}

ChangeTrackingConfig gates and tunes the ChangeTracker.

func (*ChangeTrackingConfig) Resolve

Resolve fills in defaults for zero-value fields. Safe to call on nil.

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 CommandPolicies added in v0.17.5

type CommandPolicies struct {
	Rules []CommandRule `json:"rules"`
}

CommandPolicies is the top-level config structure for user command policies (SP-123). Rules are evaluated first-match-wins before the classifier and risk profile. The three actions (allow/ask/deny) override the default approval cascade.

type CommandPolicyAction added in v0.17.5

type CommandPolicyAction string

CommandPolicyAction determines what happens when a command matches a rule.

const (
	// CommandPolicyAllow auto-approves the command, skipping classifier, risk
	// profile, and interactive prompt. Does not override Critical-tier blocks.
	CommandPolicyAllow CommandPolicyAction = "allow"
	// CommandPolicyAsk forces an interactive prompt, skipping allowlist and
	// classifier auto-approve. Classifier risk is still computed for display.
	CommandPolicyAsk CommandPolicyAction = "ask"
	// CommandPolicyDeny hard-blocks the command, returning an error immediately.
	CommandPolicyDeny CommandPolicyAction = "deny"
)

type CommandRule added in v0.17.5

type CommandRule struct {
	// Pattern is a glob pattern (Go path.Match syntax) to match against
	// shell commands. For example: "git push*", "rm -rf /tmp/*".
	Pattern string `json:"pattern"`
	// Action determines the behavior when a command matches this rule.
	Action CommandPolicyAction `json:"action"`
	// Reason is an optional user note explaining why this rule exists.
	Reason string `json:"reason,omitempty"`
}

CommandRule is a single user-defined command policy rule.

type ComputerUseConfig added in v0.16.12

type ComputerUseConfig struct {
	// Enabled is the master switch. When false the computer_user tools are never registered.
	Enabled bool `json:"enabled,omitempty"`

	// MaxActionsPerMinute caps the action rate. Default: 60. Set to 0 to disable.
	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.
	WorkspaceAllowlist []string `json:"workspace_allowlist,omitempty"`

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

	// DestructiveAppGate controls whether the destructive-app denylist gate is active. Default: true.
	DestructiveAppGate bool `json:"destructive_app_gate,omitempty"`

	// OverrideFilePath is an optional override of the denylist override file location.
	OverrideFilePath string `json:"denylist_override_file,omitempty"`
}

ComputerUseConfig gates the computer_user persona's desktop-control tools. Off by default.

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 allows history-rewriting git commands
	// (reset --hard, rebase, branch -D, tag -d) via shell_command
	// without the git tool's approval flow. Default: false (gated).
	AllowGitHistoryRewrite bool `json:"allow_git_history_rewrite,omitempty"`

	// UnifiedRiskResolver enables the unified risk resolver. When true,
	// gating uses a single ResolveToolRisk assessment instead of the
	// legacy dual-gate path. Default: true.
	UnifiedRiskResolver bool `json:"unified_risk_resolver,omitempty"`

	// DaemonMultiSession enables concurrent browser windows in daemon mode.
	// Each connection gets its own chat session and agent. Default: true.
	DaemonMultiSession bool `json:"daemon_multi_session,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.
	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"`

	// RefreshSystemPromptOnModelChange re-derives the agent's system prompt
	// on every provider/model swap. Defaults to false.
	RefreshSystemPromptOnModelChange bool `yaml:"refresh_system_prompt_on_model_change,omitempty" json:"refresh_system_prompt_on_model_change,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.
	RiskProfile string `json:"risk_profile,omitempty"`

	// ContextMode selects a named context-engine preset: "" (full default) | "full" | "low_context".
	ContextMode ContextMode `json:"context_mode,omitempty"`

	// RiskProfiles allows the user to override the baked-in rules for any named profile.
	RiskProfiles map[string]AutoApproveRules `json:"risk_profiles,omitempty"`

	// ApprovedShellCommands is the user's persistent allowlist of literal
	// shell command strings that auto-approve through the high-risk cascade.
	ApprovedShellCommands []string `json:"approved_shell_commands,omitempty"`

	// ApprovedShellCommandPatterns is the user's persistent allowlist of glob
	// patterns for shell commands that auto-approve through the high-risk cascade.
	ApprovedShellCommandPatterns []string `json:"approved_shell_command_patterns,omitempty"`

	// CommandPolicies is the unified command policy layer with three actions:
	// allow (auto-approve), ask (force prompt), deny (hard block).
	CommandPolicies *CommandPolicies `json:"command_policies,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)

	// 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)

	// Completion Configuration
	CompletionProvider string `json:"completion_provider,omitempty"` // Provider for code completions (defaults to LastUsedProvider)
	CompletionModel    string `json:"completion_model,omitempty"`    // Model for code completions (defaults to provider's default model)

	// VisionFallbackToOCR enables transparent fallback to the OCR model when
	// the primary vision model fails after retries. Default: true.
	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"`

	// ComputerUse gates the computer_user persona's desktop-control tools. Off by default.
	ComputerUse *ComputerUseConfig `json:"computer_use,omitempty"`

	// Vision controls vision-pipeline runtime: parallel workers, concurrency cap, and batching.
	Vision *VisionConfig `json:"vision,omitempty"`

	// ChangeTracking gates the ChangeTracker shell-mutation snapshot walk.
	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"`   // Enable zsh-aware command detection (default: true)
	AutoExecuteDetectedCommands bool `json:"auto_execute_detected_commands"` // Auto-execute detected commands without prompting (default: true)

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

	// Shell is the user-configurable shell permission policy.
	Shell ShellConfig `json:"shell,omitempty"`

	// MaxContextTokens caps the effective context window. Nil or 0 means no cap.
	MaxContextTokens *int `json:"max_context_tokens,omitempty"`

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

	// EditApproval 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"`

	// ShowToolInvocations controls whether the UI expands per-tool
	// invocation details in the conversation output. When false, tool
	// calls are collapsed/hidden. Defaults to true.
	ShowToolInvocations bool `json:"show_tool_invocations,omitempty"`

	// Wakeup controls auto-resume behavior for background task completions.
	Wakeup WakeupConfig `json:"wakeup,omitempty"`

	// Training controls opt-in session recording for training data collection. OFF by default.
	Training TrainingConfig `json:"training,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).

Local override files (config.local.json / workspace.local.json) are automatically loaded as a higher-precedence sibling within their scope: global config.json is overridden by global config.local.json, workspace workspace.json by workspace.local.json. The session layer (highest) remains unchanged.

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) ExplicitKeyPaths added in v0.17.17

func (c *Config) ExplicitKeyPaths() []string

ExplicitKeyPaths lists the recorded paths, sorted-insensitive. Exposed for tests and provenance reporting.

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) GetCompletionModel added in v0.17.17

func (c *Config) GetCompletionModel() string

GetCompletionModel returns the configured model for code completions. If not explicitly set, falls back to the provider's default model.

func (*Config) GetCompletionProvider added in v0.17.17

func (c *Config) GetCompletionProvider() string

GetCompletionProvider returns the configured provider for code completions. Returns an empty string if no explicit completion provider is set; callers should fall back to the main provider.

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) GetRefreshSystemPromptOnModelChange added in v0.17.7

func (c *Config) GetRefreshSystemPromptOnModelChange() bool

GetRefreshSystemPromptOnModelChange (Spec B) reports whether the agent should re-derive its system prompt on every provider/model swap instead of freezing it at agent-creation time.

When false (the default), the prompt is set once in initAgentFromResolvedProvider and never touched on subsequent SetProvider/SetModel calls. This preserves bit-identical behavior for existing sessions that may have observed and relied on a particular prompt composition.

When true, the agent's refreshSystemPrompt() (called from setClient) re-runs GetEmbeddedSystemPromptForProfile against the active provider and context window. The configured SystemPromptText override still wins — only the embedded portion is re-derived.

This getter exists alongside the field so call sites read through a stable accessor (and so future migrations can change the storage shape without touching every read site). A nil-safe return of false matches the field's zero-value default and keeps bare Config literals in tests working without explicit initialization.

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) IsExplicitlySet added in v0.17.17

func (c *Config) IsExplicitlySet(path string) bool

IsExplicitlySet reports whether a dotted JSON path was present in the layer this config was decoded from. Configs built in memory (tests, defaults) carry no provenance and always report false.

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/SPROUT_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. SaveToDir writes the config as ConfigFileName inside dir.

func (*Config) SaveToDirAs added in v0.17.17

func (c *Config) SaveToDirAs(dir, fileName string) error

SaveToDirAs writes the config to dir under an explicit filename. Workspace layers use WorkspaceConfigFileName so they can never collide with the user-level config.json when the workspace root is $HOME.

func (*Config) SectionExplicitlySet added in v0.17.17

func (c *Config) SectionExplicitlySet(section string) bool

SectionExplicitlySet reports whether any field under a section was named by the layer, e.g. SectionExplicitlySet("embedding_index").

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) SetCompletionModel added in v0.17.17

func (c *Config) SetCompletionModel(model string)

SetCompletionModel sets the model for code completions

func (*Config) SetCompletionProvider added in v0.17.17

func (c *Config) SetCompletionProvider(provider string)

SetCompletionProvider sets the provider for code completions

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 ContextMode added in v0.17.7

type ContextMode string

ContextMode is the user-facing context-engine selector. Empty string defaults to "full".

const (
	// ContextModeFull is the default: all tools, full prompt, proactive context enabled.
	ContextModeFull ContextMode = "full"

	// ContextModeLowContext activates the LCM levers: curated 12-tool allowlist, lite prompt, etc.
	ContextModeLowContext ContextMode = "low_context"
)

type ContextProfile added in v0.17.7

type ContextProfile struct {
	Mode                      ContextMode `json:"mode,omitempty"`
	ToolAllowlist             []string    `json:"tool_allowlist,omitempty"`
	SystemPromptPath          string      `json:"system_prompt_path,omitempty"`
	SkipProactiveContext      bool        `json:"skip_proactive_context,omitempty"`
	CompactionTriggerFraction float64     `json:"compaction_trigger_fraction,omitempty"`
	RecentTurnsToPreserve     int         `json:"recent_turns_to_preserve,omitempty"`
	RepoMapDefaultDepth       int         `json:"repo_map_default_depth,omitempty"`
}

ContextProfile is the resolved shape of every context-engine lever. A zero value means "use all defaults" (full mode, no overrides).

func ResolveContextProfile added in v0.17.7

func ResolveContextProfile(cfg *Config, modelContextWindow int) (ContextProfile, error)

ResolveContextProfile picks the effective profile from the user's config plus the detected model context window. Precedence: hard floor > explicit ContextMode > auto-detect by window > full default.

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)

NormalizeCustomProviderConfig fills in defaults, trims whitespace, and rejects ill-formed values. Called by Save and Load paths so a hand-edited JSON file cannot put the runtime into a state where discovery silently fails later.

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.

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.
	// nil means "not specified at this layer" — inherit from a broader layer.
	Enabled *bool `json:"enabled,omitempty"`

	// Experimental is a second, independent gate required alongside Enabled.
	// Off by default, and — critically — a workspace config persisted before
	// this field existed has no "experimental" key at all, so it decodes to
	// nil (off) regardless of what Enabled was set to. That's deliberate:
	// full-workspace auto-indexing was found to cause severe, unbounded
	// native-memory growth (multi-GB spikes, outside what Go's own memory
	// accounting or limits can see or bound — see pkg/embedding/index.go).
	// Existing users with Enabled already true from before this gate must
	// explicitly opt in again rather than silently keep auto-indexing.
	Experimental *bool `json:"experimental,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"`

	// 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.
	// nil means "not specified at this layer" — inherit from a broader layer.
	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. Enabled and AutoIndex are *bool so a layer can distinguish "off" from "unspecified".

func (*EmbeddingIndexConfig) IsAutoIndex added in v0.17.17

func (e *EmbeddingIndexConfig) IsAutoIndex() bool

IsAutoIndex reports whether the index builds automatically. Unspecified is off.

func (*EmbeddingIndexConfig) IsEnabled added in v0.17.17

func (e *EmbeddingIndexConfig) IsEnabled() bool

IsEnabled reports whether the embedding index is on. Requires both Enabled and Experimental — unspecified (nil) is off for either.

func (*EmbeddingIndexConfig) IsExperimental added in v0.17.17

func (e *EmbeddingIndexConfig) IsExperimental() bool

IsExperimental reports whether the experimental embedding-index opt-in is set. Unspecified is off.

func (*EmbeddingIndexConfig) SetAutoIndex added in v0.17.17

func (e *EmbeddingIndexConfig) SetAutoIndex(v bool)

func (*EmbeddingIndexConfig) SetEnabled added in v0.17.17

func (e *EmbeddingIndexConfig) SetEnabled(v bool)

SetEnabled, SetExperimental, and SetAutoIndex record an explicit value.

func (*EmbeddingIndexConfig) SetExperimental added in v0.17.17

func (e *EmbeddingIndexConfig) SetExperimental(v bool)

type KnownProviderInfo added in v0.16.25

type KnownProviderInfo struct {
	// Source identifies where the metadata came from.
	// "custom" = user's ~/.config/sprout/providers/<name>.json
	// "factory" = embedded config in pkg/agent_providers/configs/
	//            or upserted via refreshFromRemote
	Source string

	// Name is the canonical provider name (lowercase, trimmed).
	Name string

	// DisplayName is the friendly label shown in UI surfaces.
	DisplayName string

	// EnvVar is the environment variable the provider expects for
	// authentication (e.g. "OPENAI_API_KEY"). Empty when no auth
	// is required.
	EnvVar string

	// RequiresAPIKey reports whether the provider needs an API key.
	RequiresAPIKey bool

	// Endpoint is the chat endpoint URL when known.
	Endpoint string

	// DefaultModel is the configured default model when known.
	DefaultModel string

	// ContextSize is the configured default context size in tokens.
	ContextSize int
}

KnownProviderInfo describes a provider the runtime already knows about, either from the user's custom provider config or the embedded factory. The `sprout custom add` wizard uses this to detect when a user is "registering credentials for an existing provider" rather than "registering a brand-new OpenAI-compatible endpoint".

func LookupKnownProvider added in v0.16.25

func LookupKnownProvider(name string) (info KnownProviderInfo, ok bool)

LookupKnownProvider returns metadata for a provider the runtime knows about, checking both the user's custom provider config and the embedded factory. Returns ok=false when the name doesn't match any known provider — in which case the wizard should run the full URL/discovery flow.

The factory lookup uses GetProviderAuthMetadata, which the runtime factory populates via SetProviderConfigLookup. This is safe to call from the wizard because configuration init() ensures the package compiles even without a registered factory.

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 + SPROUT_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).
	Browser bool `json:"browser,omitempty"`
	// MinSeconds is the minimum turn duration before a notification is sent. Default: 10.
	MinSeconds float64 `json:"min_seconds,omitempty"`
}

NotificationsConfig controls how the agent notifies the user when long-running turns complete.

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. Default: true.
	ProactiveContextEnabled bool `json:"proactiveContextEnabled,omitempty"`

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

	// MinRelevanceScore is the minimum time-decayed cosine similarity score. Range: 0.0–1.0. Default: 0.50.
	MinRelevanceScore float64 `json:"minRelevanceScore,omitempty"`

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

	// WorkspaceScopedRetrieval restricts retrieval to the current workspace. Default: false.
	WorkspaceScopedRetrieval bool `json:"workspaceScopedRetrieval,omitempty"`

	// DriftDetectionEnabled controls whether conversational drift detection is active. Default: true.
	DriftDetectionEnabled bool `json:"driftDetectionEnabled,omitempty"`

	// DriftThreshold is the cosine similarity threshold below which drift is flagged. Default: 0.60.
	DriftThreshold float64 `json:"driftThreshold,omitempty"`

	// DriftCheckInterval is the number of turns between drift checks. Default: 5.
	DriftCheckInterval int `json:"driftCheckInterval,omitempty"`

	// RetentionDays controls how many days to keep persistent context entries. Default: 0 (never expire).
	RetentionDays int `json:"retentionDays,omitempty"`
}

PersistentContextConfig configures persistent conversational context and memory retrieval.

func (*PersistentContextConfig) Resolve

Resolve fills in defaults for zero-value fields. Safe to call on nil.

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. Default: 200.
	HotCount int `json:"hot_count,omitempty"`

	// WarmCount: next M revisions with conversation.json dropped. Default: 500.
	WarmCount int `json:"warm_count,omitempty"`

	// MaxDirBytes is the cap on total revisions+changes disk usage. Default: 1 GiB.
	MaxDirBytes int64 `json:"max_dir_bytes,omitempty"`

	// ArchiveFrozen: if true, dropped revisions are moved to _frozen/ instead of deleted. Default: false.
	ArchiveFrozen bool `json:"archive_frozen,omitempty"`

	// MaxChangesPerRevision caps the number of change records kept per revision. Default: 10000.
	MaxChangesPerRevision int `json:"max_changes_per_revision,omitempty"`

	// MaxChangesAgeDays drops change records older than this many days. Default: 30. Negative to disable.
	MaxChangesAgeDays int `json:"max_changes_age_days,omitempty"`
}

RevisionRetentionConfig controls the quantity-based compaction of the persistent revision history.

func (*RevisionRetentionConfig) Resolve

Resolve fills in defaults for zero-value fields. Safe to call on nil.

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 TrainingConfig added in v0.17.5

type TrainingConfig struct {
	// Endpoint is the URL to push training data to (e.g. http://localhost:8190).
	// Sessions are POSTed to {Endpoint}/sessions as JSON.
	Endpoint string `json:"endpoint,omitempty"`

	// Enabled controls whether training data is collected and pushed.
	// ALWAYS false by default — must be explicitly enabled.
	Enabled bool `json:"enabled,omitempty"`

	// ExcludePaths is a list of working directory prefixes to exclude from
	// training data. Sessions whose working directory starts with any of
	// these paths are silently skipped.
	ExcludePaths []string `json:"exclude_paths,omitempty"`
}

TrainingConfig controls opt-in session recording for training data collection. When enabled, PII-redacted conversation states are pushed to the configured endpoint after each session save.

type VisionConfig added in v0.16.19

type VisionConfig struct {
	// ParallelWorkers caps concurrent in-flight vision requests per session. Default: 3. Range: 1..32.
	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. Default: true.
	EnableBatchProcessing bool `json:"enable_batch_processing,omitempty"`

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

VisionConfig controls vision-pipeline runtime: parallel workers, concurrency cap, and batching.

func GetVisionConfig added in v0.16.19

func GetVisionConfig() VisionConfig

GetVisionConfig returns the raw VisionConfig from the on-disk config file.

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 true
	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.

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