config

package
v1.2.0 Latest Latest
Warning

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

Go to latest
Published: May 30, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

Documentation

Overview

Package config defines Harbor's strongly-typed configuration surface.

Configuration is loaded once at boot via Load (or LoadFromBytes for tests). After Load returns, the *Config is immutable; subsystems hold a *Config reference and read from it concurrently. This is the concurrent-reuse contract from D-025.

The struct layout uses one sub-struct per RFC §6 subsystem so that future phases can extend their slice without cross-package import cycles. Sub-structs whose owning phase has not yet shipped are reserved as empty zero-valued types — yaml `omitempty` keeps the boot-log readable.

Two struct tag conventions augment the standard yaml tag:

  • `reload:"live"` opts a field into hot-reload. The default is `restart` (whether explicitly tagged or absent). Phase 02 ships the mechanism; no field opts in yet.
  • `secret:"true"` marks a field whose value must be redacted by MarshalForLogging. A name-based fallback also redacts fields whose YAML name matches the canonical secret list.

Index

Constants

View Source
const (
	// DevHotReloadPolicyDrain waits for in-flight RunLoops to drain up
	// to DrainTimeout before forcing a stack restart. The default.
	DevHotReloadPolicyDrain = "drain"
	// DevHotReloadPolicyCancel cancels in-flight RunLoops immediately
	// on a triggered restart — no drain wait.
	DevHotReloadPolicyCancel = "cancel"
	// DevHotReloadPolicyDisabled is equivalent to Enabled: false; the
	// watcher is not started. Listed as a canonical value so an operator
	// can disable hot-reload via a single explicit field rather than
	// flipping the *bool.
	DevHotReloadPolicyDisabled = "disabled"
)

Canonical retain-in-flight policy values for DevHotReloadConfig. Centralised so cmd/harbor and the validator reference one spelling.

View Source
const DefaultMaxRequestBytes = 4 << 20

DefaultMaxRequestBytes is the `ProtocolConfig.MaxRequestBytes` value applied when the operator configured none — 4 MiB, the spec's "moderate-size upload" posture for the Phase 73l artifacts.put pipeline.

Variables

View Source
var (
	// ErrConfigInvalid wraps any failure to parse, override, or
	// validate a configuration source. Callers should errors.Is on
	// this sentinel to distinguish "config layer rejected the input"
	// from upstream filesystem / IO errors.
	ErrConfigInvalid = errors.New("config: invalid configuration")
	// ErrConfigNotFound is returned when Load is given a path that
	// does not exist. It wraps the originating fs error so callers
	// can still errors.Is(err, fs.ErrNotExist).
	ErrConfigNotFound = errors.New("config: file not found")
)

Sentinel errors. Callers compare against these via errors.Is.

Functions

func IsValidationError

func IsValidationError(err error) bool

IsValidationError reports whether err originated in validation (vs. a parse or env-override failure). Callers who want to distinguish boot-time misconfiguration from filesystem trouble can errors.Is on ErrConfigInvalid first, then this helper.

func KnownBuiltInTools

func KnownBuiltInTools() []string

KnownBuiltInTools returns the sorted built-in allowlist as a slice. Public so the `internal/tools/builtin` mirror test can reach it without importing internal validator state.

func KnownCustomToolTypes

func KnownCustomToolTypes() []string

KnownCustomToolTypes returns the sorted allowlist of yaml-shorthand types `tools.custom[]` accepts. Public so the scaffold engine + a future drift test can read the same source of truth.

Types

type A2APeerConfig

type A2APeerConfig struct {
	URL                   string        `yaml:"url"`
	TrustTier             int           `yaml:"trust_tier"`
	LatencyTierMS         int           `yaml:"latency_tier_ms"`
	AllowInsecureLoopback bool          `yaml:"allow_insecure_loopback,omitempty"`
	AgentCardTTL          time.Duration `yaml:"agent_card_ttl,omitempty"`
}

A2APeerConfig declares an A2A peer the southbound driver may connect to. URL is required; the driver rejects HTTP schemes unless the host is loopback or `AllowInsecureLoopback` is true (AGENTS.md §7).

`TrustTier` is an operator-set integer in [1, 5] (1 = third-party, 5 = first-party). The route-scoring registry uses this to rank peers when more than one declares the same capability.

`LatencyTierMS` is an operator hint at the peer's expected p50 latency in milliseconds. Smaller values rank higher (latency is the tie-breaker among similarly-trusted peers).

`AllowInsecureLoopback` opts a loopback HTTP peer into the driver. The flag is name-checked against loopback only — a non-loopback HTTP host is still rejected regardless. Restart-required.

`AgentCardTTL` overrides the driver-level AgentCard cache TTL. Zero falls back to the driver default (10 minutes).

type ArtifactsConfig

type ArtifactsConfig struct {
	Driver                    string `yaml:"driver"`
	FSRoot                    string `yaml:"fs_root,omitempty"`
	DSN                       string `yaml:"dsn,omitempty" secret:"true"`
	HeavyOutputThresholdBytes int    `yaml:"heavy_output_threshold_bytes,omitempty"`
	S3Bucket                  string `yaml:"s3_bucket,omitempty"`
	S3Endpoint                string `yaml:"s3_endpoint,omitempty"`
	S3Region                  string `yaml:"s3_region,omitempty"`
	S3Prefix                  string `yaml:"s3_prefix,omitempty"`
	S3AccessKeyID             string `yaml:"s3_access_key_id,omitempty" secret:"true"`
	S3SecretAccessKey         string `yaml:"s3_secret_access_key,omitempty" secret:"true"`
	S3UsePathStyle            bool   `yaml:"s3_use_path_style,omitempty"`
}

ArtifactsConfig configures the ArtifactStore driver, the filesystem-driver root path, the SQL-driver connection string, and the heavy-output threshold above which the runtime mandatorily routes payloads through the store.

`Driver` selects an artifacts driver. V1 ships five drivers: `inmem` (the floor; per-process lifetime, no persistence), `fs` (single-binary production target), `sqlite` (Phase 18 — SQLite- backed, durable across restart), `postgres` (Phase 18 — Postgres-backed, durable across restart, multi-replica safe), and `s3` (Phase 19 — S3-compatible object-store-backed, durable; presigned-URL `GetRef` via the optional `Presigner` capability). Default `inmem`.

`FSRoot` is required when `Driver == "fs"`; it is the root directory under which `<root>/<tenant>/<user>/<session>/<task>/ <namespace>/<id>` blobs land. The directory is created (`os.MkdirAll`) at driver `New` time.

`DSN` is required when `Driver` is `"sqlite"` or `"postgres"`. Format:

  • SQLite: a bare file path (e.g. `/var/lib/harbor/artifacts.sqlite`) or the `:memory:` sentinel (degenerate dev case).
  • Postgres: a standard URL form (`postgres://user:pass@host:5432/db?sslmode=disable`) or pgx key-value form.

`HeavyOutputThresholdBytes` is the byte size at which the runtime mandatorily routes a payload through the ArtifactStore. Default 32 KB (D-022, RFC §6.10). Per-tool overrides land at Phase 26 via the tool catalog; the field is the runtime-wide default. Consumed by the tool dispatcher (Phase 26+) and the LLM-edge catch-all (Phase 32) — validated today so an operator's deployment is rejected for an invalid value even before the consumers land.

S3* fields configure the Phase 19 S3-style driver (AWS S3 / MinIO / Cloudflare R2 / any S3-compat backend). `S3Bucket` is required when `Driver == "s3"`. `S3Region` defaults to "us-east-1" when unset. `S3Endpoint` is the base URL for non-AWS backends (MinIO / R2); leave empty to use AWS's default endpoint resolution. `S3Prefix` is an optional path prefix that lets multiple Harbor deployments share one bucket. `S3AccessKeyID` and `S3SecretAccessKey` are optional — when both are empty the SDK's default credential chain is used (AWS_*, IRSA, instance metadata, etc.). `S3UsePathStyle` defaults to false (AWS native); flip on for MinIO / older R2 endpoints.

type AuditConfig

type AuditConfig struct{}

AuditConfig is owned by Phase 03 + later audit phases.

type CLIConfig

type CLIConfig struct {
	DevHotReload DevHotReloadConfig `yaml:"dev_hot_reload,omitempty"`
}

