config

package
v0.30.0 Latest Latest
Warning

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

Go to latest
Published: Aug 22, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package config loads, defaults, persists, and redacts Factor's configuration.

Index

Constants

View Source
const Version = 1

Variables

This section is empty.

Functions

func ChangedSections added in v0.26.0

func ChangedSections(a, b *Config) []string

ChangedSections names the top-level sections whose content differs — "provider", "channels.voice" — which is what the reload log and the "config change applied" notice report back.

func DefaultPath

func DefaultPath() string

DefaultPath returns the default config file location.

func EnsureWorkspace

func EnsureWorkspace(workspace string) error

EnsureWorkspace creates the workspace layout and default bootstrap files.

Bootstrap files belong to the user, so an upgrade must not clobber a persona or an instruction set someone wrote by hand. It may, however, carry an improved default to an install that never edited the old one: a file whose bytes still match a superseded default is replaced, everything else is left exactly as it is.

func Home

func Home() string

Home returns $FACTOR_HOME or ~/.factor.

func Update

func Update(path string, fn func(*Config) error) error

Update atomically load-modifies-saves the config FILE. The live in-memory Config is deliberately immutable after startup (concurrent turns read it lock-free); durable changes go through here, and the gateway's Watch is what turns the saved file back into running state — by reloading the process, not by mutating what concurrent readers hold.

func Watch added in v0.26.0

func Watch(ctx context.Context, baseline *Config, every time.Duration, onChange func(*Config, []string))

Watch polls the file baseline was loaded from and calls onChange with the freshly loaded configuration — and the names of the sections that differ — whenever its content changes into something loadable. A file that cannot be loaded (a half-saved edit, a syntax error) is warned about once and retried on the next tick, so a bad save never takes the running config down. Blocks until ctx ends; run it on its own goroutine.

Types

type AgentConfig

type AgentConfig struct {
	Workspace          string `json:"workspace" env:"FACTOR_WORKSPACE"`
	MaxToolIterations  int    `json:"max_tool_iterations" env:"FACTOR_MAX_TOOL_ITERATIONS"`
	MaxConcurrentTurns int    `json:"max_concurrent_turns"`
	// ContextWindowTokens caps the window compaction budgets against. 0 means
	// auto: the model catalog answers per model. A set value can only shrink
	// what the catalog says, and stands alone for models it does not carry.
	ContextWindowTokens int `json:"context_window_tokens"`
	SummarizeAtPercent  int `json:"summarize_at_percent"`
	// MaxContextTokens is the working ceiling for one assembled request,
	// independent of what the model would accept. Quality falls off long
	// before a window is full, so a model with a huge window still answers
	// better from a small context than a large one — and a percentage of a
	// million-token window is not a budget at all. 0 means the default.
	MaxContextTokens   int    `json:"max_context_tokens"`
	KeepRecentMessages int    `json:"keep_recent_messages"`
	ExtraInstructions  string `json:"extra_instructions,omitempty"`
}

type BrowserConfig

type BrowserConfig struct {
	Enabled     bool   `json:"enabled"`
	AttachURL   string `json:"attach_url,omitempty" env:"FACTOR_BROWSER_ATTACH_URL"`
	Command     string `json:"command,omitempty"`
	Headless    bool   `json:"headless"`
	NoSandbox   bool   `json:"no_sandbox"` // needed as root, in containers, and where user namespaces are restricted
	UserDataDir string `json:"user_data_dir,omitempty"`

	// FastPath adds a second, much lighter engine that only reads pages.
	// Off unless asked for: it is a whole extra browser to install, and the
	// full suite already reads pages perfectly well.
	FastPath    bool   `json:"fast_path"`
	FastCommand string `json:"fast_command,omitempty"`
}

BrowserConfig controls the CDP browser integration. With AttachURL empty, Factor probes the standard DevTools port and falls back to launching a managed instance (visible unless Headless).

type BudgetConfig added in v0.19.0

type BudgetConfig struct {
	SessionUSD float64 `json:"session_usd" env:"FACTOR_BUDGET_SESSION_USD"`
	GlobalUSD  float64 `json:"global_usd" env:"FACTOR_BUDGET_GLOBAL_USD"`
	Period     string  `json:"period"` // day | month | total
}

