config

package
v0.1.2 Latest Latest
Warning

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

Go to latest
Published: Aug 30, 2026 License: AGPL-3.0 Imports: 34 Imported by: 0

Documentation

Overview

Package config loads mivia TOML configuration and resolves provider settings.

Index

Constants

View Source
const (
	ApprovalPolicyWriteOnly = "write-only"
	ApprovalPolicyAuto      = "auto"
	ApprovalPolicyAlways    = "always"
	ApprovalPolicyDeny      = "deny"
)

Approval policies. There are exactly three effective states, matching the TUI settings screen's three choices ("once", "always", "deny"):

  • ApprovalPolicyWriteOnly ("once"): prompt for every write/external tool call.
  • ApprovalPolicyAuto ("always"): auto-approve every tool call, no prompt.
  • ApprovalPolicyDeny ("deny"): auto-deny every gated tool call, no prompt.

ApprovalPolicyAlways is kept ONLY as a legacy input alias for the old [approvals] policy = "always" key, which meant "prompt for every call, including reads" (paranoid mode) - a different concept from the settings-screen's "always" ("accept always"/auto-approve). The two vocabularies collide on the bare word "always" with opposite meanings, which is why DefaultMode and Policy are normalized by two different functions below (NormalizeDefaultMode vs NormalizeApprovalPolicy) instead of one shared switch.

View Source
const (
	MinMemoryEntryBytes    = 256
	MaxMemoryEntryBytes    = 65536
	MaxMemorySearchResults = 50
)

memory bounds. Below the entry floor a memory cannot hold its template; above the ceiling a save would dominate the store. max_search_results is capped so one tool call stays a small, bounded read.

View Source
const (
	MinInspectRepositoryBytes      = 4 << 10
	MaxInspectRepositoryBytesLimit = 256 << 10
)

Bounds for tools max_inspect_repository_bytes. Below the floor the fixed JSON envelope (provenance + one result) cannot fit; above the ceiling the tool stops being a small, bounded read.

View Source
const (
	MinTavilyResponseBytes = 1024
	MaxTavilyResponseLimit = 64 << 20
)

Tavily response bound limits. Below the floor every legitimate response fails; above the ceiling, budget + input allowance + framing slack risks overflowing the dispatcher's ceiling derivation, which would silently drop the backstop to its floor while the wire read stayed effectively infinite.

View Source
const (
	// BatchResultBudgetOff disables the mechanism (the default).
	BatchResultBudgetOff = 0
	// BatchResultBudgetDerived derives the budget from the model's prompt
	// budget instead of naming a number.
	BatchResultBudgetDerived = -1
	// MinBatchResultBudgetBytes is the smallest literal budget that can hold:
	// it matches the loop's degrade floor, below which the first oversized
	// result overshoots by construction.
	MinBatchResultBudgetBytes = 16 << 10
)

Aggregate per-batch tool-result budget knob values (tools batch_result_budget_bytes). The agent loop owns the enforcement; these are the operator-facing constants its config surface is validated against.

View Source
const (
	DefaultProvider  = "openrouter"
	DeepSeekProModel = "deepseek-v4-pro"
)

Built-in provider defaults.

View Source
const (
	DefaultStreamIdleTimeoutSeconds      = 100
	DefaultStreamFirstByteTimeoutSeconds = 240
)

DefaultStreamIdleTimeoutSeconds and DefaultStreamFirstByteTimeoutSeconds mirror internal/provider's own watchdog defaults (provider. DefaultStreamIdleTimeout / DefaultStreamFirstByteTimeout). Duplicated rather than imported: internal/provider already imports internal/config (ollama.go, provider.go), so config importing provider back would cycle.

View Source
const DefaultAgentName = "mivia"

DefaultAgentName is the root-session agent selected when --agent is omitted and a definition with this name is available.

View Source
const DefaultMemoryBackstopMB = 256

DefaultMemoryBackstopMB is the shipped OOM guard when memory_backstop_mb is unset or non-positive (cannot be accidentally disabled via 0).

View Source
const DefaultOrchestrationTimeoutSec = 12 * 60 * 60 // 12 hours

DefaultOrchestrationTimeoutSec is the finite parent-tool / batch budget used when default_timeout_seconds is 0 (or omitted). Long enough for multi-step subagent work; never unbounded so cancel/timeout always surfaces.

View Source
const DefaultOutputReserveTokens = 32_768

DefaultOutputReserveTokens is the completion allowance assumed when the operator has NOT set [chat] max_tokens.

A model's max_output_tokens is a per-response CEILING, not a typical response size. Using it as the default reserve did two harmful things at once: it asked the provider for that much output on EVERY request, and - because the reserve is subtracted from the context window to derive the prompt budget - it permanently removed that much prompt capacity. On a 200k-window model declaring a 128k ceiling (every Claude and GLM entry in the shipped config) that left only 72k of usable prompt and compacted history at 57.6k, under a third of the window the user believed they had.

The reserve itself is not optional: providers validate input_tokens + max_tokens <= context_window and reject the request outright, so the subtracted reserve and the wire max_tokens must stay in lockstep. Only the DEFAULT value changes here. An operator who genuinely wants long responses sets [chat] max_tokens explicitly, which is authoritative up to the model ceiling.

View Source
const DefaultPromptCapTokens = 200_000

DefaultPromptCapTokens is the recommended [chat] max_prompt_tokens value. It bounds the per-request prompt budget for models with large context windows. The planner compacts history at 80% of the budget. It is a recommendation, not a compiled default: an unset knob keeps the window-derived budget.

View Source
const DefaultSubagentRequestTimeoutSec = 1800 // 30 minutes

DefaultSubagentRequestTimeoutSec is the per-LLM-request context deadline for a subagent turn when default_request_timeout_seconds is 0 (or omitted). Product decision: 30 minutes. It bounds one provider request, not the whole task. The 15-minute http.Client wire wall (DefaultHTTPTimeout) stays the hard per-attempt bound under it.

View Source
const DefaultSubagentTotalTimeoutSec = 3600 // 60 minutes

DefaultSubagentTotalTimeoutSec is the whole-subagent wall-clock budget applied when default_total_timeout_seconds is 0 (or omitted). Product decision: 60 minutes. Unlike the per-request deadline above, this bounds the ENTIRE run - every request, tool call, and wait added together - so a provider that trickles bytes forever still ends inside a finite window. It is the last-resort termination guarantee; a smaller per-task timeout from the caller wins when it is tighter. It must stay comfortably above DefaultSubagentRequestTimeoutSec: the total budget is the outer context every per-request deadline is derived from, and a child context.WithTimeout can never extend past its parent's deadline - a total shorter than the request default would silently truncate a single legitimate call before it ever reached its own documented allowance.

View Source
const DefaultTavilyAPIKeyEnv = "TAVILY_API_KEY"
View Source
const DefaultUserConfigTOML = `` /* 389-byte string literal not displayed */

DefaultUserConfigTOML is the minimal config content written for a first-time user when no config file exists anywhere. It selects the shipped default provider (openrouter) and its default model, matching the shipped example at .mivia/mivia.toml.example. internal/cli/setup.go (the explicit `mivia setup` command) and loadFile's silent auto-bootstrap path (LoadOptions.AutoBootstrapUserConfig) both write this exact content, so there is one source of truth for "what a brand-new user's config looks like".

It intentionally omits an [approvals] section: config.ApprovalsConfig's zero value already resolves to ApprovalPolicyAuto (accept every tool call, no prompts) via ApprovalsConfig.ApprovalPolicy() - see internal/config/approvals_config.go - so an absent section and an explicit `default_mode = "always"` behave identically. The commented section below only documents that default for a reader of their own generated file, mirroring .mivia/mivia.toml.example's comment.

View Source
const DefaultWorktreeBranchPrefix = "mivia/"

DefaultWorktreeBranchPrefix is the prefix for branches that mivia creates when the project config does not set [worktrees].branch_prefix.

View Source
const MaxSchemaRetryMax = 10

MaxSchemaRetryMax is the load-time ceiling for [subagents] schema_retry_max. A positive configured value above it is clamped to it, so an operator typo (40 typed instead of 4) cannot configure a 40+-call schema-retry storm, where every retry is a full provider round-trip. Values <= 0 keep the existing "use the default 2" behavior; only 1..MaxSchemaRetryMax pass through.

View Source
const MaxTimeoutSeconds = 315_360_000 // 10 years

MaxTimeoutSeconds is the overflow-safety ceiling for every timeout that EffectiveTimeoutSec returns. It is NOT a policy cap: raise-only semantics let a model push any effective timeout up to 10 years, far beyond any real task. The clamp exists so a huge model-supplied timeout_seconds (which parses fine and fits int64) cannot overflow time.Duration when multiplied by time.Second: 10 years = 3.15e17 ns << MaxInt64 (9.22e18), and even dispatchOrchestrationSec's +15s slack stays safe (3.15e8+15 << 9.2e9 s). Without it, a wrapped-negative duration is ignored by the agent loop, which falls back to DefaultToolTimeout (60s) - far below the operator floor.

View Source
const RootAgentName = "general-orchestrator"

RootAgentName is the compiled identity of the main (root) session agent. The root surface is never a registry member, so this name must stay reserved: file-backed definitions may not use it, and selecting it (flag, /agent, picker) restores the root surface. Constraint for future built-ins: no compiled built-in may carry this name either - the reservation in checkNameCollisions rejects it for every input, by design.

View Source
const UnknownContextWindowTokens = 128_000

UnknownContextWindowTokens is the fail-closed context window tokens (128,000) used when a model's context window is undeclared or unrecognized.

View Source
const UsefulToolResultRequestBytes = 4 << 20

UsefulToolResultRequestBytes is a practical upper bound for a single provider-request tool-result carry size. A nonzero max_tool_result_bytes above this is accepted but warned (never clamped).

Variables

View Source
var DefaultMemoryConfig = MemoryConfig{
	StoreBackend:     "sqlite",
	MaxEntryBytes:    8192,
	MaxEntries:       500,
	MaxSearchResults: 8,
}

DefaultMemoryConfig is the resolved default for memory (plan 68).

View Source
var DefaultMessagingConfig = MessagingConfig{

	MaxBodyBytes:         defaultMessagingMaxBodyBytes,
	MaxMessagesPerTask:   defaultMessagingMaxMessagesPerTask,
	SteerWatchdogSeconds: intPtr(defaultMessagingSteerWatchdogSeconds),
	MailboxCapacity:      defaultMessagingMailboxCapacity,
	MaxPendingQuestions:  defaultMessagingMaxPendingQuestions,
	Routing: MessagingRoutingConfig{
		Mode:                    defaultMessagingRoutingMode,
		MaxAsksPerTask:          defaultMessagingMaxAsksPerTask,
		MaxReferralDepth:        defaultMessagingMaxReferralDepth,
		MaxReferralSpawnsPerRun: defaultMessagingMaxReferralSpawns,
	},
}

DefaultMessagingConfig is the resolved default for [subagents.messaging].

View Source
var DefaultSubagentConfig = SubagentConfig{
	MaxWorkers: 0,
	MaxDepth:   0,
	MaxFanout:  0,

	DefaultTimeout:    0,
	DefaultBudget:     0,
	NestedSteps:       0,
	SystemPrompt:      "",
	MaxAuditRounds:    0,
	InlineOutputBytes: defaultInlineOutputBytes,
	SchemaRetryMax:    2,
	SpawnStaggerMs:    defaultSpawnStaggerMs,
	Messaging:         DefaultMessagingConfig,
}

Default subagent config values. All bounds default to 0 (unlimited); users who want caps set them in [subagents] in mivia.toml.

View Source
var DefaultToolsConfig = ToolsConfig{
	RunTimeoutSec:     900,
	MaxReadBytes:      0,
	MaxWriteKB:        0,
	MaxOutputBytes:    0,
	MaxListDirEntries: 0,
	RedactToolArgs:    false,

	MaxTavilyResponseBytes: 4 << 20,

	MaxFetchKB: 4096,

	MemoryBackstopMB: 256,

	MaxInspectRepositoryBytes: 64 << 10,
}

