config

package
v0.4.9 Latest Latest
Warning

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

Go to latest
Published: Jul 31, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package config loads Seamless configuration from a single YAML file with SEAMLESS_* environment overrides. Env wins over file; file wins over defaults.

Deliberately halved from Seam v1: no JWT/auth/multi-user config, no ChromaDB. Auth is a single static bearer key; vectors live in SQLite.

Index

Constants

View Source
const (
	ProviderOpenAI    = "openai"
	ProviderOllama    = "ollama"
	ProviderAnthropic = "anthropic"
)

LLM provider identifiers.

View Source
const (
	// MaxEmbeddingDimensions bounds a configured/requested vector size. Current
	// providers are in the low thousands; 65,536 permits future models while
	// preventing a typo from driving unbounded vector allocations downstream.
	MaxEmbeddingDimensions = 65_536
)

Variables

View Source
var UtilityModes = []string{"auto", "on", "off"}

UtilityModes are the accepted briefing.utility_mode values.

Functions

This section is empty.

Types

type Anthropic

type Anthropic struct {
	APIKey    string `yaml:"api_key"`
	BaseURL   string `yaml:"base_url"`
	ChatModel string `yaml:"chat_model"`
}

Anthropic is a chat-only provider (no embeddings API).

type Briefing

type Briefing struct {
	// ConstraintMaxFull is how many top-ranked constraints render as full
	// "- name: description" bullets in the Constraints section; the rest
	// collapse into one compact "+N more, equally binding" line that still
	// names every one. 0 disables the tiering (every constraint renders full,
	// the legacy behavior).
	ConstraintMaxFull int `yaml:"constraint_max_full" json:"constraintMaxFull"`
	// ConventionMaxFull is how many top-ranked conventions render as full
	// bullets in the budget-competing Conventions section; the rest stay
	// behind the section's count line ("recall kind=convention"). Starred
	// conventions always render full past the cap. 0 disables the tiering
	// (every convention renders full), matching ConstraintMaxFull semantics.
	ConventionMaxFull int `yaml:"convention_max_full" json:"conventionMaxFull"`
	// MemoryMaxAgeDays drops memory-index lines not updated within this many
	// days. 0 = no recency filter. Constraints and stages are exempt.
	MemoryMaxAgeDays int `yaml:"memory_max_age_days" json:"memoryMaxAgeDays"`
	// MemoryMaxItems caps the memory-index line count before budget packing.
	// 0 = budget-only (no cap).
	MemoryMaxItems int `yaml:"memory_max_items" json:"memoryMaxItems"`
	// FindingsCount is how many recent findings to inject. 0 hides the section.
	FindingsCount int `yaml:"findings_count" json:"findingsCount"`
	// FindingsMaxAgeDays drops findings older than this many days. 0 = no filter.
	FindingsMaxAgeDays int `yaml:"findings_max_age_days" json:"findingsMaxAgeDays"`
	// ReadyTasksShown is how many ready-task titles the ready line names.
	// 0 hides the line entirely.
	ReadyTasksShown int `yaml:"ready_tasks_shown" json:"readyTasksShown"`
	// PendingPlanMaxDays is how far back captured-but-unapproved Claude Code
	// plans earn "awaiting approval" lines. 0 = no age cutoff.
	PendingPlanMaxDays int `yaml:"pending_plan_max_days" json:"pendingPlanMaxDays"`
	// StageUnknownMaxAgeDays is the grace window (days since last update) a
	// stage memory stays pinned when its Status header is missing or not a live
	// gate (open/in_progress/blocked). Past it the stage leaves the briefing --
	// recall still finds it. 0 = pin forever (the historical behavior).
	StageUnknownMaxAgeDays int `yaml:"stage_unknown_max_age_days" json:"stageUnknownMaxAgeDays"`
	// HardCapMultiplier times budgets.max_briefing_tokens is the absolute
	// truncation ceiling. 0 falls back to 2.
	HardCapMultiplier int `yaml:"hard_cap_multiplier" json:"hardCapMultiplier"`
	// IncludeParentMemories folds a shared parent project's active memories
	// into a child project's briefing (the historical automatic behavior).
	IncludeParentMemories bool `yaml:"include_parent_memories" json:"includeParentMemories"`
	// SiblingFindingsCount is how many recent findings from family-member
	// projects to inject. 0 hides the section.
	SiblingFindingsCount int `yaml:"sibling_findings_count" json:"siblingFindingsCount"`
	// IncludeSiblingMemories folds family-member projects' active memories
	// (constraints and stages excluded) into the briefing as a low-priority
	// "Sibling memories" section. Off by default to avoid crowding.
	IncludeSiblingMemories bool `yaml:"include_sibling_memories" json:"includeSiblingMemories"`
	// UtilityWeight is utility's share of the briefing memory-index sort key:
	// (1-w)*recency + w*utility, both half-life-decayed to [0,1). 0 = pure
	// recency (the legacy order); constraints, stages, and favorites are pinned
	// regardless. Applies only where utility ranking is active (UtilityMode).
	UtilityWeight float64 `yaml:"utility_weight" json:"utilityWeight"`
	// UtilityMode gates the briefing's utility re-ordering: "auto" (default)
	// activates per project once the gardener's readiness latch trips, "on"
	// activates everywhere immediately, "off" disables it everywhere. The
	// bounded recall/prompt-recall boosts are not gated by this.
	UtilityMode string `yaml:"utility_mode" json:"utilityMode"`
}