CLIConfig is owned by the CLI phases.

`DevHotReload` configures the Phase 65 (D-099) `harbor dev` hot-reload watcher: fsnotify-driven graceful-drain restart when a watched file changes. The block is opt-out (`Enabled: true` is the default applied by the loader). Restart-required at the harbor-dev level (a change to the block takes effect on the next `harbor dev` boot).

type Config

type Config struct {
	Server      ServerConfig      `yaml:"server"`
	Identity    IdentityConfig    `yaml:"identity"`
	Telemetry   TelemetryConfig   `yaml:"telemetry"`
	State       StateConfig       `yaml:"state"`
	LLM         LLMConfig         `yaml:"llm"`
	Governance  GovernanceConfig  `yaml:"governance"`
	Distributed DistributedConfig `yaml:"distributed,omitempty"`

	// Reserved slots for future phases — owning phase fills the body.
	Runtime   RuntimeConfig   `yaml:"runtime,omitempty"`   // owned by runtime/* phases
	Memory    MemoryConfig    `yaml:"memory,omitempty"`    // owned by memory phases
	Skills    SkillsConfig    `yaml:"skills,omitempty"`    // owned by skills phases
	Tasks     TasksConfig     `yaml:"tasks,omitempty"`     // owned by tasks phases
	Sessions  SessionsConfig  `yaml:"sessions,omitempty"`  // owned by sessions phases
	Artifacts ArtifactsConfig `yaml:"artifacts,omitempty"` // owned by artifacts phases
	Events    EventsConfig    `yaml:"events,omitempty"`    // owned by events phases
	Audit     AuditConfig     `yaml:"audit,omitempty"`     // owned by Phase 03 + audit phases
	Protocol  ProtocolConfig  `yaml:"protocol,omitempty"`  // owned by protocol phases
	CLI       CLIConfig       `yaml:"cli,omitempty"`       // owned by CLI phases
	Tools     ToolsConfig     `yaml:"tools,omitempty"`     // owned by tools subsystem phases (26 / 27 / 28 / 29)
	Planner   PlannerConfig   `yaml:"planner,omitempty"`   // owned by planner phases (D-103)
	// contains filtered or unexported fields
}

Config is the root configuration. It is immutable after Load.

func Load

func Load(ctx context.Context, path string, opts ...LoadOption) (*Config, error)

Load reads a YAML configuration file at path, applies HARBOR_-prefixed environment overrides, runs Validate, and returns an immutable *Config. The returned error is wrapped under either ErrConfigNotFound (if the file is missing) or ErrConfigInvalid (parse / override / validate failure).

Options customise the load (e.g. WithLogger to redirect the deprecation-warning surface). No-option calls log via slog.Default().

func LoadFromBytes

func LoadFromBytes(ctx context.Context, data []byte, opts ...LoadOption) (*Config, error)

LoadFromBytes parses raw YAML bytes (typically from tests). It applies the same env-var overrides and validation pipeline as Load, but does not record a filesystem source — error messages will include "(source: <bytes>)" instead of a path. Options mirror Load.

func WithOverrides

func WithOverrides(c *Config, overrides map[string]string) (*Config, error)

WithOverrides applies a flat key->string override map to a previously-loaded *Config and re-validates. Keys are dotted paths matching the YAML field names ("server.bind_addr", "llm.model"). This is the seam for CLI flag layering (Phase 64) and Console pushed config (post-V1); Phase 02 ships only the mechanism.

func (*Config) LiveReloadable

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

LiveReloadable returns dotted YAML paths for every field tagged `reload:"live"`. Phase 02 ships zero live fields so this returns an empty slice; later phases that opt in extend it automatically.

func (*Config) MarshalForLogging

func (c *Config) MarshalForLogging() ([]byte, error)

MarshalForLogging produces YAML bytes with secret-shaped fields replaced by "***". Field detection prefers `secret:"true"` struct tags; falls back to the canonical name list. The result is safe to emit to slog at boot and is intended only for logging — never feed it back through Load.

func (*Config) Validate

func (c *Config) Validate() error

Validate runs every section validator and returns the first error, formatted with the offending YAML path and the source filename (when known). Nil on success.

type CustomToolConfig

type CustomToolConfig struct {
	Name        string            `yaml:"name"`
	Description string            `yaml:"description"`
	Input       map[string]string `yaml:"input,omitempty"`
	Output      map[string]string `yaml:"output,omitempty"`
}

CustomToolConfig declares one operator-defined custom tool whose Go shell is generated by `harbor scaffold` (Phase 83o / D-154). Each entry yields one `tools/<name>.go` stub + matching test file in the scaffolded project.

Fields:

  • `Name` — the catalog tool name (also drives the generated file name + Go function name). Required. Unique within `Custom`; no collision with `BuiltIn`. Operator-friendly form: lowercase + `.` separators are allowed (e.g. `weather.lookup`).
  • `Description` — one-line summary, surfaced in the generated Go comment + the planner-facing tool catalog. Required.
  • `Input` — flat map of field name → type. Generates the typed Go input struct. Type allowlist (V1.1): `string` / `integer` / `number` / `boolean` / `[]string`. Required (may be empty when the tool takes no arguments).
  • `Output` — flat map of field name → type. Same allowlist as `Input`. Required (may be empty when the tool returns nothing observable beyond success).

Restart-required.

type DevHotReloadConfig

type DevHotReloadConfig struct {
	Enabled      *bool         `yaml:"enabled,omitempty"`
	Policy       string        `yaml:"policy,omitempty"`
	DrainTimeout time.Duration `yaml:"drain_timeout,omitempty"`
	WatchRoots   []string      `yaml:"watch_roots,omitempty"`
}

DevHotReloadConfig configures the Phase 65 (D-099) `harbor dev` hot-reload watcher. The block is opt-out — the loader populates defaults that match the §13 "test stubs as production defaults" amendment for dev seams: on by default, fail-loud at boot when the configured watch root is unreadable, never silently disabled.

Fields:

  • `Enabled` — `*bool` so the loader can distinguish "operator didn't set the field" (nil → defaults to true) from "operator explicitly disabled" (&false). The CLI flag `harbor dev --no-hot-reload` is the operator-facing escape hatch — it overrides this value at boot. Default: true.
  • `Policy` — retain-in-flight policy on a triggered restart: `"drain"` (wait for in-flight RunLoops up to `DrainTimeout` before forcing close; the default), `"cancel"` (immediately cancel in-flight; no drain), or `"disabled"` (no hot-reload — equivalent to `Enabled: false`). Unknown values fail validation.
  • `DrainTimeout` — bounds the `drain` policy's wait for in-flight RunLoops. Zero / negative rejected. Default: 5s.
  • `WatchRoots` — list of paths (absolute or relative to the working directory) the fsnotify watcher monitors. The dev cmd unions this with the loaded config file's directory so a config edit also triggers a reload. Default: `[".harbor/agents"]` — the project-local Phase 66 draft-save directory.

Restart-required (no `reload:"live"` tag): the watcher itself is the reload mechanism; reconfiguring the watcher at runtime would race the watcher's own goroutine.

type DistributedConfig

type DistributedConfig struct {
	BusDriver    string `yaml:"bus_driver"`
	RemoteDriver string `yaml:"remote_driver"`
}

DistributedConfig configures Harbor's distributed contracts (Phase 22). `BusDriver` selects the MessageBus driver; `RemoteDriver` selects the RemoteTransport driver. V1 ships only `"loopback"` for both. Post-V1 phase 86 adds durable bus drivers; Phase 29 adds the A2A wire RemoteTransport driver. Restart-required (no `reload:"live"`).

type EventsConfig

type EventsConfig struct {
	Driver                   string        `yaml:"driver"`
	MaxSubscribersPerSession int           `yaml:"max_subscribers_per_session"`
	SubscriberBufferSize     int           `yaml:"subscriber_buffer_size"`
	IdleTimeout              time.Duration `yaml:"idle_timeout"`
	DropWindow               time.Duration `yaml:"drop_window"`
	ReplayBufferSize         int           `yaml:"replay_buffer_size"`
	StateDriver              string        `yaml:"state_driver,omitempty"`
	StateDSN                 string        `yaml:"state_dsn,omitempty" secret:"true"`
}

