Documentation
¶
Overview ¶
Package config carries the runtime configuration the evva agent and its bundled tools read at startup and during a session.
The package is brand-neutral: a Config is constructed via Load(appName, appHome, workdir) so downstream apps can choose their own home directory (e.g. ~/.myapp/) and binary name. LoadDefault preserves evva's historical behavior (~/.evva/) for the bundled CLI.
There is no package-level singleton. Callers construct one Config per process (or per agent, if running multiple agents with different configurations) and pass it through agent.New via WithConfig.
Index ¶
- Constants
- Variables
- func DisplayVersion() string
- func ResolveDefaultModel(provider, model string) (constant.LLMProvider, constant.Model, error)
- func SaveFileConfig(path string, cfg FileConfig) error
- type APIConfig
- type Config
- func (c *Config) Clone() *Config
- func (c *Config) Effort() string
- func (c *Config) GetAutoCompactThreshold() float64
- func (c *Config) GetDisplayThinking() bool
- func (c *Config) GetEnableAutoMemory() bool
- func (c *Config) IsDevelopment() bool
- func (c *Config) IsProduction() bool
- func (c *Config) SaveFile() error
- func (c *Config) SetAutoCompactThreshold(v float64) error
- func (c *Config) SetDefaultEffort(level string) error
- func (c *Config) SetDefaultModel(provider constant.LLMProvider, model constant.Model) error
- func (c *Config) SetDefaultProfile(name string) error
- func (c *Config) SetDisplayThinking(v bool) error
- func (c *Config) SetEnableAutoMemory(v bool) error
- func (c *Config) SetFetchMaxBytes(n int) error
- func (c *Config) SetMaxIterations(n int) error
- func (c *Config) SetMaxTokens(n int) error
- func (c *Config) SetProviderAPIKey(name, key string) error
- func (c *Config) SetProviderAPIURL(name, url string) error
- func (c *Config) SetProviderCredentials(name, apiURL, apiKey string) error
- func (c *Config) SetTavilyAPIKey(s string) error
- type FileConfig
- type FileProviderConfig
- type LoadOptions
Examples ¶
Constants ¶
const ( DefaultAppName = "evva" DefaultAppVersion = "0.1.0" )
Default values that appear unchanged across all Config instances.
Variables ¶
var BuildDate string
BuildDate is the UTC build timestamp injected at build time. Empty in dev builds.
var CommitSHA string
CommitSHA is the git commit hash injected at build time via ldflags. Empty in dev builds.
var Version string
Version is the canonical version string injected at build time via ldflags:
go build -ldflags "-X github.com/johnny1110/evva/pkg/config.Version=v1.2.3"
When empty (dev builds, go run), DefaultAppVersion is used as the fallback.
Functions ¶
func DisplayVersion ¶
func DisplayVersion() string
DisplayVersion returns the best available version string: the ldflags-injected Version, or DefaultAppVersion if not set, followed by the commit and build date when available. The result is meant for --version output.
func ResolveDefaultModel ¶
ResolveDefaultModel parses the (provider name, model name) pair from the YAML and returns the typed constants. Validates that the model is actually one the provider lists — a typo or a model/provider mismatch fails fast at startup with a clear message rather than a confusing runtime "unknown model" from the LLM API.
func SaveFileConfig ¶
func SaveFileConfig(path string, cfg FileConfig) error
SaveFileConfig writes cfg to path atomically (temp file + rename) so a crash mid-write never leaves a truncated YAML behind.
Types ¶
type APIConfig ¶
APIConfig carries the per-provider credentials an LLM client needs to talk to its backend. The host (cmd/evva or a downstream consumer) constructs one APIConfig per provider from whatever config source it uses (YAML, env vars, secret manager) and passes it to the registry-resolved ClientFactory in pkg/llm.
Defined in pkg/config rather than pkg/llm to avoid a cycle: pkg/llm imports pkg/tools, pkg/tools imports pkg/config (for the State.Config() return type). pkg/config sits at the bottom and is imported by both pkg/llm (via alias) and pkg/tools.
type Config ¶
type Config struct {
// OS / runtime
OS string
// Logging
LogLevel string // default: "info"
LogFormat string // default: "text"
LogDir *string // default: nil → stdout only
// Application
AppEnv string // default: "development"
AppName string // default: "evva" — the binary / brand name; drives AppHome layout.
AppVersion string
// Per-user home dir layout
AppHome string
AppHomeSkillsDir string
AppHomeUserProfile string
AppHomeConfigFile string // absolute path to <app>-config.yml under AppHome/config/
AutoCompactThreshold float64
// Workdir layout
WorkDir string
WorkDirSkillsDir string
// llm providers(from <app>-config.yml) key: provider name, value: provider APIConfig
LLMProviderConfig map[string]APIConfig
// DefaultProvider / DefaultModel are the (provider, model) the agent
// boots with. Sourced from <app>-config.yml; the /model switch updates
// them in-memory and persists via SaveFile().
DefaultProvider constant.LLMProvider
DefaultModel constant.Model
// DefaultEffort is the user-facing effort level name: low|medium|high|ultra.
// Defaults to "medium". Sourced from <app>-config.yml; /effort updates it.
DefaultEffort string
// DefaultProfile is the persona the root agent boots into ("evva", "nono",
// etc). Sourced from <app>-config.yml; /profile updates it. Empty falls
// back to "evva" at bootstrap so old configs keep working.
DefaultProfile string
// PermissionMode is the startup permission stance: one of
// default|accept_edits|plan|bypass|auto. The -permission-mode CLI flag
// overrides this at boot; the TUI's Shift+Tab cycle mutates the
// in-memory value via SetPermissionMode (not yet persisted).
PermissionMode string
// Loaded metadata
LoadedAt time.Time
// DefaultMaxIterations is the loop's safety cap. Hitting it emits a
// KindIterLimit event and pauses the agent; the caller may invoke
// Continue(ctx) to keep going.
DefaultMaxIterations int
// DefaultMaxTokens is the per-completion output-token cap passed to
// the LLM. 0 → let the provider apply its own default.
DefaultMaxTokens int
// UI
DisplayThinking bool
// Auto-memory subsystem. When true (default), update_user_profile +
// update_project_memory tools are registered on Main and the system
// prompt carries the auto-memory guidance + project-memory index.
// /config (or hand-edit) flips this; EVVA_AUTO_MEMORY=0 forces off at
// boot regardless of the YAML.
EnableAutoMemory bool
// Web tools
TavilyAPIKey string // empty → web_search reports "not configured"
FetchMaxBytes int // cap on extracted text returned by web_fetch
// contains filtered or unexported fields
}
Config holds all parsed runtime configuration. Most fields are populated once during Load and treated as read-only; the small subset that the /config and /model setters mutate at runtime is guarded by c.mu.
AppHome-prefixed paths point inside the per-user home dir (~/.<app>/) where skills, USER_PROFILE.md, evva-config.yml, and logs live. WorkDir-prefixed paths point inside the process's current working directory where workdir-local resources (skills, EVVA.md, plans) live.
func Get ¶
func Get() *Config
Get returns a process-wide Config initialized lazily via LoadDefault. Subsequent calls return the same pointer.
Get is a host-side convenience for the bundled cmd/evva binary and for the reference TUI (which reads runtime settings without an injected pointer). Library code inside the agent loop and tools must NOT call Get — they receive *Config through dependency injection (agent.WithConfig, toolset.ToolState.SetConfig, function parameters).
Downstream hosts that want a non-default AppHome should call Load with explicit LoadOptions and pass the result into agent.WithConfig.
func Load ¶
func Load(opts LoadOptions) (*Config, error)
Load parses env vars + the per-user YAML and returns a populated Config. Each LoadOptions field has a sensible default (see LoadOptions doc).
Unlike LoadDefault, Load returns an error instead of calling os.Exit so downstream hosts can surface it through their own error path.
Example ¶
ExampleLoad demonstrates the canonical downstream-app load: pick an AppName + AppHome, let Load auto-create the YAML on first run, and trust the returned *Config from then on.
AppName drives the AppHome layout (~/.{AppName}/) AND the first-run YAML's `default_profile` value — running this with AppName="friday" stamps `default_profile: friday`, not "evva" (Phase 19b).
package main
import (
"fmt"
"path/filepath"
"strings"
"github.com/johnny1110/evva/pkg/config"
)
func main() {
tmp, _ := filepath.Abs("/tmp/evva-example-load")
cfg, err := config.Load(config.LoadOptions{
AppName: "friday",
AppHome: tmp,
WorkDir: tmp + "/work",
})
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println("app:", cfg.AppName)
fmt.Println("default_profile:", cfg.DefaultProfile)
fmt.Println("config_file_endswith:", strings.HasSuffix(cfg.AppHomeConfigFile, "friday-config.yml"))
}
Output: app: friday default_profile: friday config_file_endswith: true
func LoadDefault ¶
func LoadDefault() *Config
LoadDefault returns a Config populated with evva's historical defaults: AppName="evva", AppHome=~/.evva/, WorkDir=os.Getwd(). Intended for the bundled cmd/evva binary and for backward-compatible callers.
Startup failures (missing/invalid YAML, unknown provider/model) bail with os.Exit so the user gets a clear single-line error rather than a panic stack from deep inside the agent boot path.
func (*Config) Clone ¶
Clone returns a shallow copy of c with fresh mutexes. Used by callers that need to override a small subset of fields (notably WorkDir) for a scoped agent — the AgentTool isolation path does this so a subagent can run with cfg.WorkDir = <worktree path> while the parent keeps its own. The copy reads through c.mu so concurrent mutations don't tear across fields.
"Shallow" — the LLMProviderConfig map is reused by reference. That's safe today because providers are loaded once at boot and never mutated after; if that invariant ever changes, this method should deep-copy the map.
func (*Config) GetAutoCompactThreshold ¶
GetAutoCompactThreshold returns the current threshold under the read lock. compact.go reads this every turn.
func (*Config) GetDisplayThinking ¶
GetDisplayThinking returns the current DisplayThinking flag under the read lock. Agent code reads this every turn (state_machine.go, stream.go); the UI may write it via /config.
func (*Config) GetEnableAutoMemory ¶
GetEnableAutoMemory returns the auto-memory flag under the read lock. Read by agent.Main (to decide whether to attach the memory tools) and by the sysprompt builder (to decide whether to inject the auto-memory guidance section).
func (*Config) IsDevelopment ¶
IsDevelopment / IsProduction — semantic helpers so call sites don't hardcode string literals scattered across the codebase.
func (*Config) IsProduction ¶
func (*Config) SaveFile ¶
SaveFile re-serializes the user-tunable subset to AppHomeConfigFile. The /config setters and the runtime /model switch both call this.
Snapshots all fields under c.mu.RLock, releases that lock before blocking on disk I/O, then takes c.saveMu so concurrent saves don't interleave on the file.
func (*Config) SetAutoCompactThreshold ¶
SetAutoCompactThreshold validates 0 < v <= 1 and persists.
func (*Config) SetDefaultEffort ¶
SetDefaultEffort validates the effort level name and persists it.
func (*Config) SetDefaultModel ¶
SetDefaultModel updates the (provider, model) pair the agent boots with and persists it. Phase-3's runtime /model swap calls this after rebuilding the Agent's llm.Client so next launch starts with the user's last choice. Validates that the model is actually offered by the provider.
func (*Config) SetDefaultProfile ¶
SetDefaultProfile persists the chosen persona name. Validation against the agent registry happens at the call site (AgentRegistry lives in internal/agent, which can't be imported from config without a cycle). Empty string is accepted — bootstrap interprets "" as "fall back to evva".
func (*Config) SetDisplayThinking ¶
SetDisplayThinking mutates the in-memory flag and persists to disk.
func (*Config) SetEnableAutoMemory ¶
SetEnableAutoMemory toggles the auto-memory subsystem and persists. Takes effect for the prompt and tool registration on next agent boot.
func (*Config) SetFetchMaxBytes ¶
SetFetchMaxBytes validates > 0 and persists.
func (*Config) SetMaxIterations ¶
SetMaxIterations validates >0 and persists. NOTE: this only updates the YAML default; the live cap on a running agent is on Agent itself — call Controller.SetMaxIterations to mutate it.
func (*Config) SetMaxTokens ¶
SetMaxTokens validates >=0 and persists. 0 means "provider default". Effective on next launch — the agent's profile snapshots this at construction.
func (*Config) SetProviderAPIKey ¶
SetProviderAPIKey installs an api key for the named provider and persists. Empty key removes the provider from LLMProviderConfig (cloud providers require a key to be listed). The constant.LLMProvider must already be known.
func (*Config) SetProviderAPIURL ¶
SetProviderAPIURL overrides the api_url for the named provider. Empty resets to the provider's built-in default.
func (*Config) SetProviderCredentials ¶
SetProviderCredentials writes the (apiURL, apiKey) pair for the named LLM provider into Config.LLMProviderConfig under the mutex.
This is the documented path for downstream apps to install credentials at runtime — direct map assignment (`cfg.LLMProviderConfig["deepseek"] = ...`) still works but races with concurrent reads. SetProviderCredentials takes c.mu so two goroutines wiring different providers at startup don't tear.
An empty name is rejected. Unknown provider names are NOT rejected: downstream apps register custom providers into pkg/llm's registry without touching constant, and the agent's LLM-build step will surface the typo if no factory matches. apiURL may be empty — providers with a sane default (DeepSeek, Anthropic) fall back to it. apiKey may be empty for local providers (Ollama, ...).
Models on the existing APIConfig (if any) are preserved. Pass through the public map slot when a custom Models list is also needed.
Example ¶
ExampleConfig_SetProviderCredentials shows the Phase 19b thread-safe setter for LLM credentials. Prefer this over direct map assignment when wiring providers at runtime — direct writes race concurrent reads on the same *Config.
package main
import (
"fmt"
"path/filepath"
"github.com/johnny1110/evva/pkg/config"
)
func main() {
tmp, _ := filepath.Abs("/tmp/evva-example-creds")
cfg, _ := config.Load(config.LoadOptions{
AppName: "alpha", AppHome: tmp, WorkDir: tmp,
})
if err := cfg.SetProviderCredentials(
"deepseek",
"https://api.deepseek.com",
"sk-example-key",
); err != nil {
fmt.Println("error:", err)
return
}
got := cfg.LLMProviderConfig["deepseek"]
fmt.Println("api_url:", got.ApiURL)
fmt.Println("api_secret_present:", got.ApiSecret != "")
}
Output: api_url: https://api.deepseek.com api_secret_present: true
func (*Config) SetTavilyAPIKey ¶
SetTavilyAPIKey persists the key. Empty string disables web_search.
type FileConfig ¶
type FileConfig struct {
MaxIterations int `yaml:"max_iterations"`
MaxTokens int `yaml:"max_tokens"`
AutoCompactThreshold float64 `yaml:"auto_compact_threshold"`
DisplayThinking bool `yaml:"display_thinking"`
// DefaultProvider / DefaultModel are the (provider, model) pair the
// agent boots with. Phase 3's /model switch will mutate these and call
// Save to persist across launches.
DefaultProvider string `yaml:"default_provider"`
DefaultModel string `yaml:"default_model"`
DefaultEffort string `yaml:"default_effort"`
// DefaultProfile is the persona the root agent boots into. Phase 6's
// /profile switch mutates this and calls Save to persist across launches.
// Empty falls back to "evva" at bootstrap.
DefaultProfile string `yaml:"default_profile"`
// PermissionMode is the agent's startup stance. One of:
// "default" | "accept_edits" | "plan" | "bypass" | "auto". Defaults to
// "default" when omitted. The -permission-mode CLI flag overrides this.
PermissionMode string `yaml:"permission_mode"`
FetchMaxBytes int `yaml:"fetch_max_bytes"`
TavilyAPIKey string `yaml:"tavily_api_key"`
// EnableAutoMemory gates the auto-memory subsystem (update_user_profile,
// update_project_memory tools + the per-session prompt section). Default
// true; users opt out via /config or by setting this to false. Pointer so
// missing-key in YAML preserves the default rather than zeroing.
EnableAutoMemory *bool `yaml:"enable_auto_memory,omitempty"`
Providers map[string]FileProviderConfig `yaml:"providers"`
}
FileConfig is the on-disk schema for $EvvaHome/config/evva-config.yml. It owns the user-tunable subset of configuration; deployment knobs (LOG_LEVEL, APP_ENV, ...) stay in .env.
func LoadFileConfig ¶
func LoadFileConfig(path, appName string) (FileConfig, bool, error)
LoadFileConfig reads the YAML at path. On first launch (file absent) it writes a default YAML whose default_profile is the caller's appName — so a friday-flavoured Load writes "default_profile: friday" instead of bleeding evva's persona into a sibling app's config.
Returns (cfg, created, err):
- created=true means the file didn't exist and was just written with defaults; callers can use this to surface a one-time first-launch notice.
- Missing keys in an existing file fall back to defaultFileConfig values via pre-population, so partial YAML never crashes startup.
type FileProviderConfig ¶
FileProviderConfig carries per-provider credentials. Empty ApiURL falls back to the constant's built-in default.
type LoadOptions ¶
type LoadOptions struct {
AppName string // brand identifier; drives the AppHome layout. Defaults to "evva".
AppHome string // absolute path; defaults to ~/.<AppName>/.
WorkDir string // process cwd; defaults to os.Getwd().
AppVersion string // version string for diagnostics; defaults to DefaultAppVersion.
// EnvAliases maps the caller's preferred env-var names onto evva's
// canonical ones BEFORE godotenv.Load runs. Useful when a downstream
// app advertises friendlier spellings — e.g. `{"LOGDIR": "LOG_DIR",
// "LOGLEVEL": "LOG_LEVEL"}` lets a friday user write either form in
// `~/.friday/.env` and have evva's loader pick it up.
//
// The promotion is non-overriding: an alias only seeds the canonical
// name when that canonical name is unset. Existing canonical exports
// win, so a deliberate `LOG_DIR=...` is never clobbered by a stray
// alias.
EnvAliases map[string]string
// EnvOverrides runs AFTER the YAML + canonical env-vars have built
// the Config. Each function gets the populated *Config and can fold
// in env vars that don't have a native hook inside Load (e.g.
// MAX_ITERS → cfg.SetMaxIterations). The first error short-circuits
// the rest and is returned from Load.
//
// Use this to translate downstream-flavoured env conventions
// (APIKEY → cfg.SetProviderCredentials, MAX_ITERS → cfg.SetMaxIterations)
// in one place instead of post-Load shim code at every call site.
EnvOverrides []func(*Config) error
}
LoadOptions tunes Load. Zero-value fields fall back to LoadDefault behavior — AppName="evva", AppHome=~/.evva/, WorkDir=os.Getwd(), AppVersion=DefaultAppVersion. Downstream apps that want a different home dir or app name fill in the relevant fields.