Briefing tunes what the SessionStart briefing auto-injects: how many items each section carries, recency filters, related-project cross-over, and the hard-cap multiplier. Defaults reproduce the historical hardcoded behavior. The JSON tags back the console's runtime override row (see store.BriefingConfig), which layers on top of this file/env base.

The never-drop invariant: constraints and active-plan rollups are exempt from every filter here, and so is a pinned stage while its Status header marks a live gate -- recency and count filters apply only to the memory index, findings, and sibling sections. ConstraintMaxFull shapes how constraints render (full vs compact), never whether they appear. A stage with no live gate holds its pin only through the StageUnknownMaxAgeDays grace window.

func (Briefing) Validate

func (b Briefing) Validate() error

Validate rejects hard-invalid briefing knobs. Shared by the file/env load path and the console's settings form.

type Budgets

type Budgets struct {
	MaxBriefingTokens  int `yaml:"max_briefing_tokens"`
	RecallBudgetTokens int `yaml:"recall_budget_tokens"`
	// ToolEventMaxChars caps each captured field (tool-call args value, result,
	// hook prompt, session findings) of an Interactions transport event at this
	// many runes. 0 = unlimited (the default): content is stored in full, and the
	// tool-event retention prune -- not truncation -- is the growth control.
	ToolEventMaxChars int `yaml:"tool_event_max_chars"`
}

Budgets holds token budgets for retrieval.

type Capture

type Capture struct {
	// AllowedPorts are the only destination ports capture_url may dial, enforced
	// on the initial URL and on every redirect hop. Empty is deliberately NOT
	// "any port": an unset key, an explicit `allowed_ports: []`, or an empty env
	// override all fall back to the 80/443 default, so the SSRF port guard cannot
	// be switched off by omission. Ports outside 1-65535 are rejected by Validate.
	AllowedPorts []int `yaml:"allowed_ports"`
}

Capture configures the SSRF-guarded URL fetch behind the capture_url tool. Unrelated to PlanCapture, which is about Claude Code plan mode.

type Config

type Config struct {
	// Addr is the HTTP bind address (host:port). Defaults to 127.0.0.1:8081.
	Addr string `yaml:"addr"`
	// DataDir holds the SQLite database and markdown trees. A leading ~ expands.
	DataDir string `yaml:"data_dir"`

	MCP         MCP         `yaml:"mcp"`
	Budgets     Budgets     `yaml:"budgets"`
	Briefing    Briefing    `yaml:"briefing"`
	Search      Search      `yaml:"search"`
	LLM         LLM         `yaml:"llm"`
	Gardener    Gardener    `yaml:"gardener"`
	Capture     Capture     `yaml:"capture"`
	PlanCapture PlanCapture `yaml:"plan_capture"`
	// contains filtered or unexported fields
}

Config is the fully-resolved Seamless configuration.

func Defaults

func Defaults() Config

Defaults returns the built-in configuration. File and env values are layered on top of these, so absent keys keep their default.

func EnsureAPIKey

func EnsureAPIKey(cfg Config) (Config, string, error)

EnsureAPIKey makes a true first run self-configuring: when no config file exists anywhere in the search order and SEAMLESS_MCP_API_KEY is absent from the environment, it generates a bearer key, writes it to ~/.config/seamless/seamless.yaml (0600), and returns the updated config plus the path it wrote. In every other case it changes nothing and returns "": a key already set, an owner-authored config file (even one with an empty key), or an env override (even set-but-empty) are never edited on the owner's behalf -- the existing empty-key warning paths stay in charge there.

func Load

func Load() (Config, error)

Load resolves configuration from the first config file found in the search order ($SEAMLESS_CONFIG, ~/.config/seamless/seamless.yaml, ./seamless.yaml), then applies SEAMLESS_* environment overrides and expands paths.

func LoadFrom

func LoadFrom(path string) (Config, error)

LoadFrom loads defaults, overlays the YAML file at path (if non-empty), applies environment overrides, and expands paths. An empty path uses defaults + env.

func (Config) DBPath

func (c Config) DBPath() string

DBPath is the SQLite database path.

func (Config) MemoryDir

func (c Config) MemoryDir() string

MemoryDir is the root of the per-project memory markdown tree.

func (Config) NotesDir

func (c Config) NotesDir() string

NotesDir is the root of the notes markdown tree.

func (Config) SourcePath

func (c Config) SourcePath() string

SourcePath returns the config file that was loaded, or "" if defaults+env only.

func (Config) Validate

func (c Config) Validate() error

Validate rejects hard-invalid configuration. Soft issues (empty API keys) are surfaced as warnings by the doctor, not here.