EventsConfig configures the event bus driver and its in-process limits. Phase 05 filled the previously-reserved slot with the inmem driver's defaults; Phase 06 adds ReplayBufferSize for the in-memory ring buffer that backs Replayer. Phase 57 registers the `durable` driver and adds the two optional StateStore-selection fields below without changing the existing field shape.

ReplayBufferSize=0 disables replay entirely on the inmem driver (Replay returns ErrReplayUnavailable immediately). The default applied by Load is 10000. On the `durable` driver ReplayBufferSize sizes the best-effort fallback ring used ONLY when no StateStore is configured.

StateDriver / StateDSN select the StateStore the `durable` driver (Phase 57) persists events through. They are OPTIONAL and ignored by every other driver: when Driver=="durable" and StateDriver is empty, the durable driver auto-degrades to a best-effort in-memory ring buffer and emits a loud runtime.warning (D-074) — replay is then NOT durable across restarts. StateDSN is required whenever StateDriver is a non-inmem driver (sqlite / postgres), mirroring StateConfig's driver/DSN pairing.

type GovernanceConfig

type GovernanceConfig struct {
	RepairAttempts int `yaml:"repair_attempts"`

	// DefaultTier is the tier name applied to an identity that does
	// not match a custom resolver mapping. Empty = no default tier =
	// no enforcement for unmatched identities (latent default).
	DefaultTier string `yaml:"default_tier,omitempty"`

	// IdentityTiers maps tier name to its policy bundle. Empty = no
	// enforcement (latent default). Each entry's fields are
	// independently opt-in — set `budget_ceiling_usd` only to enforce
	// cost ceilings, leaving rate-limit + MaxTokens latent.
	IdentityTiers map[string]GovernanceTierConfig `yaml:"identity_tiers,omitempty"`
}

GovernanceConfig holds the V1 governance policy surface — per-tier cost ceilings, rate limits, and MaxTokens caps — all enforced exclusively through `IdentityTiers` (Phase 36a + 36b). Hot-reload is not yet wired; every field is restart-required.

**Latent default (Wave 7b scoping):** an empty `IdentityTiers` map + empty `DefaultTier` disables every per-policy enforcement. Operators turn on enforcement per tier by populating `IdentityTiers` with at least one `GovernanceTierConfig` entry and (optionally) setting `DefaultTier`.

**Removed in D-081 (chore/governance-config-consolidation):** the pre-Phase-36a single-knob fields `default_max_tokens`, `cost_ceiling_usd`, and `rate_limit_tps` are no longer accepted on `GovernanceConfig`. They were validated-but-ignored stubs: the loader took them, the enforcement engine never consumed them, and an operator setting `cost_ceiling_usd: 100` in YAML saw silent no-op behaviour. The loader now emits a structured `config.deprecated_field` slog warning when any of those keys appears in YAML, drops the value, and proceeds. Operators migrating from a pre-Phase-36a config build a `default` tier with the equivalent values under `IdentityTiers`.

type GovernanceRateLimitConfig

type GovernanceRateLimitConfig struct {
	Capacity       int           `yaml:"capacity,omitempty"`
	RefillTokens   int           `yaml:"refill_tokens,omitempty"`
	RefillInterval time.Duration `yaml:"refill_interval,omitempty"`
}

GovernanceRateLimitConfig is the token-bucket shape (Phase 36b). `Capacity` is the bucket ceiling (max reservable tokens). `RefillTokens` are added every `RefillInterval`. A zero `Capacity` disables the rate limit entirely.

type GovernanceTierConfig

type GovernanceTierConfig struct {
	BudgetCeilingUSD float64                   `yaml:"budget_ceiling_usd,omitempty"`
	RateLimit        GovernanceRateLimitConfig `yaml:"rate_limit,omitempty"`
	MaxTokens        int                       `yaml:"max_tokens,omitempty"`
}

GovernanceTierConfig is one tier's policy bundle (Phase 36a + 36b). Each field is independently opt-in: set the cost field only to enforce the cost ceiling, leave the rest zero-valued for latent behaviour.

`BudgetCeilingUSD` — Phase 36a. Per-identity cost ceiling. PreCall blocks when the (identity, tier) accumulator total ≥ this. 0 = no ceiling.

`RateLimit` — Phase 36b. Per-(identity, model) token bucket. Zero- valued (Capacity == 0) = no rate limit.

`MaxTokens` — Phase 36b. Per-call cap. Requests whose `MaxTokens` exceed this fail loudly with `ErrMaxTokensExceeded`. 0 = no cap.

type IdentityConfig

type IdentityConfig struct {
	JWTAlgorithms []string `yaml:"jwt_algorithms"`
	Issuer        string   `yaml:"issuer"`
	Audience      string   `yaml:"audience"`
	JWKSURL       string   `yaml:"jwks_url,omitempty"`
	JWKSFile      string   `yaml:"jwks_file,omitempty"`
}

IdentityConfig configures JWT validation. Per AGENTS.md §7 the algorithm allowlist must contain only asymmetric algorithms.

type LLMConfig

type LLMConfig struct {
	Driver               string                           `yaml:"driver"`
	Provider             string                           `yaml:"provider"`
	Model                string                           `yaml:"model"`
	APIKey               string                           `yaml:"api_key" secret:"true"`
	BaseURL              string                           `yaml:"base_url,omitempty"`
	Timeout              time.Duration                    `yaml:"timeout"`
	ContextWindowReserve float64                          `yaml:"context_window_reserve,omitempty"`
	ModelProfiles        map[string]LLMModelProfileConfig `yaml:"model_profiles,omitempty"`
	// Corrections toggles + per-model-profile-override the Phase 34
	// provider-correction layer. Omitted = enabled (production
	// default). See `LLMCorrectionsConfig` for the wire shape.
	Corrections LLMCorrectionsConfig `yaml:"corrections,omitempty"`

	// CustomProviders is the registry of operator-declared
	// OpenAI-compatible providers (Phase 33a). Each entry adds a new
	// `ModelProvider` to the bifrost account so operators can wire
	// NIM, vLLM, ollama, lm-studio, in-house gateways, or any other
	// OpenAI-compatible endpoint via yaml without per-provider Go
	// code. When `LLMConfig.Provider` matches a custom-provider
	// `Name`, the entry's `BaseURL` / `APIKeyEnvVar` / `Models` /
	// network knobs apply (the legacy single-provider fields
	// `APIKey` / `BaseURL` / `Timeout` are ignored for that case).
	CustomProviders []LLMCustomProviderConfig `yaml:"custom_providers,omitempty"`

	// NetworkDefaults applies to every provider (native + custom)
	// when the per-provider override is absent. Zero-valued fields
	// fall through to bifrost's package-level defaults (Phase 33a
	// unification of timeout/retry knobs that were previously
	// scattered). Restart-required.
	NetworkDefaults LLMNetworkDefaults `yaml:"network_defaults,omitempty"`
}

LLMConfig is the default LLM client surface for the runtime (Phase 32+).

`Driver` selects the §4.4 LLM driver. Phase 32 ships `"mock"`; Phase 33 registers `"bifrost"`. Empty defaults to `"mock"` so a missing configuration value does NOT silently route real LLM traffic — operators opt in to bifrost explicitly.

`Provider` / `Model` / `APIKey` / `BaseURL` / `Timeout` are the per-bifrost-driver knobs. They are REQUIRED when `Driver != "mock"` — `validateLLM` enforces. The mock driver ignores them.

`ContextWindowReserve` is the safety-net token-budget margin (default 0.05 / 5%). The safety pass fails with `ErrContextWindowExceeded` when the estimated token count is within this fraction of a model's configured cap. Range [0.0, 1.0).

`ModelProfiles` carries per-model knobs (context-window cap, estimator, JSON-schema mode, default max tokens, reasoning effort, cost overrides). The safety net's token-budget guard REQUIRES a profile entry for every model the request mentions; missing profiles surface at request time as `ErrUnsupportedModel`.

type LLMCorrectionsConfig

type LLMCorrectionsConfig struct {
	Enabled *bool `yaml:"enabled,omitempty"`
}

LLMCorrectionsConfig is the top-level toggle for the Phase 34 per-provider correction layer. The layer is enabled by default; operators set `enabled: false` only for testing scenarios that need to exercise the safety pass in isolation.

