Documentation
¶
Index ¶
- Constants
- Variables
- func InitOnCallWorkflow(awsClient *ssmincidents.Client, redisClient redis.UniversalClient)
- func IsOnCallWorkflowInitialized() bool
- func SetOnCallWorkflow(w *OnCallWorkflow)
- type AIAgent
- type AICallResult
- type AIFinding
- type AITask
- type AITaskKind
- type AgentResult
- type AgentVerdict
- type Alert
- type AlertProvider
- type AlertResult
- type AnalyzeIncidentSnapshot
- type AnalyzeTask
- type AnalyzeTool
- type Attachment
- type AttachmentSender
- type Bucket
- type DetectTask
- type Detector
- type Embedder
- type EvidenceItem
- type NotableIncident
- type Observation
- type OnCallProvider
- type OnCallWorkflow
- type QueueListener
- type Readiness
- type ReportImage
- type ReportModel
- type ReportRenderer
- type RootCauseHypothesis
- type Scrubber
- type Signal
- type SignalDetector
- type SignalLearner
- type SignalSource
- type SourceRewinder
- type TextSender
- type ToolCallTrace
- type ToolResult
- type TypedVerdict
Constants ¶
const ( // FieldService is the Signal.Fields key holding the discovered service // name (string). Empty when the source did not stamp one. FieldService = "service" // FieldSignal is the Signal.Fields key holding the logical signal name // (string) — e.g. a metric golden-signal or trace operation label. FieldSignal = "signal" )
Canonical Signal.Fields keys shared across the agent pipeline. They name the two attributes every signal type can carry regardless of source: the discovered service and the logical signal name (a metric's golden-signal, a trace operation, or a log template label). They live in OSS so any consumer — the worker's learn-exclusion chokepoint, an enterprise metric/trace brain, a standing data source — keys off ONE definition. Enterprise packages (pkg/intel, pkg/datasource) keep their own copies to stay decoupled; a cross-package drift test pins them equal.
Variables ¶
var CreateOnCallProvider func(cfg *config.Config, awsClient *ssmincidents.Client) (OnCallProvider, error)
Function that will be implemented in the common package to avoid circular imports
Functions ¶
func InitOnCallWorkflow ¶
func InitOnCallWorkflow(awsClient *ssmincidents.Client, redisClient redis.UniversalClient)
InitOnCallWorkflow initializes the global singleton instance This is called once from main.go with the Redis client and AWS client
func IsOnCallWorkflowInitialized ¶ added in v1.4.4
func IsOnCallWorkflowInitialized() bool
IsOnCallWorkflowInitialized reports whether the global on-call workflow singleton has been initialized via InitOnCallWorkflow. Callers should check this before GetOnCallWorkflow so an uninitialized singleton is skipped instead of panicking the process.
func SetOnCallWorkflow ¶ added in v1.4.13
func SetOnCallWorkflow(w *OnCallWorkflow)
SetOnCallWorkflow installs (or, with a nil argument, clears) the global on-call workflow singleton directly, bypassing the one-shot boot path in InitOnCallWorkflow. It lets callers wire a pre-built workflow after startup (or reset it) without the sync.Once guard.
Types ¶
type AIAgent ¶ added in v1.4.3
type AIAgent interface {
Name() string
Kind() AITaskKind
Run(ctx context.Context, task AITask) (*AICallResult, error)
}
AIAgent is one concrete model + prompt + (optional) tool wiring, dedicated to a single AITaskKind. Implementations live under pkg/agent/ai (e.g. detect, analyze).
Run is expected to be self-contained: it owns the model call, but the router handles cache / rate-limit / persistence around it.
type AICallResult ¶ added in v1.4.0
type AICallResult struct {
Finding *AIFinding
UserPrompt string
RawResponse string
DurationMs int64
Model string
// ToolCalls is populated by tool-using agents (analyze). It is
// nil for tool-free agents (detect).
ToolCalls []ToolCallTrace
}
AICallResult bundles the parsed finding with the inputs and outputs of the underlying model call. The trace fields (UserPrompt, RawResponse, DurationMs, Model) are persisted into the detect log so operators can audit what was sent to the model and what came back.
SystemPrompt is intentionally omitted — it is a constant per build and would bloat every record. Operators can fetch the current assembled system prompt via the agent admin API.
type AIFinding ¶ added in v1.3.9
type AIFinding struct {
Title string
Summary string
Severity string // critical|high|medium|low
Category string // e.g. "database", "auth", "deploy"
Confidence float64 // 0..1
Suggestions []string
SampleIDs []string // signal IDs / pattern IDs for traceability
// Analyze-mode fields. All optional; omitempty so detect-mode
// payloads stay byte-compatible with the detect-only shape.
RootCauseHypotheses []RootCauseHypothesis `json:"root_cause_hypotheses,omitempty"`
Evidence []EvidenceItem `json:"evidence,omitempty"`
RelatedPatternIDs []string `json:"related_pattern_ids,omitempty"`
NextSteps []string `json:"next_steps,omitempty"`
}
AIFinding is the structured response returned by an AISRE for an unknown / spiking pattern. Used by detect mode.
Detect mode populates Title, Summary, Severity, Category, Confidence, Suggestions, SampleIDs. The lower block (RootCauseHypotheses, Evidence, RelatedPatternIDs, NextSteps) is populated by analyze mode only; detect leaves them empty and they marshal as omitempty.
type AITask ¶ added in v1.4.3
type AITask interface {
Kind() AITaskKind
CacheKey() string
}
AITask is the input to an AIAgent.Run call. Each concrete task type carries the inputs needed by its kind and exposes a CacheKey() the router uses for memoisation. An empty CacheKey disables caching for that call.
type AITaskKind ¶ added in v1.4.3
type AITaskKind string
AITaskKind identifies what an AIAgent is being asked to do.
The dispatcher uses the kind to pick the right agent, cache, and rate limiter. New kinds are added by extending this enum and the router's wiring.
const ( // AITaskDetect is a cheap, tool-free, single-call classification of // an unknown or spiking log pattern. The output is an AIFinding // emitted as an incident through services.CreateIncidentFromFinding. AITaskDetect AITaskKind = "detect" // AITaskAnalyze is an operator-triggered, tool-using investigation // of a single incident. The output is an AIFinding persisted to the // analyses storage blob. Analyze NEVER fans out to notification // channels. AITaskAnalyze AITaskKind = "analyze" )
type AgentResult ¶ added in v1.3.9
type AgentResult struct {
Verdict AgentVerdict
PatternID string // empty when VerdictUnknown and pattern not yet stored
Template string // human-readable pattern template ("Failed to ... at <*>:<*>")
SampleSignals []Signal // representative signals (capped, post-redaction)
Frequency int // matches in the current tick / window
Baseline float64 // EWMA baseline for the pattern (0 when unknown)
// RuleSeverity is the strongest operator-declared severity carried by the
// grouped signals (e.g. an anomaly rule's `severity: critical`). Empty for
// auto-discovered signals with no declared severity. It acts as a floor:
// the AI may escalate but must not silently demote below it.
RuleSeverity string
}
AgentResult is the output of a Detector for one batch of signals that share the same fingerprint.
type AgentVerdict ¶ added in v1.3.9
type AgentVerdict int
AgentVerdict is the classification a Detector pipeline assigns to a batch of signals that share a fingerprint.
const ( // VerdictKnownPattern means the pattern is in the catalog and current // frequency is within baseline — suppress (no AI, no incident). VerdictKnownPattern AgentVerdict = iota // VerdictUnknown means the pattern was never seen during training — // forward to AI for analysis. VerdictUnknown // VerdictSpike means the pattern is known but frequency exceeds the // baseline by more than the configured threshold — forward to AI. VerdictSpike )
func (AgentVerdict) String ¶ added in v1.3.9
func (v AgentVerdict) String() string
String renders an AgentVerdict for logging.
type Alert ¶
type Alert struct {
// contains filtered or unexported fields
}
func NewAlert ¶
func NewAlert(providers ...AlertProvider) *Alert
func (*Alert) SendAlert ¶
SendAlert is the legacy "first error wins" API. New code should use SendAllAlerts so a flaky Slack does not silently mute Telegram and Email. Kept here so external callers (and existing tests) compile without churn; internally we now delegate to SendAllAlerts and surface only the joined error.
func (*Alert) SendAllAlerts ¶ added in v1.4.0
func (a *Alert) SendAllAlerts(incident *m.Incident) AlertResult
SendAllAlerts tries every configured provider regardless of earlier failures. A single broken channel never prevents the others from firing — this is the multi-channel promise. The returned Err is non-nil iff at least one provider failed.
type AlertProvider ¶
AlertProvider is implemented by every notification channel (Slack, Telegram, MS Teams, Lark, Viber, Email, …). Name() identifies the channel in failure reports so operators can tell which one failed without parsing the wrapped error.
type AlertResult ¶ added in v1.4.0
type AlertResult struct {
Succeeded []string
Failed map[string]error
Err error // joined errors from Failed, nil when none
}
AlertResult captures the outcome of SendAllAlerts: which providers succeeded, which failed (with their error), and a joined error containing all failures (nil when every provider succeeded). The list of Succeeded channel names is what the incident persistence layer should store as ChannelsNotified — it is the truth about what actually reached its destination, not a list of what was enabled in config.
type AnalyzeIncidentSnapshot ¶ added in v1.4.3
type AnalyzeIncidentSnapshot struct {
IncidentID string `json:"incident_id"`
Title string `json:"title,omitempty"`
Service string `json:"service,omitempty"`
Source string `json:"source,omitempty"`
Severity string `json:"severity,omitempty"`
Resolved bool `json:"resolved,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
AckedAt *time.Time `json:"acked_at,omitempty"`
ResolvedAt *time.Time `json:"resolved_at,omitempty"`
// Content is the alert payload (the same map persisted on the
// incident record). Operators see this verbatim in the UI.
Content map[string]any `json:"content,omitempty"`
// RequestedBy identifies the operator that triggered the analysis
// (gateway-authenticated, so today this is just a free-form label
// like "admin"). Stored on the AnalysisRecord for audit.
RequestedBy string `json:"requested_by,omitempty"`
}
AnalyzeIncidentSnapshot is the input payload handed to the analyze-kind AIAgent. It captures the minimal incident metadata the agent needs to plan its tool calls; richer context (related logs, nearby incidents, pattern history) is fetched on demand via the agent's read-only tools, not pre-loaded into the snapshot.
The struct deliberately does not import pkg/storage — `pkg/core` must stay leaf-level. Callers (the admin controller, the worker) flatten storage records into this shape.
type AnalyzeTask ¶ added in v1.4.3
type AnalyzeTask struct {
Snapshot AnalyzeIncidentSnapshot
}
AnalyzeTask wraps an on-demand analysis request. The snapshot carries the incident payload the agent inspects; richer context (related logs, pattern history) is fetched via the agent's read-only tools at run time, not pre-loaded here.
func (AnalyzeTask) CacheKey ¶ added in v1.4.3
func (t AnalyzeTask) CacheKey() string
CacheKey implements AITask. Empty disables caching — operators expect a fresh tool walk on every analyze request.
func (AnalyzeTask) Kind ¶ added in v1.4.3
func (AnalyzeTask) Kind() AITaskKind
Kind implements AITask.
type AnalyzeTool ¶ added in v1.4.3
type AnalyzeTool interface {
// Name is the model-visible tool name. Must be a stable identifier
// (snake_case is the convention).
Name() string
// Description is the one-line model-visible doc. The model uses it
// to decide when to call the tool.
Description() string
// ArgsSchema returns a JSON schema (drafted as a generic map) for
// the tool's argument object. Eino converts this into the model's
// tool-call schema.
ArgsSchema() map[string]any
// Invoke runs the tool with the model-provided JSON args. The
// returned ToolResult is serialised to JSON and fed back to the
// model as the tool message. Errors are surfaced to the model as
// a tool error so it can adapt.
Invoke(ctx context.Context, args json.RawMessage) (*ToolResult, error)
}
AnalyzeTool is the contract every analyze-side read-only tool satisfies. Tools are registered with the analyze agent at construction time; the agent surfaces them to the model as Eino ToolInfo and dispatches model-requested calls back to this interface.
Implementations MUST be read-only. The compile-time guard in pkg/agent/ai/analyze rejects any import of services.CreateIncident transitively.
type Attachment ¶ added in v1.4.8
type Attachment struct {
Filename string // "incident-<shortid>.png"
MIME string // "image/png"
Data []byte // the rendered image bytes
Caption string // short redacted text summary (also the fallback body)
}
Attachment is one rendered artifact (today, an incident report image) bound for a channel. Caption is the short text summary that image-capable channels attach alongside the binary AND that image-incapable channels fall back to. The Data bytes are already the final encoded image; the text is already redacted by the report assembler.
type AttachmentSender ¶ added in v1.4.8
type AttachmentSender interface {
SendAttachment(incident *m.Incident, att Attachment) error
}
AttachmentSender is an OPTIONAL capability a notification channel may implement on top of the mandatory AlertProvider — exactly like storage.Searcher / storage.Lifecycle. A channel that can upload a binary image (Slack, Telegram, Email) implements it; a channel that cannot (Teams, Viber, Lark webhooks) simply does not, and the report delivery path detects the difference with a type assertion. The interface is deliberately generic: no per-channel special-casing leaks into the caller.
type Bucket ¶ added in v1.4.8
Bucket is one labelled tally in an aggregate breakdown — a severity band, a trend time-slice, or a top service. Count is the total; AIDetect and Webhook carry the per-origin split so the trend timeline can draw a stacked bar (AIDetect + Webhook == Count). Labels are already redacted for the top-services list (a service name is attacker-influenced webhook content); the severity/trend labels are renderer-controlled constants.
type DetectTask ¶ added in v1.4.3
type DetectTask struct {
Result AgentResult
}
DetectTask wraps an AgentResult for detect-mode classification.
func (DetectTask) CacheKey ¶ added in v1.4.3
func (t DetectTask) CacheKey() string
CacheKey implements AITask. Detect memoisation is keyed by pattern id; an empty pattern id (unknown pattern not yet stored) disables caching for that call.
func (DetectTask) Kind ¶ added in v1.4.3
func (DetectTask) Kind() AITaskKind
Kind implements AITask.
type Detector ¶ added in v1.3.9
type Detector interface {
Name() string
Process(ctx context.Context, batch []Signal) ([]AgentResult, error)
}
Detector consumes signals and emits AgentResults.
type Embedder ¶ added in v1.4.4
type Embedder interface {
// Embed returns one vector per input string, in the same order. An
// empty input yields an empty (non-nil) slice and a nil error.
Embed(ctx context.Context, texts []string) ([][]float32, error)
}
Embedder is the leaf-level contract for turning text into vector embeddings. It is the single embeddings seam every RAG-bearing agent in the suite reuses (the runbook-RAG find_runbook tool is the first consumer). Like AnalyzeTool / ToolResult, it lives in pkg/core so the tools layer can depend on it without importing the concrete Eino wrapper (pkg/agent/ai/eino) that constructs it.
Implementations are an external trust boundary: callers MUST scrub any incident-derived text through the redactor before passing it to Embed, exactly as the analyze tools scrub log lines before the chat-completion call. The runbook corpus that is embedded at ingestion time is operator-authored content (an accepted boundary, documented in the runbook-RAG docs).
type EvidenceItem ¶ added in v1.4.3
type EvidenceItem struct {
Source string `json:"source"`
Summary string `json:"summary"`
Detail string `json:"detail,omitempty"`
}
EvidenceItem records one piece of supporting evidence the analyze agent gathered (typically from a tool call). Source identifies where it came from (e.g. "tool:get_related_logs"); Summary is a one-liner shown in the UI; Detail is the long form (capped on persist).
type NotableIncident ¶ added in v1.4.8
type NotableIncident struct {
ShortID string
Title string
Service string
Severity string
CreatedAt time.Time
}
NotableIncident is one row in the "recent high-severity" notable list. Every string field is ALREADY redacted by the assembler — the renderer draws it verbatim and never reaches back into raw content.
type Observation ¶ added in v1.4.4
type Observation struct {
Key string // stable per-type fingerprint
Service string // attribution (feeds grace + routing)
Signal string // human label: template | "latency_p99" | "GET /checkout error_rate"
Timestamp time.Time // observation time (seasonality needs it)
Value float64 // the scalar the learner folds + the detector scores
Frequency int // tick count (logs) / sample count (metrics/traces)
Samples []Signal // representative raw signals for the AI-analyze handoff
// IsNew reports whether Group saw this Key for the first time during this
// tick (a freshly discovered template / metric / operation). It is used
// only for discovery logging and never affects classification. (Additive
// to the design-doc struct.)
IsNew bool
}
Observation is one keyed unit a typed pipeline learns/scores for a tick.
logs → one drain-template bucket (Value = tick frequency) metrics → one (service, golden-signal) window (Value = latest sample / p99 / error-ratio) traces → one (service, operation) window (Value = window p99 latency or error-ratio)
type OnCallProvider ¶ added in v1.3.0
type OnCallProvider interface {
TriggerOnCall(ctx context.Context, incidentID string, cfg *config.OnCallConfig) error
}
OnCallProvider defines the interface for on-call notification providers
type OnCallWorkflow ¶
type OnCallWorkflow struct {
// contains filtered or unexported fields
}
OnCallWorkflow coordinates on-call escalation with a single provider
func GetOnCallWorkflow ¶
func GetOnCallWorkflow() *OnCallWorkflow
GetOnCallWorkflow returns the global singleton instance This maintains compatibility with existing code
func NewOnCallWorkflow ¶ added in v1.3.0
func NewOnCallWorkflow(redisClient redis.UniversalClient, provider OnCallProvider) *OnCallWorkflow
NewOnCallWorkflow creates a new on-call workflow with the given provider
func (*OnCallWorkflow) Ack ¶
func (w *OnCallWorkflow) Ack(incidentID string) error
Ack acknowledges an incident to prevent escalation
func (*OnCallWorkflow) Start ¶
func (w *OnCallWorkflow) Start(incidentID string, oc config.OnCallConfig) error
Start initiates the on-call workflow for an incident
type QueueListener ¶
type Readiness ¶ added in v1.4.7
type Readiness struct {
// Ready reports the signal has reached its settled/known state.
Ready bool `json:"ready"`
// Seen is the evidence folded so far (log sightings / folded samples).
Seen int `json:"seen"`
// Needed is the evidence required to reach Ready. 0 is the INDETERMINATE
// sentinel: no count gate applies for this signal kind. The UI shows
// "Manual only", never "X of 0". (Log patterns always ship a positive
// Needed — a non-positive auto_promote_after is normalized to the default.)
Needed int `json:"needed"`
// RatePerMin is the observed arrival rate of NEW evidence for this key,
// in evidence/minute, used to derive the ETA. 0 is the UNKNOWN/STALLED
// sentinel: no honest rate yet, or the signal stopped flowing — the UI
// shows no ETA (never ∞).
RatePerMin float64 `json:"rate_per_min"`
}
Readiness is how close a signal is to its settled/known state — the point at which the agent's judgment of it is final rather than provisional. It is a generic, type-agnostic value produced at each read boundary (the log catalog reader in OSS, the enterprise metric/trace baseline reader). It is a plain value type with no behaviour: presentation (remaining evidence, ETA, progress bar) is DERIVED from these facts by the UI — the server ships facts, not formatted strings.
Derivations the UI does: remaining = max(0, Needed-Seen) (when Needed>0); etaMinutes = remaining / RatePerMin (when RatePerMin>0, Needed>0, !Ready); progress = Seen/Needed (when Needed>0).
type ReportImage ¶ added in v1.4.8
type ReportImage struct {
Data []byte // encoded image bytes (PNG)
MIME string // "image/png"
Filename string // e.g. "incidents-today-20260704.png"
Width int
Height int
}
ReportImage is a rendered report: the encoded image bytes plus enough metadata for a channel to upload it or an HTTP handler to serve it.
type ReportModel ¶ added in v1.4.8
type ReportModel struct {
// Window identity. Timestamps are expressed in TZLabel's location (UTC by
// default), so a renderer/caption can print them verbatim.
Window string // "today" | "24h" | "7d"
WindowStart time.Time // inclusive
WindowEnd time.Time // exclusive (== GeneratedAt for today/24h)
GeneratedAt time.Time
// TZLabel is the IANA name of the timezone WindowStart/WindowEnd/
// GeneratedAt are expressed in (e.g. "UTC" or "Asia/Ho_Chi_Minh"). The
// renderer and channel caption print it beside the times so a reader knows
// the wall-clock zone. Empty is treated as "UTC".
TZLabel string
// Headline stats.
Total int // incidents in the window
ByOrigin map[string]int // ai_detect / webhook counts (primary category axis)
Resolved int
Open int
CriticalHigh int // count of critical + high severity incidents
// Chart series (hand-drawn, no chart lib).
BySeverity []Bucket // ordered critical..unknown (category breakdown)
Trend []Bucket // hourly (today/24h) or daily (7d) counts
TrendUnit string // "hour" | "day" — labels the trend axis
// Notable lists (bounded, redacted labels).
TopServices []Bucket // top-N services by count
Notable []NotableIncident // recent high-severity incidents
// IncludeCharts mirrors the runtime include_chart toggle: when false the
// renderer omits the trend + severity charts (headline + lists remain).
IncludeCharts bool
// enterprise white-label renderer overrides it behind the same seam.
Footer string
}
ReportModel is the channel-agnostic, ALREADY-REDACTED aggregate summary of every incident in a time window. It is assembled by querying the incident history over the window (both ai_detect and webhook) and handed to a ReportRenderer. Every string field has already passed through a Scrubber, so a renderer draws it verbatim and must never reach back into raw incident content. No AckURL, token, or config value is ever placed here.
type ReportRenderer ¶ added in v1.4.8
type ReportRenderer interface {
Render(ctx context.Context, m ReportModel) (*ReportImage, error)
}
ReportRenderer turns a ReportModel into a ReportImage. The OSS build wires the default pure-Go PNG card renderer at boot via services.SetReportRenderer; an enterprise build may install a branded / high-fidelity renderer behind the SAME seam without the API, channel delivery, or UI changing. Mirrors the SetAnalyzeAgent seam.
type RootCauseHypothesis ¶ added in v1.4.3
type RootCauseHypothesis struct {
Hypothesis string `json:"hypothesis"`
Confidence float64 `json:"confidence"`
Rationale string `json:"rationale,omitempty"`
}
RootCauseHypothesis is one candidate explanation produced by an analyze-mode agent. Confidence is in [0, 1]; Rationale is a short human-readable justification.
type Scrubber ¶ added in v1.4.8
Scrubber is the minimal DLP contract the report path depends on: turn a possibly-sensitive string into a safe one. It is satisfied by agent.Redactor.Scrub, so the report assembler can scrub every field WITHOUT the services/report layers importing the heavier agent package. Every text value drawn onto a shared report image is run through a Scrubber first — defence-in-depth, because webhook incident Content is attacker-influenced input.
type Signal ¶ added in v1.3.9
type Signal struct {
Source string // e.g. "elasticsearch:prod-app"
Timestamp time.Time
Severity string // raw severity from source, best-effort
Message string // primary text payload
Fields map[string]interface{} // structured fields
Raw map[string]interface{} // original document (capped size)
}
Signal is one normalized observation pulled from a SignalSource.
Fields holds best-effort structured fields; Raw is the original source document (capped to AgentConfig.SignalMaxBytes by the SignalSource so the pipeline cannot OOM on large documents).
type SignalDetector ¶ added in v1.4.4
type SignalDetector interface {
// Kind identifies the signal type and must match its paired Learner.
Kind() string
// Classify scores one observation against the learned expectation
// (mean, std, confident) captured by SignalLearner.Expected.
Classify(obs Observation, mean, std float64, confident bool) TypedVerdict
}
SignalDetector is the pure SCORING POLICY: a function of (observation, learned expectation) → verdict. The per-type knob (z threshold, spike multiplier, sustained-tick debounce) lives here, not in the worker. The detector scores against the PASSED expectation (the pre-fold snapshot), never by re-reading the learner, so the result is independent of whether the worker has already folded the tick.
type SignalLearner ¶ added in v1.4.4
type SignalLearner interface {
// Kind identifies the signal type: "logs" | "metrics" | "traces".
Kind() string
// Group keys a tick's batch into observations WITHOUT mutating the model.
Group(ctx context.Context, batch []Signal) ([]Observation, error)
// Expected returns the learned mean/spread for a key at ts, and whether
// the model is confident enough to score (the per-key training gate).
// It must be a pure read of pre-fold state: the worker snapshots it
// BEFORE Learn folds the tick, so a deviation is judged against the
// pre-tick baseline.
Expected(ctx context.Context, key string, ts time.Time) (mean, std float64, confident bool)
// Learn folds observations into the model. Called every tick in EVERY
// mode (training/shadow/detect) so the baseline keeps adapting to drift.
Learn(ctx context.Context, obs []Observation) error
}
SignalLearner owns the per-type LEARNED MODEL: keying + folding + the "what is normal" estimate. Implementations persist their derived state via the model-state seam (opaque bytes, keyed by OrgID). One implementation per signal type (logs in OSS; metrics/traces in Versus Enterprise).
type SignalSource ¶ added in v1.3.9
type SignalSource interface {
// Name uniquely identifies the configured source instance. It is used as
// part of Redis cursor keys, so it must be stable across restarts.
Name() string
// Pull returns all signals strictly newer than `since`, plus the new
// cursor (== max timestamp seen, or `since` if no signals were returned).
//
// Tailing invariant: the returned cursor MUST NOT exceed the wall-clock
// time at pull, and a cursor-driven source MUST NOT scan past `now`.
// Document timestamps are untrusted producer data; a single future-dated
// record would otherwise push the cursor ahead of real time and make every
// following `>= cursor` query match nothing until that time arrives — the
// source stops emitting until its cursor is wiped. Sources bound their scan
// at `now` (Loki `end`, Graylog `to`, Splunk `latest`, Elasticsearch `lte`,
// CloudWatch `EndTime`) and clamp the returned cursor via
// signalsources.ClampCursor.
Pull(ctx context.Context, since time.Time) ([]Signal, time.Time, error)
}
SignalSource is a puller for one external observability backend (Elasticsearch, Loki, CloudWatch, ...).
type SourceRewinder ¶ added in v1.4.7
SourceRewinder is the OPTIONAL capability a SignalSource implements when it keeps its OWN internal read position — a byte offset, page token, or similar — that is INDEPENDENT of the poll cursor the worker passes to Pull as `since`. The file source is the canonical example: it tails a log file by byte offset (persisted in a sidecar) and ignores `since` entirely.
Clearing the learned catalog rewinds the worker's poll cursor, which makes cursor-driven sources (Elasticsearch, Loki, CloudWatch, …) re-read their lookback window on the next tick. A source that tracks its own position must additionally rewind THAT position here — otherwise the clear leaves it pinned past the already-consumed data and it never re-emits, so the SAME running worker stops learning until the process is recreated (which reconstructs the source from scratch). Rewind reconciles the two cursors of truth so a clear behaves like a fresh process start in-place.
Sources whose position is driven purely by `since` do not implement it; the worker skips them. Implementations must be safe to call concurrently with Pull and must leave the source in the state a freshly-constructed instance would have.
type TextSender ¶ added in v1.4.8
TextSender is the OPTIONAL text-fallback sibling of AttachmentSender. A channel that cannot upload a binary but CAN post text (Teams, Viber, Lark webhooks) implements it so the report delivery path can still deliver the already-redacted caption + a short note to that channel. Like AttachmentSender it is generic and detected via type assertion; a channel that implements neither simply receives no report and the caller records a graceful fallback outcome.
type ToolCallTrace ¶ added in v1.4.3
ToolCallTrace is one model-issued tool round-trip captured for audit.
type ToolResult ¶ added in v1.4.3
type ToolResult struct {
Tool string `json:"tool"`
Found bool `json:"found"`
Data map[string]any `json:"data,omitempty"`
}
ToolResult is the uniform envelope every AnalyzeTool returns. The shape is stable so the model sees a predictable schema across tools; the per-tool payload lives in Data as JSON-encodable values.
Tool — the tool name (mirrors AnalyzeTool.Name) so a model parsing multiple tool responses can disambiguate without relying on call ordering.
Found — optional flag for lookup-style tools (pattern_history, describe_service) to signal "no such entity" without an error. Defaults to true; lookups that miss should set it to false and leave Data empty (or populated with just the query echo).
Data — the typed payload. Keys are tool-specific; values must be JSON-marshalable (no channels, funcs, or unexported structs).
type TypedVerdict ¶ added in v1.4.4
type TypedVerdict struct {
Class AgentVerdict // reuse the enum: VerdictKnownPattern | VerdictUnknown | VerdictSpike
Confident bool // false ⇒ this key is still TRAINING ⇒ lifecycle suppresses, any mode
Score float64 // z-score (metrics/traces) or spike ratio (logs)
Baseline float64 // learned expected value (for the finding text)
Reason string // "p99 4.2σ above the learned Tue-14:00 baseline (≈ 180ms)"
}
TypedVerdict is the per-type classification of one observation.