BudgetConfig caps spend. Zero is no cap, on both scopes: a budget that starts refusing turns is something you ask for, never something you inherit. Period says what "global" counts — this day, this month, or everything Factor has ever spent.

func (BudgetConfig) Off added in v0.19.0

func (b BudgetConfig) Off() bool

Off reports that neither cap is set.

type Candidate

type Candidate struct {
	Type      string           `json:"type"`
	APIKey    string           `json:"api_key,omitempty"`
	APIBase   string           `json:"api_base,omitempty"`
	Model     string           `json:"model"`
	Reasoning *ReasoningConfig `json:"reasoning,omitempty"`
}

Candidate identifies one provider+model combination in the failover chain. A nil Reasoning inherits the provider-level setting.

type Config

type Config struct {
	ConfigVersion int                        `json:"version"`
	Agent         AgentConfig                `json:"agent"`
	Provider      ProviderConfig             `json:"provider"`
	Memory        MemoryConfig               `json:"memory"`
	Channels      map[string]json.RawMessage `json:"channels,omitempty"`
	MCP           MCPConfig                  `json:"mcp"`
	Tools         ToolsConfig                `json:"tools"`
	Desktop       DesktopConfig              `json:"desktop"`
	Browser       BrowserConfig              `json:"browser"`
	Heartbeat     HeartbeatConfig            `json:"heartbeat"`
	Gateway       GatewayConfig              `json:"gateway"`
	Upgrade       UpgradeConfig              `json:"upgrade"`
	Cost          CostConfig                 `json:"cost"`

	// LogLevel is the lowest severity that reaches the log: "debug", "info"
	// (the default), "warn", or "error". Debug is where the per-decision
	// detail lives — which profile a voice matched and how closely, how long
	// an utterance was, a memory write that was dropped — too noisy for a
	// normal run and the first thing wanted when one goes wrong.
	LogLevel string `json:"log_level,omitempty" env:"FACTOR_LOG_LEVEL"`
	// contains filtered or unexported fields
}

Config is the root configuration. Channel sections stay raw JSON so connectors can define and decode their own config without touching core.

func Default

func Default() *Config

Default returns a fully populated configuration.

func Load

func Load(path string) (*Config, error)

Load reads the config file (DefaultPath when path is empty), overlays it on defaults, then applies FACTOR_* environment overrides.

func ReadFile

func ReadFile(path string) (*Config, error)

ReadFile returns a fresh file-backed copy (no env overlay), for introspection consistent with what Update sees.

func (*Config) ApplyLogLevel added in v0.26.0

func (c *Config) ApplyLogLevel()

ApplyLogLevel installs log_level process-wide. It moves the default handler's threshold rather than replacing the handler, so the log keeps the format every other line is already written in — and so Factor's slog.Debug calls, which Go's default drops on the floor, become reachable at all. An unrecognized name is reported and left at info rather than silencing the log over a typo.

func (*Config) FilterSecrets

func (c *Config) FilterSecrets(s string) string

FilterSecrets replaces occurrences of secret values in s with a placeholder.

func (*Config) Get

func (c *Config) Get(dottedKey string) (any, error)

Get returns the (secret-redacted) value at a dotted path, e.g. "provider.model" or "" for the whole config.

func (*Config) Path

func (c *Config) Path() string

Path returns where this config was loaded from (or will be saved to).

func (*Config) RedactedMap

func (c *Config) RedactedMap() (map[string]any, error)

RedactedMap returns the config as a generic map with secret-looking values masked.

func (*Config) Save

func (c *Config) Save() error

Save writes the config as indented JSON, creating parent directories.

func (*Config) SecretValues

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

SecretValues returns every configured secret worth filtering out of output.

func (*Config) Set

func (c *Config) Set(dottedKey string, value any) error

Set applies a value at a dotted path, validates the resulting config by round-tripping it through the schema, and updates the receiver in place. The caller is responsible for calling Save.

type CostConfig added in v0.19.0

type CostConfig struct {
	Track        bool             `json:"track"`
	Budget       BudgetConfig     `json:"budget"`
	Prices       map[string]Price `json:"prices,omitempty"` // model id -> USD per million tokens
	PricesURL    string           `json:"prices_url,omitempty"`
	RefreshHours int              `json:"refresh_hours"`
}

