Documentation
¶
Overview ¶
Package config defines RunLore's configuration, including provider wiring and the trigger policy that decides which incidents start an investigation.
The trigger policy is what keeps RunLore from firing on every alert: it filters incidents by environment, severity, namespace/team/label, and dedups still-firing alerts — controlling noise, relevance, and LLM cost.
Index ¶
- Constants
- func ValidateEffort(field, provider, effort string) error
- type AWSFlowCfg
- type ActionAllow
- type ActionMode
- type ActionPolicy
- type AutoPolicy
- type Catalog
- type CatalogGit
- type Cloud
- type Coalesce
- type Config
- type Curate
- type Dedup
- type Duration
- type Embeddings
- type Endpoint
- type Forge
- type GCPFlowCfg
- type GitHubApp
- type GitOps
- type GitOpsFailureTrigger
- type HubbleCfg
- type IncidentMatch
- type IncidentTrigger
- type InstantRecall
- type Investigation
- type LeaderElection
- type LogFields
- type Logging
- type LogsConfig
- type MCP
- type MCPServer
- type MatrixNotify
- type MetricsConfig
- type Model
- type ModelOverride
- type Network
- type Notify
- type Outcome
- type Pricing
- type ProgressUpdates
- type RateLimit
- type ServerConfig
- type SlackNotify
- type Telemetry
- type TriggerPolicy
Constants ¶
const ( MetricsFlavorPrometheus = "prometheus" // generic Prometheus HTTP API only MetricsFlavorVictoriaMetric = "victoriametrics" // also accepts MetricsQL (PromQL superset) )
Metrics backend flavors for config.metrics.flavor. The flavor unlocks backend-specific query guidance (VictoriaMetrics also speaks MetricsQL, a PromQL superset). Empty ⇒ auto-detect at startup (probe /api/v1/status/buildinfo), failing safe to generic Prometheus behaviour when the probe can't identify the backend — no MetricsQL claims are made unless the backend is known to be VM.
const ( NetworkHubble = "hubble" // Cilium Hubble Relay (requires the Cilium CNI) NetworkAWSVPCFlowLogs = "aws-vpc-flow-logs" // AWS VPC Flow Logs via CloudWatch Logs (any AWS VPC; CNI-agnostic) NetworkGCPFirewallLogs = "gcp-firewall-logs" // GCP Firewall Rules Logging via Cloud Logging (any GCP VPC; CNI-agnostic) )
Network provider identifiers for config.network.provider. The network signal is PLUGGABLE and assumes no particular CNI — pick the one matching your environment.
Variables ¶
This section is empty.
Functions ¶
func ValidateEffort ¶ added in v0.3.0
ValidateEffort checks an effective (provider, effort) pair against the per-provider effort vocabulary, so callers that build a ModelProvider outside config.Load (e.g. the eval model-comparison runner) share one source of truth. field names the setting in the returned error. Empty effort is always valid.
Types ¶
type AWSFlowCfg ¶
type AWSFlowCfg struct {
Region string `yaml:"region"` // AWS region (default: AWS_REGION / IMDS)
LogGroup string `yaml:"log_group"` // CloudWatch Logs group that receives the VPC Flow Logs (required)
// FlowFormat selects the VPC Flow Logs field layout. Default (empty / "v2")
// uses the standard v2 default format (14 fields). Set to "custom" and
// configure FlowFields when the log group was created with a custom field
// list; custom-format log groups silently return no results under "v2" because
// the positional field assumptions no longer hold.
FlowFormat string `yaml:"flow_format"` // "" | "v2" (default) | "custom"
// FlowFields maps flow field names to their 0-based column index in the
// space-delimited log record. Only consulted when FlowFormat is "custom".
// Required keys: srcaddr, dstaddr, srcport, dstport, protocol.
// Example for a custom format omitting the first two standard fields:
// flow_fields: {srcaddr: 1, dstaddr: 2, srcport: 3, dstport: 4, protocol: 5}
FlowFields map[string]int `yaml:"flow_fields"`
}
AWSFlowCfg configures the AWS VPC Flow Logs network provider. Auth is in-cluster identity (EKS Pod Identity / IRSA) via the AWS default credential chain.
type ActionAllow ¶
type ActionAllow struct {
ReversibleOnly bool `yaml:"reversible_only"` // never auto-apply irreversible actions
Namespaces []string `yaml:"namespaces"` // allowlist of target namespaces; empty = no executable target permitted
ProtectedNamespaces []string `yaml:"protected_namespaces"` // never an action target (added to the built-ins flux-system, kube-system)
MaxBlastRadius int `yaml:"max_blast_radius"` // cap on affected workloads
Kinds []string `yaml:"kinds"` // resource kinds that may be acted on
}
ActionAllow bounds what may be acted on, even in approve/auto modes. Irreversibility is the trip-wire for mandatory human approval.
type ActionMode ¶
type ActionMode string
ActionMode controls how far RunLore may go when acting on the cluster. Default (zero value) is read-only.
const ( ActionOff ActionMode = "off" // read-only (default): no action tools registered ActionSuggest ActionMode = "suggest" // propose a command/PR; never execute ActionApprove ActionMode = "approve" // execute only after explicit human approval ActionAuto ActionMode = "auto" // EXPERIMENTAL/frozen (FEAT-1): execute in-envelope, no click — not for prod )
Action modes, from read-only to autonomous.
type ActionPolicy ¶
type ActionPolicy struct {
Mode ActionMode `yaml:"mode"` // off | suggest | approve | auto (experimental, frozen)
Allow ActionAllow `yaml:"allow"` // envelope, enforced even in approve/auto
RequireApproval bool `yaml:"require_approval"` // force a human click for gated actions
ApprovalTokenEnv string `yaml:"approval_token_env"` // env var with a shared secret for the approval endpoints
AuditLogPath string `yaml:"audit_log_path"` // append-only, hash-chained action audit log (required for auto)
Auto AutoPolicy `yaml:"auto"` // rung-3 unattended-execution safety controls
}
ActionPolicy gates cluster-mutating actions — the upper rungs of the autonomy ladder. v1 ships ActionOff; the type exists so active tools can be added later behind a gate without re-architecting (see docs/design.md §9, "Act").
func (ActionPolicy) Enabled ¶
func (a ActionPolicy) Enabled() bool
Enabled reports whether any cluster-mutating action is permitted.
type AutoPolicy ¶
type AutoPolicy struct {
DryRun bool `yaml:"dry_run"` // log "would execute" without executing
MinConfidence float64 `yaml:"min_confidence"` // only auto-execute when the investigation is at least this confident
MaxPerWindow int `yaml:"max_per_window"` // rate limit; 0 = unlimited (not recommended)
Window Duration `yaml:"window"` // rate-limit window (default 1h)
}
AutoPolicy bounds unattended execution (mode "auto"). Even within these, auto only ever runs REVERSIBLE actions, and every decision is audited + delivered.
type Catalog ¶
type Catalog struct {
Dir string `yaml:"dir"` // OKF bundle path (mounted ConfigMap, or the git-sync mirror)
Git CatalogGit `yaml:"git"`
InstantRecall InstantRecall `yaml:"instant_recall"`
}
Catalog configures the OKF knowledge catalog read by the agent. Provide either a mounted Dir (e.g. a ConfigMap) or a Git repo to sync (which closes the read/write loop — the curator's merged PRs flow back into what the agent reads).
type CatalogGit ¶
type CatalogGit struct {
URL string `yaml:"url"` // repo to clone/pull; empty disables git-sync
Branch string `yaml:"branch"` // default "main"
Interval Duration `yaml:"interval"` // re-sync period (default 5m)
TokenEnv string `yaml:"token_env"` // env var with a read token (empty = anonymous/public)
}
CatalogGit configures periodic Git sync of the catalog into Dir.
type Cloud ¶
type Cloud struct {
Provider string `yaml:"provider"` // "" (disabled) | "aws"
Region string `yaml:"region"` // e.g. eu-west-3 (default: AWS_REGION / IMDS)
ClusterName string `yaml:"cluster_name"` // EKS cluster name, scopes nodegroup/ASG queries
}
Cloud configures the cloud context provider. Auth is in-cluster identity (EKS Pod Identity / IRSA) via the AWS SDK's default credential chain — no static keys. Empty Provider disables the cloud tools (default — cloud is opt-in).
type Coalesce ¶
type Coalesce struct {
Enabled bool `yaml:"enabled"`
Debounce Duration `yaml:"debounce"`
MaxWait Duration `yaml:"max_wait"`
MaxBatch int `yaml:"max_batch"`
Cooldown Duration `yaml:"cooldown"`
CorrelationLabels []string `yaml:"correlation_labels"` // empty ⇒ AM groupKey, else namespace+label values
}
Coalesce folds correlated incidents into one investigation.
type Config ¶
type Config struct {
GitOps GitOps `yaml:"gitops"` // engine selection (flux default | argocd)
Triggers TriggerPolicy `yaml:"triggers"`
// Sources is the per-source enablement map: a key under `sources.<name>`
// enables that source adapter, and its value is the adapter's own raw config
// (decoded lazily by each adapter's Build). Presence is enablement — e.g.
// `sources.alertmanager: {}` turns on the Alertmanager webhook source. This
// keeps adding a source from requiring a central-struct edit. The webhook
// auth token stays server-level (server.webhook_token_env).
Sources map[string]yaml.Node `yaml:"sources"`
Actions ActionPolicy `yaml:"actions"` // read-only by default; the upper rungs of the autonomy ladder
Forge Forge `yaml:"forge"` // git-forge auth (GitHub App) for diff access + curation
Curate Curate `yaml:"curate"` // Phase-2 backlog groomer settings
Model Model `yaml:"model"` // optional; when BaseURL is set, serve uses the LLM investigator
Notify Notify `yaml:"notify"` // chat delivery for findings
Catalog Catalog `yaml:"catalog"` // OKF knowledge catalog
Outcome Outcome `yaml:"outcome"` // learning-loop outcome ledger
LeaderElection LeaderElection `yaml:"leader_election"` // HA: only the leader investigates
Metrics MetricsConfig `yaml:"metrics"` // PromQL backend (VictoriaMetrics/Prometheus) for query_metrics
Logs LogsConfig `yaml:"logs"` // LogsQL backend (VictoriaLogs) for query_logs
Network Network `yaml:"network"` // network-flow data source (pluggable, CNI-agnostic); empty Provider disables it
Cloud Cloud `yaml:"cloud"` // cloud-side context (AWS); empty Provider disables it
MCP MCP `yaml:"mcp"` // external MCP servers whose tools the agent may call (opt-in)
Server ServerConfig `yaml:"server"` // HTTP ingress (webhook authentication)
Investigation Investigation `yaml:"investigation"` // coalescing + rate-limit + per-investigation token controls
Telemetry Telemetry `yaml:"telemetry"` // OpenTelemetry metrics
Logging Logging `yaml:"logging"` // structured-logging format + verbosity
}
Config is the top-level RunLore configuration (loaded from YAML).
type Curate ¶
type Curate struct {
StaleAfter Duration `yaml:"stale_after"` // close unprotected KB PRs idle longer than this; 0 disables (default 720h)
RecurrenceThreshold int `yaml:"recurrence_threshold"` // open a knowledge-gap issue after this many unresolved occurrences of a pattern; 0 ⇒ default 3
}
Curate configures the Phase-2 backlog groomer (lore curate).
type Dedup ¶
type Dedup struct {
Window Duration `yaml:"window"`
}
Dedup suppresses re-investigation of a still-firing alert within Window.
type Duration ¶
Duration is a time.Duration that unmarshals from a Go duration string ("30m").
type Embeddings ¶
type Embeddings struct {
BaseURL string `yaml:"base_url"`
Model string `yaml:"model"`
APIKeyEnv string `yaml:"api_key_env"`
}
Embeddings configures an OpenAI-compatible /embeddings endpoint (vLLM/Ollama/ OpenAI) for hybrid catalog recall — served by the same kind of endpoint as the model; keyless when APIKeyEnv is empty.
type Endpoint ¶
type Endpoint struct {
URL string `yaml:"url"`
// TokenEnv names an env var holding a bearer token for the backend. When set
// and the var is non-empty, requests carry "Authorization: Bearer <token>".
// Empty (default) ⇒ no Authorization header (unchanged, keyless behaviour).
TokenEnv string `yaml:"token_env"`
// Headers are static request headers added to every backend request — e.g. a
// tenant header for a multi-tenant VictoriaMetrics/VictoriaLogs instance
// ("X-Scope-OrgID: <tenant>"). Empty (default) ⇒ no extra headers.
Headers map[string]string `yaml:"headers"`
}
Endpoint is a backend base URL with optional auth; empty URL disables the corresponding tool. Auth follows the secrets-by-indirection convention used elsewhere (model.api_key_env, forge.*_env): the config stores the NAME of an env var, never the secret itself, and the value is read at runtime.
type Forge ¶
type Forge struct {
GitHubApp GitHubApp `yaml:"github_app"`
KBRepo string `yaml:"kb_repo"` // "owner/name" — the catalog repo for curation
BaseBranch string `yaml:"base_branch"` // PR target branch (default "main")
GitHubAPIURL string `yaml:"github_api_url"` // override for GHES/tests (default https://api.github.com)
DupScore float64 `yaml:"dup_score"` // file-time catalog BM25 dedup threshold (default 5.0)
MinConfidence float64 `yaml:"min_confidence"` // file-time quality gate: min overall confidence (default 0.75)
// SkipVerdicts lists investigation verdicts that must NOT draft a KB PR — the
// finding is still delivered to chat, but no repo artifact is created. Values are
// validated against the verdict enum (no_action|action_suggested|action_required|
// inconclusive). Empty (default) draws no distinction: every verdict is eligible,
// preserving pre-gate behaviour. Recommended production value: ["no_action"].
SkipVerdicts []string `yaml:"skip_verdicts"`
}
Forge holds git-forge authentication and the curation target repo.
type GCPFlowCfg ¶
type GCPFlowCfg struct {
Project string `yaml:"project"` // GCP project ID (default: ADC / metadata server)
}
GCPFlowCfg configures the GCP Firewall Rules Logging network provider. Auth is Workload Identity / Application Default Credentials.
type GitHubApp ¶
type GitHubApp struct {
AppID int64 `yaml:"app_id"`
InstallationID int64 `yaml:"installation_id"`
PrivateKeyRef string `yaml:"private_key_ref"` // Secret name/key (e.g. via External Secrets)
PrivateKeyEnv string `yaml:"private_key_env"` // v1: env var holding the PEM private key
}
GitHubApp holds GitHub App credentials. The private key mints 1-hour installation tokens (no long-lived PAT); it is referenced from a Secret, never inlined. Required permissions: contents:read (diff), and on the KB repo issues:write + pull_requests:write + contents:write (curation).
type GitOps ¶
type GitOps struct {
Engine string `yaml:"engine"` // "flux" (default) | "argocd"
}
GitOps selects the GitOps engine RunLore reads (what-changed + failure watch).
type GitOpsFailureTrigger ¶
type GitOpsFailureTrigger struct {
// Debounce is a pointer so an unset key (nil ⇒ 60s default, applied in
// applyDefaults) is distinguishable from an explicit `debounce: 0` (fire
// immediately). A plain Duration can't tell the two apart — both are the zero
// value — which is why `debounce: 0` used to be silently clobbered to 60s.
Debounce *Duration `yaml:"debounce"`
}
GitOpsFailureTrigger holds the GitOps-failure-driven investigation POLICY. Enablement now lives under `sources.gitops.enabled`; this struct keeps only the debounce window. Debounce delays an investigation until the failure has persisted for that window (re-checked still Ready=False), filtering reconcile-churn transients that would otherwise produce confident-but-wrong root causes. A zero Debounce fires immediately on every Ready=False (the original behavior).
func (GitOpsFailureTrigger) DebounceWindow ¶
func (g GitOpsFailureTrigger) DebounceWindow() time.Duration
DebounceWindow is the GitOps-failure debounce window. nil (unset) reads as 0 here, but applyDefaults fills an unset trigger with 60s; an explicit 0 means fire immediately on every Ready=False.
type HubbleCfg ¶
type HubbleCfg struct {
URL string `yaml:"url"` // Hubble Relay gRPC address (host:port), e.g. hubble-relay.kube-system:80
// TLS enables encrypted transport to the Hubble Relay endpoint. Default
// (false) keeps the existing plaintext/insecure behaviour so the maintainer's
// test cluster (Cilium/Hubble Relay, currently plaintext) keeps connecting
// without any config change.
TLS bool `yaml:"tls"` // false (default) = insecure/plaintext; true = TLS (credentials.NewTLS)
}
HubbleCfg configures the Cilium Hubble Relay network provider.
type IncidentMatch ¶
type IncidentMatch struct {
Severity []string `yaml:"severity"` // e.g. [critical]
Environment []string `yaml:"environment"` // e.g. [prod]
Namespaces []string `yaml:"namespaces"` // glob patterns
AlertNames []string `yaml:"alertnames"` // glob patterns
Labels map[string]string `yaml:"labels"` // arbitrary label matchers
}
IncidentMatch is a set of matchers ANDed together; empty fields match anything.
type IncidentTrigger ¶
type IncidentTrigger struct {
Match IncidentMatch `yaml:"match"` // must match to investigate
Ignore IncidentMatch `yaml:"ignore"` // excludes even if Match passes
Dedup Dedup `yaml:"dedup"`
// Debounce is the pre-investigation hold for a firing alert: after admission
// (match + dedup) RunLore waits this long and investigates only if the alert
// is still active — i.e. no matching Alertmanager `resolved` webhook arrived
// within the window. It filters self-resolving alerts (e.g. a
// KubeDaemonSetRolloutStuck during a Karpenter node-churn cycle) that would
// otherwise burn a full investigation on noise. A zero window (the default)
// disables the hold and investigates immediately, preserving today's behavior;
// it is opt-in per deployment. It composes with `coalesce` (which batches the
// survivors afterwards) and `dedup` (which still suppresses re-fires before the
// hold begins).
Debounce Duration `yaml:"debounce"`
// CancelQueuedOnResolve drops a QUEUED — accepted but not yet started —
// investigation when the matching Alertmanager `resolved` webhook arrives
// first. It extends Debounce past the hold window: without it, a fire→resolve
// sequence whose firing already passed into the investigation queue still burns
// a full paid investigation. Opt-in (default false, preserving today's
// behavior) because some teams deliberately want the post-hoc answer to "why
// did it fire?" even after self-resolution — mirroring Debounce being opt-in.
// Boundaries: an IN-FLIGHT investigation is never cancelled, and a coalesced
// multi-alert batch is not cancelled on one member's resolve (see
// investigate.Queue.CancelByFingerprint).
CancelQueuedOnResolve bool `yaml:"cancel_queued_on_resolve"`
}
IncidentTrigger holds the incident/alert MATCH policy. Enablement of the alertmanager source now lives under `sources.alertmanager`; this struct is purely the match/ignore/dedup criteria applied to admitted alerts.
func (IncidentTrigger) MatchFields ¶ added in v0.2.0
func (t IncidentTrigger) MatchFields(title, severity, environment, namespace string, labels map[string]string) bool
MatchFields reports whether an incident passes this trigger policy: matched by Match and not excluded by a non-empty Ignore. Enablement is the source's job (sources.alertmanager); matching here is purely criteria. title is the alertname.
type InstantRecall ¶
type InstantRecall struct {
Enabled bool `yaml:"enabled"`
MinScore float64 `yaml:"min_score"` // similarity floor for the top hit
MarginGap float64 `yaml:"margin_gap"` // top hit must beat the runner-up by at least this
SoloFloor float64 `yaml:"solo_floor"` // confident bar when there is only one hit (higher than MinScore)
RequireWorkloadMatch bool `yaml:"require_workload_match"` // true = exact namespace+workload (also disables scopeless matching); false = namespace-level agreement is enough
OutcomePrior float64 `yaml:"outcome_prior"` // Beta prior strength for outcome decay
OutcomeFloor float64 `yaml:"outcome_floor"` // reject a recall when the outcome factor drops below this
// Rerank adds an LLM reranking stage to the recall short-circuit: it ranks the
// top-K structurally-agreeing candidates against the incident with ONE cheap
// model call and gates the fire on the reranker's CALIBRATED match confidence
// (RerankThreshold) instead of the corpus-dependent BM25 magnitude
// (SoloFloor/MarginGap). This is the principled gate: an enriched real-corpus
// BM25 score is ~0.1–1.2 (an order of magnitude below the default SoloFloor 4.0),
// so the magnitude gate only fires where the operator hand-tuned solo_floor to
// their corpus, whereas a calibrated confidence needs no per-corpus tuning —
// measured 0/11 → 11/11 fire at default thresholds with perfect precision.
//
// Three-state (*bool): **nil (unset) ⇒ ON** whenever instant_recall is enabled,
// so it works out of the box; explicit `false` disables it (the BM25-magnitude
// gate is then used, byte-for-byte unchanged); explicit `true` is the same as
// unset. Use RerankEnabled(). Routes to model.verify (cheaper/faster) when
// configured, else the main model.
Rerank *bool `yaml:"rerank"` // nil/true ⇒ reranker ON (default); false ⇒ off (legacy BM25-magnitude gate)
RerankThreshold float64 `yaml:"rerank_threshold"` // calibrated match-confidence bar to short-circuit (default 0.7; corpus-independent)
RerankK int `yaml:"rerank_k"` // max structurally-agreeing candidates ranked in one call (default 5; bounded for cost)
RerankMinScore float64 `yaml:"rerank_min_score"` // trivial retrieval-score floor below which retrieval found nothing plausible → skip the paid call (cost guard; default 0.1)
// Hybrid switches recall to fused BM25 + embedding retrieval, gated on COSINE
// similarity instead of the BM25 score above. Requires model.embeddings to be
// configured (else recall stays BM25). EXPERIMENTAL — tune the cosine thresholds
// against the instant-recall eval before relying on it; the defaults are
// conservative placeholders, not measured values.
Hybrid bool `yaml:"hybrid"` // enable hybrid (cosine-gated) recall
HybridMinScore float64 `yaml:"hybrid_min_score"` // cosine floor for the top hit (default 0.80)
HybridMarginGap float64 `yaml:"hybrid_margin_gap"` // cosine margin over the runner-up (default 0.05)
}
InstantRecall short-circuits the investigation loop when the catalog has a high-confidence match for the symptom. Off by default; MinScore is the BM25 relevance floor (tune for your catalog).
func (InstantRecall) RerankEnabled ¶ added in v0.8.0
func (ir InstantRecall) RerankEnabled() bool
RerankEnabled reports whether the instant-recall LLM reranker should run. It is ON by default whenever instant_recall is enabled (nil ⇒ on) — the calibrated, corpus-independent gate is what makes recall fire out of the box; only an explicit `rerank: false` falls back to the legacy BM25-magnitude gate.
type Investigation ¶
type Investigation struct {
Coalesce Coalesce `yaml:"coalesce"`
RateLimit RateLimit `yaml:"rate_limit"`
MaxSteps int `yaml:"max_steps"` // 0 ⇒ loop default (20)
MaxToolOutputBytes int `yaml:"max_tool_output_bytes"` // unset/0 ⇒ bounded default (32768); -1 ⇒ unlimited
MaxTokensPerInvestigation int `yaml:"max_tokens_per_investigation"` // unset/0 ⇒ bounded default (100000); -1 ⇒ unlimited
Timeout Duration `yaml:"timeout"` // per-investigation deadline; 0 ⇒ default (10m) via applyDefaults
ToolTimeout Duration `yaml:"tool_timeout"` // per-TOOL-call timeout so one hung tool can't eat the budget; 0 ⇒ default (60s) at construction
// RecurrenceCooldown (opt-in, 0 = off) suppresses re-investigating a trigger
// whose previous investigation completed less than this long ago, concluded
// (verdict ≠ inconclusive), and has no standing 👎 feedback. Without it a
// still-firing alert re-investigates on every Alertmanager repeat_interval and
// a persistently-failing GitOps resource on every informer resync (~10m).
// Requires outcome.ledger_path (the gate reads the ledger's trigger index).
RecurrenceCooldown Duration `yaml:"recurrence_cooldown"`
// Compaction selects how mid-loop history compaction treats the tool outputs it
// elides once the estimate crosses the compaction target. "" / "elide" is the
// default: drop their bodies for short markers (lossy). "summarize" first asks a
// model (the verify-tier model when configured, else the main model) for one
// compact factual digest of the batch and keeps that in place of the markers,
// falling back to plain elision on any summarizer error/refusal/truncation.
Compaction string `yaml:"compaction"` // "" | "elide" (default) | "summarize"
// PodLogNamespaces lists extra namespaces (beyond the incident's own) that
// pod_logs may read controller/crash logs from. pod_logs streams raw pod logs
// (which carry secrets/PII) to the external LLM, so the model is constrained to
// the incident namespace plus this allowlist at the application layer — not just
// by Kubernetes RBAC. Set this to match the Helm rbac.controllerLogNamespaces
// (e.g. [flux-system]); empty means the incident namespace only.
PodLogNamespaces []string `yaml:"pod_log_namespaces"`
// ProgressUpdates opts into interim progress notifications during a long
// investigation. Off by default (zero behaviour change, zero extra model calls).
ProgressUpdates ProgressUpdates `yaml:"progress_updates"`
}
Investigation holds cost/throughput controls on the alert→investigation→LLM path.
type LeaderElection ¶
type LeaderElection struct {
Enabled bool `yaml:"enabled"`
Name string `yaml:"name"` // Lease name (default "runlore-leader")
}
LeaderElection configures high availability. When enabled, replicas elect a leader via a Lease; only the leader runs the informer watch + investigation queue and reports ready (so the Service routes webhooks to it). Run >1 replica for failover. Disabled by default (single-replica / local).
type LogFields ¶ added in v0.9.0
type LogFields struct {
// ContainerField / NamespaceField / PodField are the STREAM label names used to
// build a `{k=v}` selector (query_logs) and to derive the compact pod/container
// identity in the renderer. Defaults: kubernetes.container_name /
// kubernetes.pod_namespace / kubernetes.pod_name.
ContainerField string `yaml:"container_field"`
NamespaceField string `yaml:"namespace_field"`
PodField string `yaml:"pod_field"`
// LevelField is the severity field query_logs filters on (after unpack_json) and
// that the error-summary histogram splits by. Defaults: log.level.
LevelField string `yaml:"level_field"`
// UnpackPipe is the LogsQL pipe that promotes JSON body fields to top-level
// fields so LevelField becomes filterable. Default: unpack_json. Set to a
// different pipe (or leave empty to disable) if your logs are already flat.
UnpackPipe string `yaml:"unpack_pipe"`
}
LogFields names the collector's log-schema fields. Every value defaults (via Resolved) to EXACTLY the string the code hardcoded before this was configurable, so an unset `logs.fields` is a no-op — the maintainer's test cluster keeps working. Override only the field(s) your collector renames.
func (LogFields) Resolved ¶ added in v0.9.0
Resolved returns the field convention with every unset value filled from the shipped defaults, so callers can use the result without repeating the fallbacks. UnpackPipe is deliberately allowed to be explicitly empty (already-flat logs), so only an unset (zero) LogFields as a whole restores the default pipe — an operator who sets any field but leaves unpack_pipe empty still gets the default pipe unless they had a fully-zero struct. To keep the "any override" case simple, an empty UnpackPipe here falls back to the default; disabling it is out of scope for v1.
type Logging ¶
type Logging struct {
Format string `yaml:"format"` // "text" (default) | "json"
Level string `yaml:"level"` // "debug" | "info" (default) | "warn" | "error"
}
Logging configures the structured logger. Format selects human-readable text (default, for local CLI) or JSON (for in-cluster log aggregation). Level sets verbosity. Both are overridable at startup via RUNLORE_LOG_FORMAT / RUNLORE_LOG_LEVEL (see internal/logging).
type LogsConfig ¶ added in v0.9.0
LogsConfig is the logs backend endpoint plus the OPTIONAL collector field-naming convention. The endpoint keys (url/token_env/headers) are inlined so the existing `logs: {url: …}` shape is unchanged; Fields is a new opt-in sub-key that lets an operator whose collector labels logs differently (e.g. Loki-style `namespace` instead of `kubernetes.pod_namespace`) retarget every logs query and the renderer WITHOUT a code change. Empty Fields ⇒ the shipped VictoriaLogs/vector convention.
type MCP ¶ added in v0.3.0
type MCP struct {
Servers []MCPServer `yaml:"servers"`
}
MCP configures outbound connections to external MCP servers whose tools the investigation loop may call. Empty Servers disables it (the default — MCP is opt-in).
type MCPServer ¶ added in v0.3.0
type MCPServer struct {
Name string `yaml:"name"` // identifier; namespaces its tools as name__tool
Endpoint `yaml:",inline"`
}
MCPServer is one external MCP server reachable over streamable-HTTP.
type MatrixNotify ¶
type MatrixNotify struct {
Homeserver string `yaml:"homeserver"`
RoomID string `yaml:"room_id"`
AccessTokenEnv string `yaml:"access_token_env"` // env var holding the access token
// FeedbackReactions (opt-in, default off) records 👍/👎 reactions on RunLore's
// investigation messages into the outcome ledger, where they weigh recalled-
// entry trust exactly like Slack's feedback buttons. Unlike Slack, nothing is
// exposed: reactions arrive over the client-server /sync long-poll — an
// OUTBOUND request authenticated by the access token above. Requires the three
// notifier fields and outcome.ledger_path; Validate fails loud otherwise.
FeedbackReactions bool `yaml:"feedback_reactions"`
}
MatrixNotify configures Matrix delivery.
type MetricsConfig ¶ added in v0.9.0
type MetricsConfig struct {
Endpoint `yaml:",inline"`
// Flavor optionally pins the backend flavor instead of auto-detecting it:
// "victoriametrics" enables MetricsQL query guidance; "prometheus" (or an unknown
// value) keeps generic behaviour. Empty ⇒ probe at startup.
Flavor string `yaml:"flavor"`
}
MetricsConfig is the metrics backend endpoint plus an OPTIONAL flavor override. The endpoint keys (url/token_env/headers) are inlined so the existing `metrics: {url: …}` shape is unchanged; Flavor is a new opt-in sub-key. Empty Flavor ⇒ auto-detect (see MetricsFlavor*), which fails safe to plain Prometheus.
type Model ¶
type Model struct {
Provider string `yaml:"provider"` // "openai" (default) | "anthropic" | "gemini"
BaseURL string `yaml:"base_url"` // OpenAI: required; Anthropic/Gemini: optional (built-in default endpoint). Must be https when api_key_env is set on a public host (validated).
Model string `yaml:"model"` // model name
APIKeyEnv string `yaml:"api_key_env"` // env var holding the API key (empty = keyless)
// MaxTokens caps the model's output (generated) tokens per request. 0 = use the
// 8192 default. Streaming providers send it (Anthropic max_tokens, OpenAI
// max_tokens, Gemini generationConfig.maxOutputTokens); a too-low value truncates.
MaxTokens int `yaml:"max_tokens"`
// Effort opts into deeper model reasoning per request. Provider-specific
// vocabulary, validated at startup: anthropic low|medium|high|max (sent as
// output_config.effort), openai minimal|low|medium|high (sent as
// reasoning_effort). Empty = omitted from requests (today's behavior). Not
// supported for provider gemini (its thinkingConfig has replay semantics —
// thought signatures — the provider-agnostic history can't carry). Models that
// don't support the knob return a 400, which is classified permanent.
Effort string `yaml:"effort"`
// Thinking opts into adaptive extended thinking, sent as thinking:{type:"adaptive"}.
// Only value is "adaptive"; only supported for provider anthropic (the client
// replays the signed thinking blocks across the tool loop). Empty = omitted from
// requests (today's behavior, byte-for-byte). Validated at startup; any other value
// or any non-anthropic provider is a clear config error. Interacts with effort:
// both may be set (effort is soft guidance for how much thinking Claude does).
// Give max_tokens headroom — thinking consumes output tokens.
Thinking string `yaml:"thinking"`
// Verify optionally routes the adversarial verify pass to a cheaper/faster model;
// unset fields inherit from the parent above (so `verify: {model: <cheap>}` reuses
// the same provider/endpoint/key). Absent ⇒ verify runs on the main model.
Verify *ModelOverride `yaml:"verify"`
// Embeddings optionally configures an OpenAI-compatible /embeddings endpoint used
// for hybrid recall (instant_recall.hybrid). Unset ⇒ BM25-only recall.
Embeddings *Embeddings `yaml:"embeddings"`
// Pricing optionally sets token rates so RunLore can estimate and report a
// per-investigation cost. Unset ⇒ token totals are reported without a dollar
// figure. Rates are validated non-negative.
Pricing *Pricing `yaml:"pricing"`
}
Model configures the LLM used for investigation. Provider selects the wire protocol: "openai" (default, OpenAI-compatible: vLLM/Ollama/OpenAI) or "anthropic" (native Messages API). When unconfigured, serve uses the log-only investigator.
type ModelOverride ¶
type ModelOverride struct {
Provider string `yaml:"provider"`
BaseURL string `yaml:"base_url"`
Model string `yaml:"model"`
APIKeyEnv string `yaml:"api_key_env"`
// MaxTokens overrides the parent's effective output-token cap for the verify pass;
// 0 inherits the parent's effective value.
MaxTokens int `yaml:"max_tokens"`
// Effort overrides the parent's effort for the verify pass; empty inherits the
// parent's value (same vocabulary and validation as model.effort).
Effort string `yaml:"effort"`
// Thinking overrides the parent's thinking mode for the verify pass; empty inherits
// the parent's value (same vocabulary and validation as model.thinking). Note the
// verify pass always forces a tool_choice, so the Anthropic client drops thinking
// for that request anyway — this knob only affects any non-forced verify calls.
Thinking string `yaml:"thinking"`
// Pricing overrides the parent's token rates for the verify pass (a cheaper
// verify model has its own cost); nil inherits model.pricing.
Pricing *Pricing `yaml:"pricing"`
}
ModelOverride is a partial Model used to route the verify pass to a cheaper model; empty fields inherit from the parent Model.
type Network ¶
type Network struct {
Provider string `yaml:"provider"` // "" (disabled) | hubble | aws-vpc-flow-logs | gcp-firewall-logs
Hubble HubbleCfg `yaml:"hubble"` // when provider=hubble
AWS AWSFlowCfg `yaml:"aws"` // when provider=aws-vpc-flow-logs
GCP GCPFlowCfg `yaml:"gcp"` // when provider=gcp-firewall-logs
// URL keeps the pre-pluggable `network: {url: ...}` shape working: a bare url with
// no provider is treated as Hubble (with a deprecation warning at wiring time).
URL string `yaml:"url"`
}
Network configures the network-flow data source backing the network_drops tool. The signal is pluggable and CNI-agnostic: RunLore does NOT assume Cilium (or any particular CNI). Empty Provider disables the tool (the default — network is opt-in).
type Notify ¶
type Notify struct {
Slack SlackNotify `yaml:"slack"`
Matrix MatrixNotify `yaml:"matrix"`
Extra map[string]yaml.Node `yaml:",inline"` // notify.<name> blocks for registered (non-built-in) notifiers
}
Notify configures where investigation findings are delivered.
type Outcome ¶
type Outcome struct {
LedgerPath string `yaml:"ledger_path"` // append-only JSONL path (e.g. the git-sync mirror PV); empty disables
// MaxEvents bounds the JSONL before it is compacted on load (startup / leadership
// Reload): older paired events are folded into a checkpoint record so the file stops
// growing forever. A pointer for a three-state knob: nil (key absent) ⇒ a generous
// default (outcome.DefaultMaxEvents); an explicit 0 ⇒ compaction disabled.
MaxEvents *int `yaml:"max_events"`
}
Outcome configures the learning-loop outcome ledger.
type Pricing ¶ added in v0.3.0
type Pricing struct {
InputUSDPerMTok float64 `yaml:"input_usd_per_mtok"`
OutputUSDPerMTok float64 `yaml:"output_usd_per_mtok"`
CachedInputUSDPerMTok float64 `yaml:"cached_input_usd_per_mtok"`
}
Pricing sets model token rates in USD per MILLION tokens, used to estimate a per-investigation cost. All rates are optional and default to 0; a rate must be non-negative.
type ProgressUpdates ¶ added in v0.3.0
type ProgressUpdates struct {
Enabled bool `yaml:"enabled"`
EverySteps int `yaml:"every_steps"` // emit a ping every N steps; 0 ⇒ default 5 (applyDefaults). Must be > 0 when enabled.
}
ProgressUpdates configures opt-in interim progress notifications: a long investigation (up to 20 steps) is otherwise silent until the final message. Off by default. When enabled, the loop emits one ping every EverySteps steps to any notifier that supports it (Slack first); a ping is best-effort and never fails the investigation.
type RateLimit ¶
type RateLimit struct {
MaxPerWindow int `yaml:"max_per_window"` // 0 ⇒ unlimited
Window Duration `yaml:"window"`
MaxRequeues int `yaml:"max_requeues"` // drop a key after this many backoff requeues
}
RateLimit caps investigation starts per sliding window.
type ServerConfig ¶
type ServerConfig struct {
// WebhookTokenEnv names an env var holding a shared secret required on
// POST /webhook/alertmanager as "Authorization: Bearer <token>". Empty leaves
// the webhook unauthenticated (rejected by Validate when actions.mode=auto).
WebhookTokenEnv string `yaml:"webhook_token_env"`
}
ServerConfig configures the HTTP ingress.
type SlackNotify ¶
type SlackNotify struct {
WebhookURLEnv string `yaml:"webhook_url_env"` // env var holding the incoming-webhook URL
BotTokenEnv string `yaml:"bot_token_env"` // env var holding a bot token (xoxb-…) for chat.postMessage
Channel string `yaml:"channel"` // channel ID or name to post to (required with bot_token_env)
SigningSecretEnv string `yaml:"signing_secret_env"` // env var with the Slack signing secret (verifies button clicks)
ApproverIDs []string `yaml:"approver_ids"` // Slack user IDs allowed to approve actions (empty = no Slack approvals)
// FeedbackButtons (opt-in, default off) renders 👍/👎 buttons on investigation
// messages; clicks are recorded in the outcome ledger and weigh the recalled
// entry's trust like resolve signals do. Requires exposing POST
// /slack/interactions to Slack (an interactivity Request URL — the same
// endpoint approve-mode buttons use) plus signing_secret_env and
// outcome.ledger_path; Validate fails loud when either is missing.
FeedbackButtons bool `yaml:"feedback_buttons"`
}
SlackNotify configures Slack delivery and (for rung-2 actions) interactive approve/reject buttons. Delivery uses either an incoming webhook (WebhookURLEnv) or a bot token posting to a channel via chat.postMessage (BotTokenEnv+Channel); if both are set, the bot token wins.
type Telemetry ¶
type Telemetry struct {
MetricsEnabled bool `yaml:"metrics_enabled"` // serve OTel metrics on GET /metrics (Prometheus exposition)
OTLPEndpoint string `yaml:"otlp_endpoint"` // optional OTLP push (phase-2); empty ⇒ scrape-only
}
Telemetry configures OpenTelemetry metrics export.
type TriggerPolicy ¶
type TriggerPolicy struct {
Incidents IncidentTrigger `yaml:"incidents"` // primary trigger
GitOpsFailures GitOpsFailureTrigger `yaml:"gitops_failures"` // secondary trigger
}
TriggerPolicy decides what RunLore reacts to.