configuration

package
v0.17.11 Latest Latest
Warning

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

Go to latest
Published: Jul 30, 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: SP-125 low-context mode (LCM) abstraction.

ContextProfile is the resolved shape every downstream call site reads when it needs to know whether sprout is operating in full-context mode (default) or low-context mode. The profile is computed once at agent creation by ResolveContextProfile and stored on the Agent — call sites must never re-derive it (see SP-125 R5 / "Resolution is centralized" in the roadmap).

The split between Config.ContextMode (the user-facing selector) and ContextProfile (the resolved lever values) mirrors the existing Config.RiskProfile / AutoApproveRules split: a small user-facing knob expands into a struct full of concrete preset values. A future medium mode is one new preset value, not a new config field.

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

Index

Constants

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

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

ContextFloor is the absolute minimum context window at which sprout will start at all, regardless of profile. Below this, even the lite prompt (~1.5K tokens) plus a single read_file call (~2.5K tokens) plus a minimal response leaves no room to operate. ResolveContextProfile hard-errors when the caller reports a known window below this floor so the user gets a clear directive rather than a silent broken session.

Set at 8K: comfortably above the ~4K absolute minimum (prompt + one tool I/O + response) but below the smallest practical model context windows on the market. Models in the 8K–32K band get LCM; below 8K gets refused. Deliberately not user-tunable; the floor is a guardrail, not a knob.

View Source
const EffectiveContextCapMinimum = 1024

EffectiveContextCapMinimum (SP-126) is the minimum cap a user may explicitly set via Config.MaxContextTokens. Below this, the agent is not operable: every prompt exceeds the cap on the first iteration, compaction fires immediately, and the model produces nothing user-visible. The minimum matches the value already enforced by the /max-context slash command (max_context.go:64) and the settings validator (settings_defs.go), so users see a consistent message regardless of which surface they used to set the cap.

Set at 1024: enough for a system prompt + one tool round-trip on the most aggressive LCM profile, and small enough that no one accidentally gets a "sprout produces no output" experience.

Nil/zero caps (no cap configured) bypass this minimum and resolve to the native window — only EXPLICITLY SET caps below this minimum trigger the error.

View Source
const ProvidersDirName = "providers"

Variables

This section is empty.

Functions

func AllowedSkillsPath

func AllowedSkillsPath(projectRoot string) string

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

func BootstrapIsolatedConfig

func BootstrapIsolatedConfig(configDir string) error

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

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

func CanonicalizeCustomProviderName

func CanonicalizeCustomProviderName(name string) (string, error)

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

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 EffectiveContextCapMinimum. The wording matches the existing /max-context and settings_defs validators exactly so users see the same message regardless of which surface they used:

"value must be at least 1024 when setting a cap (got X)"

Centralized here so the three call sites (slash command, settings validator, resolver) stay in sync.

func EnsureProviderAPIKey

func EnsureProviderAPIKey(provider string, apiKeys *APIKeys) error

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

func GetAPIKeysPath

func GetAPIKeysPath() (string, error)

GetAPIKeysPath returns the full path to the API keys file

func GetAvailableProviders

func GetAvailableProviders() []string

GetAvailableProviders returns all supported providers

func GetConfigDir

func GetConfigDir() (string, error)

GetConfigDir returns the configuration directory path

func GetConfigPath

func GetConfigPath() (string, error)

GetConfigPath returns the full path to the config file

func GetCustomProviderPath

func GetCustomProviderPath(name string) (string, error)

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 path to workspace-level config

func HasProviderAuth

func HasProviderAuth(provider string) bool

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

func Initialize

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

Initialize loads or creates configuration with first-run setup

func IsConfigConflict

func IsConfigConflict(err error) bool

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

func IsCriticalOperation

func IsCriticalOperation(command string) bool

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

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

Covers:

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

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

func IsValidRiskProfile

func IsValidRiskProfile(s string) bool

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

func IsWorkspaceConfigPresent

func IsWorkspaceConfigPresent(workspaceRoot string) bool

IsWorkspaceConfigPresent checks if a workspace config file exists

func KnownProviderNames added in v0.16.12

func KnownProviderNames() []string

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

func LoadCustomProviders

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

LoadCustomProviders loads all custom provider configs. 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 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 for this session — the smaller of (a) the model's native context window and (b) the user's configured MaxContextTokens cap. This is the single source of truth for the cap; every call site in sprout MUST read the value returned here (typically once at agent creation, then stored on the Agent for hot-path access) and MUST NOT re-derive it from Config.MaxContextTokens or call client.GetModelContextLimit() directly — those paths bypass the cap.