CostConfig turns token counts into money. Tracking is on by default because it costs nothing — every provider already reports the counts, and a spend you cannot see is one you find out about on an invoice. Prices are fetched from the model catalog and cached; an entry here overrides one for a model the catalog does not carry (a private endpoint, a negotiated rate).

type DesktopConfig

type DesktopConfig struct {
	Enabled       *bool  `json:"enabled,omitempty"`
	ScreenshotDir string `json:"screenshot_dir,omitempty"`
}

DesktopConfig controls the desktop-control tools (windows, screenshots, mouse, keyboard, clipboard, notifications). Enabled is a tri-state: unset means "register them when a graphical session is detected", which keeps a headless server's prompt free of tools that could never work there.

func (DesktopConfig) Register

func (d DesktopConfig) Register(hasDisplay bool) bool

Register reports whether the desktop tools should be registered.

type GatewayConfig

type GatewayConfig struct {
	Host string `json:"host" env:"FACTOR_GATEWAY_HOST"`
	Port int    `json:"port" env:"FACTOR_GATEWAY_PORT"`
}

type HeartbeatConfig

type HeartbeatConfig struct {
	Enabled         bool `json:"enabled"`
	IntervalMinutes int  `json:"interval_minutes"`
}

type MCPConfig

type MCPConfig struct {
	Servers map[string]MCPServer `json:"servers,omitempty"`
}

type MCPServer

type MCPServer struct {
	Command string            `json:"command"`
	Args    []string          `json:"args,omitempty"`
	Env     map[string]string `json:"env,omitempty"`
	Enabled *bool             `json:"enabled,omitempty"` // nil means enabled
}

func (MCPServer) IsEnabled

func (s MCPServer) IsEnabled() bool

type MemoryConfig

type MemoryConfig struct {
	Mode                string   `json:"mode" env:"FACTOR_MEMORY_MODE"` // sidecar | external | off
	URL                 string   `json:"url,omitempty" env:"FACTOR_MEMORY_URL"`
	Command             string   `json:"command"`
	AutoInstall         bool     `json:"auto_install"` // install smrti (uv/pipx/pip/venv) when missing
	KeepAlive           bool     `json:"keep_alive"`   // sidecar outlives Factor; later runs adopt it warm
	Host                string   `json:"host"`
	Port                int      `json:"port" env:"FACTOR_MEMORY_PORT"`
	DBPath              string   `json:"db_path"`
	Tenant              string   `json:"tenant"`
	Space               string   `json:"space"`
	SpaceStrategy       string   `json:"space_strategy"` // origin: conversations write to `space`, cron/job turns to `system_space`, each reading the other as an overlay (needs a smrti with space routing; older engines fall back to single). single: everything in `space`.
	SystemSpace         string   `json:"system_space"`
	SharedSpace         string   `json:"shared_space"` // where a turn somebody else can hear is written and read; a private turn reads it too, a shared turn reads nothing else. Empty disables audience scoping, which makes a channel that reports one refuse to recall rather than recall everything.
	Personality         string   `json:"personality" env:"FACTOR_MEMORY_PERSONALITY"`
	APIKey              string   `json:"api_key,omitempty" env:"FACTOR_MEMORY_API_KEY"`
	RecallTopK          int      `json:"recall_top_k"`
	RecallMinConfidence float64  `json:"recall_min_confidence"`
	QueryContextMsgs    int      `json:"query_context_msgs"`
	QueryMaxChars       int      `json:"query_max_chars"`
	InjectMaxChars      int      `json:"inject_max_chars"`
	ReflectIntervalSecs int      `json:"reflect_interval_secs"`
	ExtractMode         string   `json:"extract_mode,omitempty" env:"FACTOR_MEMORY_EXTRACT_MODE"` // hybrid | llm | local | "" = auto
	ExtractURL          string   `json:"extract_url,omitempty" env:"FACTOR_MEMORY_EXTRACT_URL"`
	ExtractModel        string   `json:"extract_model,omitempty" env:"FACTOR_MEMORY_EXTRACT_MODEL"`
	ExtractAPIKey       string   `json:"extract_api_key,omitempty" env:"FACTOR_MEMORY_EXTRACT_API_KEY"`
	IgnorePatterns      []string `json:"ignore_patterns,omitempty"`
	StartupTimeoutSecs  int      `json:"startup_timeout_secs"`
}