DefaultToolsConfig defines the built-in tool policy defaults.

View Source
var DefaultWritePathBlocklist = []string{}

DefaultWritePathBlocklist is the built-in set of workspace paths that workflow agent write tools refuse. It ships empty: protection is opt-in via tools write_path_blocklist, not a compiled-in default a project must opt out of. A project that wants .git and .mivia/mivia.toml protected again (recommended - see .mivia/mivia.toml.example) adds them explicitly; tools write_path_blocklist_remove still works against whatever a project adds, in case a future built-in entry is reintroduced.

Functions

func ClearProviderDefaultModel

func ClearProviderDefaultModel(path, providerName string) error

ClearProviderDefaultModel removes the default_model key from [providers.<providerName>] in the TOML config file at path, leaving the rest of that provider's stanza (if any) intact. If removing the key leaves the stanza empty, the stanza itself is dropped so clearing a project-scope override does not litter the project file with an inert [providers.x] table that sets nothing. A path/provider with no default_model key set is not an error: clearing an override that is already absent is a no-op, the same tolerance a repeated command should have. Locked and atomic (see updateConfigFile).

func DefaultConfigCandidates

func DefaultConfigCandidates() []string

DefaultConfigCandidates returns config paths in search order.

func DefaultEnvCandidates

func DefaultEnvCandidates() []string

DefaultEnvCandidates returns env file paths when env_file is unset.

func DefaultStorePathForWorkspace

func DefaultStorePathForWorkspace(root string) string

DefaultStorePathForWorkspace returns the default SQLite path for root.

func EffectiveOutputTokens

func EffectiveOutputTokens(profile ModelSpec, requested *int) *int

EffectiveOutputTokens returns the response allowance for one request: the completion size asked for on the wire, and the reserve subtracted from the context window to derive the prompt budget. Those two must stay in lockstep - providers validate input_tokens + max_tokens <= context_window - so this is the single place both are decided. A nil result means no ceiling applies.

An EXPLICIT request ([chat] max_tokens) is authoritative up to the model's own ceiling. An UNSET request falls back to the model ceiling capped at max(DefaultOutputReserveTokens, reasoning.OutputReserveFloor(profile.Reasoning)), because a model's max_output_tokens is a per-response maximum rather than a sensible per-request default; see DefaultOutputReserveTokens for the prompt-budget damage the uncapped fallback caused. The reasoning. OutputReserveFloor term matters because the wire request layer (internal/provider's effectiveMaxTokens) applies that SAME floor as its own max_tokens fallback for an unset request - reserving only DefaultOutputReserveTokens here for a high-reasoning-effort model (e.g. z.ai's GLM-5.3 family at "max" effort, floor 65536) would let this budget pack history right up to a boundary the wire request then asks to exceed, risking a prompt_tokens+max_tokens over-context-window rejection this function has every input needed to avoid.

func EffectivePromptTokens

func EffectivePromptTokens(profile ModelSpec, maxTokens *int, operatorCap, requested int) int

EffectivePromptTokens computes the local prompt budget after reserving the configured completion allowance and applying an optional operator/session cap.

The returned budget is never negative. When the completion reserve consumes or exceeds the whole context window, the budget is 0: the guard fails closed and the provider-side prompt-too-long recovery is the backstop. Validated config cannot reach that state - load rejects context_window_tokens below minContextWindowTokens (1024) and windows at or below the max_tokens reserve - so 0 only surfaces for hand-constructed profiles in tests and runtime bindings.

A non-positive ContextWindowTokens is the undeclared/unrecognized window fallback: it fails closed at UnknownContextWindowTokens (128k). Validated config also never reaches that branch (load rejects windows below 1024); it exists for hand-constructed profiles in tests and legacy runtime bindings.

func EffectiveTimeoutSec

func EffectiveTimeoutSec(configured int, overrides ...int) int

EffectiveTimeoutSec returns a positive timeout in seconds for subagent / orchestration work. configured is DefaultTimeout or a batch/task override; when configured is <= 0, DefaultOrchestrationTimeoutSec is used as the floor so work cannot hang forever. The function is raise-only: overrides can push the effective timeout up, never below the configured floor. A smaller positive override does not shrink the budget; when several are supplied, the largest override bounds the enclosing operation.

EffectiveTimeoutSec is the right helper for fallback budgets (recovery, unset overrides, per-task floors relative to a batch). For explicit caller-requested timeouts that should be honored as the actual budget — not floored to the 12h default — use RequestedTimeoutSec instead.

The result is clamped to MaxTimeoutSeconds. This is an overflow-safety clamp, not a policy cap: raise-only semantics still hold for every value below 10 years, and every downstream time.Duration(n)*time.Second stays positive (see MaxTimeoutSeconds).

func ExpandPath

func ExpandPath(p string) string

ExpandPath expands leading ~ to the user home directory.

func FirstExisting

func FirstExisting(paths []string) (string, bool)

FirstExisting returns the first path that exists as a regular file.

func IsAlwaysPolicy

func IsAlwaysPolicy(raw string) bool

IsAlwaysPolicy reports whether the policy string represents always-prompt (the legacy "paranoid" policy vocabulary, not the settings-screen "always"/accept-always choice).

func IsAutoPolicy

func IsAutoPolicy(raw string) bool

IsAutoPolicy reports whether the policy string represents auto-approval (YOLO mode).

func IsDefaultOrchestrationStorePath added in v0.1.1

func IsDefaultOrchestrationStorePath(path string) bool

IsDefaultOrchestrationStorePath reports whether path is the config-layer default orchestration-ledger location (see defaultStorePath): the one tier whose directory no operator manages, so opens may harden it 0600/0700. An operator-configured store_path compares false and keeps its modes.

func IsDenyPolicy

func IsDenyPolicy(raw string) bool

IsDenyPolicy reports whether the policy string represents auto-deny (every gated tool call is rejected without a prompt).

func IsInteractiveTTY

func IsInteractiveTTY(f any) bool

IsInteractiveTTY reports whether f is an *os.File attached to a terminal. Shared helper for the `mivia setup` prompt gate and mivia chat's first-run auto-setup gate so both use the identical check. f is typically an io.Reader (stdin) or io.Writer (stdout); it is checked structurally rather than typed as one or the other so one helper serves both.

func IsOllamaLoopback

func IsOllamaLoopback(raw string) bool

IsOllamaLoopback reports whether raw is an absolute http(s) URL whose hostname is a loopback literal (127.0.0.1, ::1, localhost), with no userinfo and no fragment. The hostname is matched as a literal string only — no DNS resolution, no CIDR, no IP normalization — so any other host text fails closed. localhost is trusted as loopback per the locked plan; environments where localhost does not resolve to loopback should use 127.0.0.1. The provider layer complements this literal predicate with a construction-time resolution check and a pinned dial (newLoopbackDialContext) so keyless traffic can only reach a verified loopback address; a localhost that resolves to a non-loopback address fails closed.

func LoadProviderDefaultOverrides

func LoadProviderDefaultOverrides(path string) (map[string]string, error)

LoadProviderDefaultOverrides reads path's own [providers.<name>] default_model keys, unmerged with any other config layer, and returns them as a provider-name-to-model map. It exists so a caller can tell which file actually SETS a given provider's default apart from Load()'s merged/effective result (loadFile's workspace overlay folds a project file's default_model into the same in-memory ProviderConfig the base file populated - see workspace_overlay_test.go - so the merged Resolved alone cannot say whether a provider's effective default came from the user file or a project override). An empty or missing path, or one with no [providers] table, returns an empty map and no error: this is a display-only read, not config validation, and must not block the settings screen from opening over a malformed or absent project file the way Load()'s closed-shape decode would. Providers with no default_model key are simply absent from the returned map rather than mapped to "". This is a read, not covered by updateConfigFile's write lock, but readConfigMap's os.ReadFile call is itself atomic with respect to any concurrent writeFileAtomic rename (a reader either sees the file entirely before or entirely after a rename, never a partial write).

func LoadWorkspaceVerifiers

func LoadWorkspaceVerifiers(workspaceRoot string) (map[string]VerifierProfile, error)

LoadWorkspaceVerifiers parses ONLY the [verifiers] tables of one workspace's own .mivia/mivia.toml. It is the single source of declared profiles for every surface: config.Load populates Resolved.Verifiers from it (never from a user-level base layer), and `mivia workflows validate` calls it directly, so validation and the run resolve the same catalogue on every machine. A missing config file means no declared profiles, not an error.

func Lookup

func Lookup(key string, file map[string]string) (string, bool)

Lookup returns the value for key, preferring the process environment over the file map. A blank process environment value falls through to the file; an unset process environment returns the file value; a blank file value falls back to a set-but-empty process environment value. The double os.LookupEnv call keeps the historical contract: a key set to "" in the process environment and absent from file resolves to ("", true), so callers can distinguish unset from set-but-blank when that distinction matters.

func MCPConfigDigest

func MCPConfigDigest(cfg MCPConfig) (string, error)

MCPConfigDigest returns a stable, secret-free digest of MCP configuration.

func MemoryBackstopBytes

func MemoryBackstopBytes(memoryBackstopMB int) int

MemoryBackstopBytes converts a memory backstop in megabytes to bytes.

func ModelOffersReasoning

func ModelOffersReasoning(spec ModelSpec) bool

ModelOffersReasoning reports whether this model declares any reasoning effort. It is the one predicate every surface asks - config validation, the /model annotation, and the /effort picker's empty state - so "offers nothing" cannot mean different things in different places.

func ModelReasoning

func ModelReasoning(spec ModelSpec) reasoning.Setting

ModelReasoning projects a model profile's reasoning pair as one value, so the request paths that carry it thread a single field instead of two that can drift apart.

func NormalizeApprovalPolicy

func NormalizeApprovalPolicy(raw string) string

NormalizeApprovalPolicy returns the normalized approval policy ("write-only", "auto", "always", or "deny") for the legacy [approvals] policy field and the --approval-policy CLI flag, both of which speak the write-only/auto/always vocabulary. The default fallback for an unset value is ApprovalPolicyAuto: a fresh mivia.toml with no [approvals] section accepts all tools by default rather than prompting.

func NormalizeDefaultMode

func NormalizeDefaultMode(raw string) string

NormalizeDefaultMode returns the normalized approval policy for the TUI settings screen's "approval default" vocabulary ("once" | "always" | "deny", config key [approvals] default_mode). Unlike NormalizeApprovalPolicy, "always" here means "accept always" (auto approve) - it normalizes to ApprovalPolicyAuto, not ApprovalPolicyAlways. The default fallback for an unset value is ApprovalPolicyAuto, matching the shipped default of accepting all tools out of the box.

func NormalizeModelName

func NormalizeModelName(name string) (string, error)

NormalizeModelName canonicalizes a model identifier accepted from config, flags, slash commands, or persisted sessions. The error deliberately omits the supplied value because model identifiers reach terminal output.

func ParseTruthyEnv added in v0.1.2

func ParseTruthyEnv(v string) bool

ParseTruthyEnv reports whether an environment value names truth: 1, true, yes, on, y, t (case-insensitive). Anything else is false. Exported for the launcher's MIVIA_MOUSE override; internal resolvers use it too.

func ProjectConfigExists added in v0.1.1

func ProjectConfigExists(root string) bool

ProjectConfigExists reports whether root has its own project-scoped config file at <root>/.mivia/mivia.toml, without loading it - a narrower question than Load()'s own `found`, which is also satisfied by the shared user-level ~/.mivia/mivia.toml. Used to decide, at storage-path-resolution time, whether root is a real mivia project (safe to default a durable per-project store under root) or an ad-hoc directory (fall back to config.TempStorePath instead).

func ProjectConfigPath

func ProjectConfigPath(workspaceRoot string) string

