config

package
v0.6.2 Latest Latest
Warning

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

Go to latest
Published: Oct 8, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Index

Constants

View Source
const (
	DefaultSubagentMaxChildIterations = 50
	DefaultSubagentTimeoutS           = 300
	DefaultSubagentResultMaxChars     = 8000
	DefaultSubagentMaxConcurrent      = 8
)

Defaults for tools.subagent.*. Zero selects the default, never unbounded.

View Source
const (
	// SavedModelsName holds model rows in the same format as the models: list
	// of config.yaml. Load appends them after the rows of the config file.
	SavedModelsName = "models.yaml"

	// CredentialsName holds API keys as a JSON object from environment
	// variable name to key, such as {"OPENCODE_API_KEY": "sk-..."}. A row's
	// api_key_env names the variable; the saved value is used when the
	// variable is not set. Keeping keys out of the environment matters: every
	// command the agent's Shell tool runs inherits the environment, so a key
	// placed there could be printed by any command the model chooses to run.
	CredentialsName = "credentials.json"

	// SavedToolsName holds whether Web.search and Web.fetch are on, in the
	// same form as the tools: block of config.yaml. A setting config.yaml
	// writes itself takes precedence over the one here.
	SavedToolsName = "tools.yaml"
)

Three files beside the config in ~/.kvit-coder hold what kvit-coder-ui saves when a model or web search is set up from inside it, so that nobody has to edit config.yaml to add a model or export a key in a shell profile (spec/configing.md). All three are read here, by Load, because the agent is a new process every turn and finds its model and tools by reading the configuration again: a row the front end kept only in memory would be refused by the agent. config.yaml itself is only ever written by a person.

View Source
const ConfigPathEnv = "KVIT_CODER_CONFIG"

ConfigPathEnv names a config file for every kvit-coder run in the shell that exports it. A -config flag still wins over it.

View Source
const DefaultConfigName = "config.yaml"

DefaultConfigName is the file the binaries look for when nobody names one with -config.

View Source
const DefaultReportRepairAttempts = 3

DefaultReportRepairAttempts is how many rejected reports a turn may submit before it is told to stop repairing and end with prose instead. Past this the model is spending the turn on the schema rather than on the work.

View Source
const DefaultWebSearchKeyEnv = "BRAVE_API_KEY"

DefaultWebSearchKeyEnv is the variable Web.search reads its key from when tools.web.api_key_env names none.

Variables

View Source
var CanonicalEfforts = []string{"none", "minimal", "low", "medium", "high", "xhigh", "max"}

