config

package
v0.2.4-alpha.2 Latest Latest
Warning

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

Go to latest
Published: May 22, 2026 License: MIT Imports: 10 Imported by: 0

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

Examples

Constants

View Source
const (
	DefaultAppName    = "evva"
	DefaultAppVersion = "0.1.0"
)

Default values that appear unchanged across all Config instances.

Variables

View Source
var BuildDate string

BuildDate is the UTC build timestamp injected at build time. Empty in dev builds.

View Source
var CommitSHA string

CommitSHA is the git commit hash injected at build time via ldflags. Empty in dev builds.

View Source
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

func ResolveDefaultModel(provider, model string) (constant.LLMProvider, constant.Model, error)

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

type APIConfig struct {
	ApiURL    string
	ApiSecret string
	Models    []constant.Model
}

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

func (c *Config) Clone() *Config

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

func (c *Config) Effort() string

Effort returns the current effort level name under the read lock.

func (*Config) GetAutoCompactThreshold

func (c *Config) GetAutoCompactThreshold() float64

GetAutoCompactThreshold returns the current threshold under the read lock. compact.go reads this every turn.

func (*Config) GetDisplayThinking

func (c *Config) GetDisplayThinking() bool

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

func (c *Config) GetEnableAutoMemory() bool

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

func (c *Config) IsDevelopment() bool

IsDevelopment / IsProduction — semantic helpers so call sites don't hardcode string literals scattered across the codebase.

func (*Config) IsProduction

func (c *Config) IsProduction() bool

func (*Config) SaveFile

func (c *Config) SaveFile() error

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

func (c *Config) SetAutoCompactThreshold(v float64) error

SetAutoCompactThreshold validates 0 < v <= 1 and persists.

func (*Config) SetDefaultEffort

func (c *Config) SetDefaultEffort(level string) error

SetDefaultEffort validates the effort level name and persists it.

func (*Config) SetDefaultModel

func (c *Config) SetDefaultModel(provider constant.LLMProvider, model constant.Model) error

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

func (c *Config) SetDefaultProfile(name string) error

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

func (c *Config) SetDisplayThinking(v bool) error

SetDisplayThinking mutates the in-memory flag and persists to disk.

func (*Config) SetEnableAutoMemory

func (c *Config) SetEnableAutoMemory(v bool) error

SetEnableAutoMemory toggles the auto-memory subsystem and persists. Takes effect for the prompt and tool registration on next agent boot.

func (*Config) SetFetchMaxBytes

func (c *Config) SetFetchMaxBytes(n int) error

SetFetchMaxBytes validates > 0 and persists.

func (*Config) SetMaxIterations

func (c *Config) SetMaxIterations(n int) error

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

func (c *Config) SetMaxTokens(n int) error

SetMaxTokens validates >=0 and persists. 0 means "provider default". Effective on next launch — the agent's profile snapshots this at construction.

func (*Config) SetProviderAPIKey

func (c *Config) SetProviderAPIKey(name, key string) error

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

func (c *Config) SetProviderAPIURL(name, url string) error

SetProviderAPIURL overrides the api_url for the named provider. Empty resets to the provider's built-in default.

func (*Config) SetProviderCredentials

func (c *Config) SetProviderCredentials(name, apiURL, apiKey string) error

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

func (c *Config) SetTavilyAPIKey(s string) error

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

type FileProviderConfig struct {
	APIKey string `yaml:"api_key"`
	APIURL string `yaml:"api_url"`
}

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.

Jump to

Keyboard shortcuts

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