`Enabled` is `*bool` so the loader can distinguish "operator didn't set the field" (nil → defaults to true) from "operator explicitly disabled" (&false). Restart-required.

type LLMCorrectionsProfileConfig

type LLMCorrectionsProfileConfig struct {
	MessageOrdering        string `yaml:"message_ordering,omitempty"`
	SchemaMode             string `yaml:"schema_mode,omitempty"`
	ReasoningEffortRouting string `yaml:"reasoning_effort_routing,omitempty"`
	ResponseFormatShape    string `yaml:"response_format_shape,omitempty"`
	UsageBackfillEnabled   bool   `yaml:"usage_backfill_enabled,omitempty"`
}

LLMCorrectionsProfileConfig is one per-model profile override for the Phase 34 correction layer. Each field is the operator-facing string form of the equivalent `internal/llm` enum (`MessageOrderingPolicy`, `SchemaSanitizationMode`, `ReasoningRouting`, `ResponseFormatProfile`). Empty string → use the per-provider default keyed by model-name prefix.

Valid enum values:

  • `message_ordering`: "" / "system_first_strict"
  • `schema_mode`: "" / "openai_strict" / "permissive"
  • `reasoning_effort_routing`: "" / "thinking_model"
  • `response_format_shape`: "" / "json_only" / "anthropic"
  • `usage_backfill_enabled`: bool

type LLMCostOverridesConfig

type LLMCostOverridesConfig struct {
	InputPer1M     float64 `yaml:"input_per_1m_tokens"`
	OutputPer1M    float64 `yaml:"output_per_1m_tokens"`
	ReasoningPer1M float64 `yaml:"reasoning_per_1m_tokens,omitempty"`
	Currency       string  `yaml:"currency,omitempty"`
}

