Documentation
¶
Overview ¶
Package investigate provides types and helpers for correlating Prometheus metrics, Jaeger traces, and code symbols into a ranked list of root-cause hypotheses for the debug_investigate MCP tool.
Pure package: no HTTP, no database, no external state. Ranking is delegated to go-kit/rerank (LinearMinMax fusion). The MCP tool layer (cmd/go-code/tool_debug_investigate.go) wires it to live data sources and may inject LLM-derived Confidence labels before passing through RankHypotheses.
internal/investigate/fleet_report.go
internal/investigate/lifecycle.go
internal/investigate/prompt.go
Index ¶
- Constants
- func BuildSystemPrompt(c PromptContext) string
- func OperationToFuncName(op string) string
- type AlertViolation
- type ConfidenceLevel
- type Diagnostics
- type FleetDiffRow
- type FleetReport
- type FleetSiblingDriftRow
- type FleetSiblingVariant
- type FleetTargetRow
- type HistoricalIncident
- type Hypothesis
- type ImpactInfo
- type InvestigationResult
- type InvestigationStore
- func (s *InvestigationStore) Fail(service string, start, end time.Time, repo string, errMsg string)
- func (s *InvestigationStore) Finish(service string, start, end time.Time, repo string, res *InvestigationResult)
- func (s *InvestigationStore) Get(service string, start, end time.Time, repo string) (*State, bool)
- func (s *InvestigationStore) Start(service string, start, end time.Time, repo string) (*State, bool)
- type LogExcerpt
- type MetricSpike
- type NextCheck
- type OperationInfo
- type PromptContext
- type RecentChange
- type State
- type Status
- type SymbolBodyInfo
- type TimeRange
Constants ¶
const ( ConfidenceLow = score.ConfidenceLow ConfidenceMedium = score.ConfidenceMedium ConfidenceHigh = score.ConfidenceHigh )
Re-exported confidence labels — identical to the score package consts.
const ( BlastRadiusNone = "none" BlastRadiusLow = "low" BlastRadiusMedium = "medium" BlastRadiusHigh = "high" )
BlastRadius enum values for ImpactInfo.BlastRadius.
const ( HypothesisSourceSpan = "span" HypothesisSourceHintMatch = "hint_match" HypothesisSourceAlert = "alert" // HypothesisSourceUpstream marks a hypothesis generated by the upstream // callgraph walk (Sprint B2). These hypotheses are callers of a Tier-3 // span-attributed hypothesis and represent root-cause candidates upstream // of the observed metric attribution site. HypothesisSourceUpstream = "upstream_caller" // HypothesisSourceDownstream marks a hypothesis generated by the downstream // callgraph walk (Sprint B4). These hypotheses are callees of the top-1 // span-attributed hypothesis and represent root-cause candidates downstream // of the observed metric attribution site (e.g. an event-loop dispatcher // that calls the handler that actually contains the bug). HypothesisSourceDownstream = "downstream_callee" )
Source values for Hypothesis.Source (γ.C.3).
Variables ¶
This section is empty.
Functions ¶
func BuildSystemPrompt ¶
func BuildSystemPrompt(c PromptContext) string
BuildSystemPrompt assembles the LLM correlation prompt with hard constraints against hallucination. Layout: role + ground truth + reasoning rules + output schema.
func OperationToFuncName ¶
OperationToFuncName extracts a Go-friendly function name from a Jaeger span operation name. Handles three shapes:
- gRPC: "/pkg.Service/Method" → "Method"
- HTTP: "GET /api/v1/users" → "users" (last non-empty path segment)
- Plain: "ProcessMessage" or "(*Server).Handle" → "ProcessMessage" / "Handle"
Returns empty string if no meaningful identifier can be extracted. The output is the symbol name to feed into compound.FindSymbol — best-effort, not guaranteed to match an existing function.
Types ¶
type AlertViolation ¶
type AlertViolation struct {
AlertName string `json:"alert_name"`
Severity string `json:"severity"`
Service string `json:"service"`
Summary string `json:"summary"`
Description string `json:"description,omitempty"`
Runbook string `json:"runbook,omitempty"`
ActiveAt string `json:"active_at,omitempty"`
}
AlertViolation captures a firing Prometheus alert that matches the investigated service. These represent constant-state invariant violations that Phase 4 spike detection misses because there is no delta — the ratio never changes, it is just always wrong.
type ConfidenceLevel ¶
type ConfidenceLevel = score.ConfidenceLevel
ConfidenceLevel is re-exported from go-kit/score so callers of the investigate package don't need to import score directly. The underlying type is identical — investigate.ConfidenceLevel == score.ConfidenceLevel.
func ConfidenceFromScore ¶
func ConfidenceFromScore(s float64) ConfidenceLevel
ConfidenceFromScore is re-exported as a thin wrapper around score.ConfidenceFromScore. Defaults: <0.2 → low, <0.7 → medium, ≥0.7 → high.
type Diagnostics ¶
type Diagnostics struct {
MetricsQueried int `json:"metrics_queried"`
TracesFetched int `json:"traces_fetched"`
SpansAnalyzed int `json:"spans_analyzed"`
SymbolsTouched int `json:"symbols_touched"`
AlertsQueried int `json:"alerts_queried,omitempty"`
LogsFetched int `json:"logs_fetched,omitempty"`
HypothesesDroppedAsDead int `json:"hypotheses_dropped_as_dead,omitempty"`
LearningsPersisted bool `json:"learnings_persisted,omitempty"`
LLMCacheHit bool `json:"llm_cache_hit,omitempty"`
LLMSkippedReason string `json:"llm_skipped_reason,omitempty"`
Warnings []string `json:"warnings,omitempty"`
}
Diagnostics records counters from the investigation run for transparency.
type FleetDiffRow ¶
type FleetDiffRow struct {
Image string `json:"image"`
Status string `json:"status"` // "Match" / "TagDrift" / ... (from fleet.DiffStatus)
PinnedTag string `json:"pinned_tag,omitempty"`
RuntimeTag string `json:"runtime_tag,omitempty"`
PinnedDigest string `json:"pinned_digest,omitempty"`
RuntimeDigest string `json:"runtime_digest,omitempty"`
Source string `json:"source,omitempty"` // pinned source file
Container string `json:"container,omitempty"` // runtime container name
Service string `json:"service,omitempty"` // compose service (either side)
State string `json:"state,omitempty"` // runtime state
StartedAt time.Time `json:"started_at,omitempty"`
Explanation string `json:"explanation,omitempty"`
Unresolved string `json:"unresolved,omitempty"` // pinned-side parse caveat
ChangelogSummary string `json:"changelog_summary,omitempty"` // first commit subject from upstream changelog, if available
}
type FleetReport ¶
type FleetReport struct {
// Single-host fields (backward compat; also populated in multi-host).
Target string `json:"target"`
Diffs []FleetDiffRow `json:"diffs"`
Error string `json:"error,omitempty"`
Summary string `json:"summary,omitempty"` // top-20-ish line for LLM
// Multi-host fields (populated when ≥2 hosts probed).
Targets []FleetTargetRow `json:"targets,omitempty"`
SiblingDrifts []FleetSiblingDriftRow `json:"sibling_drifts,omitempty"`
}
FleetReport carries the output of runFleetVersionsPhase for an investigation. Shape mirrors cmd/go-code's FleetVersionsOutput but lives in this package to keep internal/fleet from depending on cmd/.
Note: FleetReport does NOT directly reference internal/fleet types — fields are flattened to plain strings/ints so this package stays import-free of /fleet. Translation happens in runFleetVersionsPhase.
Multi-host layout: Targets holds one FleetTargetRow per probed host; SiblingDrifts carries cross-host drift rows (populated when ≥2 hosts). Single-host layout (backward compat): Target/Diffs/Error/Summary fields are also populated so existing callers continue to work.
type FleetSiblingDriftRow ¶
type FleetSiblingDriftRow struct {
Image string `json:"image"`
Variants []FleetSiblingVariant `json:"variants"`
}
FleetSiblingDriftRow is the flattened mirror of fleet.SiblingDriftRow. Lives here so internal/investigate stays import-free of internal/fleet.
type FleetSiblingVariant ¶
type FleetSiblingVariant struct {
Target string `json:"target"`
Tag string `json:"tag"`
Digest string `json:"digest,omitempty"`
Container string `json:"container,omitempty"`
State string `json:"state,omitempty"`
}
FleetSiblingVariant is one host's view of a drifting image.
type FleetTargetRow ¶
type FleetTargetRow struct {
Target string `json:"target"`
Diffs []FleetDiffRow `json:"diffs"`
Error string `json:"error,omitempty"`
}
FleetTargetRow is one host's result in a multi-host FleetReport.
type HistoricalIncident ¶
type HistoricalIncident struct {
Repo string `json:"repo"`
Symbol string `json:"symbol"`
RiskLevel string `json:"risk_level,omitempty"`
Flag string `json:"flag,omitempty"`
Note string `json:"note,omitempty"`
}
HistoricalIncident is a past investigation record retrieved from the learnings store.
type Hypothesis ¶
type Hypothesis struct {
Subject string `json:"subject"`
File string `json:"file,omitempty"`
Line int `json:"line,omitempty"`
// EndLine is the last line of the function body (1-based, inclusive).
// Populated from parser.Symbol.EndLine in Tier-3 callgraph path.
// For Tier-1 (OTEL code.* tags) it equals Line (single attribution point).
EndLine int `json:"end_line,omitempty"`
SpanCount int `json:"span_count"`
AnomalyScore float64 `json:"anomaly_score"`
// Confidence is a bucketed label (low/medium/high). Populated by
// RankHypotheses ONLY if caller left it empty — caller-set values
// (typically by the LLM correlate step which weighs evidence holistically)
// are preserved. To force the heuristic, leave this zero on input.
Confidence ConfidenceLevel `json:"confidence"`
Impact *ImpactInfo `json:"impact,omitempty"`
SymbolBody *SymbolBodyInfo `json:"symbol_body,omitempty"`
EvidenceLinks []string `json:"evidence_links,omitempty"`
NextChecks []NextCheck `json:"next_checks,omitempty"`
// Source tracks the origin of this hypothesis: span | hint_match | alert | "" (backwards compat).
Source string `json:"source,omitempty"`
// FusedScore is the multi-signal combined score (Phase γ.D). Set after
// all enrichment phases. Sortable. Higher = more likely root cause.
FusedScore float64 `json:"fused_score,omitempty"`
// SignalBreakdown shows individual normalized signal values that fed
// into FusedScore. Useful for operator triage and debugging the rank.
SignalBreakdown map[string]float64 `json:"signal_breakdown,omitempty"`
// RecentChange embeds a recent git diff for the hypothesis file (Phase γ.D).
// Set for top-1 hypothesis only when a repo path is available.
RecentChange *RecentChange `json:"recent_change,omitempty"`
// BodySource is the raw source text of the function body (Sprint B1).
// Populated for top-3 hypotheses after FusionRank. Enables LLM to
// reason about the code rather than just its metadata.
// Empty when file read fails or File is empty (no symbol resolved).
BodySource string `json:"body_source,omitempty"`
}
Hypothesis is one candidate root-cause site.
func RankHypotheses ¶
func RankHypotheses(h []Hypothesis) []Hypothesis
RankHypotheses returns a copy of h sorted by fused score (descending). Fusion: rerank.LinearMinMax with equal weights on SpanCount and AnomalyScore signals. Both signals are MinMax-normalised to [0,1] within the input set, summed (weights 1:1), resulting score is in [0,2].
Confidence policy: caller-set values are PRESERVED; empty Confidence is filled via ConfidenceFromScore on the fused score. The LLM correlate step (Task 13) populates Confidence based on holistic evidence and survives.
Stable: equal scores preserve input order (rerank.LinearMinMax guarantees).
Empty input → nil. Single-element input → that element with Confidence filled from heuristic on fused=0 (degenerate; LLM step typically produces meaningful labels in real flows).
type ImpactInfo ¶
type ImpactInfo struct {
DirectCallers int `json:"direct_callers"`
TotalAffected int `json:"total_affected"`
BlastRadius string `json:"blast_radius,omitempty"` // BlastRadiusNone | BlastRadiusLow | BlastRadiusMedium | BlastRadiusHigh
RiskScore float64 `json:"risk_score,omitempty"`
}
ImpactInfo captures blast-radius data from impact.Analyze for a hypothesis.
type InvestigationResult ¶
type InvestigationResult struct {
Service string `json:"service"`
Range TimeRange `json:"range"`
StartedAt time.Time `json:"started_at"`
FinishedAt time.Time `json:"finished_at"`
Hypotheses []Hypothesis `json:"hypotheses"`
LLMSummary string `json:"llm_summary,omitempty"`
MetricSpikes []MetricSpike `json:"metric_spikes,omitempty"`
AlertViolations []AlertViolation `json:"alert_violations,omitempty"`
HintKind string `json:"hint_kind,omitempty"`
LogExcerpts []LogExcerpt `json:"log_excerpts,omitempty"`
HistoricalIncidents []HistoricalIncident `json:"historical_incidents,omitempty"`
Diagnostics Diagnostics `json:"diagnostics"`
// RuntimeVersions, when non-nil, carries the Phase 7 deployed-image
// diff against pinned source. Nil = phase skipped or host not provided.
RuntimeVersions *FleetReport `json:"runtime_versions,omitempty"`
}
InvestigationResult is the final tool output.
Time fields:
- StartedAt / FinishedAt: wall-clock — when this investigation started and finished executing.
- Range: the *data* window the investigation analysed (Prometheus query range, Jaeger trace search range). Independent of wall-clock.
type InvestigationStore ¶
type InvestigationStore struct {
// contains filtered or unexported fields
}
InvestigationStore deduplicates concurrent debug_investigate calls and stores results for polling. Key: service + range + repo. Thread-safe.
func NewInvestigationStore ¶
func NewInvestigationStore() *InvestigationStore
NewInvestigationStore builds an empty store.
func (*InvestigationStore) Fail ¶
Fail marks the investigation failed with an error message. Last writer wins when called concurrently.
func (*InvestigationStore) Finish ¶
func (s *InvestigationStore) Finish(service string, start, end time.Time, repo string, res *InvestigationResult)
Finish marks the investigation done and stores the result. Last writer wins when called concurrently.
func (*InvestigationStore) Get ¶
Get returns the State for (service, range, repo) or (nil, false) if absent.
func (*InvestigationStore) Start ¶
func (s *InvestigationStore) Start(service string, start, end time.Time, repo string) (*State, bool)
Start either creates a new running investigation or returns the existing one. fresh=true on first call for this (service, range, repo), false if already running or completed (dedup).
type LogExcerpt ¶
type LogExcerpt struct {
Ts string `json:"ts,omitempty"`
Level string `json:"level,omitempty"`
Msg string `json:"msg,omitempty"`
Raw string `json:"raw,omitempty"`
}
LogExcerpt is a single log line from the dozor sidecar, attached to the investigation result when dozor is configured.
type MetricSpike ¶
type MetricSpike struct {
Kind string `json:"kind"` // failure | latency | saturation
MetricName string `json:"metric_name"` // full Prometheus metric name
Labels string `json:"labels"` // label-set rendered for human reading
Ratio float64 `json:"ratio"` // window_max / baseline_max
Score float64 `json:"score"` // bucketed anomaly score 0..1
}
MetricSpike captures a single metric (failure / latency / saturation) showing anomaly above baseline.
type NextCheck ¶
type NextCheck struct {
Tool string `json:"tool"` // Tool must be non-empty (the MCP tool name to invoke). e.g. "understand", "code_health"
Args map[string]string `json:"args,omitempty"` // named arguments for the tool
}
NextCheck is a structured follow-up recommendation for the operator or agent. It names the tool to call and the arguments to pass.
type OperationInfo ¶
type OperationInfo struct {
Operation string // span operation name (key)
Count int // CUMULATIVE — total spans for this op in window
HTTPRoute string // FIRST-SEEN — http.route tag (axum MatchedPath)
HTTPMethod string // FIRST-SEEN — http.method tag
CodeFilepath string // FIRST-SEEN — code.filepath OR code.file.path (absolute path inside container)
CodeLineno int // FIRST-SEEN — code.lineno OR code.line.number
CodeNamespace string // FIRST-SEEN — code.namespace OR code.module.name (e.g. Rust module path)
CodeFunction string // FIRST-SEEN — code.function OR code.function.name (e.g. Go method like (*T).Method)
}
OperationInfo aggregates per-operation context from a service's spans. Used by Phase 3 (runSymbolsPhase) to feed symbol resolution with both span counts and OTEL semantic-convention attributes. When code.* tags are present, Phase 3 resolves directly to file:line without needing a callgraph.
type PromptContext ¶
type PromptContext struct {
Service string
AvailableMetrics []string // truncated to first 80 if longer
AvailableServices []string
OperationsSeen []string // top operations from traces
FiringAlerts []string // alert names currently firing for this service
}
PromptContext is the ground-truth payload injected into the LLM system prompt. Pattern source: Zagalin Grafana plugin — list real metric names and trace services so the LLM cannot hallucinate names.
type RecentChange ¶
type RecentChange struct {
File string `json:"file"`
Since string `json:"since"` // e.g. "2026-04-09" — 30 days ago
Diff string `json:"diff,omitempty"` // unified diff, capped at maxLines
}
RecentChange captures a recent git diff for a hypothesis file.
type State ¶
type State struct {
// contains filtered or unexported fields
}
State is one investigation's transient state. All field access goes through Lock/RLock — direct field reads from outside this package are not safe.
func (*State) Result ¶
func (s *State) Result() *InvestigationResult
Result returns the stored result (only valid when Status() == StatusDone).
type Status ¶
type Status string
Status represents the lifecycle of an investigation. Only the three exported consts (StatusRunning/Done/Failed) are valid; callers must not construct Status values directly.
type SymbolBodyInfo ¶
type SymbolBodyInfo struct {
ErrorExits int `json:"error_exits"`
HasDeferCleanup bool `json:"has_defer_cleanup,omitempty"`
HasTODO bool `json:"has_todo,omitempty"`
}
SymbolBodyInfo captures structural analysis from compound.AnalyzeBody for a hypothesis.