func (MemoryConfig) BaseURL

func (m MemoryConfig) BaseURL() string

BaseURL returns the effective smrti endpoint.

type Price added in v0.19.0

type Price struct {
	Input  float64 `json:"input"`
	Output float64 `json:"output"`
}

Price is what one model charges, in USD per million tokens — the unit model pages quote, so a hand-written override reads the way it was copied.

type ProviderConfig

type ProviderConfig struct {
	Type             string          `json:"type" env:"FACTOR_PROVIDER_TYPE"`
	APIKey           string          `json:"api_key,omitempty" env:"FACTOR_PROVIDER_API_KEY"`
	APIBase          string          `json:"api_base,omitempty" env:"FACTOR_PROVIDER_API_BASE"`
	Model            string          `json:"model" env:"FACTOR_PROVIDER_MODEL"`
	Reasoning        ReasoningConfig `json:"reasoning"`
	MaxTokens        int             `json:"max_tokens"`
	Temperature      float64         `json:"temperature,omitempty"`
	Fallbacks        []Candidate     `json:"fallbacks,omitempty"`
	MaxRetries       int             `json:"max_retries"`
	RetryBackoffSecs int             `json:"retry_backoff_secs"`
}

func (ProviderConfig) Candidates

func (p ProviderConfig) Candidates() []Candidate

Candidates returns the primary candidate followed by the fallbacks, each carrying the reasoning settings it should use.

type ReasoningConfig

type ReasoningConfig struct {
	Effort    string `json:"effort"`     // xhigh | high | medium | low | minimal | none
	MaxTokens int    `json:"max_tokens"` // explicit thinking budget; wins over effort
	Exclude   bool   `json:"exclude"`    // think, but keep the reasoning out of the reply
}

ReasoningConfig asks the model to think before answering. Each backend spells this differently (OpenRouter takes a reasoning object, OpenAI and Groq take reasoning_effort, Anthropic takes a thinking budget); Factor translates one setting into whichever the active provider understands. The fields are written even when empty: the section is overlaid on top of the defaults at load time, so an omitted "effort" would silently restore the default xhigh instead of the "off" the user asked for.

func (ReasoningConfig) IsZero

func (r ReasoningConfig) IsZero() bool

IsZero reports that nothing was configured (no reasoning parameters sent).

func (ReasoningConfig) Off

func (r ReasoningConfig) Off() bool

Off reports an explicit opt-out.

type ToolsConfig

type ToolsConfig struct {
	Disabled                  []string `json:"disabled,omitempty"`
	RestrictToWorkspace       bool     `json:"restrict_to_workspace"`
	AllowReadOutsideWorkspace bool     `json:"allow_read_outside_workspace"`
	AllowPaths                []string `json:"allow_paths,omitempty"`
	ExecTimeoutSecs           int      `json:"exec_timeout_secs"`
	EnableDenyPatterns        bool     `json:"enable_deny_patterns"`
	CustomDenyPatterns        []string `json:"custom_deny_patterns,omitempty"`
	AllowInstall              bool     `json:"allow_install"`
	// SkillRegistryURL points skill_find and skill_install at something other
	// than skills.sh: a mirror, a private index, a stub in a test.
	SkillRegistryURL string `json:"skill_registry_url,omitempty" env:"FACTOR_SKILL_REGISTRY_URL"`
}

func (ToolsConfig) IsToolEnabled

func (t ToolsConfig) IsToolEnabled(name string) bool

IsToolEnabled reports whether a tool name survives the user's disabled list.

type UpgradeConfig added in v0.5.0

type UpgradeConfig struct {
	Check              bool `json:"check"`
	CheckIntervalHours int  `json:"check_interval_hours"`
}

UpgradeConfig controls the release check. The gateway only ever reports a newer version — installing it stays an explicit `factor upgrade` or a request to the agent, so a daemon never swaps its own binary out from under a live conversation.

Jump to

Keyboard shortcuts

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