type Gardener

type Gardener struct {
	Enabled bool `yaml:"enabled"`
	// IntervalMinutes is the ticker period between full gardener passes.
	IntervalMinutes int `yaml:"interval_minutes"`
	// DedupThreshold is the cosine-similarity floor at/above which two active
	// memories are proposed for a merge.
	DedupThreshold float64 `yaml:"dedup_threshold"`
	// StalenessDays is the no-activity age (no update, injection, or read) beyond
	// which an active memory is proposed for archiving.
	StalenessDays int `yaml:"staleness_days"`
	// DigestDays is the trailing window of completed sessions rolled into a
	// monthly digest proposal.
	DigestDays int `yaml:"digest_days"`
	// ToolEventRetentionDays is the age beyond which transport-level Interactions
	// events (tool.call, hook.prompt) are pruned by the gardener. 0 disables the
	// prune; domain events are never pruned regardless.
	ToolEventRetentionDays int `yaml:"tool_event_retention_days"`
	// StalePlanDays is the age beyond which a captured, never-approved Claude
	// Code plan (plan-status draft/presented) is proposed for abandonment.
	// 0 disables the pass.
	StalePlanDays int `yaml:"stale_plan_days"`
	// StaleStageDays is the age (days since last update) beyond which a stage
	// memory that is not a live gate -- Status done, missing, or unrecognized --
	// is proposed for archiving. Live gates (open/in_progress/blocked) are never
	// proposed regardless of age. 0 disables the pass.
	StaleStageDays int `yaml:"stale_stage_days"`
	// SessionIdleMinutes is the no-activity age beyond which an active session
	// is considered dead: the gardener reaper expires it and the console stops
	// counting it as live. It is the single liveness threshold shared by both,
	// so the reaper cutoff and the console "live" window never drift.
	// Must be positive. The fully resolved config already supplies 45 when the
	// key is absent, so zero is an explicit invalid value rather than a second,
	// silent spelling of the default.
	SessionIdleMinutes int `yaml:"session_idle_minutes"`
}

Gardener configures the propose-only maintenance passes and their ticker.

type LLM

type LLM struct {
	Provider  string    `yaml:"provider"`
	OpenAI    OpenAI    `yaml:"openai"`
	Ollama    Ollama    `yaml:"ollama"`
	Anthropic Anthropic `yaml:"anthropic"`
}

LLM configures chat (digests) and embeddings. OpenAI is the default provider.

func (LLM) EmbeddingModel added in v0.4.1

func (l LLM) EmbeddingModel() string

EmbeddingModel returns the embedding model the configured provider would use, or "" for a provider with no embeddings API (Anthropic) or an unknown one. It reports configuration, not capability: the provider may still lack the credential to actually serve it.

type MCP

type MCP struct {
	APIKey string `yaml:"api_key"`
}

MCP holds the static bearer key guarding /api/mcp and the console.

type Ollama

type Ollama struct {
	BaseURL        string `yaml:"base_url"`
	ChatModel      string `yaml:"chat_model"`
	EmbeddingModel string `yaml:"embedding_model"`
	EmbeddingDims  int    `yaml:"embedding_dims"`
}

Ollama is the local provider (chat + embeddings).

type OpenAI

type OpenAI struct {
	APIKey         string `yaml:"api_key"`
	BaseURL        string `yaml:"base_url"`
	ChatModel      string `yaml:"chat_model"`
	EmbeddingModel string `yaml:"embedding_model"`
	// EmbeddingDims is the model's native dimensionality; 0 = auto-detect from
	// the first embedding response.
	EmbeddingDims int `yaml:"embedding_dims"`
}

OpenAI is the first-class provider (chat + embeddings).

type PlanCapture

type PlanCapture struct {
	// Enabled turns the plan-capture hook endpoints into no-ops when false.
	Enabled bool `yaml:"enabled"`
	// AutoTask creates a tracking task ("Implement plan: ...") when a plan is
	// approved, composing it into the plan via plan_slug.
	AutoTask bool `yaml:"auto_task"`
	// InjectRelated returns related prior plans/memories as additionalContext on
	// a session's first captured plan iteration.
	InjectRelated bool `yaml:"inject_related"`
}

PlanCapture configures capturing Claude Code plan-mode iterations and planning subagents into notes via the PostToolUse/SubagentStop hooks.

type Search struct {
	// SemanticFloor is the minimum cosine similarity a semantic-only hit needs
	// to appear in search results; hits the lexical leg also matched are exempt.
	// Without it the cosine leg is pure nearest-neighbor -- there is always a
	// "nearest" item, so any query fills the page. 0 disables the floor. Useful
	// values depend on the embedding model; the default suits OpenAI
	// text-embedding-3-*.
	SemanticFloor float64 `yaml:"semantic_floor"`
}

Search tunes the human-facing console search (retrieve.Search). Agent-facing recall is deliberately not covered: an agent can judge a weak hit for itself, but an observer reads "20 results" as 20 matches.

Jump to

Keyboard shortcuts

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