LLMCostOverridesConfig is a per-model cost-table override (used when the provider response doesn't include cost). Phase 36a reads.

type LLMCustomProviderConfig

type LLMCustomProviderConfig struct {
	Name                 string            `yaml:"name"`
	BaseURL              string            `yaml:"base_url"`
	APIKeyEnvVar         string            `yaml:"api_key_env_var"`
	Models               []string          `yaml:"models"`
	BaseProviderType     string            `yaml:"base_provider_type,omitempty"`
	Timeout              time.Duration     `yaml:"timeout,omitempty"`
	MaxRetries           int               `yaml:"max_retries,omitempty"`
	RetryBackoffInitial  time.Duration     `yaml:"retry_backoff_initial,omitempty"`
	RetryBackoffMax      time.Duration     `yaml:"retry_backoff_max,omitempty"`
	Concurrency          int               `yaml:"concurrency,omitempty"`
	BufferSize           int               `yaml:"buffer_size,omitempty"`
	RequestPathOverrides map[string]string `yaml:"request_path_overrides,omitempty"`
}

LLMCustomProviderConfig declares one operator-configured OpenAI-compatible LLM endpoint (Phase 33a). At least one entry is required when `LLMConfig.Provider` names a non-native provider.

Fields:

  • `Name` — the `ModelProvider` identifier the operator picks (e.g. `"nim"`, `"vllm"`). Must be unique across the list AND must not collide with bifrost's native provider names.
  • `BaseURL` — the OpenAI-compatible endpoint root (e.g. `"https://integrate.api.nvidia.com/v1"`). Required.
  • `APIKeyEnvVar` — the environment variable name (no `env.` prefix; operator writes `"NVIDIA_API_KEY"`, driver resolves `os.Getenv(...)` at construction time). Missing → fail-closed at `New` (`ErrMissingAPIKey`).
  • `Models` — the model-name allowlist bifrost forwards to this provider. At least one entry required.
  • `BaseProviderType` — wire family. Phase 33a accepts only `""` (default to `"openai"`) and `"openai"`. Future phases widen.
  • `Timeout` / `MaxRetries` / `RetryBackoff*` / `Concurrency` / `BufferSize` — per-provider overrides. Zero-valued → fall back to `LLMConfig.NetworkDefaults`, which itself falls back to bifrost's package-level defaults.
  • `RequestPathOverrides` — optional `RequestType` → custom URL path map (forwarded to bifrost's `CustomProviderConfig`). Used when an OpenAI-compatible endpoint hosts e.g. `/chat/completions` at the root instead of `/v1/chat/completions`.

type LLMModelProfileConfig

type LLMModelProfileConfig struct {
	ContextWindowTokens int                     `yaml:"context_window_tokens"`
	TokenEstimator      string                  `yaml:"token_estimator,omitempty"`
	JSONSchemaMode      string                  `yaml:"json_schema_mode,omitempty"`
	DefaultMaxTokens    *int                    `yaml:"default_max_tokens,omitempty"`
	ReasoningEffort     string                  `yaml:"reasoning_effort,omitempty"`
	CostOverrides       *LLMCostOverridesConfig `yaml:"cost_overrides,omitempty"`
	// Corrections — per-model overrides for the Phase 34 correction
	// layer. nil → use the per-provider defaults baked into
	// `internal/llm/corrections.defaultProfileFor`. See
	// `LLMCorrectionsProfileConfig` for the wire shape.
	Corrections *LLMCorrectionsProfileConfig `yaml:"corrections,omitempty"`
	// MaxRetries (Phase 36) — caps the validator-driven retry loop the
	// `internal/llm/retry` wrapper runs against this model. Zero (the
	// default) maps to `llm.DefaultMaxRetries` (1). Negative is
	// rejected by `validateLLM`.
	MaxRetries int `yaml:"max_retries,omitempty"`
}

LLMModelProfileConfig is one entry in `LLMConfig.ModelProfiles`. Keyed by canonical model name (e.g. `"anthropic/claude-sonnet-4"`, `"google/gemini-3.1-flash-lite"`). Phase 32 ships the shape; Phase 33+ consume the fields.

  • `ContextWindowTokens` is the model's hard input-token cap. REQUIRED (> 0); the safety net's token-budget guard uses it.
  • `TokenEstimator` selects the estimator algorithm. "" / "chars_div_4" — default chars/4 + per-message overhead. Phase 33+ may register tiktoken-equivalent estimators by name.
  • `JSONSchemaMode` — Phase 35 reads ("native" / "tools" / "prompted"). Phase 32 stores opaque.
  • `DefaultMaxTokens` — Phase 36b's identity-tier override target. nil → use the runtime/governance default.
  • `ReasoningEffort` — request-level default. Empty string → "use provider default."
  • `CostOverrides` — fallback per-1M-token rates when the provider doesn't include cost in its response. Phase 36a reads when accumulating identity-scoped cost.

type LLMNetworkDefaults

type LLMNetworkDefaults struct {
	Timeout             time.Duration `yaml:"timeout,omitempty"`
	MaxRetries          int           `yaml:"max_retries,omitempty"`
	RetryBackoffInitial time.Duration `yaml:"retry_backoff_initial,omitempty"`
	RetryBackoffMax     time.Duration `yaml:"retry_backoff_max,omitempty"`
	Concurrency         int           `yaml:"concurrency,omitempty"`
	BufferSize          int           `yaml:"buffer_size,omitempty"`
}

LLMNetworkDefaults are the operator-tunable defaults that apply to every provider (native + custom) when the per-provider override is absent (Phase 33a). Zero-valued fields fall through to bifrost's package-level defaults so an operator who omits the block sees today's Phase 33 behaviour unchanged.

type LoadOption

type LoadOption func(*loadConfig)

LoadOption customises a Load / LoadFromBytes call. Options compose in declaration order; later options override earlier ones for the same setting.

func WithLogger

func WithLogger(l *slog.Logger) LoadOption

WithLogger overrides the slog.Logger the config loader emits structured warnings on (e.g. the `config.deprecated_field` warning surfaced when a removed YAML key appears in a config — D-081). A nil logger keeps the default (`slog.Default()`); callers that want to capture the warnings in a test build a logger over a bytes buffer and pass it here.

type MCPServerConfig

type MCPServerConfig struct {
	Name          string            `yaml:"name"`
	TransportMode string            `yaml:"transport_mode"`
	URL           string            `yaml:"url,omitempty"`
	Command       []string          `yaml:"command,omitempty"`
	Headers       map[string]string `yaml:"headers,omitempty" secret:"true"`
	KeepAlive     time.Duration     `yaml:"keep_alive,omitempty"`
	// Policy is the per-server default tool reliability policy
	// (Phase 26b). Optional; nil preserves today's behaviour exactly
	// (every tool inherits `tools.DefaultPolicy()` — 30 s per-attempt
	// deadline / 4 total attempts). When set, it projects to the
	// `mcpdrv.Config.DefaultPolicy` applied to every tool this server
	// registers. Per-field zero values fall through to
	// `tools.DefaultPolicy()` (see `ToolPolicyConfig`). Restart-required.
	Policy *ToolPolicyConfig `yaml:"policy,omitempty"`
	// ToolPolicies are per-tool overrides keyed by the MCP tool's name
	// (the server-side name, NOT the `<source>_<tool>` Harbor-facing
	// name). A tool named here uses the override instead of `Policy`
	// (or the package default). Optional; empty preserves today's
	// behaviour. Restart-required.
	ToolPolicies map[string]ToolPolicyConfig `yaml:"tool_policies,omitempty"`
}

MCPServerConfig is one MCP southbound attachment. `Name` is the source-id prefix (must be unique across servers); `TransportMode` selects the wire transport (`auto` / `sse` / `streamable_http` / `stdio`); `URL` is required for HTTP-flavoured transports; `Command` is required for stdio (argv form ONLY — see `internal/tools/drivers/mcp/transport_stdio.go` for the §7 security rule). `Headers` are operator-supplied auth headers (treated as secrets for redaction). `KeepAlive` is the session-ping interval; zero disables.

Restart-required.

type MemoryConfig

type MemoryConfig struct {
	Driver             string `yaml:"driver"`
	DSN                string `yaml:"dsn,omitempty" secret:"true"`
	Strategy           string `yaml:"strategy,omitempty"`
	BudgetTokens       int    `yaml:"budget_tokens,omitempty"`
	RecoveryBacklogMax int    `yaml:"recovery_backlog_max,omitempty"`
}

MemoryConfig is owned by the memory subsystem phases.

`Driver` selects a `memory.MemoryStore` driver. Phase 23 ships only `"inmem"`; Phase 25 adds `"sqlite"` and `"postgres"`. Default `inmem`.

`DSN` is required when `Driver` is `"sqlite"` or `"postgres"`. The format mirrors the StateStore + ArtifactStore drivers (bare file path or `file:` URI for SQLite; libpq-compatible connection string for Postgres). `secret:"true"` redacts the value in audit-redacted logs.

`Strategy` selects the memory shape: `"none"` (Phase 23), or `"truncation"` / `"rolling_summary"` (Phase 24). Default `none`. `memory.Open` rejects strategies the configured driver does not implement with `ErrStrategyNotImplemented`.

`BudgetTokens` is the truncation / rolling-summary budget cap (token estimate). Zero means "no budget" — appending is unbounded.

`RecoveryBacklogMax` is the bounded queue size for the `rolling_summary` strategy's recovery loop (D-035). Default 16 (applied by the loader when the section is omitted). Overflow drops oldest and emits `memory.recovery_dropped` on the bus. Ignored by the `none` and `truncation` strategies.

Restart-required (no `reload:"live"`).

type PlannerConfig

type PlannerConfig struct {
	Driver                 string                  `yaml:"driver,omitempty"`
	MaxSteps               int                     `yaml:"max_steps,omitempty"`
	ExtraGuidance          string                  `yaml:"extra_guidance,omitempty"`
	ReasoningReplay        string                  `yaml:"reasoning_replay,omitempty"`
	MaxToolExamplesPerTool int                     `yaml:"max_tool_examples_per_tool,omitempty"`
	ParallelToolCalls      *bool                   `yaml:"parallel_tool_calls,omitempty"`
	SkillsContextMax       int                     `yaml:"skills_context_max,omitempty"`
	AbsoluteMaxSpawnDepth  int                     `yaml:"absolute_max_spawn_depth,omitempty"`
	PlanningHints          PlannerPlanningHintsCfg `yaml:"planning_hints,omitempty"`
	Extra                  map[string]string       `yaml:"extra,omitempty"`
}

PlannerConfig selects the planner concrete the runtime constructs at boot. The §4.4 seam pattern applied to the planner — closes D-097's "future phases will read cfg.Planner" note and CLAUDE.md §1.3's swappable-planner property gap (D-103, closes issue #126). Mirrors D-095's `ToolOAuthProviderConfig` structurally — same shape, same validator pattern, same registry pattern (`internal/planner` driver registry; `cmd/harbor/main.go` blank-imports each driver).

`Driver` selects a self-registered planner driver in `internal/planner/<name>/`. V1 ships only the `react` driver (the reference LLM-driven ReAct planner — Phase 45 / D-051). Empty defaults to `react` so a missing configuration value boots with the V1 reference planner unchanged; operators opt into alternates explicitly when later phases land them (Plan-Execute, Workflow, Graph, Deterministic, Supervisor, MultiAgent, HumanApproval per RFC §6.2).

`MaxSteps` overrides the driver-side circuit-breaker step cap. Zero (the default) means "use the driver's internal default" — e.g. the react driver's `react.DefaultMaxSteps` (12). The validator rejects negative values pre-boot.

`ExtraGuidance` is operator-supplied domain-specific guidance injected into the rendered ReAct system prompt's `<additional_guidance>` section (Phase 83a, RFC §6.2). Empty (the default) omits the section entirely. The string is rendered verbatim — the operator owns its content hygiene. It flows to the react driver's `WithSystemPromptExtra` Option at construction; other drivers ignore it. The validator imposes no rule beyond "string" — operator copy is operator copy.

`ReasoningReplay` controls whether the ReAct planner re-injects a prior step's captured provider-side reasoning trace into the next turn's prompt (Phase 83e — D-148). The enum has two values: `never` (the default for ALL models — a prior step's captured reasoning is never re-injected) and `text` (prepend the captured trace as a text block above the prior `{tool, args}` action JSON). Empty defaults to `never`; the validator rejects any other value loudly pre-boot. There is no `provider_native` mode in V1.

`MaxToolExamplesPerTool` caps how many curated examples the ReAct prompt's `<available_tools>` section renders per tool (Phase 83b — D-144). Examples are ranked `minimal` > `common` > `edge-case` > untagged; the renderer keeps the top N. Zero (the default) resolves to 3 inside the react driver. The validator rejects negative values loudly pre-boot. Other drivers ignore it.

`ParallelToolCalls` toggles native parallel tool-call emission (Phase 107d — D-169). Pointer-bool so an omitted key (nil) resolves to `true` — the React planner emits a native `CallParallel` when the LLM returns N>1 tool-calls in one response, and the dev `ToolExecutor` dispatches the branches concurrently. An explicit `false` selects the Phase 107c serialization fallback (one `CallTool` per step via `RunContext.PendingToolCalls`). It flows to the react driver's `WithParallelToolCalls` Option only when non-nil; other drivers ignore it. No validator rule beyond "bool" — both states are correct.

`SkillsContextMax` caps how many skill bodies the dev run loop fetches from `skills.SkillStore.Search` and hands the planner via `RunContext.SkillsContext` (Phase 83f — D-149). Zero (the default) resolves to 5; the validator rejects negatives loudly pre-boot. Only consumed by the dev binary's per-task run loop; library consumers that build their own RunContext are unaffected.

`PlanningHints` is the operator-supplied per-run planning steering the dev run loop projects onto `RunContext.PlanningHints` (Phase 83f — D-149). Renders into the ReAct prompt's `<planning_constraints>` via 83c. V1.1 ships only `Constraints` and `PreferredTools`; the richer Go-struct fields (`ParallelGroups`, `DisallowTools`, `Budget`) remain reachable via a custom planner Option, not via `harbor.yaml`. The block is omitted entirely when empty.

`Extra` is the per-driver opaque extras map. Reserved for future drivers' per-flow knobs (e.g. a deterministic planner's scripted step sequence, a supervisor planner's sub-agent list). The V1 `react` driver ignores it.

Restart-required (no `reload:"live"` tag): swapping a planner at runtime would race with in-flight RunLoop goroutines holding the old concrete.

func (PlannerConfig) ParallelToolCallsEnabled added in v1.2.0

func (p PlannerConfig) ParallelToolCallsEnabled() bool

ParallelToolCallsEnabled resolves the optional `parallel_tool_calls` knob (Phase 107d — D-169). Nil (unset) resolves to `true` — native parallel tool-call emission is the default. A non-nil value is honoured verbatim. The dev stack reads this when no driver-boundary passthrough is wanted; the `*bool` itself flows through the planner boundary so the react factory can distinguish "unset" from an explicit `false`.

func (PlannerConfig) SpawnDepthCap added in v1.2.0

func (p PlannerConfig) SpawnDepthCap() int

SpawnDepthCap resolves the optional `absolute_max_spawn_depth` knob (Phase 107e — D-170). A non-positive value (unset or zero) resolves to the dev-runtime default of 4: a SpawnTask whose child would exceed this ParentTaskID-chain depth is rejected loudly so a background sub-agent cannot recurse without bound. The cap bounds depth, not breadth.

type PlannerPlanningHintsCfg

type PlannerPlanningHintsCfg struct {
	// Constraints is free-form text rendered verbatim into the
	// `<planning_constraints>` section. Voice/tone rules, hard
	// negative-space guidance ("never call X without prior approval"),
	// or domain-specific constraints. Empty omits the line.
	Constraints string `yaml:"constraints,omitempty"`
	// PreferredTools lists tool names the planner should prefer when
	// multiple satisfy the same goal. Empty omits the line.
	PreferredTools []string `yaml:"preferred_tools,omitempty"`
}

PlannerPlanningHintsCfg is the YAML-facing subset of the planner's `PlanningHints` (Phase 83f — D-149). V1.1 ships two fields; the richer Go-struct fields stay reachable through a custom planner Option, not the YAML surface. An empty struct projects to nil on `RunContext.PlanningHints`, so the `<planning_constraints>` prompt section is omitted.

func (PlannerPlanningHintsCfg) IsZero

func (p PlannerPlanningHintsCfg) IsZero() bool

IsZero reports whether every field on the YAML config is empty — the dev run loop reads this to decide whether to project onto `RunContext.PlanningHints` (nil) or to allocate a populated struct.

type ProjectedToolPolicy added in v1.2.0

type ProjectedToolPolicy struct {
	// TimeoutMS is the per-attempt deadline in milliseconds; 0 means
	// "inherit the default" at the runtime layer.
	TimeoutMS int
	// MaxRetries is `max_attempts - 1` when `max_attempts >= 1`, else
	// 0 (inherit the default total attempt count).
	MaxRetries int
	// BackoffBase / BackoffMax are the projected backoff base / cap; a
	// zero Duration inherits the default.
	BackoffBase time.Duration
	BackoffMax  time.Duration
	// BackoffMult is the per-retry multiplier; 0 inherits the default.
	BackoffMult float64
	// RetryOn carries the validated error-class strings (`transient` /
	// `timeout` / `5xx` / `permanent`). nil/empty inherits the default
	// allowlist. The strings are validated against the allowlist by
	// ToToolPolicy; the runtime copy turns them into `tools.ErrorClass`.
	RetryOn []string
	// RetryOnEmpty, when true, instructs the runtime copy to build an
	// EXPLICIT empty (non-nil) `RetryOn` slice — the policy shell reads
	// that as "retry on nothing" (exactly one attempt). Set by
	// ToToolPolicy when `max_attempts == 1` and the operator named no
	// `retry_on` allowlist; see the comment in ToToolPolicy for why the
	// MaxRetries:0 fall-through alone is insufficient. Mutually
	// exclusive with a populated RetryOn.
	RetryOnEmpty bool
}

ProjectedToolPolicy is the cycle-free, primitive-only image of a `ToolPolicyConfig` after projection. Its fields map 1:1 onto `tools.ToolPolicy`; the binary entry point (`cmd/harbor`) performs the trivial final copy into the runtime type. Splitting the projection this way keeps `internal/config` free of an import cycle (`tools` → `events` → `config`) while preserving CLAUDE.md §13's single-source rule: there is exactly ONE place that interprets the operator-facing `ToolPolicyConfig` fields — `ToToolPolicy` below — and the `tools.ToolPolicy` STRUCT remains the single definition of the policy shape.

Per-field zero-value semantics are preserved: a field that the operator omitted (its YAML value is the Go zero value) is left at the zero value here, so `tools.ToolPolicy`'s own per-field `resolved()` fall-through fills it with the package default at dispatch time. The projection NEVER substitutes a default itself — the single exception is the `max_attempts → MaxRetries` arithmetic, which only fires when `max_attempts >= 1` (an omitted `max_attempts` leaves `MaxRetries` zero, inheriting the default attempt count).

type ProtocolConfig

type ProtocolConfig struct {
	MaxRequestBytes int `yaml:"max_request_bytes,omitempty"`
}

ProtocolConfig is owned by the protocol-server phases.

`MaxRequestBytes` is the Phase 73l (D-120) upper bound on an `artifacts.put` upload body. A body above this is rejected with the canonical `CodeRequestTooLarge` / HTTP 413 — fail loud, never silently truncate. Optional; a zero value resolves to `DefaultMaxRequestBytes` (4 MiB) at load time. Operators with a larger upload need adjust this key and the Console's browser-side size hint accordingly.

func (ProtocolConfig) ResolvedMaxRequestBytes

func (c ProtocolConfig) ResolvedMaxRequestBytes() int

ResolvedMaxRequestBytes returns the configured `MaxRequestBytes`, substituting `DefaultMaxRequestBytes` when the field is zero or negative. The protocol-server boot path calls this so a fresh config produces a working upload bound with zero ceremony.

type RuntimeConfig

type RuntimeConfig struct{}

RuntimeConfig is owned by runtime/* phases (engine, streaming, cancellation, backpressure).

type ServerConfig

type ServerConfig struct {
	BindAddr            string        `yaml:"bind_addr"`
	ShutdownGracePeriod time.Duration `yaml:"shutdown_grace_period"`
	AllowedOrigins      []string      `yaml:"allowed_origins,omitempty"`
	CORSDevAllowAny     bool          `yaml:"cors_dev_allow_any,omitempty"`
}

ServerConfig is the network surface for the Harbor binary.

AllowedOrigins is the Phase 83v CORS allowlist for the D-091 multi-process Console+Runtime posture. Each entry is an exact origin (`scheme://host[:port]`) the Runtime accepts cross-origin requests from. Empty list (the default) = no CORS headers = same- origin only (the pre-83v posture). The validator rejects `*` (or any wildcard shape) unless CORSDevAllowAny is true. See D-162 + CLAUDE.md §7 — never wildcard in production.

CORSDevAllowAny is the explicit, dev-only escape hatch that opens the CORS surface to ANY origin. NEVER set in production: a `harbor dev` boot with this flag set prints a stderr banner so the posture is visibly dev-only, and the validator refuses `*` shapes without it. Provided for first-clone Console iteration against a `harbor dev` loop where the Console origin (Vite, `:5173`) varies during development.

type SessionsConfig

type SessionsConfig struct {
	IdleTTL       time.Duration `yaml:"idle_ttl"`
	HardCap       time.Duration `yaml:"hard_cap"`
	SweepInterval time.Duration `yaml:"sweep_interval"`
}

SessionsConfig configures the SessionRegistry's GC sweeper. Defaults match RFC §6.9: idle TTL 24h, hard cap 30 days, sweep every 15 min. Fields are not hot-reloadable in V1 (changing GC cadence at runtime would race with the sweeper goroutine).

type SkillsConfig

type SkillsConfig struct {
	Driver string `yaml:"driver,omitempty"`
	DSN    string `yaml:"dsn,omitempty" secret:"true"`
}

SkillsConfig is owned by the skills subsystem phases.

`Driver` selects a `skills.SkillStore` driver. Phase 37 ships the `"localdb"` driver only; Phase 49 (Portico) adds `"portico"`. Default `localdb`.

`DSN` is required when `Driver` is `"localdb"`. The format mirrors the StateStore + MemoryStore drivers (bare file path or `file:` URI for SQLite; `:memory:` honoured for tests). `secret:"true"` redacts the value in audit-redacted logs.

type StateConfig

type StateConfig struct {
	Driver string `yaml:"driver"`
	DSN    string `yaml:"dsn,omitempty" secret:"true"`
}

StateConfig selects the StateStore driver and its connection.

type TasksConfig

type TasksConfig struct {
	Driver               string        `yaml:"driver"`
	RetainTurnTimeout    time.Duration `yaml:"retain_turn_timeout"`
	ContinuationHopLimit int           `yaml:"continuation_hop_limit"`
}

TasksConfig configures the TaskRegistry driver and the Phase 21 backgroundtasks-config knobs. `Driver` selects the registered driver name; Phase 20 ships only `"inprocess"`.

`RetainTurnTimeout` is the maximum time the runtime engine will block a foreground turn waiting for retain-turn groups to resolve. Defaults to 5 minutes (RFC §6.8); zero or negative values are rejected by validation. Consumed by the engine wiring scheduled for Phase 60+ (runtime↔tasks integration); validated today so an operator's deployment is rejected for an invalid value even before the consumer lands.

`ContinuationHopLimit` caps the number of background-continuation hops a planner runtime may take before requiring user confirmation. Defaults to 8 (RFC §6.8); zero or negative values are rejected by validation. Consumed by the planner concretes (Phase 42+); same "validate today, consume later" pattern as `RetainTurnTimeout`.

Restart-required (no `reload:"live"` tag).

type TelemetryConfig

type TelemetryConfig struct {
	LogFormat    string `yaml:"log_format"`
	LogLevel     string `yaml:"log_level"`
	OTelEndpoint string `yaml:"otel_endpoint,omitempty"`
	ServiceName  string `yaml:"service_name"`
}

TelemetryConfig configures slog and OpenTelemetry export.

type ToolApprovalConfig

type ToolApprovalConfig struct {
	// Policy names which bundled approval policy to apply. The
	// catalog builder maps this name onto a concrete
	// `approval.ApprovalPolicy` instance. Allowed values:
	//   - "deny-all"      → `approval.AlwaysDenyPolicy`
	//   - "approve-all"   → `approval.AlwaysApprovePolicy` (dev only)
	//   - "tagged"        → `approval.TaggedPolicy` (consults
	//                       `RequireTags` below)
	// An unknown policy value fails the catalog build with a wrapped
	// error naming the offending value.
	Policy string `yaml:"policy"`
	// Reason is the operator-facing classification carried on
	// `tool.approval_requested`. Optional — the bundled policies
	// supply a sensible default.
	Reason string `yaml:"reason,omitempty"`
	// RequireTags is consulted by the `tagged` policy. An entry whose
	// `Policy: tagged` AND `RequireTags: []` is rejected — the
	// tagged policy with no tags is a no-op (configuration smell).
	RequireTags []string `yaml:"require_tags,omitempty"`
}

ToolApprovalConfig declares an approval-gate wiring for one tool. Phase 64a / D-090.

type ToolEntryConfig

type ToolEntryConfig struct {
	// Name is the catalog tool name the entry applies to. Required.
	// The catalog builder fails closed when no tool registered with
	// this name resolves at boot.
	Name string `yaml:"name"`
	// LoadingMode controls when this tool appears in the planner's
	// prompt-time catalog. "" or "always" = every turn (default).
	// "deferred" = hidden by default; the LLM discovers via meta-tools.
	// Phase 107c / D-167.
	LoadingMode string `yaml:"loading_mode,omitempty"`
	// Approval declares an approval-gate wiring for this tool. Omit
	// for tools that need no gating. When present, `Approval.Policy`
	// MUST be one of the canonical policy names; an unknown value
	// fails closed.
	Approval *ToolApprovalConfig `yaml:"approval,omitempty"`
	// OAuth declares an OAuth binding for this tool. Omit for tools
	// that need no OAuth. When present, `OAuth.Provider` MUST name a
	// configured OAuth source and `OAuth.BindingScope` MUST be one of
	// "user" / "agent" (Phase 30 D-083).
	OAuth *ToolOAuthConfig `yaml:"oauth,omitempty"`
}

ToolEntryConfig is one per-tool catalog wiring declaration. Phase 64a / D-090. The shape is intentionally small: the catalog builder looks up the registered tool by `Name`, then applies whichever of `Approval` and / or `OAuth` are populated.

Layout in YAML:

tools:
  entries:
    - name: delete_doc
      approval:
        policy: deny-all
        reason: "deletion requires human review"
    - name: github.repo.read
      oauth:
        provider: github
        binding_scope: user
    - name: write_to_prod
      approval:
        policy: tagged
        require_tags: ["sensitive", "write:prod"]
      oauth:
        provider: prod-api
        binding_scope: agent

An empty `Approval` AND `OAuth` block is rejected at validation time (an entry with no middleware to apply is a configuration typo).

Restart-required.

type ToolOAuthConfig

type ToolOAuthConfig struct {
	// Provider names the OAuth source the tool binds to. The catalog
	// builder consults the configured OAuth registry; a name that
	// resolves to no source fails the catalog build loud.
	Provider string `yaml:"provider"`
	// BindingScope is "user" or "agent" (Phase 30 / D-083). An
	// invalid value fails the catalog build with a wrapped error
	// naming the offending value.
	BindingScope string `yaml:"binding_scope"`
}

ToolOAuthConfig declares an OAuth binding for one tool. Phase 64a / D-090.

type ToolOAuthProviderConfig

type ToolOAuthProviderConfig struct {
	Name            string            `yaml:"name"`
	Driver          string            `yaml:"driver"`
	ClientIDEnv     string            `yaml:"client_id_env"`
	ClientSecretEnv string            `yaml:"client_secret_env"`
	Scopes          []string          `yaml:"scopes,omitempty"`
	AuthURL         string            `yaml:"auth_url,omitempty"`
	TokenURL        string            `yaml:"token_url,omitempty"`
	RedirectURL     string            `yaml:"redirect_url,omitempty"`
	Extra           map[string]string `yaml:"extra,omitempty"`
}

ToolOAuthProviderConfig declares one operator-configured OAuth provider (D-095, closes issue #116 and D-090's deferred construction gap). Each entry resolves to a self-registered driver in `internal/tools/auth/drivers/<name>/` via the §4.4 seam pattern. The constructed `auth.OAuthProvider` is keyed by `Name` in the catalog builder's `Deps.OAuthProviders` map; `tools.entries[].oauth.provider` references the same `Name`.

The V1 default driver is `oauth2` — generic OAuth2/PKCE Authorization Code flow. Future flow types (device-code, client-credentials, per-vendor extensions) add a new driver under `internal/tools/auth/drivers/<name>/` without changing this shape.

Credentials enter via env-var indirection (§7 rule 2 — never hardcoded, never logged). `ClientIDEnv` / `ClientSecretEnv` name the env vars; the driver resolves `os.Getenv` at construction and fails closed when either is empty.

Layout in YAML:

tools:
  oauth_token_kek_env: HARBOR_OAUTH_TOKEN_KEK
  oauth_providers:
    - name: github
      driver: oauth2
      client_id_env: GITHUB_OAUTH_CLIENT_ID
      client_secret_env: GITHUB_OAUTH_CLIENT_SECRET
      scopes: ["repo", "read:user"]
      auth_url: https://github.com/login/oauth/authorize
      token_url: https://github.com/login/oauth/access_token
      redirect_url: https://example.com/oauth/callback

Fields:

  • `Name` — operator-facing identifier (must be unique across the slice; referenced by `tools.entries[].oauth.provider`).
  • `Driver` — names a self-registered driver under `internal/tools/auth/drivers/<name>/`. Required. Unknown driver names fail validation with the registered-driver list in the error message.
  • `ClientIDEnv` / `ClientSecretEnv` — env-var names the driver reads at construction. Both required.
  • `Scopes` — requested OAuth scopes. Optional.
  • `AuthURL` / `TokenURL` — authorization-server endpoints. Used by the generic `oauth2` driver; driver-specific drivers may ignore.
  • `RedirectURL` — the redirect URI the operator hosts (the Harbor Protocol callback handler). Required for the `oauth2` driver.
  • `Extra` — driver-specific extras map. Reserved for future drivers' per-flow knobs (e.g. device-code's verification URI, vendor-specific tenant ID). Unused by the V1 `oauth2` driver.

Restart-required.

type ToolPolicyConfig added in v1.2.0

type ToolPolicyConfig struct {
	MaxAttempts   int      `yaml:"max_attempts,omitempty"`
	TimeoutMS     int      `yaml:"timeout_ms,omitempty"`
	RetryOn       []string `yaml:"retry_on,omitempty"`
	BackoffBaseMS int      `yaml:"backoff_base_ms,omitempty"`
	BackoffMult   float64  `yaml:"backoff_mult,omitempty"`
	BackoffMaxMS  int      `yaml:"backoff_max_ms,omitempty"`
}

ToolPolicyConfig is the operator-facing YAML projection of `tools.ToolPolicy` (Phase 26b). It is the single config→policy translation surface (CLAUDE.md §13): the projection helper in `policy_projection.go` is the only code that maps these fields onto a `tools.ToolPolicy`. There is no second policy definition.

Field semantics (all optional; a zero/omitted field falls through to `tools.DefaultPolicy()` per-field via `tools.ToolPolicy`'s own zero-value resolution):

  • MaxAttempts is the TOTAL number of attempts including the first (operators think in total attempts, not retries). It projects to `tools.ToolPolicy.MaxRetries = MaxAttempts - 1`. `1` means a single attempt with no retry; `0`/omitted falls through to the default 4 total attempts (3 retries).
  • TimeoutMS is the per-attempt deadline in milliseconds. `0`/ omitted falls through to the default 30000.
  • RetryOn is the retryable error-class allowlist. Each value must be a known `tools.ErrorClass` string (`transient` / `timeout` / `5xx` / `permanent`); validation rejects unknown classes. Empty/ omitted falls through to the default `[transient timeout 5xx]`.
  • BackoffBaseMS / BackoffMaxMS are the backoff base / cap in milliseconds; `0`/omitted fall through to the defaults (100 ms / 30 s). BackoffMult is the per-retry multiplier; `0`/omitted falls through to the default 2.

func (ToolPolicyConfig) ToToolPolicy added in v1.2.0

func (c ToolPolicyConfig) ToToolPolicy() (ProjectedToolPolicy, error)

ToToolPolicy projects the operator-facing `ToolPolicyConfig` onto the cycle-free `ProjectedToolPolicy` image. It is the single config→policy translation seam (CLAUDE.md §13).

Mapping:

  • `max_attempts` (TOTAL attempts incl. the first) → `MaxRetries = max_attempts - 1` when `max_attempts >= 1`; an omitted/zero `max_attempts` leaves `MaxRetries` zero so the runtime inherits the default total attempt count (per-field fall-through).
  • `timeout_ms` → `TimeoutMS` directly.
  • `backoff_base_ms` / `backoff_max_ms` → `time.Duration`.
  • `backoff_mult` → `BackoffMult` directly.
  • `retry_on` strings are validated against the error-class allowlist; an unknown value is a hard error (fail loud — no silent drop, CLAUDE.md §5).

A negative `max_attempts` or `timeout_ms` is rejected here as a belt-and-braces guard; config validation rejects them earlier with a field-scoped error.

type ToolsConfig

type ToolsConfig struct {
	HTTPManifests    []string                  `yaml:"http_manifests,omitempty"`
	MCPServers       []MCPServerConfig         `yaml:"mcp_servers,omitempty"`
	A2APeers         []A2APeerConfig           `yaml:"a2a_peers,omitempty"`
	Entries          []ToolEntryConfig         `yaml:"entries,omitempty"`
	OAuthProviders   []ToolOAuthProviderConfig `yaml:"oauth_providers,omitempty"`
	OAuthTokenKEKEnv string                    `yaml:"oauth_token_kek_env,omitempty"`

	// BuiltIn lists opt-in tools shipped in the Harbor binary that the
	// runtime should register against the catalog at boot. V1.1 ships
	// two names: `clock.now` (current UTC time) and `text.echo` (echo
	// input verbatim). The names are mirrored from
	// `internal/tools/builtin.KnownNames()` via `KnownBuiltInTools()`
	// below; a typo fails validation pre-boot rather than at runtime.
	// Empty list = no built-ins registered. Restart-required (no
	// `reload:"live"` tag).
	BuiltIn []string `yaml:"built_in,omitempty"`

	// Custom lists operator-declared custom tools whose Go shell is
	// generated by `harbor scaffold` (Phase 83o / D-154). Each entry
	// names the tool, gives a one-line description, and declares its
	// input + output shape as a flat map of `field: type` (V1.1
	// supports `string`, `integer`, `number`, `boolean`, `[]string`).
	// The scaffolder materialises one `tools/<name>.go` stub + matching
	// `tools/<name>_test.go` per entry; the rendered Go contains the
	// typed Input/Output structs and a `Handle` stub the operator fills
	// in. The runtime does NOT auto-discover these tools — the operator
	// imports the generated `tools/` package + calls `RegisterTools`
	// from the agent's bootstrap. Restart-required (no `reload:"live"`).
	Custom []CustomToolConfig `yaml:"custom,omitempty"`

	// GrantedScopes is the operator-declared list of authorization
	// scopes the dev binary's planner-facing catalog view treats as
	// granted. The runtime catalog projects only tools whose
	// `AuthScopes` are entirely contained in this set; tools that
	// require a missing scope are invisible to the planner.
	//
	// Phase 83m (Item 6, D-156): the field closes the runloop's
	// `nil /* TODO Phase 83m */` hard-code that left the catalog
	// unfiltered. Empty list = no scopes granted (the existing latent
	// default — tools that declare AuthScopes are invisible). The
	// scopes are operator-defined per their tool sources; the
	// validator only asserts that each entry is a non-empty string,
	// with no allowlist.
	//
	// Restart-required (no `reload:"live"` tag — scope changes
	// trigger a re-evaluation of every catalog descriptor).
	GrantedScopes []string `yaml:"granted_scopes,omitempty"`

	// SearchCacheDSN is the SQLite DSN backing the Phase 107c tool
	// SearchCache (FTS5 + regex fallback). Empty means the dev binary
	// uses an in-memory cache (the V1 default — discovery state lives
	// for the process lifetime). Operators can point at an on-disk
	// path (`file:./harbor-tools.db?_pragma=...`) to persist the
	// cache across reboots. Restart-required.
	SearchCacheDSN string `yaml:"search_cache_dsn,omitempty"`
}

ToolsConfig is owned by the tools subsystem phases (Phase 26+). The block is optional — operators who don't attach external tool sources omit it entirely.

`HTTPManifests` lists paths to UTCP-style YAML manifests loaded at boot by Phase 27's HTTP driver. Paths may be absolute or relative; the loader rejects empty strings and resolves each via `filepath.Clean` before reading. An empty list is valid.

`MCPServers` lists Phase 28's MCP southbound attachments. Each entry boots a `*mcp.Provider` whose discovered tools / resources / prompts are merged into the runtime catalog.

`A2APeers` lists Phase 29's A2A peers. The wire driver (`internal/distributed/drivers/a2a`) reads this slice at construction.

`Entries` lists per-tool catalog wiring declarations: operators attach approval policies and / or OAuth bindings to a tool name without writing Go wiring code. Phase 64a (D-090) — the catalog builder reads this list at boot and auto-wraps each named tool's descriptor with the declared middleware. An entry whose Name does not resolve to a registered tool fails the catalog build loud (§13 "fail loudly at boot"); an entry that names an unknown approval policy or OAuth provider also fails loud.

Restart-required (no `reload:"live"` tag): adding / removing tool providers at runtime is a Phase 91+ Protocol surface concern.

`OAuthProviders` closes D-090's deferred "OAuth provider construction" gap (issue #116 / D-095). Each entry declares one named OAuth provider resolved at boot through the `internal/tools/auth` driver registry (§4.4 seam). The V1 default driver is `oauth2` (generic OAuth2/PKCE Authorization Code flow). When any `tools.entries[].oauth.provider` references a name, that name MUST appear in `OAuthProviders` — validateTools enforces.

`OAuthTokenKEKEnv` names the env var holding the 32-byte hex-encoded key-encryption key (KEK) the OAuth token store consumes for AES-256-GCM encryption at rest (§7 + Phase 30). Required whenever `OAuthProviders` is non-empty; the dev-stack reads the env at boot and fails closed when the env value is empty or wrong-length.

Jump to

Keyboard shortcuts

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