Inputs:

  • cfg: the user's config. May be nil (no cap, no error).
  • nativeContextWindow: the model's native context window in tokens. May be 0 or negative (unknown). When unknown, the cap is the user-configured value alone; if neither is known the return is 0, which callers treat as "no cap".

Output:

  • cap: the resolved effective cap in tokens. 0 means "no cap".

Errors:

  • Returns an error ONLY when the user explicitly set a cap below EffectiveContextCapMinimum (1024). The nil/zero cap (no cap configured) bypasses the minimum and resolves to the native window — only EXPLICITLY SET caps below the minimum trigger the error. The error message matches the /max-context and settings_defs validators exactly so users see consistent feedback across surfaces.

Precedence (highest first):

  1. If cfg.MaxContextTokens is non-nil and > 0, AND the native window is known (> 0), return min(native, *cfg.MaxContextTokens).
  2. If only one of the two is known, return that one.
  3. If neither is known, return 0. Call sites treat 0 as "unknown" and fall back to whatever default is appropriate (typically the native value reported by the client, or a hardcoded fallback).

Independent of ContextProfile (SP-125): a 1M model can run in full mode with a 300K cap; a 32K model can run in LCM with no cap. Both are valid. The cap and the profile are deliberately separate concerns — the cap is a user cost preference, the profile is a model-size accommodation.

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 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 WriteAllowedSkills

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

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

Types

type APIKeys

type APIKeys map[string]string

func LoadAPIKeys

func LoadAPIKeys() (*APIKeys, error)

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

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

func LoadAPIKeysFromDir

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

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

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

func (APIKeys) Get

func (a APIKeys) Get(provider string) string

Optional helpers

func (*APIKeys) GetAPIKey

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

GetAPIKey returns the API key for a provider

func (*APIKeys) HasAPIKey

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

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

func (*APIKeys) PopulateFromEnvironment

func (keys *APIKeys) PopulateFromEnvironment() bool

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

func (*APIKeys) PopulateFromJSONEnv

func (keys *APIKeys) PopulateFromJSONEnv() bool

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

func (*APIKeys) Set

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

func (*APIKeys) SetAPIKey

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

SetAPIKey sets the API key for a provider

type APITimeoutConfig

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

APITimeoutConfig represents timeout settings for API calls

type AutoApproveRules

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

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

func AutoApproveRulesForProfile

func AutoApproveRulesForProfile(profile RiskProfile) AutoApproveRules

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

func DefaultAutoApproveRules

func DefaultAutoApproveRules() AutoApproveRules

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

func ResolveRiskProfileRules

func ResolveRiskProfileRules(cfg *Config, profile RiskProfile) AutoApproveRules

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

Resolution order:

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

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

type ChangeTrackingConfig

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

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

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

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

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

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

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

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

func (*ChangeTrackingConfig) Resolve

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

type CombinedAssessment

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

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

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

func CombinedSecurityAssessment

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

CombinedSecurityAssessment evaluates a tool call against the security policy.

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

This interface avoids a circular import between configuration and agent_tools.

type 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 (default) the computer_user
	// persona's mouse/keyboard/screenshot tools are never registered or
	// executed. Turning it on is a deliberate, one-time user choice.
	Enabled bool `json:"enabled,omitempty"`

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

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

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

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

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

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

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

func (*ComputerUseConfig) Resolve added in v0.16.12

func (c *ComputerUseConfig) Resolve() ComputerUseConfig

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