ProjectConfigPath returns workspaceRoot's own .mivia/mivia.toml path without checking the filesystem, mirroring UserConfigPath's shape for the project layer. Empty workspaceRoot returns "" - there is no project-scoped config to address without a workspace. Callers writing a project-scoped override must additionally check this path differs from whatever base config path is in play (see loadFile's own workspaceOverlayConfigPath guard): when they are the same file, "the project layer" is not a distinct target and treating it as one would silently read a value back as its own override.

func PromptAPIKey

func PromptAPIKey(stdout io.Writer, stdin io.Reader, provider, keyEnv string) (string, error)

PromptAPIKey prompts for a provider's API key on stdout/stdin with the input masked (golang.org/x/term), mirroring the exact prompt `mivia setup` has always used. Callers must confirm stdin/stdout are both a TTY (see IsInteractiveTTY) before calling this - it does not check itself, so calling it against non-interactive stdin will block waiting for input. Returns the trimmed key, which may be empty if the user enters nothing.

func RemoveAgentFile

func RemoveAgentFile(dir string, name string) error

RemoveAgentFile deletes an agent markdown file from the given directory. Locked against a concurrent WriteAgentFile to the same path (see lockPersistFile) so a remove cannot interleave with an in-flight write.

func RemoveMCPServerConfig

func RemoveMCPServerConfig(path string, id string) error

RemoveMCPServerConfig deletes an MCP server entry from mivia.toml. Locked and atomic (see updateConfigFile).

func RemoveProviderConfig

func RemoveProviderConfig(path string, name string) error

RemoveProviderConfig removes a provider definition from mivia.toml. Locked and atomic (see updateConfigFile).

func RequestedTimeoutSec

func RequestedTimeoutSec(configured int, explicit int, taskOverrides ...int) int

RequestedTimeoutSec returns the timeout budget when the caller provides an explicit timeout_seconds value. Unlike EffectiveTimeoutSec's raise-only floor, an explicit positive value IS the budget — it is not floored to the configured default or DefaultOrchestrationTimeoutSec. This lets the root orchestrator bound a dispatch_tasks batch or delegate call to a shorter window than the global default, which is the intended semantics of the timeout_seconds parameter on orchestration tools.

When explicit is 0 or negative ("use the default"), the configured default applies via EffectiveTimeoutSec, preserving backward compatibility. taskOverrides may still raise the budget above the explicit value: a task may legitimately need more than the batch budget, and the whole-call budget must accommodate the longest task. The result is clamped to MaxTimeoutSeconds.

func TempStorePath added in v0.1.1

func TempStorePath(root, name string) string

TempStorePath returns the OS-temp-dir path for an ad-hoc (no project config found) store named name, hash-keyed by root via the existing sanitizePath helper (see DefaultStorePathForWorkspace) so distinct ad-hoc roots never collide. Rooted at os.TempDir(), not os.UserCacheDir() like DefaultStorePathForWorkspace: an ad-hoc run names no real project to key a durable, indefinitely-retained cache entry against, so normal OS temp cleanup can reclaim it instead of silently accumulating forever under the user's real home/cache.

func ToolResultBytesWarnings

func ToolResultBytesWarnings(tc ToolsConfig) []string

ToolResultBytesWarnings returns non-fatal operator warnings for the tools result-cap surface. Values are never clamped here — only advised.

func UpdateActiveModelConfig

func UpdateActiveModelConfig(path string, provider, model string) error

UpdateActiveModelConfig updates the active provider and default model in mivia.toml. Locked and atomic (see updateConfigFile).

func UpdateChatNoticeConfig

func UpdateChatNoticeConfig(path string, showIteration, showPromptCache bool) error

UpdateChatNoticeConfig updates or sets the show_iteration_notices and show_prompt_cache_notices keys under [chat] in the TOML config file at path. Locked and atomic (see updateConfigFile in persist_lock.go) so a concurrent edit to the same file from another goroutine cannot silently lose either write - this mutator previously used its own bespoke read-marshal-write with no synchronization or atomic rename at all, unlike every other config mutator in this package.

func UpdateGeneralConfig

func UpdateGeneralConfig(path string, view GeneralSettings) error

UpdateGeneralConfig persists general and TUI options into the TOML configuration file at path. Locked and atomic (see updateConfigFile in persist_lock.go) so a concurrent edit to the same file from another goroutine cannot silently lose either write and a crash mid-write cannot corrupt the file.

func UpdateMCPServerConfig

func UpdateMCPServerConfig(path string, srv MCPServerSettings) error

UpdateMCPServerConfig inserts or updates an MCP server entry in mivia.toml. Locked and atomic (see updateConfigFile).

func UpdateProjectConfig

func UpdateProjectConfig(path string, ps ProjectSettings) error

UpdateProjectConfig persists project settings into the TOML configuration file at path. Locked and atomic (see updateConfigFile).

func UpdateProviderConfig

func UpdateProviderConfig(path string, pv ProviderSettings) error

UpdateProviderConfig adds or updates a provider and its models in mivia.toml. Locked and atomic (see updateConfigFile).

func UpdateProviderDefaultModel

func UpdateProviderDefaultModel(path, providerName, modelName string) error

UpdateProviderDefaultModel updates or sets the default_model key under [providers.<providerName>] in the TOML config file at path. If the provider section doesn't exist yet, it will be initialized with [providers.<providerName>]. Locked and atomic (see updateConfigFile in persist_lock.go) so a concurrent default-model edit to the same file from another goroutine cannot silently lose either write.

func UserAgentsDir

func UserAgentsDir() string

UserAgentsDir returns ~/.mivia/agents without checking the filesystem.

func UserAuthPath

func UserAuthPath() string

UserAuthPath returns the path to the local CLI auth token file (~/.mivia/auth.json) without checking the filesystem.

func UserConfigPath

func UserConfigPath() string

UserConfigPath returns the fixed user-level config path without checking the filesystem.

func UserEnvPath

func UserEnvPath() string

UserEnvPath returns the fixed user-level env path without checking the filesystem.

func ValidateHTTPSURL

func ValidateHTTPSURL(raw string) (*url.URL, error)

ValidateHTTPSURL rejects anything but a well-formed absolute https URL. It carries no per-provider or per-environment relaxation; a caller needing a loopback or env-var exception applies it before/after calling this.

func WorkspaceAgentsDir

func WorkspaceAgentsDir(root string) string

WorkspaceAgentsDir returns <root>/.agents/agents without checking the filesystem.

func WriteAgentFile

func WriteAgentFile(dir string, ag AgentFileSettings, systemPrompt string) error

WriteAgentFile writes an agent definition markdown file with YAML frontmatter. Locked per target path (see lockPersistFile) and written atomically via writeFileAtomic so a concurrent edit to the SAME agent file from another goroutine cannot interleave with this write, and a crash mid-write cannot leave a truncated agent file behind.

func WriteDefaultUserConfig

func WriteDefaultUserConfig(path string) error

WriteDefaultUserConfig writes DefaultUserConfigTOML to path, creating the parent directory (0700) if needed. It does not check whether path already exists - callers that must not overwrite an existing file need to Stat it themselves first (see internal/cli/setup.go's writeSetupConfigIfMissing and this file's own autoBootstrapUserConfig).

func WriteUserEnvKey

func WriteUserEnvKey(path, key, value string) error

WriteUserEnvKey writes key=value into the env file at path, preserving any existing keys, atomically and with 0600 permissions so the key never appears in a world-readable file. Shared by `mivia setup` and the mivia-chat first-run key prompt (internal/clichat) so there is one place that knows how a key gets persisted to disk.

Types

type AgentCollectionState

type AgentCollectionState string

AgentCollectionState distinguishes a missing agent namespace from an existing namespace with no definition files.

const (
	AgentCollectionNotPresent AgentCollectionState = "not present"
	AgentCollectionEmpty      AgentCollectionState = "empty"
	AgentCollectionHasEntries AgentCollectionState = "has entries"
)

type AgentDiscoveryReport

type AgentDiscoveryReport struct {
	Files         []LoadedAgentFile
	Diagnostics   []AgentFileDiagnostic
	Warnings      []string
	Collection    AgentCollectionState
	UserDirectory string
	WorkspaceDir  string
}

AgentDiscoveryReport contains every safely loaded file and every independent file-level issue. A bad file does not hide valid files from inspection.

func DiscoverAgentFilesReport

func DiscoverAgentFilesReport(workspaceRoot string, loadWorkspace bool) (AgentDiscoveryReport, error)

DiscoverAgentFilesReport discovers both user and workspace definitions while collecting independent malformed, unreadable, and shadowed rows. Workspace agent files are always candidates; loadWorkspace is retained for compatibility and does not gate this collection.

type AgentFileDiagnostic

type AgentFileDiagnostic struct {
	Name   string
	Source AgentSource
	Path   string
	State  AgentFileState
}

AgentFileDiagnostic is a bounded, non-secret inspection row. Error details are intentionally reduced to a class; callers must not print parser text.

type AgentFileSettings

type AgentFileSettings struct {
	Name        string
	Description string
	Provider    string
	Model       string
	Tools       []string
	Skills      []string
	MCPServers  []string
}

AgentFileSettings contains agent definition for markdown file persistence.

type AgentFileSpec

type AgentFileSpec struct {
	Name        *string
	Description *string
	Inherits    *string
	Tools       *[]string
	// AllowEmptyTools permits an explicitly declared empty tools list. It is
	// valid only for a standalone agent with tools = [].
	AllowEmptyTools *bool
	ToolsAdd        *[]string
	ToolsRemove     *[]string
	DisallowedTools *[]string
	// ToolsCore overrides [tools] core for this agent (plan tools/05).
	// nil = inherit (parent's decision, else the global [tools] core).
	ToolsCore *[]string
	// Skills is the skill invocation allowlist for this agent (plan 06).
	// nil = omit (root: all trusted skills; inherited: parent decision);
	// non-nil empty = none; non-nil with names = those skills only.
	Skills *[]string
	// MCPServers is the MCP server allowlist. nil inherits the root default or
	// parent list. An explicit empty list denies every MCP server.
	MCPServers *[]string
	// Provider is the built-in provider name owning Model. It is normalized to
	// lower case at parse time and may only be set together with Model: a
	// provider alone would silently pair a foreign endpoint with the session's
	// model name. Provider selection on a workspace definition is ignored
	// unless the operator enables AllowWorkspaceAgentProviders (credential-
	// routing protection); user definitions always honor it.
	Provider *string
	Model    *string
	MaxTurns *int
	// TimeoutSeconds and MaxTokens are per-agent resource ceilings, deliberately
	// independent of MaxTurns: max_turns = 0 means unlimited iterations, not
	// unlimited wall-clock time or provider spend. nil = inherit the session's.
	TimeoutSeconds *int
	MaxTokens      *int
	SystemPrompt   *string
	// OutputSchema is an optional JSON Schema for the agent's final reply
	// (plan tools/02). Pointer preserves omit vs empty for inheritance.
	OutputSchema *map[string]any
	// InputSchema optionally validates task input at admission.
	InputSchema *map[string]any
}

AgentFileSpec is one presence-preserving agent TOML definition. Pointer fields distinguish omitted keys from empty values.

func ParseAgentFileMarkdown

func ParseAgentFileMarkdown(data []byte, filename string) (AgentFileSpec, string, error)

ParseAgentFileMarkdown parses a single Markdown agent definition with YAML frontmatter and assigns the Markdown body to SystemPrompt.

func ParseAgentFileTOML

func ParseAgentFileTOML(data []byte, filename string) (AgentFileSpec, string, error)

ParseAgentFileTOML parses a single agent definition body with unknown-key rejection and presence-preserving optional fields. filename is the base name used for name agreement (e.g. "researcher.toml").

type AgentFileState

type AgentFileState string

AgentFileState describes the safe inspection state of one discovered file.

const (
	// AgentFileLoaded means the file parsed and is available for resolution.
	AgentFileLoaded AgentFileState = "loaded"
	// AgentFileShadowed means a workspace file lost to a user-name match.
	AgentFileShadowed AgentFileState = "shadowed by user"
	// AgentFileMalformed means the file was readable but did not satisfy the
	// agent-file format or safety contract.
	AgentFileMalformed AgentFileState = "malformed"
	// AgentFileUnreadable means the file or containing directory could not be
	// safely inspected.
	AgentFileUnreadable AgentFileState = "unreadable"
)

type AgentSource

type AgentSource string

AgentSource identifies the trust origin of a loaded agent definition file.

const (
	// AgentSourceUser is a trusted definition under ~/.mivia/agents/.
	AgentSourceUser AgentSource = "user"
	// AgentSourceWorkspace is an untrusted, gated definition under
	// <workspace>/.agents/agents/.
	AgentSourceWorkspace AgentSource = "workspace"
	// AgentSourceBuiltIn is a compiled definition shipped inside the mivia
	// binary. Built-ins are product content, not workspace input: they load
	// regardless of the load_workspace_config gate and always follow the
	// session provider binding.
	AgentSourceBuiltIn AgentSource = "builtin"
)

type AgentsGlobal

type AgentsGlobal struct {
	// LoadWorkspaceConfig enables workspace agent files and related
	// workspace-controlled prompt/skill surfaces. Default true.
	LoadWorkspaceConfig bool
	// AllowWorkspaceAgentProviders, when true, lets a workspace-sourced agent
	// definition select a (provider, model) binding. This is an operator opt-in
	// that accepts a real credential-routing risk: a checked-out repository
	// could route the operator's prompts, tool results, and file contents to
	// another vendor's endpoint authenticated with the operator's own
	// credentials. When false (the default), a workspace agent's
	// provider/model selection is ignored at resolve time (credential-routing
	// protection) and the agent inherits the session provider. Only the user
	// [agents] section may set this; workspace [agents] is never authoritative.
	AllowWorkspaceAgentProviders bool
	// RequireExplicitTools, when true, forces authored agents that omit tools
	// to resolve an empty allowlist (deny-by-default). Default false.
	RequireExplicitTools bool
	// FailOnEmptyToolset refuses an agent whose effective toolset is empty.
	// Default true so a typo cannot publish a no-tool agent silently.
	FailOnEmptyToolset bool
	// MandatoryToolDenylistAdditions are operator additions on top of the
	// compiled mandatory denylist. Config may only add; never remove baseline.
	MandatoryToolDenylistAdditions []string
	// Warnings are non-fatal diagnostics (e.g. workspace [agents] ignored).
	Warnings []string
	// Path is the user config file that supplied these values (may be empty).
	Path string
}

AgentsGlobal is the trusted [agents] section of ~/.mivia/mivia.toml. Only the user file owns these values; workspace [agents] is ignored.

func LoadAgentsGlobal

func LoadAgentsGlobal(workspaceRoot string) (AgentsGlobal, error)

LoadAgentsGlobal reads the trusted user config for [agents] gate and guardrails. Workspace [agents] values are never authoritative; when a workspace config path is supplied and contains [agents], a warning is added.

The user file is always UserConfigPath(). Missing user config yields defaults (gate on, fail_on_empty_toolset true).

type ApprovalsConfig

type ApprovalsConfig struct {
	Policy      string `toml:"policy"`
	DefaultMode string `toml:"default_mode"`
}

ApprovalsConfig controls tool execution approval policies. DefaultMode is the single source of truth for the TUI settings screen ("once" | "always" | "deny") and is what session construction resolves into Session.ApprovalPolicy. Policy is kept only as a legacy input alias for DefaultMode's write-only/auto/always/deny vocabulary; when both are set, DefaultMode wins.

func (ApprovalsConfig) ApprovalPolicy

func (a ApprovalsConfig) ApprovalPolicy() string

ApprovalPolicy returns the normalized approval policy ("write-only", "auto", "always", or "deny"), preferring DefaultMode (settings-screen vocabulary) over the legacy Policy field (write-only/auto/always vocabulary). An unset config (both fields empty) resolves to ApprovalPolicyAuto: accept all tools by default - this is the ONE place that default lives; NormalizeApprovalPolicy/NormalizeDefaultMode keep their own conservative (write-only) empty-string fallback because they are also used for already-running sessions (e.g. Session.ToggleYOLO's zero-value field), where "unset" must not silently mean "auto".

func (ApprovalsConfig) IsAuto

func (a ApprovalsConfig) IsAuto() bool

IsAuto reports whether the approval policy is auto (YOLO mode).

type ChatConfig

type ChatConfig struct {
	SystemPrompt    string `toml:"system_prompt"`
	MaxPromptTokens *int   `toml:"max_prompt_tokens"`
	// MaxContextTokens is retained only as a decode sentinel so the removed
	// setting cannot silently change the prompt safety budget.
	MaxContextTokens *int     `toml:"max_context_tokens"`
	Temperature      *float64 `toml:"temperature"`
	MaxTokens        *int     `toml:"max_tokens"`
	// MaxSteps bounds one interactive turn's agent loop. Unset uses the
	// built-in default; 0 means unlimited, which lets a model stuck emitting
	// tool calls run until the user interrupts it. /steps overrides per session.
	MaxSteps *int `toml:"max_steps"`
	// ShowIterationNotices controls whether per-step/iteration notices (e.g. "iteration 1")
	// are emitted in the TUI chat. Default: false (disabled).
	ShowIterationNotices *bool `toml:"show_iteration_notices"`
	// ShowPromptCacheNotices controls whether prompt cache hit/usage notices
	// are emitted in the TUI chat. Default: false (disabled).
	ShowPromptCacheNotices *bool `toml:"show_prompt_cache_notices"`
}

ChatConfig holds chat session defaults.

type ContextConfig

type ContextConfig struct {
	// MaxSourceEventBytes bounds one projected message's payload.
	MaxSourceEventBytes int `toml:"max_source_event_bytes"`
	// MaxCheckpointBytes bounds a checkpoint's serialized active context.
	MaxCheckpointBytes int `toml:"max_checkpoint_bytes"`
	// MaxCommitEvents bounds how many messages one turn may publish.
	MaxCommitEvents int `toml:"max_commit_events"`
	// MaxCommitEventBytes bounds one turn's aggregate payload bytes.
	MaxCommitEventBytes int `toml:"max_commit_event_bytes"`
	// MaxSessionStateBytes bounds a stored session's serialized messages.
	MaxSessionStateBytes int `toml:"max_session_state_bytes"`
	// MaxExportBytes bounds a context export.
	MaxExportBytes int `toml:"max_export_bytes"`
	// SummaryMetadataBytes bounds the persisted summary envelope. Zero (uncapped
	// by default) means the host imposes no compiled-in ceiling on
	// model-generated summary content.
	SummaryMetadataBytes int `toml:"summary_metadata_bytes"`
	// CheckpointMetadataBytes bounds the summary_metadata column within a
	// checkpoint record. Zero means uncapped.
	CheckpointMetadataBytes int `toml:"checkpoint_metadata_bytes"`
	// Summary is the [context.summary] policy sub-table. Unlike the numeric
	// ceilings above, it is behavior policy, not a storage bound.
	Summary ContextSummaryConfig `toml:"summary"`
}

ContextConfig is the operator's ceiling on durable context storage.

Every field is bytes-or-count, and EVERY ONE DEFAULTS TO 0 = UNCAPPED. These used to be constants compiled into the durable contract, sized far below the 200k-1M token windows this product ships against, and because publication is one transaction, exceeding one refused the whole turn: the conversation stopped persisting the first time the agent read a file, and never recovered because an active context only grows. A ceiling here is a deliberate storage decision by someone who knows their disk, never a default that destroys work the agent already finished.

type ContextSummaryConfig

type ContextSummaryConfig struct {
	// Enabled turns on the bounded provider call that summarizes what
	// compaction dropped. Nil means unset, which resolves to true - the
	// pointer exists precisely so an explicit `enabled = false` is
	// distinguishable from an absent key, which a plain bool cannot express.
	// The call still requires a resolved provider endpoint; without one the
	// summary stays off and summaryDisabledReason names why.
	Enabled *bool `toml:"enabled"`

	// Provider and Model override the binding the summary call runs on,
	// allowing summaries to use a cheaper model than the session binding.
	// Both keys must be set together, and Provider must be a known provider
	// declared under [providers] - the model is not required to appear in
	// that provider's declared models (agent-file precedent: a bad model
	// degrades at call time, and summary failures are intentionally soft).
	// Absent means the summary uses the session binding captured at setup.
	Provider *string `toml:"provider"`
	Model    *string `toml:"model"`
}

ContextSummaryConfig is the operator switch for LLM-backed compaction summaries. It is ENABLED by default: compaction drops messages permanently, so the summary is the only record of what was removed, and a workspace that configures nothing should not silently lose that record. Opting out is explicit.

func (ContextSummaryConfig) SummaryEnabled

func (c ContextSummaryConfig) SummaryEnabled() bool

SummaryEnabled reports the resolved switch: absent means on. Read this rather than the pointer so the opt-out default lives in one place.

type File

type File struct {
	EnvFile      string                    `toml:"env_file"`
	Provider     ProviderSection           `toml:"provider"`
	Providers    map[string]ProviderConfig `toml:"providers"`
	Chat         ChatConfig                `toml:"chat"`
	Subagents    SubagentConfig            `toml:"subagents"`
	Worktrees    WorktreeConfig            `toml:"worktrees"`
	Tools        ToolsConfig               `toml:"tools"`
	Privacy      PrivacyConfig             `toml:"privacy"`
	Context      ContextConfig             `toml:"context"`
	Integrations IntegrationsConfig        `toml:"integrations"`
	MCP          MCPConfig                 `toml:"mcp"`
	Memory       MemoryConfig              `toml:"memory"`
	Harness      HarnessConfig             `toml:"harness"`
	Approvals    ApprovalsConfig           `toml:"approvals"`
	TUI          TUIConfig                 `toml:"tui"`
	Workflows    WorkflowsConfig           `toml:"workflows"`
	// Verifiers is populated by LoadWorkspaceVerifiers from the WORKSPACE'S
	// own .mivia/mivia.toml only (loadFile), never by the tolerant struct
	// decode and never from a user-level base layer: a verifier table with an
	// unknown key must fail the load, and the profiles that judge a project's
	// gates must come from that project's file alone.
	Verifiers map[string]VerifierProfile `toml:"-"`
}

File is the on-disk TOML shape (no secrets).

type GeneralSettings

type GeneralSettings struct {
	Theme                  string
	Mouse                  bool
	ShowReasoning          bool
	ShowIterationNotices   bool
	ShowPromptCacheNotices bool
	ScrollLines            int
	ApprovalDefault        string
	ScreenReader           bool
	ReducedMotion          bool
}

GeneralSettings contains general and TUI settings for config updates.

type HarnessConfig

type HarnessConfig struct {
	// Sandbox controls whether the harness runs verifier/evidence-gate
	// commands inside a bubblewrap sandbox (Linux only). nil (the key
	// omitted) means enabled, so existing configs load unchanged. Disabling
	// it runs those commands directly on the host with no filesystem,
	// network, or environment isolation from the workflow - see
	// .agents/rules/10-security-privacy.md before turning this off.
	Sandbox *bool `toml:"sandbox"`
}

HarnessConfig controls harness-level execution behavior. It is distinct from verifier/project configuration: a project's evidence gates decide WHAT runs, HarnessConfig decides HOW the harness runs it.

func (HarnessConfig) SandboxEnabled

func (h HarnessConfig) SandboxEnabled() bool

SandboxEnabled reports whether the sandbox is enabled (nil means enabled).

type HookConfigFile

type HookConfigFile struct {
	// Path is the file these bytes were read from.
	Path string
	// Data is the raw TOML. Parsing happens in internal/hooks; this type owns
	// provenance only.
	Data []byte
	// Project marks the workspace's own config, as opposed to the user's. Every
	// surface that shows a hook shows this, because "which of these came with
	// the repository" is the question a reader actually has.
	Project bool
}

HookConfigFile is one file's hook bytes and where they came from.

type HooksSource

type HooksSource struct {
	// Files are the hook-bearing configs, user config FIRST.
	//
	// Order is load-bearing rather than cosmetic: PreToolUse stops at the first
	// deny, so the user's own gates answer before a repository's do.
	Files []HookConfigFile
	// Warnings are user-visible startup diagnostics - one per config file that
	// declared hooks mivia will not load. A silently ignored hook is how
	// someone concludes hooks are broken.
	Warnings []string
}

HooksSource is every config file lifecycle hooks may come from.

There are two, and they ADD rather than replace: the user config at its fixed path, and the workspace's own .mivia/mivia.toml. Hooks are the one setting mivia merges across layers, and they have to be - a project's formatter and a user's global gate are not competing answers to one question, they are two hooks, and letting the workspace file replace the user's would silently disarm a gate by opening a repository.

Reading hooks from the workspace means a cloned repository can execute commands on first launch. That is a deliberate product decision, not an oversight: project-defined hooks are the point of the feature. It is stated here, in the `/hooks` listing, at startup and in the docs, because the one thing it must never be is a surprise. Cloning a repository is taking delivery of code you are about to run.

$MIVIA_CONFIG still does not supply hooks. It names the GENERAL config, and a table in it is reported rather than loaded - the workspace file is the project surface, and a second one selected by an environment variable would make "which files can run commands here" depend on how mivia was launched.

func LoadHooksSource

func LoadHooksSource(workspaceRoot string) (HooksSource, error)

LoadHooksSource resolves every config file lifecycle hooks may come from and reports the ones that declared hooks and were not loaded.

A missing user config is not an error: hooks are optional. An unreadable or link-shaped user config IS an error - that file is the operator's own, and an ambiguous read of it fails closed rather than loading zero hooks silently.

func (HooksSource) UserPath

func (s HooksSource) UserPath() string

UserPath is the fixed user config path, for messages that name it.

type IntegrationsConfig

type IntegrationsConfig struct {
	Tavily TavilyConfig `toml:"tavily"`
}

IntegrationsConfig holds API keys and config for third-party services.

type LoadOptions

type LoadOptions struct {
	ConfigPath string
	// WorkspaceRoot selects the project MCP configuration. Empty uses the
	// current working directory for backward compatibility.
	WorkspaceRoot      string
	ProviderOverride   string
	ModelOverride      string
	AllowMissingConfig bool
	// AutoBootstrapUserConfig silently writes a minimal default config to
	// UserConfigPath() (DefaultUserConfigTOML) when the normal search
	// (opts.ConfigPath, then DefaultConfigCandidates()) finds no config file
	// anywhere. It only fires when opts.ConfigPath was left empty - an
	// explicit --config/$MIVIA_CONFIG miss stays a real error (or, for
	// $MIVIA_CONFIG, falls through to the remaining candidates, unchanged
	// pre-existing behavior) rather than being silently papered over. It
	// does not require AllowMissingConfig to also be set, but callers should
	// normally set both: if HOME cannot be resolved (UserConfigPath() is
	// ""), bootstrap is a no-op and AllowMissingConfig alone then decides
	// whether that is a hard error or a found=false load.
	//
	// Defaults to false (zero value) for every existing caller; wire it to
	// true only where a config-file-writing side effect on a missing config
	// is actually wanted (currently: `mivia chat` only - see
	// internal/clichat/chat_command.go's runChat). Every read-only/internal/
	// test caller of config.Load keeps today's found=false behavior
	// unchanged.
	AutoBootstrapUserConfig bool
}

LoadOptions controls config resolution.

type LoadedAgentFile

type LoadedAgentFile struct {
	// Name is the canonical agent name from the filename (without .toml).
	Name   string
	Source AgentSource
	Path   string
	Spec   AgentFileSpec
}

LoadedAgentFile is one safely-read agent definition with provenance.

func DiscoverAgentFiles

func DiscoverAgentFiles(workspaceRoot string, loadWorkspace bool) ([]LoadedAgentFile, []string, error)

DiscoverAgentFiles loads user agent files and workspace agent files.

Project agent definitions under <ws>/.mivia/agents/ always load when present: they replace the former ungated .mivia/agent-prompt.md surface. The user load_workspace_config gate still controls workspace mivia.toml system prompts and project skill handlers at the CLI layer - not agent file discovery.

loadWorkspace is retained for call-site compatibility and is ignored. Same-directory home/workspace is treated as user only. Workspace files that share a name with a user agent are ignored with a warning. Fail-closed on symlinks, non-regular files, hardlink ambiguity, path escapes, and replacement races.

func DiscoverAgentFilesTolerant

func DiscoverAgentFilesTolerant(workspaceRoot string, loadWorkspace bool) ([]LoadedAgentFile, []string, error)

DiscoverAgentFilesTolerant loads user agent files strictly and workspace agent files tolerantly: a single malformed, symlinked, hardlinked, or oversized file under <ws>/.mivia/agents/ must never abort chat startup (INV-AG-34). The trusted user boundary stays fail-closed - any problem in ~/.mivia/agents/ is still a hard error.

loadWorkspace is retained for call-site compatibility and is ignored. Same-directory home/workspace is treated as user only, exactly like DiscoverAgentFiles; it never routes through the tolerant workspace path. Workspace files that share a name with a user agent are ignored with the same shadow warning as DiscoverAgentFiles. Every other non-loaded workspace file becomes a class-only skip warning; raw parser text is never forwarded (see tolerantSkipWarnings and the agents_diagnostics.go contract).

type MCPConfig

type MCPConfig struct {
	Enabled                 bool              `toml:"enabled"`
	StartupTimeoutSeconds   int               `toml:"startup_timeout_seconds"`
	MaxServers              int               `toml:"max_servers"`
	MaxToolsPerServer       int               `toml:"max_tools_per_server"`
	MaxToolSchemaBytes      int               `toml:"max_tool_schema_bytes"`
	MaxToolDescriptionBytes int               `toml:"max_tool_description_bytes"`
	MaxToolResultBytes      int               `toml:"max_tool_result_bytes"`
	Servers                 []MCPServerConfig `toml:"servers"`
}

MCPConfig controls trusted MCP server definitions. A project definition replaces a user definition with the same server ID as one complete unit.

func LoadTrustedMCPConfig

func LoadTrustedMCPConfig(workspaceRoot string) (MCPConfig, []string, error)

LoadTrustedMCPConfig loads the effective user and project MCP configuration. A project server replaces a user server with the same ID as one unit.

type MCPHeaderConfig

type MCPHeaderConfig struct {
	Name     string `toml:"name"`
	ValueEnv string `toml:"value_env"`
}

MCPHeaderConfig maps an HTTP header to the name of its environment value.

type MCPServerConfig

type MCPServerConfig struct {
	ID             string            `toml:"id"`
	Transport      string            `toml:"transport"`
	Command        string            `toml:"command"`
	URL            string            `toml:"url"`
	Args           []string          `toml:"args"`
	Env            []string          `toml:"env"`
	Headers        []MCPHeaderConfig `toml:"headers"`
	Global         bool              `toml:"global"`
	TimeoutSeconds int               `toml:"timeout_seconds"`
}

MCPServerConfig is one MCP server definition. It stores only environment variable names. It never stores a secret value.

func LoadScopeMCPServers

func LoadScopeMCPServers(userPath, projectPath string) (userServers, projectServers []MCPServerConfig, err error)

LoadScopeMCPServers loads user and project MCP server configurations separately without merging.

type MCPServerSettings

type MCPServerSettings struct {
	ID        string
	Transport string
	Command   string
	Args      []string
	Endpoint  string
	EnvNames  []string
}

MCPServerSettings contains MCP server definition for config updates.

type MemoryConfig

type MemoryConfig struct {
	// Enabled controls whether the memory tools are wired. nil (the key
	// omitted) means enabled, so existing configs load unchanged.
	Enabled *bool `toml:"enabled"`
	// StoreBackend is "memory" (ephemeral, in-process) or "sqlite"
	// (durable, default). Mirrors [subagents] store_backend.
	StoreBackend string `toml:"store_backend"`
	// StorePath is the project memory database file. Empty resolves by the
	// three-tier rule in resolveMemoryConfig: a workspace with its own
	// project config defaults to <workspace>/.mivia/memory.db; an ad-hoc
	// directory defaults to a temp-dir store keyed by the sanitized root.
	// A repo owner may point it at a tracked path and commit memories with
	// the repository. Relative paths resolve against the workspace root;
	// "~/..." expands to the home directory.
	StorePath string `toml:"store_path"`
	// OrgID is the org identity for org-scoped memory, honored from the
	// user config file only. Empty means org scope is unavailable.
	OrgID string `toml:"org_id"`
	// MaxEntryBytes caps one rendered entry. Default 8192.
	MaxEntryBytes int `toml:"max_entry_bytes"`
	// MaxEntries caps the row count per store file. Default 500.
	MaxEntries int `toml:"max_entries"`
	// MaxSearchResults caps memory_search results. Default 8.
	MaxSearchResults int `toml:"max_search_results"`
	// BlockPatterns are regexes; a save whose content matches any of them is
	// refused. Configuration-only, like the privacy redaction patterns.
	BlockPatterns []string `toml:"block_patterns"`
	// InjectCore enables auto-injecting the bounded "core" memory tier into
	// the system prompt at session start (D1, plan 76). Default false: it
	// changes every session's prompt composition, and round-2 review of
	// plan 76 found operators who allowlist the mivia binary in
	// [tools].run_allowlist weaken D1a's promotion gate - shipping this off
	// by default means an operator opts into that exposure per repo,
	// consciously, rather than getting new prompt content on upgrade.
	InjectCore bool `toml:"inject_core"`
}

MemoryConfig configures durable agent memory (plan 68).

Org identity is USER-owned: org_id is honored only from the user config file (~/.mivia/mivia.toml). A workspace config is repo-controlled and must not name the org store its agents write into, so a workspace org_id is ignored at load (see resolveMemoryConfig).

func (MemoryConfig) IsEnabled

func (m MemoryConfig) IsEnabled() bool

IsEnabled reports whether memory is enabled (nil means enabled).

type MessagingConfig

type MessagingConfig struct {
	// Enabled is ignored: messaging is always enabled. Retained so older
	// configs with enabled=true|false still parse without error.
	Enabled *bool `toml:"enabled"`
	// MaxBodyBytes is the per-message inline body budget. Default 2048.
	MaxBodyBytes int `toml:"max_body_bytes"`
	// MaxMessagesPerTask is the child upstream send quota per attempt. Default 32.
	MaxMessagesPerTask int `toml:"max_messages_per_task"`
	// MailboxCapacity is parent→child mailbox depth (phase 03). Default 32.
	MailboxCapacity int `toml:"mailbox_capacity"`
	// MaxPendingQuestions is RESERVED and a no-op: the effective value is always
	// 1. Exactly one park per task is structurally enforced by the question
	// registry (one pendingQuestion per runID/taskID key) plus the awaiting_input
	// single-bit ledger status; N>1 is unsupported. The field still parses from
	// TOML (and the config resolver still fills the default of 1) so existing
	// configs load unchanged, but nothing reads it for behavior.
	MaxPendingQuestions int `toml:"max_pending_questions"`
	// SteerWatchdogSeconds: nil = default (300s); explicit 0 = disabled
	// (unbounded); positive = seconds.
	SteerWatchdogSeconds *int `toml:"steer_watchdog_seconds"`
	// Routing is parent-side Ask referral policy (plan 53.04). Always active.
	Routing MessagingRoutingConfig `toml:"routing"`
}

MessagingConfig is the [subagents.messaging] surface for typed, budgeted agent messages. Messaging is always on; Enabled is accepted in TOML for forward compatibility but ignored (IsEnabled always returns true).

func (MessagingConfig) IsEnabled

func (m MessagingConfig) IsEnabled() bool

IsEnabled always returns true. Messaging cannot be disabled (product decision 2026-08-03); the TOML enabled field is ignored if present.

func (MessagingConfig) SteerWatchdogSecondsResolved

func (m MessagingConfig) SteerWatchdogSecondsResolved() int

SteerWatchdogSecondsResolved returns the effective steer watchdog interval in seconds: nil → the default 300, an explicit 0 → disabled (unbounded), otherwise the configured value. Single source of truth for the CLI handler construction sites (plan 54 §4.5); the config-layer resolver (resolveMessagingConfig) fills nil the same way, so this is idempotent on both resolved and raw configs.

type MessagingRoutingConfig

type MessagingRoutingConfig struct {
	// Mode is "policy" (default) or "parent" (unimplemented).
	Mode string `toml:"mode"`
	// MaxAsksPerTask bounds UNANSWERED asks posted by one task. Default 4.
	// Semantics: the slot is released when an ask is answered or sealed.
	MaxAsksPerTask int `toml:"max_asks_per_task"`
	// MaxReferralDepth is max hops in an ask chain (A→B→C = 2). Default 2.
	MaxReferralDepth int `toml:"max_referral_depth"`
	// Allow is "from_role->to_role" pairs. Empty = any live same-run role;
	// referral-as-spawn always requires an explicit pair.
	Allow []string `toml:"allow"`
	// MaxReferralSpawnsPerRun caps referral-as-spawn. Default 4.
	MaxReferralSpawnsPerRun int `toml:"max_referral_spawns_per_run"`
}

MessagingRoutingConfig is [subagents.messaging.routing] for peer referral. mode "policy" is implemented; "parent" is declared but unimplemented.

type ModelSettings

type ModelSettings struct {
	Name                string
	ContextWindowTokens int
	MaxOutputTokens     int
	Reasoning           string
	ReasoningEfforts    []string
}

ModelSettings contains model information for config updates.

type ModelSpec

type ModelSpec struct {
	Name                string `toml:"name"`
	ContextWindowTokens int    `toml:"context_window_tokens"`
	MaxOutputTokens     int    `toml:"max_output_tokens,omitempty"`
	// ReasoningEfforts is the ordered set of reasoning levels this model
	// offers. Empty means the model has no reasoning surface. Order is
	// preserved because it is the order the /effort picker lists.
	ReasoningEfforts []reasoning.Level `toml:"reasoning_efforts,omitempty"`
	// Reasoning is this model's DEFAULT effort and must be a member of
	// ReasoningEfforts. Empty means the model ships with no reasoning field
	// sent, which a model that offers efforts may still choose - the user
	// opts in through /effort. Reasoning belongs to the model rather than to
	// [chat] because capabilities and value sets differ per model, so one
	// session-global value would be wrong for every model it did not match.
	Reasoning reasoning.Level `toml:"reasoning,omitempty"`
	// ReasoningDialect is this model's wire shape. Empty uses the provider's
	// vetted default where one exists; load refuses an active level with no
	// resolvable dialect rather than letting the key silently do nothing.
	ReasoningDialect reasoning.Dialect `toml:"reasoning_dialect,omitempty"`
}

ModelSpec is one explicitly configured provider model and its physical context capacity. The name is provider-qualified by its containing group.

func ResolveModelProfile

func ResolveModelProfile(profiles []ModelSpec, name string) (ModelSpec, bool)

ResolveModelProfile finds the profile named name in profiles. If absent, it synthesizes a profile with UnknownContextWindowTokens and reports found=false.

func (*ModelSpec) UnmarshalTOML

func (m *ModelSpec) UnmarshalTOML(value *unstable.Node) error

UnmarshalTOML enforces the narrow model object shape. A scalar model array is rejected instead of being silently treated as an empty catalog.

It implements go-toml's unstable.Unmarshaler, which is unversioned by name and whose signature has already changed across releases (*unstable.Node in v2.2.3, []byte later). Dispatch is a runtime type assertion, so a dependency bump that changes it does not break the build: this method would simply stop being called, every check below would become dead code, and the closed model shape would silently reopen. Any go-toml upgrade must re-run the model shape tests, not just `go build`.

type PrivacyConfig

type PrivacyConfig struct {
	RedactToolArgs bool `toml:"redact_tool_args"`
	// RedactionPatterns are regexes applied to operator-visible text (tool
	// previews, event bodies, audit metadata). Nothing is compiled in: unset
	// means no text is redacted anywhere. An invalid pattern is a load error.
	RedactionPatterns []string `toml:"redaction_patterns"`
	// RedactionKeyNames are JSON object keys whose values are elided wholesale
	// (case-insensitive substring match). Unset means no key-based redaction.
	RedactionKeyNames []string `toml:"redaction_key_names"`
	// RedactionPlaceholder replaces each match. Defaults to "[redacted]".
	RedactionPlaceholder string `toml:"redaction_placeholder"`
}

PrivacyConfig controls operator-visible redaction of tool I/O. RedactToolArgs defaults to false (show argv/args). Enable via TOML or MIVIA_REDACT_TOOL_ARGS for stricter privacy in shared/recorded sessions.

type ProjectSettings

type ProjectSettings struct {
	EnvFile         string
	BranchPrefix    string
	SystemPrompt    string
	Temperature     string
	MaxTokens       string
	MaxPromptTokens string
	MaxSteps        string
	RunTimeoutSec   int
	StoreBackend    string
	StorePath       string
	Sandbox         bool
	RedactToolArgs  bool
}

ProjectSettings contains project-scoped configuration for config updates.

type ProviderConfig

type ProviderConfig struct {
	// Models is the explicit, finite model catalog for this provider.
	Models []ModelSpec `toml:"models,omitempty"`
	// DefaultModel is this provider's default model. When Models is non-empty,
	// it must be a member of the allowlist.
	DefaultModel string `toml:"default_model,omitempty"`
	// LegacyModel is a decode sentinel. The old scalar provider model key is
	// rejected explicitly so it cannot override an explicit catalog entry.
	LegacyModel *string `toml:"model"`
	BaseURL     string  `toml:"base_url"`
	APIKeyEnv   string  `toml:"api_key_env"`
	HTTPReferer string  `toml:"http_referer"`
	XTitle      string  `toml:"x_title"`
}

ProviderConfig holds non-secret provider settings.

type ProviderModelGroup

type ProviderModelGroup struct {
	Provider       string
	DefaultModel   string
	Models         []ModelSpec
	Active         bool
	Selectable     bool
	DisabledReason string
}

ProviderModelGroup is a secret-free provider group for the model picker.

type ProviderRuntime

type ProviderRuntime struct {
	ProviderName string
	BaseURL      string
	APIKeyEnv    string
	APIKeySet    bool
	APIKey       string
	HTTPReferer  string
	XTitle       string
	Models       []ModelSpec
}

ProviderRuntime contains resolved provider construction settings. It is not returned by ModelCatalog and must never be rendered or sent to model-facing tools. APIKey is only consumed by the provider factory.

type ProviderSection

type ProviderSection struct {
	Name string `toml:"name"`
	// PromptCache is "auto" (default) or "off". "auto" enables capture of
	// provider-reported prompt-cache usage accounting and, on providers that
	// honor explicit markers (openrouter), emission of cache_control markers
	// on the stable prefix. "off" disables both. It cannot disable a
	// provider's own automatic server-side caching.
	PromptCache string `toml:"prompt_cache"`
	// StreamIdleTimeoutSeconds bounds the gap between successive bytes on any
	// provider read (streaming or non-streaming), once the first byte has
	// arrived. Unset (nil) resolves to DefaultStreamIdleTimeoutSeconds
	// (100s). This is a process-wide setting, not per-provider: mivia runs
	// one active provider configuration per process.
	StreamIdleTimeoutSeconds *int `toml:"stream_idle_timeout_seconds"`
	// StreamFirstByteTimeoutSeconds bounds the wait for the first byte of a
	// provider read, from request-issued. Unset (nil) resolves to
	// DefaultStreamFirstByteTimeoutSeconds (240s).
	StreamFirstByteTimeoutSeconds *int `toml:"stream_first_byte_timeout_seconds"`
}

ProviderSection selects the active provider.

type ProviderSettings

type ProviderSettings struct {
	Name         string
	BaseURL      string
	APIKeyEnv    string
	DefaultModel string
	Models       []ModelSettings
}

ProviderSettings contains provider information for config updates.

type Resolved

type Resolved struct {
	// RedactionPolicy is compiled during Load so an invalid pattern fails at
	// startup. Nil means the workspace configured none, which redacts nothing.
	RedactionPolicy *redact.Policy
	// MaxSteps is nil when unconfigured, so the chat default applies. A
	// configured 0 is meaningful (unlimited) and must not be confused with it.
	MaxSteps     *int
	ConfigPath   string
	EnvFilePath  string
	EnvFileUsed  bool
	ProviderName string
	Model        string
	// Models is retained as a compatibility projection of ModelProfiles.
	Models []string
	// ModelProfiles is the active provider's copied model catalog.
	ModelProfiles []ModelSpec
	// ProviderRuntimes contains resolved backend material for provider.NewForProvider.
	ProviderRuntimes map[string]ProviderRuntime

	BaseURL   string
	APIKeyEnv string
	APIKeySet bool
	// APIKey is populated only for runtime use; never print it.
	APIKey          string
	HTTPReferer     string
	XTitle          string
	SystemPrompt    string
	MaxPromptTokens *int
	// MaxContextTokens is retained as a compatibility projection of the
	// selected model's effective prompt budget.
	MaxContextTokens       int
	Temperature            *float64
	MaxTokens              *int
	ShowIterationNotices   bool
	ShowPromptCacheNotices bool
	Subagents              SubagentConfig
	Worktrees              WorktreeConfig
	StoreBackend           string
	StorePath              string
	// StorePathSet reports whether [subagents].store_path was set in the
	// selected configuration. It lets repository storage resolve its default
	// from the repository root instead of the current worktree.
	StorePathSet bool
	// Privacy is resolved from [privacy] TOML and MIVIA_REDACT_TOOL_ARGS.
	Privacy PrivacyConfig
	// Context is the operator's durable storage ceilings, uncapped by default.
	Context ContextConfig
	// Tools is the resolved tool execution policy.
	Tools ToolsConfig
	// MCP is the resolved MCP server configuration.
	MCP MCPConfig
	// MCPWarnings are scrubbed operator diagnostics for the MCP configuration.
	MCPWarnings []string
	// Memory is the resolved [memory] configuration.
	Memory MemoryConfig
	// Harness is the resolved [harness] configuration.
	Harness HarnessConfig
	// Approvals is the resolved [approvals] configuration.
	Approvals ApprovalsConfig
	// TUI is the resolved [tui] configuration.
	TUI TUIConfig
	// Workflows is the resolved [workflows] configuration.
	Workflows WorkflowsConfig
	// Verifiers is the workspace-declared verifier profile set from the
	// [verifiers.<name>] tables. The host ships no built-in profiles.
	Verifiers map[string]VerifierProfile

	// TavilyAPIKey is the Tavily web search API key (set via TAVILY_API_KEY env).
	// When set, the search tool uses Tavily as the primary web search engine.
	TavilyAPIKey string
	// StreamIdleTimeout is the resolved [provider] stream_idle_timeout_seconds,
	// defaulted when unset. See ProviderSection.StreamIdleTimeoutSeconds.
	StreamIdleTimeout time.Duration
	// StreamFirstByteTimeout is the resolved [provider]
	// stream_first_byte_timeout_seconds, defaulted when unset. See
	// ProviderSection.StreamFirstByteTimeoutSeconds.
	StreamFirstByteTimeout time.Duration

	// PromptCache is the resolved "auto" or "off" policy for prompt-cache
	// usage capture and explicit cache_control markers. Always one of those
	// two values after Load - see ProviderSection.PromptCache.
	PromptCache string
	// contains filtered or unexported fields
}

Resolved is the fully resolved runtime config used by the CLI.

func Load

func Load(opts LoadOptions) (*Resolved, error)

Load resolves config + env credentials.

func (*Resolved) AllowsModel

func (r *Resolved) AllowsModel(name string) bool

AllowsModel reports whether name may be selected under the resolved policy.

func (*Resolved) ModelCatalog

func (r *Resolved) ModelCatalog() []ProviderModelGroup

ModelCatalog returns a deep copy of the secret-free provider catalog. ReasoningEfforts is cloned per model because cloning the []ModelSpec alone would leave every caller sharing one backing array with the stored catalog.

func (*Resolved) ModelChoices

func (r *Resolved) ModelChoices() string

ModelChoices renders the selectable set for usage and error messages.

func (*Resolved) ModelChoicesFor

func (r *Resolved) ModelChoicesFor(providerName string) string

ModelChoicesFor renders the selectable catalog for one provider.

func (*Resolved) OtherProvidersWithModel

func (r *Resolved) OtherProvidersWithModel(exclude, name string) []string

OtherProvidersWithModel returns the provider names, in ModelCatalog order, of every Selectable provider (other than exclude) whose catalog contains a model named exactly name. Matching is exact (case-sensitive, trimmed), matching AllowsModel's and the model picker's own comparison - model names are provider-declared identifiers, not free text, so this does not normalize case the way a user-facing search would.

This is the single cross-provider model lookup for BOTH the classic REPL (internal/clichat) and the new TUI (internal/uiadapter): those two packages do not import each other, so the shared logic lives here, one level below both, rather than being duplicated (or worse, silently diverging - see the pre-existing bug this fixes: internal/uiadapter's resolveProviderAndModel used to return the FIRST provider whose catalog happened to contain the name, in catalog order, with no check for a second match - a silent, order-dependent provider switch on any name collision). Only Selectable providers are considered: a provider with no API key set is not a switch that would actually work, so it must not appear in a "found under provider X" hint that tells the user to try it.

func (*Resolved) SetModelCatalogForTest

func (r *Resolved) SetModelCatalogForTest(catalog []ProviderModelGroup)

SetModelCatalogForTest sets the model catalog on Resolved for testing purposes.

func (*Resolved) Validate

func (r *Resolved) Validate() error

type SubagentConfig

type SubagentConfig struct {
	MaxWorkers int `toml:"max_workers"`
	// MaxDepth caps the dependency depth of one orchestrated task graph
	// (spawn_agent depends_on chains). 0 means unlimited (default); a
	// positive value caps the depth.
	MaxDepth int `toml:"max_depth"`
	// MaxFanout caps the number of tasks admitted in one orchestration.
	// 0 means unlimited (default); a positive value caps the count.
	MaxFanout      int `toml:"max_fanout"`
	DefaultTimeout int `toml:"default_timeout_seconds"`
	// DefaultRequestTimeoutSec is the per-LLM-request timeout for subagents
	// (seconds). When 0, requestTimeout() uses DefaultSubagentRequestTimeoutSec
	// (1800s, 30 minutes) as the per-request context deadline. The 15-minute
	// http.Client wall stays the hard per-attempt bound; the 12-hour
	// orchestration default no longer feeds individual subagent requests.
	DefaultRequestTimeoutSec int `toml:"default_request_timeout_seconds"`
	// DefaultTotalTimeoutSec is the whole-subagent wall-clock budget
	// (seconds). 0 = unset = DefaultSubagentTotalTimeoutSec (3600s, 60
	// minutes). Negative = off: a direct spawn with no per-task timeout then
	// has no handler-level bound at all, and workflow or panel steps whose
	// own timeout is unset stay bounded only by workflow policy - this is an
	// explicit operator opt-out of the last-resort termination guarantee. A
	// positive value is the budget itself; a tighter per-task timeout from
	// the caller still wins.
	DefaultTotalTimeoutSec int `toml:"default_total_timeout_seconds"`
	// WireStream opts nested subagent calls into the wire-stream transport:
	// the request goes to the provider with stream:true while the call keeps
	// its non-stream contract - the full answer is assembled before it comes
	// back. Nil means unset, which resolves to true - the pointer exists so
	// an explicit `wire_stream = false` is distinguishable from an absent
	// key, which a plain bool cannot express. False opts out and keeps every
	// nested call on the plain non-stream endpoint. See WireStreamResolved
	// for why this default was briefly flipped off and then restored.
	WireStream    *bool  `toml:"wire_stream"`
	DefaultBudget int    `toml:"default_budget"`
	SystemPrompt  string `toml:"system_prompt"`
	NestedSteps   int    `toml:"nested_steps"`

	// StoreBackend selects the ledger storage backend: "memory" (default) or "sqlite".
	StoreBackend string `toml:"store_backend"`

	// StorePath is the configured SQLite file path. Chat uses this path for
	// sessions, context, worktree routes, and orchestration.
	StorePath string `toml:"store_path"`

	// HandleRetentionSeconds controls how long completed orchestration run
	// handles remain accessible via inspect_agents/join_run/cancel_run
	// before automatic eviction. Default: 600 (10 minutes). 0 = no retention.
	HandleRetentionSeconds int `toml:"handle_retention_seconds"`

	// MaxAuditRounds controls the maximum number of ADLC Step 5 bug audit
	// rounds. When 0 (default), rounds are unlimited. Set to a positive
	// value to cap.
	MaxAuditRounds int `toml:"max_audit_rounds"`

	// InlineOutputBytes is the per-task output size threshold (bytes). Task
	// results whose output body is at or below this threshold are inlined in
	// the model-visible result envelope (the "output" field). Results above
	// this threshold emit only "output_ref", "output_bytes", and a bounded
	// "synopsis"; the parent fetches the full body via ledger_read.
	// Default: 4096. An explicit 0 means "always use refs" (never inline) and
	// is preserved through resolution via inlineOutputBytesSet; an absent key
	// falls back to the 4096 default. Errors follow the same rule with
	// "error"/"error_ref".
	InlineOutputBytes int `toml:"inline_output_bytes"`

	// SchemaRetryMax is how many corrective re-entries a multi-step child may
	// take after an invalid schema-validated reply (plan tools/02). Default 2.
	// The initial attempt is separate: retry_max=2 allows two corrective turns.
	// Clamped at load: <= 0 means the default 2; a positive value above
	// MaxSchemaRetryMax is clamped to it.
	SchemaRetryMax int `toml:"schema_retry_max"`

	// SpawnStaggerMs staggers the start of each task after the first within
	// one dispatch batch by this many milliseconds, so concurrent workers do
	// not fire their first provider call on the same instant (the step-1
	// thundering-herd hang behind overloaded local proxies). Default: 150.
	// An explicit 0 disables staggering and is preserved through resolution
	// via spawnStaggerMsSet, mirroring inline_output_bytes; an absent key
	// falls back to the default. Values above 1000 are clamped to 1000.
	SpawnStaggerMs int `toml:"spawn_stagger_ms"`

	// Messaging configures typed agent-to-agent messaging (plan 53). Nested
	// under [subagents.messaging]. Always enabled (product decision 2026-08-03).
	Messaging MessagingConfig `toml:"messaging"`

	// TaskRetry configures automatic retry/backoff for a dispatched task whose
	// failure is classified transient (provider.IsTransient - network blip,
	// 429, 5xx, timeout) or that timed out. Nested under [subagents.retry].
	// Distinct from SchemaRetryMax above, which governs corrective re-entries
	// after an invalid schema-validated reply within one task, not whole-task
	// retry. All-zero (the default) disables retry entirely, identical to
	// today's behavior: a deployment must opt in. See task_retry_config.go
	// for the TaskRetryConfig type.
	TaskRetry TaskRetryConfig `toml:"retry"`
	// contains filtered or unexported fields
}

SubagentConfig holds subagent execution policy and storage configuration.

func (SubagentConfig) WireStreamResolved added in v0.1.1

func (c SubagentConfig) WireStreamResolved() bool

WireStreamResolved reports the resolved wire-stream switch: absent means on. Read this rather than the pointer so the opt-out default lives in one place.

This default was briefly flipped off as a mitigation after two sessions reported dispatch_tasks batches sticking at "running" with no output. Root-caused (see DefaultMaxBudget in internal/subagents/subagents.go): the actual cause was an unrelated admission-control bug - a 1000 default MaxBudget silently rejecting realistic multi-thousand-per-task budgets before any provider call was ever made (the reported "0ms" failures and "budget limit exceeded"/"run budget exceeded" errors both point directly at it). Confirmed by live reproduction: the exact failing batch (4 tasks, budget 6000 each) succeeds on the first call with wire_stream left on once the budget default is fixed, and a concurrency+stall stress test against wire_stream found no hang (openai_compat_turnstream_concurrency_test.go). Restored to on.

type TUIConfig

type TUIConfig struct {
	Theme string `toml:"theme"`
	// Mouse is the cockpit's mouse-capture default: true (default)
	// enables in-app drag-select, copy, and wheel; false hands the mouse
	// to the terminal for native selection. MIVIA_MOUSE overrides it at
	// startup; Settings → General changes it live. See
	// docs/design/cockpit-research.md rule 6.5.
	Mouse         *bool `toml:"mouse"`
	ShowReasoning *bool `toml:"show_reasoning"`
	ScrollLines   *int  `toml:"scroll_lines"`
	ScreenReader  *bool `toml:"screen_reader"`
	ReducedMotion *bool `toml:"reduced_motion"`
}

TUIConfig controls terminal user interface preferences.

type TaskRetryConfig

type TaskRetryConfig struct {
	// MaxRetries is the maximum retry attempts per task. 0 (default) disables
	// retry.
	MaxRetries int `toml:"max_retries"`
	// BaseBackoffSeconds is the initial backoff before the first retry.
	BaseBackoffSeconds float64 `toml:"base_backoff_seconds"`
	// MaxBackoffSeconds caps the per-retry backoff.
	MaxBackoffSeconds float64 `toml:"max_backoff_seconds"`
	// BackoffFactor is the exponential multiplier applied after each attempt.
	// 0 selects the coordinator's default (2.0) once MaxRetries > 0.
	BackoffFactor float64 `toml:"backoff_factor"`
	// JitterFraction randomizes each backoff by ±JitterFraction/2. 0 disables
	// jitter.
	JitterFraction float64 `toml:"jitter_fraction"`
}

TaskRetryConfig is the [subagents.retry] surface for whole-task retry. Fields mirror coordinator.RetryPolicy; internal/config cannot import internal/coordinator (coordinator already imports config), so the CLI wiring layer (internal/cli/orchestration_state.go) converts this into a coordinator.RetryPolicy. All-zero means "no retry" - the safe default.

Retry is NOT safe for a task with side effects it can't undo. A retry re-dispatches the whole task from scratch (a fresh attempt, the same goal and tool access) - it does not skip tool calls the failed attempt already made. A task that writes a file, calls an external API, or sends a message and only THEN hits a late transient error will repeat those side effects on retry. Enable this only for tasks whose tool calls are safe to repeat, or where that risk is acceptable.

type TavilyConfig

type TavilyConfig struct {
	// APIKeyEnv overrides the env var name (default "TAVILY_API_KEY").
	APIKeyEnv string `toml:"api_key_env"`
	// Disable explicitly disables Tavily even if the env var is set.
	Disable bool `toml:"disable"`
}

TavilyConfig configures the Tavily web search integration.

type ToolsConfig

type ToolsConfig struct {
	// RunAllowlist extends the built-in default allowlist (union).
	RunAllowlist []string `toml:"run_allowlist"`
	// RunAllowlistOnly replaces the built-in default allowlist entirely.
	RunAllowlistOnly []string `toml:"run_allowlist_only"`
	// RunBlocklist removes programs from the resolved allowlist (takes precedence).
	RunBlocklist []string `toml:"run_blocklist"`
	// DisableTools removes built-in tools by name.
	DisableTools []string `toml:"disable_tools"`
	// EnvAllowlist extends the built-in default env var allowlist (union).
	// Entries ending in "*" are treated as prefix rules (e.g. "GIT_*" allows all GIT_ vars).
	EnvAllowlist []string `toml:"env_allowlist"`
	// EnvAllowlistOnly replaces the built-in default env var allowlist entirely.
	// Entries ending in "*" are treated as prefix rules (e.g. "GIT_*" allows all GIT_ vars).
	EnvAllowlistOnly []string `toml:"env_allowlist_only"`
	// EnvBlocklist removes vars from the resolved env allowlist (takes precedence).
	// Entries ending in "*" are treated as prefix rules (e.g. "GIT_*" blocks all GIT_ vars).
	EnvBlocklist []string `toml:"env_blocklist"`
	// EnvAllowKeywordBlocklist drops variables whose name contains any of these
	// substrings even when a prefix rule admitted them. Exact-name entries in
	// EnvAllowlist are unaffected, so a build needing one names it explicitly.
	EnvAllowKeywordBlocklist []string `toml:"env_allow_keyword_blocklist"`
	// RunTimeoutSec is the default timeout for run_command (seconds).
	RunTimeoutSec int `toml:"run_timeout_seconds"`
	// ToolRunTimeoutSec is the registry-wide default run timeout (seconds)
	// the SDK tool-registry backstop applies to tools that declare no
	// Capability.Timeout. Positive = that bound. 0 (default) or negative =
	// no registry-wide cap (the agent layer maps it to the SDK's
	// TimeoutNone). Uncapped is the correct default: the CLI's own
	// dispatcher already arms every tool call's Capability.Timeout (or the
	// [subagents] default_timeout_seconds fallback) as a real context
	// deadline, so the SDK backstop is a second, redundant enforcement
	// layer that must never be tighter than the CLI's declared budgets
	// unless the operator explicitly asks for it. Without this mapping the
	// SDK's hardcoded 10-minute default silently killed long-budget tools
	// (dispatch_tasks' 12h orchestration window).
	ToolRunTimeoutSec int `toml:"tool_run_timeout_seconds"`
	// MaxReadBytes caps read_file output (bytes).
	MaxReadBytes int `toml:"max_read_bytes"`
	// MaxEditFileBytes caps the file size editable in place by search_replace and multi_edit (bytes).
	// 0 means uncapped today; expected to become an explicit opt-out once the derived budget profile ships.
	MaxEditFileBytes int `toml:"max_edit_file_bytes"`
	// MaxWriteKB caps write_file content (KiB).
	MaxWriteKB int `toml:"max_write_kb"`
	// MaxOutputBytes caps run_command output (bytes).
	MaxOutputBytes int `toml:"max_output_bytes"`
	// MaxListDirEntries caps list_dir output.
	MaxListDirEntries int `toml:"max_list_dir_entries"`
	// MaxToolResultBytes caps each tool result stored in agent-loop history
	// (bytes), applied identically to the interactive session loop and nested
	// sub-agent loops. 0 (the default) means uncapped: per-tool budgets are
	// the bound. Positive values below 1024 are rejected at load.
	MaxToolResultBytes int `toml:"max_tool_result_bytes"`
	// BatchResultBudgetBytes bounds what ONE tool batch may add to history,
	// across all of its parallel calls together. max_tool_result_bytes bounds
	// each call in isolation and cannot see the others, so N calls that are
	// each honestly under it still blow the context when they land in the same
	// step; this is the only bound that sees the batch as a whole.
	//
	// nil (absent) resolves to the derived budget; explicit 0 disables it;
	// any negative derives it from the model's prompt budget (a quarter of
	// it, floor 256 KiB; inert when there is no prompt budget). A positive
	// value is the literal byte budget and must be at least 16 KiB - below
	// that every batch would degrade to references.
	//
	// Over-budget results are degraded to content references, never failed:
	// the model already paid for the calls and their side effects already
	// happened.
	BatchResultBudgetBytes *int `toml:"batch_result_budget_bytes"`
	// RefOnlyTools is an opt-in list of tool names whose results are always
	// spooled and replaced by a ref-only notice, never inlined whole; empty
	// = off.
	RefOnlyTools []string `toml:"ref_only_tools"`
	// MaxTavilyResponseBytes bounds the bytes read from a Tavily API response
	// body - the `search` tool's Tavily path and `extract`. It is NOT a
	// truncation cap: the tools never cut content. The bound exists so their
	// maximum output is a finite, declarable number, which the dispatcher's
	// output backstop is derived from; a response over the bound is refused
	// with an explicit error naming this key. 0 or negative resolves to the
	// built-in default (never "unlimited" - an unlimited response could not be
	// declared, and undeclared output is what the backstop destroys). Values
	// outside [1024, 64 MiB] are rejected at load.
	MaxTavilyResponseBytes int `toml:"max_tavily_response_bytes"`
	// MaxFetchKB bounds the body read by fetch_url (KiB). Default 4096 (4 MiB).
	// 0 means unlimited. Unlimited is safe for fetch_url because it truncates
	// an over-bound body instead of refusing it - unlike the Tavily bound, an
	// unbounded read still yields a bounded, usable result, so nothing the
	// dispatcher derives from a declared budget depends on this number.
	MaxFetchKB int `toml:"max_fetch_kb"`
	// MemoryBackstopMB is the OOM guard (MiB) for tools that may load whole
	// files into memory when volume caps are uncapped (read/edit/list budgets).
	// Shipped default 256. This is NOT a context-cost cap. 0 or negative
	// resolves to the default so the guard cannot be accidentally disabled.
	MemoryBackstopMB int `toml:"memory_backstop_mb"`
	// RedactToolArgs hides argv from operator-visible output.
	RedactToolArgs bool `toml:"redact_tool_args"`
	// SecretPathPatterns replaces the hard-coded secret path blocklist.
	SecretPathPatterns []string `toml:"secret_path_patterns,omitempty"`
	// SecretPathExceptions adds exceptions to the secret path blocklist.
	SecretPathExceptions []string `toml:"secret_path_exceptions,omitempty"`
	// Core is the always-advertised tool tier (plan tools/05). nil (the key
	// omitted) keeps every authorized tool core, which is byte-identical to the
	// behavior before deferred loading existed. When set, tools outside it are
	// deferred: their schemas are advertised (plan tools-advertising/01) but
	// locked for execution until the model loads them with load_tools.
	// Naming a tool here never grants authority - the list is intersected
	// with the agent's effective tool set.
	Core *[]string `toml:"core,omitempty"`
	// SearchIgnorePatterns adds directory/file names to skip during grep/glob walks.
	// Extends the built-in defaults (.git, node_modules, vendor). Does not replace them.
	SearchIgnorePatterns []string `toml:"search_ignore_patterns,omitempty"`
	// WritePathBlocklist names workspace-relative paths or directories whose
	// write tools refuse to change. There is no compiled-in default
	// (DefaultWritePathBlocklist is empty); protection is opt-in. Removal of
	// an entry named here is via WritePathBlocklistRemove. Entries use
	// forward slashes and are normalized
	// (trimmed, cleaned) at load; an entry that is empty, ".", or absolute is
	// a load error.
	// The blocklist applies to workflow agent steps, whose write tools
	// (write_file, search_replace, multi_edit, delete_file) refuse listed
	// paths. The interactive session registry does not enforce it.
	WritePathBlocklist []string `toml:"write_path_blocklist,omitempty"`
	// WritePathBlocklistRemove removes workspace-relative paths or directories
	// from the effective write-path blocklist (the built-in defaults plus
	// WritePathBlocklist) for workflow agent steps. It is the explicit opt-out
	// for a default entry (.git, .mivia/mivia.toml) or a project addition, and
	// it is the only way to unblock those two defaults. Unblocking a path is a
	// trust decision: .mivia/mivia.toml carries this very blocklist, so an
	// agent that can edit it can remove its own restrictions (including
	// .mivia/agents, .mivia/policy, .mivia/workflows, go.mod, go.sum, and
	// go.work), and .git carries commit
	// history and hooks, so an agent that can edit it can rewrite history or
	// plant hooks that bypass the host hook-policy gates. Keep the defaults
	// blocked unless a host-owned step genuinely needs the path. Entries use
	// the same normalization and validation rules as WritePathBlocklist; an
	// entry listed in BOTH keys is a load error (the keys contradict).
	WritePathBlocklistRemove []string `toml:"write_path_blocklist_remove,omitempty"`
	// MaxInspectRepositoryBytes caps the inspect_repository result envelope
	// (bytes). Unlike MaxReadBytes/MaxOutputBytes there is no uncapped state:
	// the tool's output must always be valid, bounded JSON. 0 or negative
	// resolves to the built-in 64 KiB default. Values outside
	// [MinInspectRepositoryBytes, MaxInspectRepositoryBytes] are rejected at load.
	MaxInspectRepositoryBytes int `toml:"max_inspect_repository_bytes"`
	// DiagnosticsCommand is the DEPRECATED alias for the reserved "default"
	// entry of DiagnosticsCommands. It is the argv of the project diagnostics
	// command the get_diagnostics tool runs. resolveToolsConfig folds a set
	// alias into DiagnosticsCommands["default"] and then clears it, so
	// Validate (and every consumer) sees exactly one surface; setting BOTH
	// the alias and the map is ambiguous and rejected at load. Empty (unset
	// or []) means the tool is not registered. When set, argv[0] must be a
	// bare program name on the resolved run allowlist (run_allowlist_only
	// when set, else run_allowlist); validation rejects anything else at
	// load, mirroring the run_command gate, so a diagnostics tool that could
	// never register cannot load clean.
	DiagnosticsCommand []string `toml:"diagnostics_command"`
	// DiagnosticsCommands maps a command name to the argv of a project
	// diagnostics command the get_diagnostics tool runs. Empty or unset means
	// the tool is not registered; "default" is the reserved name for the
	// deprecated diagnostics_command alias (declaring it explicitly is
	// allowed). Every entry is validated at load like the run_command gate:
	// non-empty argv, argv[0] a bare program name on the EFFECTIVE run
	// allowlist (run_allowlist_only when set, else run_allowlist, minus
	// run_blocklist - the tools layer subtracts the blocklist in
	// configuredRunAllowlist, so a command that could never register is a
	// load error). Command names must be non-empty, non-whitespace, and
	// unique after case folding.
	DiagnosticsCommands map[string][]string `toml:"diagnostics_commands"`
}

ToolsConfig configures tool execution policies.

type VerifierCommand

type VerifierCommand struct {
	Check   string
	Program string
	Args    []string
}

VerifierCommand is one sandboxed command of a declared profile. Program is a bare executable name; Args are argv verbatim, never a shell string.

type VerifierProfile

type VerifierProfile struct {
	// GoModuleBaseline marks a profile whose commands read Go module files.
	// When any referenced profile sets it, admission captures go.mod/go.sum
	// and the sandbox pins them so a workflow agent cannot change what the
	// gate builds against.
	GoModuleBaseline bool
	Commands         []VerifierCommand
}

VerifierProfile is one [verifiers.<name>] table: an ordered command list and whether the profile needs the pinned Go module baseline.

type WorkflowPanelLimits

type WorkflowPanelLimits struct {
	MemberMaxOutputPerCall    *int `toml:"member_max_output_per_call"`
	MemberMaxToolCalls        *int `toml:"member_max_tool_calls"`
	SynthesisMaxOutputPerCall *int `toml:"synthesis_max_output_per_call"`
	SynthesisMaxToolCalls     *int `toml:"synthesis_max_tool_calls"`
	// MemberDeadlineDefaultSeconds overrides the wall-clock default a
	// panel member attempt gets when the workflow declares no run
	// deadline (max_duration_seconds = 0). Seconds, matching
	// definition.Limits.MaxDurationSeconds' unit.
	MemberDeadlineDefaultSeconds *int `toml:"member_deadline_default_seconds"`
}

WorkflowPanelLimits overrides the compiled defaults every agent_panel step's member and synthesis children run under (internal/workflows/controller.DefaultPanelLimits, applied in buildPanelAttempt/buildPanelSynthesisWork). A nil field keeps the compiled default; this mirrors the [chat] max_steps *int nil-means-default pattern (ChatConfig.MaxSteps above), not a new convention.

type WorkflowsConfig

type WorkflowsConfig struct {
	Panels WorkflowPanelLimits `toml:"panels"`
}

WorkflowsConfig holds workflow-engine defaults.

type WorktreeConfig

type WorktreeConfig struct {
	// BranchPrefix is the prefix for branches that mivia creates for worktrees.
	BranchPrefix string `toml:"branch_prefix"`
}

WorktreeConfig controls worktree branch settings.

func LoadWorktreeConfig

func LoadWorktreeConfig(mainRepoRoot string) (WorktreeConfig, error)

LoadWorktreeConfig reads the worktree settings from the main repository. It does not use general config discovery. A linked worktree therefore uses the main repository setting, not its current directory or MIVIA_CONFIG.

Jump to

Keyboard shortcuts

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