CanonicalEfforts are the reasoning-effort values the Responses backend passes through verbatim (cf. grok's ReasoningEffort enum). The config's per-model menu is the authority on what is *selectable*; this set is only what is *spellable*.

View Source
var LegacyEfforts = []string{"xhigh", "high", "medium", "low"}

LegacyEfforts is the effort menu for a `responses` model that lists no explicit menu, in presentation order.

Functions

func AppendSavedModels added in v0.6.2

func AppendSavedModels(rows ...ModelEntry) error

AppendSavedModels adds rows at the end of ~/.kvit-coder/models.yaml. Ids are the caller's to make unique; Load leaves out a saved row whose id is taken.

func CredentialsPath added in v0.6.2

func CredentialsPath() (string, error)

CredentialsPath is ~/.kvit-coder/credentials.json.

func DeleteCredential added in v0.6.2

func DeleteCredential(envName string) (bool, error)

DeleteCredential removes the key saved under an environment variable name and reports whether there was one.

func EntryAPIKey

func EntryAPIKey(entry ModelEntry) string

EntryAPIKey resolves which key an entry authenticates with: the variable its api_key_env names when that is set, else the key saved under the same name in credentials.json, else its literal api_key, else none (a local endpoint needs no key, and the client omits the Authorization header then).

func EntryDisplay

func EntryDisplay(entry ModelEntry, effort string) string

EntryDisplay is ModelDisplay for a catalog row: "model:effort" when an effort is set, plain "model" otherwise.

func ExpandHome

func ExpandHome(path string) string

ExpandHome turns a leading ~/ into the user's home directory.

func IsCanonicalEffort

func IsCanonicalEffort(s string) bool

IsCanonicalEffort reports whether s names a known effort value.

func IsStrongProfile

func IsStrongProfile(p string) bool

IsStrongProfile reports whether a profile value selects the strong behavior. Empty and unknown values are strong: strong is the default, and only an explicit "weak" (case-insensitive) turns the compensation machinery on. Validation rejects unknown values at load, so unknown here means a Config built without Load (tests, benchmarks constructing one by hand), which should behave exactly like the default.

func LookupKey added in v0.6.2

func LookupKey(envName string) string

LookupKey resolves an api_key_env name: the environment variable when it is set, else the key saved under that name in credentials.json.

func NormalizeAndValidatePath

func NormalizeAndValidatePath(workspaceRoot, path string) (string, bool, error)

NormalizeAndValidatePath is a helper function to normalize and validate paths This should be moved to a shared utility package in a real implementation

func ParseIndexedCommand

func ParseIndexedCommand(cmd string) (kind byte, n int, ok bool)

ParseIndexedCommand parses the word after the colon of :mN / :eN: 'm' or 'e' followed only by digits. Anything else (including bare "m"/"e", which take a value argument) is not an indexed command.

func RemoveSavedModel added in v0.6.2

func RemoveSavedModel(id string) (bool, error)

RemoveSavedModel deletes the row with this id from ~/.kvit-coder/models.yaml and reports whether there was one.

func ResolveNamed

func ResolveNamed(explicit, name string) (string, string, error)

ResolveNamed is ResolvePath for a config file under some other name, which is what the benchmark modes use when they look for config-<suffix>.yaml.

func ResolvePath

func ResolvePath(explicit string) (string, string, error)

ResolvePath finds the configuration file to load. An explicit path is returned untouched, so a bad -config fails with the name the user typed rather than silently falling back to another file. Otherwise it looks, in order, at $KVIT_CODER_CONFIG, config.yaml in the current directory, ~/.kvit-coder/config.yaml, and config.yaml beside the binary. The last of those is what lets the command run from any directory: a symlink on $PATH resolves to the checkout, and the checkout's own config.yaml is found there.

The second return value is a note for the user about a candidate that was passed over — empty when there is nothing to say.

func SaveCredential added in v0.6.2

func SaveCredential(envName, key string) error

SaveCredential stores key under an environment variable name in ~/.kvit-coder/credentials.json, which is written readable by its owner only.

func SaveWebTools added in v0.6.2

func SaveWebTools(search, fetch *bool) error

SaveWebTools records in ~/.kvit-coder/tools.yaml whether Web.search and Web.fetch are on. A nil value leaves that tool as the file has it.

func SavedKey added in v0.6.2

func SavedKey(envName string) string

SavedKey is the key credentials.json holds under an environment variable name, or "" when there is none. The file is read on every call: it is a few hundred bytes, the agent asks once or twice per turn, and reading it fresh means a key saved by one front end is seen by the next turn of another. An unreadable or malformed file reads as empty here; Load reports it.

func SavedModelsPath added in v0.6.2

func SavedModelsPath() (string, error)

SavedModelsPath is ~/.kvit-coder/models.yaml.

func SavedToolsPath added in v0.6.2

func SavedToolsPath() (string, error)

SavedToolsPath is ~/.kvit-coder/tools.yaml.

func SetLinePrompter

func SetLinePrompter(fn func(prompt string) (string, bool))

SetLinePrompter installs the function used to ask the person at the terminal a question. Passing nil restores reading the terminal directly, which is what a run with no line reader does.

func SplitModelEffort

func SplitModelEffort(ref string) (modelPart, effortPart string)

SplitModelEffort splits a -m/--model value into its model and effort halves: "m3:xhigh" gives ("m3", "xhigh"), "qwen-local:low" gives ("qwen-local", "low"), ":xhigh" gives ("", "xhigh", default model with that effort), and anything without a known effort suffix stays whole — only the last colon counts, and only when the suffix names a canonical effort, so a wire id with colons ("openai:gpt-4") is never cut.

func UserDir added in v0.6.2

func UserDir() (string, error)

UserDir is ~/.kvit-coder (%USERPROFILE%\.kvit-coder on Windows), where the sessions, the update-check record and the files above live.

func ValidBackend

func ValidBackend(b string) bool

ValidBackend reports whether b names a known wire protocol (or is empty, which means the chat-completions default). "messages" is Claude's API.

func WireID

func WireID(display string) string

WireID strips a ":effort" suffix back off a display string, so a recorded "model:effort" can be compared against a wire id. A suffix that is not a known effort value is left alone (the wire id itself may contain colons).

func WriteSavedModels added in v0.6.2

func WriteSavedModels(rows []ModelEntry) error

WriteSavedModels replaces ~/.kvit-coder/models.yaml with these rows.

Types

type AccessType

type AccessType int

AccessType defines the type of file access being requested

const (
	AccessRead AccessType = iota
	AccessWrite
)

type AgentConfig

type AgentConfig struct {

	// Profile says how much the loop should compensate for the model.
	//
	// "weak" keeps every mechanism that exists to catch a model getting
	// confused: backtracking away from a bad tool call, the duplicate-call
	// kill switch, the confirm handshake before an edit is applied, scraping
	// tool calls out of prose, fuzzy matching and indentation repair, asking
	// the model to explain itself after an anomaly, and retrying an empty
	// answer. Each of those was written for a model that needed it.
	//
	// "strong", the default, skips all of it. On a model that does not make
	// those mistakes, each mechanism is a tax: a retry that discards good
	// work, a handshake that costs two round trips per edit, a fuzzy match
	// that silently edits the wrong lines.
	//
	// This is the default for models without their own `profile:` — see
	// ModelEntry.Profile and Config.ProfileFor.
	Profile string `yaml:"profile"`

	MaxIterations int `yaml:"max_tool_iterations"`

	AgentFile string `yaml:"agent_file"`

	ProjectInstructions ProjectInstructionsConfig `yaml:"project_instructions"`
}

AgentConfig configures the agent loop and startup instruction sources.

func (AgentConfig) IsStrong

func (a AgentConfig) IsStrong() bool

IsStrong reports whether the loop should skip the machinery that exists to compensate for a weak model. It is the default.

After ApplyModel runs (once per turn, for the selected catalog entry) this reflects the selected model's effective profile — see ProfileFor — so the places that read it follow a :mN switch with no other change.

type AuditConfig

type AuditConfig struct {
	Enabled       bool   `yaml:"enabled"`
	LogDir        string `yaml:"log_dir"`
	RedactSecrets bool   `yaml:"redact_secrets"`
	RetentionDays int    `yaml:"retention_days"`
}

AuditConfig configures safety audit logging

type BacktrackConfig

type BacktrackConfig struct {
	Enabled           bool `yaml:"enabled"`             // Enable backtrack mode (default: true)
	MaxRetries        int  `yaml:"max_retries"`         // Max retries at same history point (default: 5)
	InjectUserMessage bool `yaml:"inject_user_message"` // On limit reached: backtrack + inject user message instead of error-in-history
}

BacktrackConfig configures the backtrack error handling mode

type BatchToolConfig

type BatchToolConfig struct {
	Enabled bool `yaml:"enabled"`
}

BatchToolConfig configures the Batch tool, which runs several independent tool calls in one request.

type Config

type Config struct {
	LLM struct {
		BaseURL   string `yaml:"base_url"`
		APIKey    string `yaml:"api_key"`
		APIKeyEnv string `yaml:"api_key_env"`
		Model     string `yaml:"model"`
		// APIBackend picks the wire protocol: "chat_completions" (default),
		// "responses", or "messages" (Claude's API). Some hosted models are
		// served only on one protocol and return an error on the others.
		APIBackend string `yaml:"api_backend"`
		// Headers are extra "Key=Value" request headers sent with every call
		// (supports ${VAR}). Needed by endpoints that demand a routing or
		// session header of their own.
		Headers []string `yaml:"headers"`
		// ReasoningEffort asks a reasoning model for more or less thinking.
		// Values are the provider's (commonly minimal, low, medium, high,
		// xhigh). The "responses" backend sends it as reasoning.effort; the
		// chat-completions backend sends it wherever EffortField points.
		ReasoningEffort string `yaml:"reasoning_effort"`
		// EffortField names which field of a chat-completions request carries
		// the effort: "chat_template_kwargs" (the default: a model whose chat
		// template reads it, which is how the local servers work) or
		// "reasoning_effort" (OpenAI's own top-level field, which hosted
		// gateways such as opencode.ai read). An endpoint that wants one
		// ignores the other without saying so, so a wrong value here means the
		// effort picked with :eN quietly does nothing. The "responses" backend
		// ignores this.
		EffortField string `yaml:"effort_field"`
		// ReasoningSummary asks a reasoning model for readable text describing
		// its thinking. Only the "responses" backend sends it; values are the
		// provider's (auto, concise, detailed), and "off" suppresses it.
		// Empty follows merge_thinking, because thinking that is never
		// requested cannot be merged.
		ReasoningSummary string `yaml:"reasoning_summary"`
		// RequestTimeout bounds one HTTP request to the model, in seconds.
		// A request that exceeds it is retried, so this times the retry count
		// is how long a turn can stall on an endpoint that never answers.
		// Default 600.
		RequestTimeout int     `yaml:"request_timeout"`
		Temperature    float32 `yaml:"temperature"`
		MaxTokens      int     `yaml:"max_output_tokens"`
		Context        int     `yaml:"context"`        // Max context size for display (0 = don't show)
		MergeThinking  bool    `yaml:"merge_thinking"` // Merge reasoning_content into content (default: false, discard thinking)
		Verbose        int     `yaml:"verbose"`        // 0 = off, >0 = show tool output up to N lines
		// GenerationStats asks the endpoint for per-request cost and native
		// token counts after each answer. It is an OpenRouter endpoint
		// (/generation) and returns 404 everywhere else, so it is off by
		// default: leaving it on doubles the HTTP requests per iteration.
		GenerationStats bool   `yaml:"generation_stats"`
		BenchmarkCmd    string `yaml:"benchmark_cmd"` // External command for benchmarks (use {prompt} placeholder)
	} `yaml:"llm"`

	Workspace struct {
		Root                  string   `yaml:"root"`
		Lock                  bool     `yaml:"lock"`             // Refuse to start when another agent is working in this directory (default: false)
		PathSafetyMode        string   `yaml:"path_safety_mode"` // "allow", "block", "warn", "ask_once", "ask_always"
		AllowOutsideWorkspace bool     `yaml:"allow_outside_workspace"`
		AllowedPaths          []string `yaml:"allowed_paths"`
		AllowedReadPaths      []string `yaml:"allowed_read_paths"`
		DeniedPaths           []string `yaml:"denied_paths"`
		// DeniedWritePaths may be read and never written, inside the
		// workspace or an allowed path as well as outside.
		DeniedWritePaths []string `yaml:"denied_write_paths"`
		// ReadOnlyRoot makes the workspace readable only: a file in it is
		// written only where allowed_paths also names it.
		ReadOnlyRoot bool `yaml:"read_only_root"`
	} `yaml:"workspace"`

	Agent AgentConfig `yaml:"agent"`

	Backtrack BacktrackConfig `yaml:"backtrack"`

	Tools ToolsConfig `yaml:"tools"`

	Prompts PromptsConfig `yaml:"prompts"`

	Safety SafetyConfig `yaml:"safety"`

	Diagnostics DiagnosticsConfig `yaml:"diagnostics"`

	Thinkbench ThinkbenchConfig `yaml:"thinkbench"`

	MCP MCPConfig `yaml:"mcp"`

	// ToolGroups defines sets of tools whose schemas and documentation reach the
	// model only when it asks for them. See ToolGroupConfig.
	ToolGroups []ToolGroupConfig `yaml:"tool_groups"`

	UI UIConfig `yaml:"ui"`

	// Updates controls the release check in kvit-coder-ui: at most one
	// GitHub query per interval, a one-line notice when a newer release
	// exists, and the :update command that installs it. The headless agent
	// never checks.
	Updates UpdatesConfig `yaml:"updates"`

	// Models is the optional multi-model catalog (docs/model-selection.md).
	// Order is stable: :m1 is the first entry, :m2 the second. When absent,
	// ModelList synthesizes a one-entry catalog from the llm: block, so old
	// configs behave exactly as before.
	Models []ModelEntry `yaml:"models"`
	// DefaultModel selects the catalog row a fresh session starts on: a
	// 1-based index, id, display name or wire model id (see ResolveModel).
	// Empty means the row matching llm.model, else the first row.
	DefaultModel string `yaml:"default_model"`
	// DefaultEffortValue is the effort fallback after an entry's own
	// `default: true` row and llm.reasoning_effort.
	DefaultEffortValue string `yaml:"default_effort"`
	// contains filtered or unexported fields
}

func Load

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

Load reads a config file, together with the model rows, API keys and web tool switches kvit-coder-ui saved beside it in ~/.kvit-coder (see userfiles.go).

func LoadWith added in v0.6.2

func LoadWith(path string, opts LoadOptions) (*Config, error)

LoadWith is Load with options; see LoadOptions.

func (*Config) ApplyModel

func (c *Config) ApplyModel(entry ModelEntry, effort string)

ApplyModel makes an entry plus a resolved effort the active endpoint: the `llm:` fields a model switch can change are overwritten, everything else (headers, timeouts, merge_thinking, ...) stays from the `llm:` block. Downstream code (client construction, session records, context accounting) reads cfg.LLM, so it follows the selection with no other change.

It also makes the entry's effective profile (see ProfileFor) the active one, so a :mN switch to a weak model turns the compensation machinery back on for the next turn, and switching back to a strong one turns it off again. The agent is one process per turn, so this runs once per turn in resolveModelSelection; ApplyProfile restores from the pre-profile snapshot, so repeated calls are idempotent.

func (*Config) ApplyProfile

func (c *Config) ApplyProfile(profile string)

ApplyProfile makes a profile value the active one: it sets Agent.Profile and forces the weak-model machinery off (strong) or restores the file's own settings (weak). It is idempotent — calling it for the same profile twice, or alternating between profiles, always converges — because the weak branch restores from the snapshot taken before any profile ran, never from the current (possibly forced-off) values.

Configs built without Load (tests constructing Config by hand) have no snapshot yet; the first call snapshots the current values as the file's own, so a manual Config behaves like a file that said exactly that.

func (*Config) CheckPathPermission

func (c *Config) CheckPathPermission(path string, accessType AccessType) (PermissionResult, error)

CheckPathPermission validates if a path can be accessed based on workspace config

func (*Config) CheckPathSafety

func (c *Config) CheckPathSafety(toolName, identifier string) error

func (*Config) DefaultEffort

func (c *Config) DefaultEffort(entry ModelEntry) string

DefaultEffort is the effort a fresh selection of this entry runs with: "" when the entry has no menu (a non-reasoning model never takes an effort, so switching to one clears it), else its `default: true` row, else `llm.reasoning_effort`, else `default_effort`, else "" (no `reasoning:` object on the wire).

func (*Config) DefaultModelIndex

func (c *Config) DefaultModelIndex() int

DefaultModelIndex is which catalog row a fresh UI (or a headless run with no --model) starts on: `default_model` when it resolves, else the row whose wire id matches `llm.model`, else the first row.

func (*Config) DeferredBuiltinTools

func (c *Config) DeferredBuiltinTools() map[string]string

DeferredBuiltinTools returns the built-in tool names claimed by an enabled group, mapped to the group that claimed them. SetupRegistry uses it to hand those tools to their group instead of registering them.

func (*Config) DeferredMCPServers

func (c *Config) DeferredMCPServers() map[string]bool

DeferredMCPServers returns the names of MCP servers that belong to an enabled group. Those are the servers Connect must not dial at startup: the group dials them the first time the model calls one of their tools.

func (*Config) EffortOptions

func (c *Config) EffortOptions(entry ModelEntry) []EffortOption

EffortOptions returns the :eN menu for an entry in config order: its own list, else the legacy four for a `responses` model without one, else empty (a non-reasoning model has no menu and switching to it clears the effort).

func (*Config) EnabledToolGroups

func (c *Config) EnabledToolGroups() []ToolGroupConfig

EnabledToolGroups returns the groups that are switched on, in configuration order. Order is stable so the tools the model is offered stay byte-identical between requests and the server-side prompt cache keeps hitting.

func (*Config) FromSavedModels added in v0.6.2

func (c *Config) FromSavedModels(i int) bool

FromSavedModels reports whether row i of the model list came from models.yaml rather than from the config file.

func (*Config) HeadersFor added in v0.6.2

func (c *Config) HeadersFor(entry ModelEntry) map[string]string

HeadersFor is LLMHeaders for an entry other than the active one, such as the summarizer entry that writes session titles: llm.headers with that entry's headers laid over them.

func (*Config) IsStrongFor

func (c *Config) IsStrongFor(entry ModelEntry) bool

IsStrongFor reports whether the effective profile for an entry skips the weak-model compensation machinery.

func (*Config) LLMHeaders

func (c *Config) LLMHeaders() map[string]string

LLMHeaders parses llm.headers, with the active entry's own headers laid over them, into the header map the LLM client wants. ${VAR} references are expanded against the environment so a session or routing header can come from a secret the config does not contain. ${KVIT_RUN_ID} expands to an ID unique to this process unless the environment already sets one.

func (*Config) ModelDisplay

func (c *Config) ModelDisplay() string

ModelDisplay is the model name as shown in startup banners and :config: "model:effort" when llm.reasoning_effort is set, plain "model" otherwise, so the banner reflects what the Responses backend is actually asked for.

func (*Config) ModelList

func (c *Config) ModelList() []ModelEntry

ModelList returns the catalog: `models:` in file order followed by the rows saved in models.yaml (see mergeSavedModels), or one entry synthesized from the `llm:` block when there are none. It never returns an empty slice, so callers can index [DefaultModelIndex()].

func (*Config) ProfileFor

func (c *Config) ProfileFor(entry ModelEntry) string

ProfileFor returns the effective profile for an entry: its own `profile:` when set, else the global agent.profile default. The result is normalized to lowercase ("strong", "weak", or "" for the default-strong), so callers can compare or display it directly.

func (*Config) ReasoningSummaryOrDefault

func (c *Config) ReasoningSummaryOrDefault() string

ReasoningSummaryOrDefault says what to ask the provider for. Turning on merge_thinking without this asks the model to think and then throws the readable part away, so an unset value follows merge_thinking.

func (*Config) ResolveEffort

func (c *Config) ResolveEffort(entry ModelEntry, token string) (string, error)

ResolveEffort checks an --effort token (or :e value) against one entry's menu. "" always resolves to "" (clear: omit `reasoning:`), which is what a non-reasoning model needs. A value the model does not offer is rejected with the offered list rather than sent to the API.

func (*Config) ResolveModel

func (c *Config) ResolveModel(ref string) (ModelEntry, int, error)

ResolveModel finds a catalog row by 1-based index ("1"), id, display name, or wire model id, in that order. Indexing is stable config-file order; rows are never sorted (prompt-cache determinism).

The :mN spellings the UI uses are accepted too: a leading ':' is dropped and 'm' followed only by digits means that index, so "-m m3" on the command line finds what ":m3" finds in the UI. (A catalog id that is literally "m<N>" is shadowed by the index; ids look like "spark-go", so this costs nothing in practice.)

func (*Config) ResolveSelection

func (c *Config) ResolveSelection(modelRef, effortRef string, effortSet bool) (int, string, error)

ResolveSelection resolves a startup/flag selection to a catalog index plus a resolved effort. modelRef may carry an inline ":effort" ("m3:xhigh", "qwen-local:low"); an explicit effort (effortSet) wins over the inline one. Empty modelRef means the default row; "" effort means that row's default ("" for a model with no menu). Errors name what is offered, for the UI banner path and the headless --model path alike.

func (*Config) SummarizerEntry

func (c *Config) SummarizerEntry() (ModelEntry, bool)

SummarizerEntry returns the catalog row flagged `summarizer: true` for session-title generation. It reports false when `models:` is absent (a legacy single-model config has nowhere to flag) or when no entry sets the flag, in which case the caller falls back to the prompt's first words. Validation guarantees at most one entry sets it, so the first match is the only match.

func (*Config) UseAPIBackend added in v0.3.0

func (c *Config) UseAPIBackend(backend string)

UseAPIBackend records a wire protocol chosen on the command line. It wins over the protocol a catalog entry would otherwise select.

func (*Config) WebSearchKeyEnv added in v0.6.2

func (c *Config) WebSearchKeyEnv() string

WebSearchKeyEnv is the variable Web.search reads its key from. The key is found the way a model's is, with LookupKey: the variable when it is set, else the key kvit-coder-ui saved under that name.

func (*Config) WebSetInConfig added in v0.6.2

func (c *Config) WebSetInConfig() (search, fetch bool)

WebSetInConfig reports which of Web.search and Web.fetch the config file turns on or off itself. tools.yaml cannot change those, so kvit-coder-ui shows them as decided there.

func (*Config) WouldPromptForPath added in v0.5.0

func (c *Config) WouldPromptForPath(toolName, identifier string) bool

WouldPromptForPath reports whether CheckPathSafety(toolName, identifier) would prompt a person, without performing any I/O. It mirrors CheckPathSafety's decision structure exactly: inside-workspace and pre-permitted paths never prompt, and the warn/block/allow modes never prompt (warn only prints). Only ask_once (unconfirmed) and ask_always return true.

A subagent child has no UI and would wedge the turn waiting for an answer, so every tool site that calls CheckPathSafety first fails closed with a semantic error when the context IsSubagentChild() and this helper is true (spec/subagents-phase2.md section 3). config cannot import tools, so the child flag never reaches this package — the tools side combines the two.

type DiagnosticsConfig

type DiagnosticsConfig struct {
	// InterrogateOnAnomaly enables interrogation. Default off.
	InterrogateOnAnomaly bool `yaml:"interrogate_on_anomaly"`
	// InterrogateTriggers selects which anomaly triggers fire interrogation. Empty = all.
	// Valid values: duplicate_call, pending_blocked, alternating_loop, fatal, backtrack.
	InterrogateTriggers []string `yaml:"interrogate_triggers"`
	// InterrogateIdenticalThreshold is the consecutive-identical-call count that triggers
	// interrogation. Default 2.
	InterrogateIdenticalThreshold int `yaml:"interrogate_identical_threshold"`
	// InterrogateMaxPerTask hard-caps interrogations per task to avoid interrogation loops.
	// Default 3.
	InterrogateMaxPerTask int `yaml:"interrogate_max_per_task"`
	// InterrogateLogDir is where JSONL episode logs are written. Default
	// "benchmarks/.kvit-coder-benchmark/interrogations".
	InterrogateLogDir string `yaml:"interrogate_log_dir"`
	// InterrogateThenInject is reserved for the later recovery variant; no-op in v1.
	InterrogateThenInject bool `yaml:"interrogate_then_inject"`
}

DiagnosticsConfig configures anomaly interrogation/logging (Improvement 2). When enabled, the agent loop asks the model to explain "odd" behavior (loops, repeated identical calls, ignored confirm/cancel) and logs the Q&A to a side channel for offline analysis. Observability only: it does not alter recovery.

func (*DiagnosticsConfig) GetInterrogateIdenticalThreshold

func (d *DiagnosticsConfig) GetInterrogateIdenticalThreshold() int

GetInterrogateIdenticalThreshold returns the identical-call threshold, default 2.

func (*DiagnosticsConfig) GetInterrogateLogDir

func (d *DiagnosticsConfig) GetInterrogateLogDir() string

GetInterrogateLogDir returns the log directory, with a default under benchmarks/.

func (*DiagnosticsConfig) GetInterrogateMaxPerTask

func (d *DiagnosticsConfig) GetInterrogateMaxPerTask() int

GetInterrogateMaxPerTask returns the per-task interrogation cap, default 3.

func (*DiagnosticsConfig) TriggerEnabled

func (d *DiagnosticsConfig) TriggerEnabled(trigger string) bool

TriggerEnabled reports whether the given anomaly trigger should fire interrogation. An empty trigger list means all triggers are enabled.

type EditToolConfig

type EditToolConfig struct {
	Enabled               bool    `yaml:"enabled"`
	Mode                  string  `yaml:"mode"` // "lines" (default), "searchreplace", or "patch"
	MaxFileSizeKB         int     `yaml:"max_file_size_kb"`
	PreviewMode           bool    `yaml:"preview_mode"`            // enables edit.confirm/edit.cancel
	ReadBeforeEditMsgs    int     `yaml:"read_before_edit_msgs"`   // require read within N messages before edit (0 = disabled)
	PendingConfirmRetries int     `yaml:"pending_confirm_retries"` // max retries when LLM ignores confirm/cancel (0 = disabled, default 5)
	FuzzyThreshold        float64 `yaml:"fuzzy_threshold"`         // searchreplace mode: gates ONLY the fuzzy level (0 = fuzzy off). Whitespace-normalization levels still run unless ExactMatchOnly is set.

	// ExactMatchOnly forces searchreplace matching to byte-exact (Level 0) only:
	// no rstrip, no leading/trailing whitespace normalization, no fuzzy fallback.
	// A non-exact search returns a clean no-match instead of being silently
	// re-anchored to a normalized (line-start) position. Default off = today's
	// cascade behavior for all existing configs.
	ExactMatchOnly bool `yaml:"exact_match_only"`

	// ExplicitDelete enables the DeleteLines tool and flips lines-mode empty new_text
	// semantics: with this on, an Edit replace with new_text="" blanks the addressed
	// line(s) (keeps them) instead of deleting, and deletes go through DeleteLines.
	// Default off = today's behavior exactly ("" deletes, no DeleteLines tool).
	ExplicitDelete bool `yaml:"explicit_delete"`
}

EditToolConfig configures the edit tool

func (*EditToolConfig) GetEditMode

func (e *EditToolConfig) GetEditMode() string

GetEditMode returns the edit mode, defaulting to "lines" for backward compatibility.

type EffortOption

type EffortOption struct {
	Value   string `yaml:"value"`
	Label   string `yaml:"label,omitempty"`
	Default bool   `yaml:"default,omitempty"`
}

EffortOption is one row of a model's :eN menu. Order in the file is presentation order; Default marks the entry's effort default.

func (EffortOption) Display

func (e EffortOption) Display() string

Display returns the menu text: the label when set, else the value.

type GitSafetyConfig

type GitSafetyConfig struct {
	BlockPush             bool `yaml:"block_push"`
	BlockHardReset        bool `yaml:"block_hard_reset"`
	BlockCheckoutDiscard  bool `yaml:"block_checkout_discard"`
	BlockStashDrop        bool `yaml:"block_stash_drop"`
	BlockCleanForce       bool `yaml:"block_clean_force"`
	WarnBranchForceDelete bool `yaml:"warn_branch_force_delete"`
}

GitSafetyConfig configures git operation safety

type GlobToolConfig added in v0.4.0

type GlobToolConfig struct {
	Enabled    bool `yaml:"enabled"`
	MaxResults int  `yaml:"max_results"` // Paths returned per call (default: 100); above truncates with a flag
}

GlobToolConfig configures the glob tool

type ImagesConfig

type ImagesConfig struct {
	Enabled      bool `yaml:"enabled"`
	MaxSizeMB    int  `yaml:"max_size_mb"`
	MaxDimension int  `yaml:"max_dimension"`
}

ImagesConfig configures image ingestion: the ReadImage tool and the -image flag share the caps. The tool is opt-in like every other tool; -image works whenever it is passed, because naming a file is its own authorization.

type InterpreterConfig

type InterpreterConfig struct {
	BlockOneLiners bool     `yaml:"block_one_liners"`
	Allowed        []string `yaml:"allowed"`
}

InterpreterConfig configures interpreter one-liner blocking

type LoadOptions added in v0.6.2

type LoadOptions struct {
	// SkipSavedModels leaves out the rows in ~/.kvit-coder/models.yaml. The
	// benchmark modes set it, so a run does not depend on the models someone
	// set up on the machine it runs on.
	SkipSavedModels bool

	// SkipSavedTools leaves out ~/.kvit-coder/tools.yaml, for the same
	// reason: web search turned on in kvit-coder-ui would change the tools a
	// benchmark run offers its model.
	SkipSavedTools bool
}

LoadOptions changes what Load reads besides the config file itself.

type MCPConfig

type MCPConfig struct {
	// Enabled is the group toggle. When false (default) no servers are dialed.
	Enabled bool `yaml:"enabled"`
	// StartupTimeout is the per-server connect + tools/list deadline, in seconds
	// (default 20). A server that exceeds it is logged and skipped, never fatal.
	StartupTimeout int `yaml:"startup_timeout"`
	// CallTimeout is the default per-tool-call deadline, in seconds (default 120),
	// overridable per server. MCP calls are exempt from the agent loop's blanket
	// 15s tool timeout and use this deadline instead.
	CallTimeout int `yaml:"call_timeout"`
	// Confirm is the default trust policy for MCP tool calls, overridable per
	// server: "block" | "ask_once" | "ask_always" | "trust" (default "ask_once").
	Confirm string `yaml:"confirm"`
	// SanitizeSchemas flattens/strips JSON Schema constructs ($ref, oneOf, allOf,
	// ...) that some grammar-constrained tool-calling templates reject. Opt-in so
	// faithful schemas are the default.
	SanitizeSchemas bool `yaml:"sanitize_schemas"`
	// Servers lists the configured MCP servers.
	Servers []MCPServerConfig `yaml:"servers"`
}

MCPConfig configures the Model Context Protocol (MCP) client. MCP lets kvit-coder connect to external tool servers (over stdio or HTTP) and surface each server's tools to the model as ordinary agent tools. The section is absent by default, so existing configs are unaffected and the feature is zero-cost when unused. See docs/archive/mcp-plan.md.

func (*MCPConfig) GetCallTimeout

func (m *MCPConfig) GetCallTimeout() int

GetCallTimeout returns the default per-call deadline in seconds, default 120.

func (*MCPConfig) GetConfirm

func (m *MCPConfig) GetConfirm() string

GetConfirm returns the default confirm policy, default "ask_once".

func (*MCPConfig) GetStartupTimeout

func (m *MCPConfig) GetStartupTimeout() int

GetStartupTimeout returns the per-server connect deadline in seconds, default 20.

type MCPServerConfig

type MCPServerConfig struct {
	// Name namespaces the server's tools (mcp.<name>.<tool>); must be unique.
	Name string `yaml:"name"`
	// Enabled opts this server in. Zero value false = skipped (opt-in).
	Enabled bool `yaml:"enabled"`
	// Transport selects the wire protocol: "stdio" (default) or "http".
	Transport string `yaml:"transport"`

	// stdio transport
	Command string   `yaml:"command"` // executable to spawn
	Args    []string `yaml:"args"`    // arguments
	Env     []string `yaml:"env"`     // extra "KEY=VALUE" entries appended to inherited env
	// Cwd is the working directory for the stdio subprocess. Empty defaults to
	// the workspace root, so a server that keys off its own working directory
	// (e.g. a codebase indexer) targets the project kvit-coder operates on rather
	// than wherever kvit-coder happened to be launched. A relative path is
	// resolved against the workspace root.
	Cwd string `yaml:"cwd"`

	// http transport
	URL     string   `yaml:"url"`     // streamable-HTTP endpoint
	Headers []string `yaml:"headers"` // extra "Key=Value" request headers (supports ${VAR})

	// CallTimeout optionally overrides MCPConfig.CallTimeout for this server, in seconds.
	CallTimeout int `yaml:"call_timeout"`
	// Confirm optionally overrides MCPConfig.Confirm for this server.
	Confirm string `yaml:"confirm"`
	// Tools optionally filters which of the server's tools are exposed.
	Tools MCPToolFilter `yaml:"tools"`
}

MCPServerConfig configures a single MCP server.

func (*MCPServerConfig) GetCallTimeout

func (s *MCPServerConfig) GetCallTimeout(group *MCPConfig) int

GetCallTimeout returns the per-call deadline for this server in seconds, preferring the server override, then the group default, then 120.

func (*MCPServerConfig) GetConfirm

func (s *MCPServerConfig) GetConfirm(group *MCPConfig) string

GetConfirm returns the confirm policy for this server, preferring the server override, then the group default.

func (*MCPServerConfig) GetTransport

func (s *MCPServerConfig) GetTransport() string

GetTransport returns the transport, defaulting to "stdio".

type MCPToolFilter

type MCPToolFilter struct {
	Allow []string `yaml:"allow"`
	Deny  []string `yaml:"deny"`
}

MCPToolFilter optionally restricts which tools a server exposes. Names are matched against the raw (server-side) tool name. An empty Allow list allows all tools; Deny is applied after Allow.

type ModelEntry

type ModelEntry struct {
	ID         string         `yaml:"id"`
	Name       string         `yaml:"name,omitempty"`
	Model      string         `yaml:"model"`
	BaseURL    string         `yaml:"base_url,omitempty"`
	APIBackend string         `yaml:"api_backend,omitempty"`
	APIKey     string         `yaml:"api_key,omitempty"`
	APIKeyEnv  string         `yaml:"api_key_env,omitempty"`
	Context    int            `yaml:"context,omitempty"`
	Efforts    []EffortOption `yaml:"efforts,omitempty"`

	// EffortField names which field of a chat-completions request carries
	// the reasoning effort for this endpoint: "chat_template_kwargs" (the
	// default, for a model whose chat template reads it) or
	// "reasoning_effort" (OpenAI's own top-level field, which hosted
	// gateways read). Empty keeps the `llm:` block's setting. An endpoint
	// that wants one spelling ignores the other silently, so a wrong value
	// means the effort picked with :eN does nothing at all. The "responses"
	// backend ignores this.
	EffortField string `yaml:"effort_field,omitempty"`

	// Profile overrides agent.profile for this entry: "strong" (the default)
	// or "weak". Empty inherits the global agent.profile. A weak entry keeps
	// the compensation machinery (backtracking, the duplicate-call kill
	// switch, the edit confirm handshake, fuzzy matching, anomaly
	// interrogation, prose scraping, empty-answer retries, interpreter
	// one-liner rules); a strong one skips it.
	Profile string `yaml:"profile,omitempty"`

	// Summarizer marks the entry used to generate session titles: a short
	// 3-6 word summary of the first prompt, stored in meta.json. At most
	// one entry may set it; when none does (or the call fails) the title
	// falls back to the first words of the prompt. It never affects model
	// switching — see SummarizerEntry.
	Summarizer bool `yaml:"summarizer,omitempty"`

	// Headers are extra "Key=Value" request headers for this entry only, in
	// the form of llm.headers and with the same ${VAR} expansion. They are
	// sent on top of llm.headers, and one with the same name replaces the
	// llm.headers value. A gateway that wants headers of its own, such as
	// OpenCode's User-Agent and x-opencode-session, can then be described by
	// its rows alone, without changing what every other model sends.
	Headers []string `yaml:"headers,omitempty"`
}

ModelEntry is one catalog row: everything about an endpoint that switching models can change. Non-endpoint settings (headers, timeouts, merge_thinking, ...) always come from the `llm:` block — except the agent profile, which an entry may override per model (see ProfileFor).

func ReadSavedModels added in v0.6.2

func ReadSavedModels() ([]ModelEntry, error)

ReadSavedModels returns the rows in ~/.kvit-coder/models.yaml, or none when the file does not exist.

type PDFReadConfig

type PDFReadConfig struct {
	// MaxPages caps how many pages one call extracts. Output size usually binds
	// first, since a dense page is 4-5KB against a 24KB cap.
	MaxPages int `yaml:"max_pages"`

	// CacheDir is where the PDF reader keeps its translated code between runs.
	// Empty means ~/.kvit-coder/wasm-cache. Losing it costs about 1.8 seconds
	// on the next turn that reads a PDF, and it is rebuilt automatically.
	CacheDir string `yaml:"cache_dir"`
}

PDFReadConfig configures what happens when Read is given a PDF. There is no enable flag: a PDF handed to Read has to produce something, and the choice is between its text and its object structure.

type PermissionResult

type PermissionResult int

PermissionResult indicates the result of a permission check

const (
	PermissionGranted PermissionResult = iota
	PermissionReadOnly
	PermissionDenied
	PermissionPromptRequired
)

type ProcsToolsConfig

type ProcsToolsConfig struct {
	Enabled bool `yaml:"enabled"`
	// KillOnExit stops everything this session started when the turn is
	// interrupted. On by default: an interrupt that leaves a dev server
	// running is a surprise. Turn it off to keep long-lived services alive
	// across interrupts.
	KillOnExit *bool `yaml:"kill_on_exit"`
}

ProcsToolsConfig configures the tools for work that outlives a turn's iterations: Shell.start, Shell.output, Shell.status, Shell.list, Shell.kill, Shell.tune, Observe.wait and Observe.add. Ephemeral processes die with the turn; persistent ones survive it.

func (*ProcsToolsConfig) ShouldKillOnExit

func (p *ProcsToolsConfig) ShouldKillOnExit() bool

ShouldKillOnExit reports whether an interrupt stops background processes.

type ProjectInstructionsConfig

type ProjectInstructionsConfig struct {
	// Enabled defaults to true when unset, so an existing CLAUDE.md is picked up.
	Enabled *bool `yaml:"enabled"`
	// Path is resolved relative to the directory kvit-coder was launched from.
	// Defaults to "CLAUDE.md".
	Path string `yaml:"path"`
}

ProjectInstructionsConfig controls the Claude-Code-style project instruction file loaded for headless -p runs.

func (ProjectInstructionsConfig) IsEnabled

func (p ProjectInstructionsConfig) IsEnabled() bool

IsEnabled reports whether project instructions should be loaded.

func (ProjectInstructionsConfig) PathOrDefault

func (p ProjectInstructionsConfig) PathOrDefault() string

PathOrDefault returns the configured project instructions path.

type PromptsConfig

type PromptsConfig struct {
	// UseTemplates enables template-based prompt generation (default: false)
	UseTemplates bool `yaml:"use_templates"`
	// TemplatesDir overrides embedded templates with filesystem directory
	TemplatesDir string `yaml:"templates_dir"`
	// HotReload enables template reloading on each request (dev mode)
	HotReload bool `yaml:"hot_reload"`
}

PromptsConfig configures prompt template system

type QuestionToolConfig

type QuestionToolConfig struct {
	Enabled bool `yaml:"enabled"`
	// Timeout is how long a run with no terminal waits for an answer to appear
	// in the session inbox, in seconds. Zero, the default, falls back at once,
	// so a benchmark or scripted run never hangs on a question. A supervised
	// headless run sets it to a few minutes.
	Timeout int `yaml:"timeout"`
}

QuestionToolConfig configures the Question tool, which asks the person running kvit-coder something and waits for the answer.

type ReadToolConfig

type ReadToolConfig struct {
	Enabled         bool          `yaml:"enabled"`
	MaxFileSizeKB   int           `yaml:"max_file_size_kb"`
	MaxReadSizeKB   int           `yaml:"max_read_size_kb"`
	MaxPartialLines int           `yaml:"max_partial_lines"`
	ShowLineNumbers *bool         `yaml:"show_line_numbers"` // nil = default true, for backward compat
	PDF             PDFReadConfig `yaml:"pdf"`
}

ReadToolConfig configures the read tool

func (*ReadToolConfig) GetShowLineNumbers

func (r *ReadToolConfig) GetShowLineNumbers() bool

GetShowLineNumbers returns whether line numbers should be shown in Read output. Defaults to true for backward compatibility.

type ReportMode

type ReportMode string

ReportMode says which turns must end with a structured report.

const (
	// ReportModeOff never asks for one.
	ReportModeOff ReportMode = "off"
	// ReportModeMutating asks for one from a turn that changed a file, ran a
	// command or started a process. A turn that only read and answered ends
	// with prose as before. This is the default.
	ReportModeMutating ReportMode = "mutating"
	// ReportModeTools asks for one from any turn that used a tool.
	ReportModeTools ReportMode = "tools"
	// ReportModeAlways asks for one from every turn.
	ReportModeAlways ReportMode = "always"
)

type ReportToolConfig

type ReportToolConfig struct {
	Enabled bool `yaml:"enabled"`
	// Mode is which turns must end with one: off, mutating (the default),
	// tools, or always.
	Mode ReportMode `yaml:"mode"`
	// MaxRepairAttempts bounds how many rejected reports one turn may submit.
	// Zero selects DefaultReportRepairAttempts.
	MaxRepairAttempts int `yaml:"max_repair_attempts"`
	// MaxBlocks bounds the block list. Zero selects the package default of 20.
	MaxBlocks int `yaml:"max_blocks"`
}

ReportToolConfig configures the Report tool, which ends a turn with a validated structured report instead of free prose. See docs/structured-reports.md.

func (ReportToolConfig) Required

func (c ReportToolConfig) Required(usedTools, mutated bool) bool

Required reports whether a turn with these properties owes a report.

func (ReportToolConfig) ResolvedMode

func (c ReportToolConfig) ResolvedMode() ReportMode

ResolvedMode returns the configured mode, defaulting to mutating. An unrecognized value is treated as the default rather than failing a session, because the cost of a stray report is a card nobody asked for.

type RmSafetyConfig

type RmSafetyConfig struct {
	AllowInTemp         bool `yaml:"allow_in_temp"`
	AllowInWorkspaceCwd bool `yaml:"allow_in_workspace_cwd"`
	BlockWorkspaceRoot  bool `yaml:"block_workspace_root"`
}

RmSafetyConfig configures rm command safety

type SafetyConfig

type SafetyConfig struct {
	StrictMode   bool              `yaml:"strict_mode"`   // Fail-closed on parse errors
	ParanoidMode bool              `yaml:"paranoid_mode"` // Aggressive restrictions
	Audit        AuditConfig       `yaml:"audit"`
	Git          GitSafetyConfig   `yaml:"git"`
	Rm           RmSafetyConfig    `yaml:"rm"`
	Interpreters InterpreterConfig `yaml:"interpreters"`
}

SafetyConfig holds enhanced safety mode configuration

type SafetyConfirmation

type SafetyConfirmation struct {
	ToolName  string    `yaml:"-"`
	Path      string    `yaml:"-"`
	Timestamp time.Time `yaml:"-"`
}

SafetyConfirmation tracks user confirmations for path access This is memory-only and does not persist across sessions

type SearchToolConfig

type SearchToolConfig struct {
	Enabled           bool `yaml:"enabled"`
	MaxSnippetResults int  `yaml:"max_snippet_results"` // Show full snippets up to this many (default: 20)
	MaxCompactResults int  `yaml:"max_compact_results"` // Show file:line:char up to this many (default: 100)

}

SearchToolConfig configures the search tool

type ShellToolConfig

type ShellToolConfig struct {
	Enabled            bool     `yaml:"enabled"`
	AllowedCommands    []string `yaml:"allowed_commands"`    // allowlist (empty = allow all)
	DisallowedCommands []string `yaml:"disallowed_commands"` // blocklist (checked after allowlist)

	// InjectEnv, when non-empty, replaces the child process environment for shell
	// commands with os.Environ() plus these "KEY=VALUE" entries. Used by the
	// thinkbench harness to point python3/pip at the uv environment without naming
	// an outside path in the command string. Empty = inherit parent env (default).
	InjectEnv []string `yaml:"-"`
	// ExecPrefix, when non-empty, is prepended (as an argv) before "sh -c <command>"
	// when running shell commands. Used by the thinkbench harness to wrap agent
	// commands in an OS sandbox (e.g. bwrap). Empty = run "sh -c" directly (default).
	ExecPrefix []string `yaml:"-"`
	// AllowInterpreters, when true, lifts the default block on interpreter
	// one-liners (python -c, node -e, perl -e, ...). Set by the thinkbench harness,
	// whose tasks are interpreter-driven and sandboxed, and opt-in via benchmark
	// configs (e.g. SWE-bench, which runs in a Docker container and needs python -c
	// to introspect the codebase). Default false (blocked) for normal use.
	AllowInterpreters bool `yaml:"allow_interpreters"`

	// AllowWithoutAsking answers yes to every command that would otherwise
	// stop and ask, except the few that destroy something unrecoverably
	// (dd, mkfs and its family). --yolo sets it. What is refused outright
	// stays refused: this answers questions, and a denial was never one.
	//
	// It matters most with no terminal, where an unanswered question is a
	// refusal rather than a pause: without this, a headless --yolo run fails
	// on the first curl it needs.
	AllowWithoutAsking bool `yaml:"allow_without_asking"`

	// DefaultTimeout is how long a shell command may run when the call does
	// not ask for a timeout, in seconds. Default 120.
	DefaultTimeout int `yaml:"default_timeout"`
	// MaxTimeout caps what a call may ask for, in seconds. Default 600.
	MaxTimeout int `yaml:"max_timeout"`
}

ShellToolConfig configures the shell tool

type SubagentToolConfig added in v0.5.0

type SubagentToolConfig struct {
	Enabled            bool `yaml:"enabled"`
	MaxChildIterations int  `yaml:"max_child_iterations"`
	TimeoutS           int  `yaml:"timeout_s"`
	ResultMaxChars     int  `yaml:"result_max_chars"`
	MaxConcurrent      int  `yaml:"max_concurrent"`
}

SubagentToolConfig configures the Subagent tool, which runs a child agent loop in-process with its own history and a restricted tool set, returning only the final summary text to the parent. Phase 1 is read-only delegation (research); write-capable delegation (general) follows once the safety story is settled.

func (SubagentToolConfig) ResolvedMaxChildIterations added in v0.5.0

func (c SubagentToolConfig) ResolvedMaxChildIterations() int

ResolvedMaxChildIterations returns max_child_iterations, defaulting to 50. A child that needs 1000 iterations is a turn wearing a costume.

func (SubagentToolConfig) ResolvedMaxConcurrent added in v0.5.0

func (c SubagentToolConfig) ResolvedMaxConcurrent() int

ResolvedMaxConcurrent returns max_concurrent, defaulting to 8. The permit is per concurrent Subagent: background children hold one from spawn to collect/kill/exit, and blocking runs hold one while they run.

func (SubagentToolConfig) ResolvedResultMaxChars added in v0.5.0

func (c SubagentToolConfig) ResolvedResultMaxChars() int

ResolvedResultMaxChars returns result_max_chars, defaulting to 8000.

func (SubagentToolConfig) ResolvedTimeoutS added in v0.5.0

func (c SubagentToolConfig) ResolvedTimeoutS() int

ResolvedTimeoutS returns timeout_s, defaulting to 300.

type TerminalTitleConfig

type TerminalTitleConfig struct {
	// Enabled set to false stops the title being written. Unset means on;
	// it is only ever written to a terminal, so there is nothing for a
	// piped run to break.
	Enabled *bool `yaml:"enabled"`
	// Running is the icon shown while a turn is running, Asking the one
	// shown while a prompt is waiting for an answer (a Question, a path or
	// MCP confirmation, the pause prompt), and Waiting the one shown while
	// nothing is running and the composer is open. Unset takes the
	// ui.DefaultTitle*Icon defaults.
	Running *string `yaml:"running"`
	Waiting *string `yaml:"waiting"`
	Asking  *string `yaml:"asking"`
}

TerminalTitleConfig controls the terminal's window title, which a tabbed terminal such as the one in VS Code also uses as the tab label. kvit-coder-ui sets it to an icon for what is happening followed by the session's title. The agent process it spawns writes it too, but only while a prompt of its own is waiting for an answer and only when it was started by the front end; a headless run leaves the title alone.

The pointers are so that "unset" and "set to empty" are different things: an unset icon takes the default, an icon set to "" means that state shows the session title with no icon at all.

func (TerminalTitleConfig) Icons

func (t TerminalTitleConfig) Icons(defRunning, defWaiting, defAsking string) (string, string, string)

Icons returns the configured icons, falling back to the defaults passed in for whichever of the three the config leaves unset. The defaults come from the caller because internal/ui, which owns them, is a package this one cannot import.

func (TerminalTitleConfig) On

func (t TerminalTitleConfig) On() bool

On reports whether the front end should set the window title.

type ThinkbenchConfig

type ThinkbenchConfig struct {
	// SuiteDir overrides the auto-discovered suite directory
	// (default: benchmarks/thinkbench/suite, discovered from the repo root).
	SuiteDir string `yaml:"suite_dir"`
	// TimeoutPerRun is the per-task agent-loop timeout in seconds (default: 600).
	TimeoutPerRun int `yaml:"timeout_per_run"`
	// UVPython pins the python version for the uv env (default: "3.11").
	UVPython string `yaml:"uv_python"`
	// Sandbox selects the OS sandbox for agent run_command:
	// "auto" (default; bwrap→firejail→none), "bwrap", "firejail", "none", or
	// "require" (auto, but hard-fail if no sandbox tool is available).
	Sandbox string `yaml:"sandbox"`
	// Types restricts which task types run (default: all graded types, excluding observed).
	Types []string `yaml:"types"`
	// Trials is the default trials-per-task (overridden by -n on the command line).
	Trials int `yaml:"trials"`
	// PipPackages are installed into the shared uv env at preflight so agents can
	// use common test deps without reaching outside the sandbox to find them.
	// nil (unset) defaults to ["pytest"]; an explicit empty list installs nothing.
	PipPackages []string `yaml:"pip_packages"`
	// IncludeObserved runs the ungraded ambiguous-spec tasks too (persist-only).
	IncludeObserved bool `yaml:"include_observed"`
}

ThinkbenchConfig configures the thinkbench benchmark family (autonomous coding agent tasks graded by a held-out python grader). See thinkbench-plan.md.

func (*ThinkbenchConfig) GetPipPackages

func (t *ThinkbenchConfig) GetPipPackages() []string

GetPipPackages returns the packages to pre-install into the uv env. An unset (nil) list defaults to ["pytest"]; an explicit empty list installs nothing.

func (*ThinkbenchConfig) GetSandbox

func (t *ThinkbenchConfig) GetSandbox() string

GetSandbox returns the sandbox mode, defaulting to "auto".

func (*ThinkbenchConfig) GetTimeoutPerRun

func (t *ThinkbenchConfig) GetTimeoutPerRun() int

GetTimeoutPerRun returns the per-run timeout in seconds, defaulting to 600.

func (*ThinkbenchConfig) GetUVPython

func (t *ThinkbenchConfig) GetUVPython() string

GetUVPython returns the pinned python version, defaulting to "3.11".

type ToolGroupConfig

type ToolGroupConfig struct {
	// Name is the tool the model sees, e.g. "Web.browsing". Must be unique
	// across groups and must not collide with a built-in tool.
	Name string `yaml:"name"`

	// Enabled opts the group in. Zero value false = skipped (opt-in).
	Enabled bool `yaml:"enabled"`

	// Description is what the model reads when deciding whether to open the
	// group, and the only thing a group costs while it is unopened. Write it
	// as when to reach for the group rather than as a list of what is in it.
	Description string `yaml:"description"`

	// Category places the group's documentation in the system prompt:
	// "filesystem", "shell", "web", "plan" or "mcp".
	// Defaults to "mcp".
	Category string `yaml:"category"`

	// Instructions is prose returned with the reference, for the things the
	// member schemas cannot say. InstructionsFile is read into it at load
	// time when set; a relative path resolves against the config file's
	// directory, so a missing file fails at startup rather than at the call.
	Instructions     string `yaml:"instructions"`
	InstructionsFile string `yaml:"instructions_file"`

	// MCPServers names entries of mcp.servers whose tools become this group's
	// members. Those servers are not dialed at startup and their tools are not
	// advertised to the model directly.
	MCPServers []string `yaml:"mcp_servers"`

	// Tools names built-in tools to move into this group. They are registered
	// as members instead of as tools of their own, so they stop appearing in
	// every request.
	Tools []string `yaml:"tools"`
}

ToolGroupConfig configures one tool group.

func (*ToolGroupConfig) GetCategory

func (g *ToolGroupConfig) GetCategory() string

GetCategory returns the system-prompt category, defaulting to "mcp".

type ToolsConfig

type ToolsConfig struct {
	Read     ReadToolConfig     `yaml:"read"`
	Images   ImagesConfig       `yaml:"images"`
	Edit     EditToolConfig     `yaml:"edit"`
	Search   SearchToolConfig   `yaml:"search"`
	Glob     GlobToolConfig     `yaml:"glob"`
	Shell    ShellToolConfig    `yaml:"shell"`
	Question QuestionToolConfig `yaml:"question"`
	Report   ReportToolConfig   `yaml:"report"`
	Procs    ProcsToolsConfig   `yaml:"procs"`
	Batch    BatchToolConfig    `yaml:"batch"`
	Web      WebToolsConfig     `yaml:"web"`
	Subagent SubagentToolConfig `yaml:"subagent"`

	// Safety confirmations (runtime only, not persisted)
	SafetyConfirmations map[string]SafetyConfirmation `yaml:"-"`
}

ToolsConfig holds per-tool configuration with explicit enable/disable

type UIConfig

type UIConfig struct {
	// Markdown selects when the final answer is styled as markdown:
	// "auto" (the default), "always", or "never". Auto styles only on a
	// terminal; piped output and --json stay raw markdown. Empty and
	// unknown values are auto.
	Markdown string `yaml:"markdown"`

	// TerminalTitle controls the window title the interactive front end
	// keeps up to date while it runs.
	TerminalTitle TerminalTitleConfig `yaml:"terminal_title"`
}

UIConfig controls terminal output styling.

type UpdatesConfig added in v0.4.1

type UpdatesConfig struct {
	// Enabled gates the automatic check. Nil (unset) means on, so existing
	// configs check daily; false turns the background query off while
	// :update --check still works on demand. KVIT_NO_UPDATE_CHECK=1 does
	// the same without a config edit.
	Enabled *bool `yaml:"enabled"`
	// IntervalHours is the minimum time between automatic checks (default
	// 24). Zero or negative means the default.
	IntervalHours int `yaml:"interval_hours"`
}

UpdatesConfig controls the release check in kvit-coder-ui.

func (UpdatesConfig) AutoCheckEnabled added in v0.4.1

func (u UpdatesConfig) AutoCheckEnabled() bool

AutoCheckEnabled reports whether the background check may run.

func (UpdatesConfig) Interval added in v0.4.1

func (u UpdatesConfig) Interval() time.Duration

Interval is the minimum time between automatic checks.

type WebFetchToolConfig

type WebFetchToolConfig struct {
	Enabled   bool   `yaml:"enabled"`
	Timeout   int    `yaml:"timeout"`
	MaxBytes  int64  `yaml:"max_bytes"`
	UserAgent string `yaml:"user_agent"`
}

WebFetchToolConfig configures Web.fetch.

type WebSearchToolConfig

type WebSearchToolConfig struct {
	Enabled     bool `yaml:"enabled"`
	Count       int  `yaml:"count"`
	MaxAttempts int  `yaml:"max_attempts"`
	Timeout     int  `yaml:"timeout"`
}

WebSearchToolConfig configures Web.search.

MaxAttempts is a setting rather than a constant because the right value depends on a rate limit this program does not control. Four suits one request per second, where a collision clears in about a second and retrying quietly beats handing the model a failure to reason about; a backend without a per-second limit wants one.

type WebToolsConfig

type WebToolsConfig struct {
	APIKeyEnv string `yaml:"api_key_env"`
	BaseURL   string `yaml:"base_url"`
	UsageLog  string `yaml:"usage_log"`

	Search WebSearchToolConfig `yaml:"search"`
	Fetch  WebFetchToolConfig  `yaml:"fetch"`
}

WebToolsConfig configures the tools that reach the network: Web.search, which queries the Brave Search API, and Web.fetch, which retrieves one page and converts it to markdown. The key is named rather than written here, the way llm.api_key_env does it, because the same key is usually shared with other programs and this file is checked in.

Jump to

Keyboard shortcuts

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