type Config

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

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

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

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

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

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

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

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

	// DaemonMultiSession enables N concurrent browser windows per user
	// when the server runs in service/daemon mode (SPROUT_SERVICE=1).
	// When true, the WebSocket dispatcher routes new connections through
	// the multi-session path (handleWebSocket_Daemon); each connection
	// gets its own chat session and its own agent without colliding
	// with sibling windows. When false, the daemon falls back to the
	// single-active-session path (handleWebSocket_Agent) and the second
	// window triggers session_conflict + takeover — the pre-SP-118
	// behavior.
	//
	// Effective value at the dispatch site is
	//   effective = (agentEnforceSingleSession == false) && DaemonMultiSession
	// i.e. the agent path always uses Mode 1 regardless of this flag,
	// and the daemon path uses Mode 2 only when this flag is true.
	//
	// Default: true (the daemon opens with multi-session on, so three
	// browser windows on the same daemon each get their own chat
	// without a takeover prompt). Set to false to opt out without
	// editing code — useful for rolling forward / back during the
	// SP-118 rollout window. SP-118 Phase 4 will flip the rollout so
	// this default is the new normal.
	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.
	// When true, models like Qwen3, Qwen3.5, GLM models, and Minimax models will
	// not use their thinking/reasoning mode. Note: GPT-OSS models do not support
	// disabling thinking (they use reasoning_effort instead).
	DisableThinking bool `json:"disable_thinking,omitempty"`

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

	// RefreshSystemPromptOnModelChange (Spec B): when true, re-derive the
	// agent's system prompt on every provider/model swap instead of
	// freezing it at agent-creation time. Different models respond very
	// differently to the same prompt — a Claude-tuned prompt sent to
	// GPT-4 may be silently ignored. With this on, the agent re-runs
	// GetEmbeddedSystemPromptForProfile on each SetProvider / SelectProvider
	// so the prompt stays in sync with the active provider and context
	// window. Defaults to false so existing sessions are bit-identical.
	// Flip to true after observability shows the new behavior lands
	// cleanly. The agent's `SystemPromptText` override still wins — only
	// the embedded portion is re-derived, the configured override is
	// re-applied on top.
	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. Empty or unrecognized values resolve to "default"
	// via AutoApproveRulesForProfile. Per-persona AutoApproveRules
	// always win over the profile. The CLI's --risk-profile flag and
	// a workflow step's "risk_profile" field both override this
	// value.
	RiskProfile string `json:"risk_profile,omitempty"`

	// ContextMode selects a named context-engine preset: "" (full
	// default) | "full" | "low_context". Resolved into a ContextProfile
	// via ResolveContextProfile at agent creation; call sites read
	// fields off the resolved profile rather than this string directly.
	// Empty/unrecognized values fall through to auto-detection by the
	// resolver (which honors a small enough model window).
	ContextMode ContextMode `json:"context_mode,omitempty"`

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

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

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

	// CommandPolicies is the unified command policy layer (SP-123) with
	// three actions: allow (auto-approve), ask (force prompt), deny
	// (hard block). Rules are evaluated before the classifier and risk
	// profile, first-match-wins. Replaces the fragmented
	// approved_shell_commands / approved_shell_command_patterns /
	// security_policy.denied_commands surfaces. Legacy fields remain for
	// backward compatibility; MigrateCommandPolicies converts them on load.
	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)

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

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

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

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

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

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

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

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

	// Zsh Command Execution
	EnableZshCommandDetection   bool `json:"enable_zsh_command_detection"`   // 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 Configuration — user-configurable shell permission policy
	// (SP-049 Phase 2). Lets users define safe/dangerous command patterns
	// and a workspace-overlay mode.
	Shell ShellConfig `json:"shell,omitempty"`

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

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

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

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

	// 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
	// (SP-108). When enabled, the daemon automatically processes pending
	// notifications by calling ProcessQueryWithContinuity so the agent can
	// act on completed background tasks without the user sending a manual
	// message. Budget controls prevent unattended token burn loops.
	Wakeup WakeupConfig `json:"wakeup,omitempty"`

	// Training controls opt-in session recording for training data
	// collection. When enabled, each saved session is PII-redacted and
	// pushed to the configured endpoint as JSON. OFF by default — must
	// be explicitly enabled via --train flag, SPROUT_TRAIN_ENABLED env,
	// or config.json.
	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).

func LoadOrInitConfig

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

func MergeConfig

func MergeConfig(base, override *Config) *Config

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

func NewConfig

func NewConfig() *Config

NewConfig creates a new configuration with sensible defaults

func RedactConfig

func RedactConfig(cfg *Config) Config

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

func (*Config) GetAllEnabledSkills

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

GetAllEnabledSkills returns all enabled skills

func (*Config) GetCommitModel

func (c *Config) GetCommitModel() string

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

func (*Config) GetCommitProvider

func (c *Config) GetCommitProvider() string

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

func (*Config) 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) 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.

func (*Config) SetCommitModel

func (c *Config) SetCommitModel(model string)

SetCommitModel sets the model for commit message generation

func (*Config) SetCommitProvider

func (c *Config) SetCommitProvider(provider string)

SetCommitProvider sets the provider for commit message generation

func (*Config) SetModelForProvider

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

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

func (*Config) SetPersonaDisabled added in v0.16.4

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

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

func (*Config) SetReviewModel

func (c *Config) SetReviewModel(model string)

SetReviewModel sets the model for review commands

func (*Config) SetReviewProvider

func (c *Config) SetReviewProvider(provider string)

SetReviewProvider sets the provider for review commands

func (*Config) SetSubagentModel

func (c *Config) SetSubagentModel(model string)

SetSubagentModel sets the model for subagents

func (*Config) SetSubagentProvider

func (c *Config) SetSubagentProvider(provider string)

SetSubagentProvider sets the provider for subagents

func (*Config) Validate

func (c *Config) Validate() error

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

type ConfigConflictError

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

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

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

func (*ConfigConflictError) Error

func (e *ConfigConflictError) Error() string

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

type ContextMode added in v0.17.7

type ContextMode string

ContextMode is the user-facing context-engine selector. The empty string ("") is treated identically to "full" for resolution — it's the zero value of the field and must be safe to leave unset. Persisted as json:"context_mode,omitempty"; any value that doesn't match one of the two named constants falls through to auto-detection via ResolveContextProfile (so a typo in the config file degrades gracefully to the default rather than becoming a hard error).

const (
	// ContextModeFull is the default sprout mode: all 44 tools, the full
	// orchestrator system prompt, project AGENTS.md injected, proactive
	// context enabled, and the standard compaction trigger (0.70).
	// Resolved by passing a zero-value ContextProfile — every lever
	// reads as empty/zero/false and downstream code already treats that
	// as "use built-in default".
	ContextModeFull ContextMode = "full"

	// ContextModeLowContext activates the LCM levers (curated 8-tool
	// allowlist, lite prompt, no proactive context, compaction trigger
	// 0.85, recency 2, repo-map depth 1). AGENTS.md is still injected
	// (project conventions are mandatory in every mode). Activated
	// explicitly via config, or auto-detected when the selected model
	// reports a context window below SubagentMinContext (64K).
	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. Constructed exactly once via ResolveContextProfile at agent creation and read downstream at every call site that depends on it. A zero value is intentionally safe and means "use all defaults" — i.e. full mode, no overrides.

Field semantics:

  • Mode: which preset was selected (Full vs LowContext). Call sites that want to branch on *intent* read this; call sites that want to branch on *behavior* read the boolean/value fields below.

  • ToolAllowlist: if non-empty, downstream tool registration filters BuildToolDefinitions to only these names. Order is preserved when exposed to the model. Empty means "all tools available" — the zero/full default.

  • SystemPromptPath: the embedded prompt path to load (relative to pkg/agent/prompts). Empty means use the default full prompt; downstream code maps the path suffix to the right //go:embed variable. The two currently-known values are "prompts/system_prompt.md" (default, empty string equivalent) and "prompts/system_prompt.lite.md" (LCM).

  • SkipProactiveContext: when true, downstream prompt builders skip the semantically-recalled prior-turn block injected after turn 1. Disables cross-session recall in this session.

  • CompactionTriggerFraction: when non-zero, overrides the default trigger fraction (1.0 - totalReservedFraction(), 0.70 in full mode). LCM uses 0.85 to push compaction closer to the window edge so the model has more working room per turn.

  • RecentTurnsToPreserve: when non-zero, overrides the default recent-turn count kept at full fidelity during rollups (default 5 in full mode). LCM uses 2 because LCM sessions are short (2–4 round-trips) and the recency window is almost the whole conversation.

  • RepoMapDefaultDepth: when non-zero, overrides the default depth passed to repo_map (default 3 in full mode). LCM uses 1 (directory tree only, no symbols) to keep repo_map output under ~800 tokens.

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. Called once at agent creation; the result is stored on the Agent and read by every downstream call site.

Precedence (highest first):

  1. Hard floor — if modelContextWindow is a known positive value below ContextFloor (8K), return an error. This is unconditional: even explicit "full" cannot rescue the session because no amount of lever-pulling fits prompt + one tool round-trip + a response below ~4K tokens. The caller is expected to surface the error to the user.

  2. Explicit cfg.ContextMode — "low_context" or "full" both win outright over auto-detection. A user who explicitly sets the field has overridden any window-based guess.

  3. Auto-detect — a known context window below subagentContextThreshold (64K) flips LCM on with a strong warning (callers can detect the auto-detect case by comparing the returned Mode to what cfg requested, or via a future explicit-notice hook).

  4. Default — fullContextProfile. Applies when cfg is nil, when cfg.ContextMode is empty or unrecognized, or when the model context window is unknown (0 / negative).

Both cfg==nil and modelContextWindow<=0 are tolerated — those are the "we don't know yet" inputs and they must not error. They resolve to the full preset (step 4).

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 (SP-072).

func (*EditApprovalConfig) Resolve added in v0.16.12

func (*EditApprovalConfig) ShouldGate added in v0.16.12

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

type EmbeddingIndexConfig

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

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

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

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

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

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

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

type 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, SP-070-4).
	Browser bool `json:"browser,omitempty"`
	// MinSeconds is the minimum turn duration (in seconds) before a
	// notification is sent.  Default: 10.0.  Turns completing in less
	// than this are considered too brief to warrant a notification.
	MinSeconds float64 `json:"min_seconds,omitempty"`
}

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

func (*NotificationsConfig) Resolve added in v0.16.12

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

type PersistentContextConfig

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

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

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

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

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

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

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

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

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

PersistentContextConfig configures persistent conversational context and memory retrieval.

func (*PersistentContextConfig) Resolve

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

type ProviderAuthMetadata

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

func GetProviderAuthMetadata

func GetProviderAuthMetadata(provider string) (ProviderAuthMetadata, error)

type ProviderConfigLookupFunc added in v0.16.8

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

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

type ProviderDiscoveryModel

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

func DiscoverCustomProviderModels

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

type ProviderDisplayNameLookupFunc added in v0.16.8

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

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

type ProviderNamesLookupFunc added in v0.16.8

type ProviderNamesLookupFunc func() []string

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

type RevisionRetentionConfig

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

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

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

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

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

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

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

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

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

See AGENTS.md "Change Tracking" for context.

func (*RevisionRetentionConfig) Resolve

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

type RiskLevel

type RiskLevel string

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

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

func MoreRestrictiveRiskLevel added in v0.16.7

func MoreRestrictiveRiskLevel(a, b RiskLevel) RiskLevel

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

func (RiskLevel) IsAtLeast added in v0.16.7

func (r RiskLevel) IsAtLeast(other RiskLevel) bool

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

func (RiskLevel) Rank added in v0.16.7

func (r RiskLevel) Rank() int

Rank returns the severity ordering of a risk level:

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

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

type RiskProfile

type RiskProfile string

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

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

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

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

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

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

type SecurityPolicy

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

SecurityPolicy defines workspace-level security rules

func DefaultSecurityPolicy

func DefaultSecurityPolicy() *SecurityPolicy

DefaultSecurityPolicy returns a conservative default policy

func LoadSecurityPolicy

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

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

func (*SecurityPolicy) Evaluate

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

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

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

func (*SecurityPolicy) IsCommandDenied

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

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

func (*SecurityPolicy) IsPathAllowed

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

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

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

func (*SecurityPolicy) IsPathDenied

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

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

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

func (*SecurityPolicy) MaxAllowedRisk

func (p *SecurityPolicy) MaxAllowedRisk() int

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

type SecurityPolicyAction

type SecurityPolicyAction string

SecurityPolicyAction represents the action to take when a rule matches

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

func (SecurityPolicyAction) IsValid

func (a SecurityPolicyAction) IsValid() bool

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

type SecurityRule

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

SecurityRule defines a pattern-based rule for command evaluation

type ShellConfig added in v0.16.12

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

ShellConfig holds user-configurable shell permission policy.

func (*ShellConfig) Validate added in v0.16.12

func (sc *ShellConfig) Validate() error

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

type ShellPattern added in v0.16.12

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

ShellPattern is a single user-defined shell classification pattern.

type Skill

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

Skill defines an Agent Skill that can be loaded into context

type SubagentType

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

SubagentType defines a specialized subagent persona with its own configuration

func (*SubagentType) EvaluateOperationRisk

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

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

func (*SubagentType) GetAutoApproveRules

func (st *SubagentType) GetAutoApproveRules() AutoApproveRules

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

func (*SubagentType) HasCapability added in v0.16.4

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

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

type 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. Vision requests are heavyweight (large payloads, slow
	// provider round-trips) so this defaults to 3 — independent of the
	// generic request_parallelism setting.
	//
	// Range: 1..32. Values outside this range are clamped in Resolve().
	// Set to 0 to fall back to the default (3).
	ParallelWorkers int `json:"parallel_workers,omitempty"`

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

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

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

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

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

func GetVisionConfig added in v0.16.19

func GetVisionConfig() VisionConfig

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

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

func (*VisionConfig) Resolve added in v0.16.19

func (c *VisionConfig) Resolve() VisionConfig

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

type WakeupConfig added in v0.16.19

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

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

func DefaultWakeupConfig added in v0.16.19

func DefaultWakeupConfig() WakeupConfig

DefaultWakeupConfig returns conservative defaults.

type WorkspaceOverlayConfig added in v0.16.12

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

WorkspaceOverlayConfig controls how workspace-rooted policy files are loaded.

Jump to

Keyboard shortcuts

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